集合
数组在查询和修改文件的时候效率是很快的,但是新增和删除效率不高,因为他们都是根据索引查值,这里我们用集合来替换数组
集合
理论上存储不同类型的不定量的元素数据。但是实际开发中还是存储相同类型的数据。且集合只能存储引用类型的数据
:泛型的标志 类型的自动转换 默认object,T就类似形式参数一样,是不确定的,但是必须是引用类型
集合里面使用泛型,表示集合数据有了限定,只能存储参数化的真实类型的数据
Collection
元素都是单值,加了泛型只能存一组类型相同的元素
子接口 | 元素是否有序 | 元素是否可重复 |
---|---|---|
List |
有序(所有元素都有索引位置) | 可以 |
Set |
无序 | 不可以 |
翻看Java的api文档可以找到Collection还有一个父接口:
public interface Collection
我们先来看他父接口Iterable
常用方法:
1. iterator() //获得指定集合迭代器对象
2.forEach(Consumer<? super T> action)//循环遍历集合元素
Collection接口:常用方法:
增: add(E e)
删:
boolean remove(Object o) 从此集合中删除元素
default boolean removeIf(Predicate<? super E> filter)删除满足条件的元素
void clear() 清空集合的所有元素
改:由于Collection没有索引值,因此需要子接口或者类来实现该功能
查:
Iterable<E> iterator():
hasNext()判断指针时候是否有更多元素需要迭代
next() 获得光标之后的数据
remove()删除当前元素数据
forEach(Consumer<? super T> action)
判断:
boolean contains(Object o) 如果此集合包含指定的元素,则返回 true
boolean isEmpty() 如果此集合不包含元素,则返回 true
集合转数组:
Object[] toArray()
<T> T[] toArray(T[] a)
获得集合元素个数:int size()
default Stream<E> stream() 串行化
default Stream<E> parallelStream() 并行化
讲了这么多方法,先拿个不带泛型的集合来用一下,注释都写在代码里面
//创建集合对象,接口,用多态创建对象
Collection collection = new ArrayList();
//此时没有加泛型,因此元素为object类型
//add
((ArrayList) collection).add(100);
((ArrayList) collection).add(100);
((ArrayList) collection).add(100);
((ArrayList) collection).add("hello");
((ArrayList) collection).add(false);
((ArrayList) collection).add(new Object());
System.out.println(collection.size());//7
//delete
collection.remove(100);//只删除一个100
System.out.println(((ArrayList) collection).size());//6
collection.removeIf(new Predicate() {
@Override
public boolean test(Object o) {
return Integer.valueOf(100).equals(o);
}
});
//我们用lamda格式来替换抽象方法
collection.removeIf(o->{
return Integer.valueOf(100).equals(o);
});
上面我们只写了增删操作,下面我来实现遍历集合元素:
- 利用增强for来遍历我们元素,且遍历的同时我要删除某个元素
for(Object o : collection) {
if(o.equals(100))collection.remove(o);
System.out.println(o);
}
出现异常,ConcurrentModificationException 并发修改异常,说明普通for是不能进行增删改的操作的,但是可以遍历查询所有元素。
- 利用迭代器来遍历,并且修改某个值
//1、迭代器iterator()
//获得集合对象的迭代器对象
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
if (new Integer(100).equals(object))
iterator.remove();
}
System.out.println(collection);
输出正常,并且将条件满足的元素全部删除,该方法可行。
- foreach
collection.forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
collection.forEach((Object obj)->{
System.out.println(obj);
});
点开底层他用的也是增强for,所以还是不能进行增删改,说明在需要修改的时候,只能利用迭代器。
但是下面的遍历又可以拼接成功,因为下面是改变了list里面的值
Collection collection1=new ArrayList();
((ArrayList) collection1).add("hello");
((ArrayList) collection1).add(100);
((ArrayList) collection1).add(200);
//给集合中string类型的元素拼接world
collection1.forEach((Object obj)->{
if (obj instanceof String) {
((ArrayList) collection1).set(0,obj.toString().concat("abc"));
}
});
System.out.println(collection1);//[helloabc, 100, 200]
上面的是不带泛型的,下面就是带泛型的集合了:
涉及集合转数组:
<T> T[] toArray(T[] a)
Collection<Integer> collection=new ArrayList<>();
((ArrayList<Integer>) collection).add(100);
((ArrayList<Integer>) collection).add(200);
((ArrayList<Integer>) collection).add(300);
((ArrayList<Integer>) collection).add(400);
for (Integer integer : collection) {
if(integer.equals(new Integer(100))) System.out.println(integer);
}
//集合转数组,使用相同类型的数组来接收,并且新数组一般长度给0
Integer[] integers = collection.toArray(new Integer[0]);
System.out.println(Arrays.toString(integers));
1、List
list也是一种接口,因此还是需要通过创建new一个接口实现类的方式(多态)来创建我们的对象
list的实现类有以下几个类,我们来比较一下:
接口实现类 | 数据结构(底层) | 线程安全 | 效率 |
---|---|---|---|
ArrayList |
动态数组 | 否 | 查询/修改高,新增/删除慢 |
LinkedList |
双向链表 | 否 | 新增/删除快,查询/修改慢 |
Vector |
动态数组 | 是 | 性能一般 |
CopyOnWriteArrayList |
动态数组 | 是 | 性能最慢 |
链表结构:
单向链表:
元素数据 记的是下一个元素的引用 next
双向链表:
元素数据 上一个元素引用prev,当前元素引用,下一个元素引用next
ArrayList
常用方法:
new ArrayList<>();--初始容量为10
set(int index, E element);--修改
add(element);--新增
list.forEach(Consumer<? super T> action);--遍历
list.remove(index);--删除
list.subList(first index,last index);--截取元素
list.size();--元素个数
list.iterator()-->遍历
private static void demo4() {
List<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
list.add(3000);
list.add(234);
list.add(89);
list.add(198);
System.out.println(list);//[100, 200, 3000, 234, 89, 198]
list.forEach(System.out::print);//100200300023489198
System.out.println();
for (int i = 0; i < list.size(); i++) {
Integer obj= list.get(i);
if(obj.equals(new Integer(89)))list.remove(i);
if(obj.equals(new Integer(200)))list.set(i,200);
}
List<Integer> list1=list.subList(0,3);//操作list1会作用到原集合上面,此时原集合成为一种视图,只能遍历,不能增删改
list.forEach(System.out::println);
list.add(1000);//ConcurrentModificationException
list.remove(0);//ConcurrentModificationException
System.out.println(list1);
}
源码分析:
add代码,第一次扩容和它的源码
ArrayList<Integer> arrayList=new ArrayList<>();
arrayList.add(100);
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
//此时size=0;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!并且确保容量足够
elementData[size++] = e;
return true;
}
//此时minCapacity=1,elementdate和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是相等的空数组
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private static final int DEFAULT_CAPACITY = 10;
//此时minCapacity=DEFAULT_CAPACITY=10,还要继续执行ensureExplicitCapacity(minCapacity);
protected transient int modCount = 0;//用于统计新增删除的次数
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//大于零执行下面grow扩容
grow(minCapacity);
}
//此时minCapacity=10,传进来,判断是否需要扩容或者超过最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
//当空间10填满的时候会进行二次扩容,也就是此时minCapacity=11的时候,newCapacity等于10+10/2=15.第二次扩容的容量为15
remove代码和源码分析:
- 根据索引删除元素
ArrayList<Integer> arrayList=new ArrayList<>();
arrayList.add(100);
arrayList.add(200);
arrayList.add(300);
arrayList.remove(2);
public E remove(int index) {//根据索引删除并且返回被删除的元素
rangeCheck(index);//判断索引是否超出范围
modCount++;//计算删除次数
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)//移位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
- 根据元素删除元素
arrayList.remove(Integer.valueOf(200));
public boolean remove(Object o) {//返回是否删除成功
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//删除并移位
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
取值get源码分析:
arrayList.get(1);
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
修改set源码分析:
arrayList.set(0,1);
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
构造源码分析:
ArrayList<Integer> arrayList=new ArrayList<>(10);
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
但是这个类是不安全的,当把他当作成员变量来使用的时候,会出现异常:
static ArrayList<Integer> integers = new ArrayList<>();
private static void demo8() {
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
integers.add(100);
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
integers.add(100);
}
}).start();
}
异常:
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 2776
at java.util.ArrayList.add(ArrayList.java:459)
at SetDemo.lambda$demo8$0(SetDemo.java:24)
at java.lang.Thread.run(Thread.java:745)
LinkedList
底层是双向链表Node(Node
LinkedList<String> linkedList = new LinkedList();
linkedList.add("adv");
linkedList.add("hello");
linkedList.add(3, "java");
linkedList.addFirst("liku");
linkedList.addLast("abc");
linkedList.push("liku");//默认加到第一个元素
linkedList.remove();
linkedList.remove("abc");
linkedList.remove(4);
linkedList.removeFirst();
linkedList.removeLast();
linkedList.removeLastOccurrence("ha");
linkedList.removeFirstOccurrence("hello");
linkedList.indexOf("hello");//出现元素的第一个索引位
linkedList.lastIndexOf("hello");//出现元素的最后一个索引
linkedList.get(3);//得到指定索引位的元素
linkedList.getFirst();//得到第一个元素
linkedList.getLast();//得到最后一个元素
linkedList.peek();//得到第一个元素
linkedList.peekFirst();//得到第一个元素
linkedList.peekLast();//得到最后一个元素
linkedList.poll();//得到第一个元素并且把他从集合中移除
linkedList.pollFirst();//得到第一个元素并从一个中移除该元素;
linkedList.pollLast();//得到最后一个元素并移除
linkedList.pop();//liku
构造源码:
LinkedList<String> linkedList=new LinkedList<>();
/**
* Constructs an empty list.
*/
public LinkedList() {
}
add源码:
linkedList.add("abc");
//第一次add的时候
public boolean add(E e) {
linkLast(e);
return true;
}
transient Node<E> first;//链表第一个,会记录上一个prev,当前结点,和下一个next结点元素
transient Node<E> last;//链表最后一个,默认null
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;//null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;//最后一个结点为null,将新结点给第一个结点
else
l.next = newNode;
size++;
modCount++;
}
//这是一个双向链表,记录前一个元素和当前元素以及下一个元素
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
//第二次add
public boolean add(E e) {
linkLast(e);
return true;
}
transient Node<E> first;//"abc"
transient Node<E> last;//null
void linkLast(E e) {
final Node<E> l = last; "abc",第二次添加的元素,null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;//将新增的元素放在上一个新增的元素的next
size++;
modCount++;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
linkedList.add(2,"adm");
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));//放在索引所在结点之前
}
//检查位置索引是否越界,越界直接抛异常
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
//检索索引对应的元素值
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//将e元素放入索引所在结点前面
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
删除remove源码:
public E remove(int index) {
checkElementIndex(index);//判断是否越界
return unlink(node(index));
}
//将index后面的元素左移,x的下一个变成x的位置,并且解绑x与他前后的关系
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
get源码:
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
set源码:
public E set(int index, E element) {
checkElementIndex(index);
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
但是作为成员变量,线程也是不安全的
Vector
底层是synchronized修饰的,底层是同步的,线程安全,但是很慢,可以将线程放在集合里面等线程走完
static Vector<Integer> vector = new Vector<>();
private static void demo9() {
ArrayList<Thread> threads = new ArrayList<>();
long begin = System.currentTimeMillis();
for (int i = 0; i < 4; i++) {
threads.add(new Thread(() -> {
for (int i1 = 0; i1 < 100000; i1++) {
vector.add(100);
}
}));
}
for (Thread thread : threads) {
thread.start();
}
threads.forEach((thread) -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end= System.currentTimeMillis();
System.out.println(end-begin);//77毫秒
}
vector是二倍扩容方式,当容量为20的时候再次扩容为2倍40
CopyOnWriteArrayList
线程是安全的,但是速度是最慢的
static CopyOnWriteArrayList<Integer> vector = new CopyOnWriteArrayList<>();
private static void demo9() {
ArrayList<Thread> threads = new ArrayList<>();
long begin = System.currentTimeMillis();
for (int i = 0; i < 4; i++) {
threads.add(new Thread(() -> {
for (int i1 = 0; i1 < 10000; i1++) {
vector.add(100);
}
}));
}
for (Thread thread : threads) {
thread.start();
}
threads.forEach((thread) -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end= System.currentTimeMillis();
System.out.println(end-begin);//1043
}
2、Set
元素唯一且无序,用该集合需要重写equals和hashcode。实现类的区别:
接口实现类 | 数据结构 | 线程安全 | 元素是否可以为null |
---|---|---|---|
HashSet | HashMap(数组+单向链表+红黑树) | 否 | 可以 |
LinkedHashSet | HashMap | 否 | 可以 |
TreeSet | 红黑树 TreeMap | 否 | 不可以 |
CopyOnWriteArraySet | CopyOnWriteArrayList | 是 | 可以 |
HashSet
HashSet() //构造一个新的空集合实例,具有默认初始容量(16)和负载因子(0.75)
HashSet(int initialCapacity) //创建指定容量空集合 负载因子0.75
HashSet(int initialCapacity, float loadFactor)//指定容量和负载因子
其他方法跟collection一样,但是元素唯一且无序,一般不会给太高容量或太低负载,而且可以给null值
HashSet<Integer> hashSet=new HashSet<>();
hashSet.add(234);
hashSet.add(null);
Iterator<Integer> integers=hashSet.iterator();
while (integers.hasNext()){
System.out.println(integers.next());
}
LinkedHashSet
元素有序,新增的顺序与遍历顺序是一致的,且可以为空
LinkedHashSet()
LinkedHashSet(int initialCapacity)
LinkedHashSet(int initialCapacity, float loadFactor)
private static void demo1() {
LinkedHashSet<Integer> linkedHashSet=new LinkedHashSet<>();
linkedHashSet.add(100);
linkedHashSet.add(null);
System.out.println(linkedHashSet);
}
方法都是继承的,没什么区别,唯一的区别就是元素有序
TreeSet
排序,元素都是有序的,按照自然顺序进行排列,升序排列,不能为null,
TreeSet(Comparator<? super E> comparator)自定义比较器
TreeSet<Integer> treeSet=new TreeSet<>();
//treeSet.add(null);
treeSet.add(7);
treeSet.add(89);
treeSet.add(8);
treeSet.add(2);
treeSet.first();//2
treeSet.last();//89
System.out.println(treeSet);//[2, 7, 8, 89]
因为上面的包装类是实现了comparable接口public final class Integer extends Number implements Comparable
我写了这样一个用户类:
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private String name;
private Integer id;
}
并且将他放到集合里面:
private static void demo3() {
TreeSet<User> treeSet=new TreeSet();
treeSet.add(new User("liku",35));
treeSet.add(new User("mark",35));
treeSet.add(new User("lily",35));
treeSet.add(new User("lucy",35));
System.out.println(treeSet);
}
由于user对象并没有实现Comparable接口,因此会报异常:
listdemo.User cannot be cast to java.lang.Comparable
索引在用户类上面我们需要实现Comparable接口,比较对象为用户类型,重写里面的compare方法:
@Override
public int compareTo(User user) {
return this.id.compareTo(user.id);
//因为integer实现了compareTo,因此用compareTo来进行比较
}
这是跟着编号降序排列的,来但是这时候会出现一个问题,就是当id一样的时候,treeset会认为他们是同一个对象,不会保留重复元素,因此此时输出treeset他还是只有一个元素,
[User(name=liku, id=35)]
因为此时集合是按照编号排序并且编号不允许重复,编号不同的时候就是不同的元素了
TreeSet<User> treeSet=new TreeSet();
treeSet.add(new User("liku",35));
treeSet.add(new User("mark",5));
treeSet.add(new User("lily",37));
treeSet.add(new User("lucy",3));
System.out.println(treeSet);
//[User(name=lucy, id=3), User(name=mark, id=5), User(name=liku, id=35), User(name=lily, id=37)]
将数组转成集合:
Arrays.asList();
返回的是一个视图,不可更改,
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {@link Collection#toArray}. The returned list is
* serializable and implements {@link RandomAccess}.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* @param <T> the class of the objects in the array
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
如果要对返回的集合进行操作需要将返回的数组new一遍,并且覆盖返回的集合
CopyOnWriteArraySet
下面写一个小泛型:
public class DynamicArray<E> {
private static final int DEFAULT_CAPACITY=10;
private int size;
private Object[] elementData;
public DynamicArray() {
this.elementData=new Object[DEFAULT_CAPACITY];
}
private void ensureCapacity(int capacity){
int oldCapacity=elementData.length;
if(oldCapacity>capacity)return;
int newCapacity=capacity*2;
if(newCapacity<capacity)newCapacity=capacity;
elementData=Arrays.copyOf(elementData,newCapacity);
}
public void add(E e){
ensureCapacity(size+1);
elementData[size++]=e;
}
public E get(int index){
return (E) elementData[index];
}
public int size(){
return size;
}
public E set(int index,E element){
E oldValue=get(index);
elementData[index]=element;
return oldValue;
}
}