Java集合——Collection接口

摘要:本文主要介绍了Java集合的Collection接口。

概述

Collection是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。Collection包含了List和Set两大分支。

常用方法

添加单个元素:boolean add(Object object);

添加一个集合里的所有元素:boolean addAll(Collection<? extends E> collection);

删除单个元素:boolean remove(Object object);

删除指定集合里有的元素:boolean removeAll(Collection collection);

删除两个集合都有的元素:boolean retainAll(Collection collection);

判断是否包含某个元素:boolean contains(Object object);

判断是否包含指定集合的所有元素:boolean containsAll(Collection<?> collection);

判断集合是否为空:boolean isEmpty();

清除集合里的元素:void clear();

获取集合元素个数:int size();

将集合转换为数组:Object[] toArray();

将集合转换为指定类型的数组:<T> T[] toArray(T[] array);

获取集合迭代器:Iterator iterator();

集合同数组的比较

数组长度一旦固定,不能再改变,集合的长度是可以改变的。

数组只能保存相同类型的数据,集合可以保存指定类型或其子类型的数据。

数组在使用的时候相对比较麻烦,集合可以利用多种方法,还有工具类。

List接口

List接口继承自Collection接口,允许定义一个重复的有序集合,集合中的每个元素都有对应的一个索引,可以通过索引访问List中的元素。

实现List接口的实现类主要有:ArrayList、LinkedList、Vector、Stack。

特点

允许重复。

有序,取出的顺序和插入的顺序一致。

为每一个元素提供一个索引值,默认从0开始。

常用方法

在指定索引位置添加单个元素:void add(int index, Object object);

在指定索引位置添加一个集合:boolean addAll(int index, Collection<? extends E> collection);

删除指定位置的单个元素:Object remove(int index);

获取指定位置的单个元素:Object get(int index);

替换指定位置的单个元素:Object set(int index, Object object);

获取指定元素的出现的第一个索引:int indexOf(Object object);

获取指定元素的出现的最后一个索引:int lastIndexOf(Object object);

获取指定位置的集合,包含起始位置,不包含结束位置:List<E> subList(int fromIndex, int toIndex);

获取集合迭代器:ListIterator<E> listIterator();

ArrayList类

特点

ArrayList是动态数组结构,也是我们最常用的集合,允许任何符合规则的元素插入,包括null。

ArrayList提供了索引机制,可以通过索引迅速查找元素,查找效率高。但是每次增加或删除元素时,身后的元素都要移动,所以增删效率低。

ArrayList的操作是非同步的,是线程不安全的。

扩容机制

数组结构都会有容量的概念,ArrayList的初始容量为10,加载因子是1,当快插入元素后长度超出原有长度时会进行扩增,扩容增量是0.5,扩增后容量为1.5倍,可使用方法手动扩容和缩减。

如果一开始就明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间和效率。

属性



1 // 默认的初始容量为10。
 2 private static final int DEFAULT_CAPACITY = 10;
 3 // 空数组。
 4 private static final Object[] EMPTY_ELEMENTDATA = {};
 5 // 默认容量的空数组。
 6 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 7 // 数组。
 8 transient Object[] elementData;
 9 // 元素个数。
10 private int size;



构造方法



1 // 空参构造器,返回默认容量为10的集合。
 2 public ArrayList() {
 3     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 4 }
 5 
 6 // 指定长度的构造器,如果长度为0,则返回容量为0的集合。
 7 public ArrayList(int initialCapacity) {
 8     if (initialCapacity > 0) {
 9         this.elementData = new Object[initialCapacity];
10     } else if (initialCapacity == 0) {
11         this.elementData = EMPTY_ELEMENTDATA;
12     } else {
13         throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
14     }
15 }
16 
17 // 传入了一个集合的构造器,如果集合长度为0,返回容量为0的集合。
18 public ArrayList(Collection<? extends E> c) {
19     elementData = c.toArray();
20     if ((size = elementData.length) != 0) {
21         // Object[]数组里的类型不一定都是Object类型的,有可能是Object的子类,所以要判断一下。
22         if (elementData.getClass() != Object[].class)
23             elementData = Arrays.copyOf(elementData, size, Object[].class);
24     } else {
25         this.elementData = EMPTY_ELEMENTDATA;
26     }
27 }



常用方法



1 // 对集合进行扩容。
  2 public void ensureCapacity(int minCapacity) {
  3     // 如果集合不为空,则设置扩充值为0,如果为空,则设置扩充值为10。
  4     int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
  5     // 如果指定容量大于扩充值,则进行扩容。
  6     if (minCapacity > minExpand) {
  7         ensureExplicitCapacity(minCapacity);
  8     }
  9 }
 10 
 11 // 对集合进行扩容。
 12 private void ensureCapacityInternal(int minCapacity) {
 13     // 如果集合为空,则在默认大小和最小值之间取最大的。
 14     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 15         minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 16     }
 17     // 进行扩容。
 18     ensureExplicitCapacity(minCapacity);
 19 }
 20 
 21 // 对集合进行扩容,实际操作。
 22 private void ensureExplicitCapacity(int minCapacity) {
 23     // 操作数加一。
 24     modCount++;
 25     // 如果最小值大于数组长度,则进行扩容。
 26     if (minCapacity - elementData.length > 0)
 27         grow(minCapacity);
 28 }
 29 
 30 // 数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。
 31 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 32 
 33 // 扩容计算。
 34 private void grow(int minCapacity) {
 35     // 获取原容量大小。
 36     int oldCapacity = elementData.length;
 37     // 右移一位,变为原来的0.5倍,然后加上原大小,扩容后变为1.5倍。
 38     int newCapacity = oldCapacity + (oldCapacity >> 1);
 39     // 如果扩容后小于最小值,则设置容量为最小值。
 40     if (newCapacity - minCapacity < 0)
 41         newCapacity = minCapacity;
 42     // 如果扩容后大于最大值,则进一步设置容量。
 43     if (newCapacity - MAX_ARRAY_SIZE > 0)
 44         newCapacity = hugeCapacity(minCapacity);
 45     // minCapacity is usually close to size, so this is a win:
 46     elementData = Arrays.copyOf(elementData, newCapacity);
 47 }
 48 
 49 // 扩容后容量过大的处理方法。
 50 private static int hugeCapacity(int minCapacity) {
 51     // 如果最小值小于零,则表示溢出,抛出内存溢出异常。
 52     if (minCapacity < 0)
 53         throw new OutOfMemoryError();
 54     // 如果扩容后的值大于最大值,则使用Integer的最大值,否则就使用最大值。
 55     return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
 56 }
 57 
 58 // 获取指定下标的元素。
 59 public E get(int index) {
 60     // 检查指定下标是否越界。
 61     rangeCheck(index);
 62     // 返回指定下标的元素。
 63     return elementData(index);
 64 }
 65 
 66 // 设置指定下标的指定元素,并返回旧元素。
 67 public E set(int index, E element) {
 68     // 检查指定下标是否越界。
 69     rangeCheck(index);
 70     // 设置指定元素并返回旧元素。
 71     E oldValue = elementData(index);
 72     elementData[index] = element;
 73     return oldValue;
 74 }
 75 
 76 // 添加元素,并返回是否成功。
 77 public boolean add(E e) {
 78     // 扩容并增加操作数。
 79     ensureCapacityInternal(size + 1);
 80     // 元素个数增加并设置指定元素。
 81     elementData[size++] = e;
 82     // 返回成功。
 83     return true;
 84 }
 85 
 86 // 在指定位置添加指定元素。
 87 public void add(int index, E element) {
 88     // 检查指定下标是否越界。
 89     rangeCheckForAdd(index);
 90     // 扩容并增加操作数。
 91     ensureCapacityInternal(size + 1);
 92     // 拷贝数组并设置元素,增加元素个数。
 93     System.arraycopy(elementData, index, elementData, index + 1, size - index);
 94     elementData[index] = element;
 95     size++;
 96 }
 97 
 98 // 删除指定位置的元素,并返回删除的元素。
 99 public E remove(int index) {
100     // 检查指定下标是否越界。
101     rangeCheck(index);
102     // 增加操作数。
103     modCount++;
104     // 获取指定下标的元素。
105     E oldValue = elementData(index);
106     // 需要移动的元素个数。
107     int numMoved = size - index - 1;
108     if (numMoved > 0)
109         System.arraycopy(elementData, index+1, elementData, index, numMoved);
110     // 将最后一个元素设置为null并减少容量大小。
111     elementData[--size] = null;
112     // 返回指定下标的元素。
113     return oldValue;
114 }
115 
116 // 删除第一次出现的指定元素。
117 public boolean remove(Object o) {
118     // 如果指定元素是null。
119     if (o == null) {
120         for (int index = 0; index < size; index++)
121             if (elementData[index] == null) {
122                 fastRemove(index);
123                 return true;
124             }
125     } else {
126         for (int index = 0; index < size; index++)
127             if (o.equals(elementData[index])) {
128                 fastRemove(index);
129                 return true;
130             }
131     }
132     return false;
133 }



遍历方法

ArrayList提供了两种迭代器,实现了Iterator接口的Itr类,以及实现了ListIterator接口并继承了Itr类的ListItr类。

ListItr类对Itr类的方法进行了扩展,提供了添加和修改的方法,这里只学习Itr类。



1 // 获取Iterator迭代器。
  2 public Iterator<E> iterator() {
  3     return new Itr();
  4 }
  5 
  6 // Iterator内部类,实现了Iterator接口。
  7 private class Itr implements Iterator<E> {
  8     // 下一个元素的位置。
  9     int cursor;
 10     // 最后一个元素的位置。
 11     int lastRet = -1;
 12     // 预期的操作数。
 13     int expectedModCount = modCount;
 14 
 15     // 是否存在下一个元素。
 16     public boolean hasNext() {
 17         return cursor != size;
 18     }
 19 
 20     // 获取下一个元素。
 21     @SuppressWarnings("unchecked")
 22     public E next() {
 23         // 校验预期操作数和实际操作数。
 24         checkForComodification();
 25         // 将下一个元素的位置赋值给i。
 26         int i = cursor;
 27         // 如果i大于或等于集合大小,说明没有元素了,抛出异常。
 28         if (i >= size)
 29             throw new NoSuchElementException();
 30         // 获取集合数组。
 31         Object[] elementData = ArrayList.this.elementData;
 32         // 如果i大于或等于数组长度,说明有其他线程改过了,抛出异常。
 33         if (i >= elementData.length)
 34             throw new ConcurrentModificationException();
 35         // 下一个元素位置加一。
 36         cursor = i + 1;
 37         // 获取元素并返回,设置最后一个元素位置。
 38         return (E) elementData[lastRet = i];
 39     }
 40 
 41     // 删除当前元素。
 42     public void remove() {
 43         // 如果当前位置小于零,抛出异常。
 44         if (lastRet < 0)
 45             throw new IllegalStateException();
 46         // 校验预期操作数和实际操作数。
 47         checkForComodification();
 48         try {
 49             // 移除当前位置上的元素。
 50             ArrayList.this.remove(lastRet);
 51             // 将当前位置赋值给下一个元素位置。
 52             cursor = lastRet;
 53             // 将当前位置设置为-1,表示没有此位置。
 54             lastRet = -1;
 55             // 同步预期操作数和操作数。
 56             expectedModCount = modCount;
 57         } catch (IndexOutOfBoundsException ex) {
 58             throw new ConcurrentModificationException();
 59         }
 60     }
 61 
 62     // 循环遍历一次,JDK1.8新增方法。
 63     @Override
 64     @SuppressWarnings("unchecked")
 65     public void forEachRemaining(Consumer<? super E> consumer) {
 66         // 判断非空。
 67         Objects.requireNonNull(consumer);
 68         // 集合容量大小。
 69         final int size = ArrayList.this.size;
 70         // 将下一个元素的位置赋值给i。
 71         int i = cursor;
 72         // 如果i大于或等于集合长度,则返回。
 73         if (i >= size) {
 74             return;
 75         }
 76         // 获取集合数组。
 77         final Object[] elementData = ArrayList.this.elementData;
 78         // 如果i大于或等于数组长度,说明有其他线程改过了,抛出异常。
 79         if (i >= elementData.length) {
 80             throw new ConcurrentModificationException();
 81         }
 82         // 循环遍历,当下一个元素不为集合的大小,并且预期操作数和实际操作数相等。
 83         while (i != size && modCount == expectedModCount) {
 84             // 执行方法,并且每次循环i加一。
 85             consumer.accept((E) elementData[i++]);
 86         }
 87         // 将循环后的i赋值,导致下个元素的位置和集合长度相等,该迭代器不能再次遍历集合了。
 88         cursor = i;
 89         // 设置最后一个元素位置。
 90         lastRet = i - 1;
 91         // 校验预期操作数和实际操作数。
 92         checkForComodification();
 93     }
 94 
 95     // 校验预期操作数和实际操作数。
 96     final void checkForComodification() {
 97         if (modCount != expectedModCount)
 98             throw new ConcurrentModificationException();
 99     }
100 }



Vector类

特点

与ArrayList相似,它的操作与ArrayList几乎一样。

Vector是同步的,是线程安全的动态数组,但是效率低。

扩容机制

初识容量为10,加载因子为1,扩容增量是1,扩增后容量为原来长度的2倍,适用于数据量大的环境。

LinkedList类

特点

LinkedList是双向链表结构,额外提供了操作列表首尾元素的方法,因为不是数组结构,所以不存在扩容机制。

LinkedList使用了链表结构,通过修改前后两个元素的链接指向实现增加和删除操作,增删效率高,但是查找操作必须从开头或者结尾便利整个列表,所以查找效率低。

LinkedList的操作是非同步的,是线程不安全的。

插入方法



1 // 在首部位置插入元素。
 2 public void push(E e) {
 3     addFirst(e);
 4 }
 5     
 6 // 在尾部位置插入元素。
 7 public boolean offer(E e) {
 8     return add(e);
 9 }
10 
11 // 在首部位置插入元素。
12 public boolean offerFirst(E e) {
13     addFirst(e);
14     return true;
15 }
16 
17 // 在尾部位置插入元素。
18 public boolean offerLast(E e) {
19     addLast(e);
20     return true;
21 }
22 
23 // 在指定位置插入元素。
24 public void add(int index, E element) {
25     checkPositionIndex(index);
26 
27     if (index == size)
28         linkLast(element);
29     else
30         linkBefore(element, node(index));
31 }
32 
33 // 在尾部位置插入元素。
34 public boolean add(E e) {
35     linkLast(e);
36     return true;
37 }
38 
39 // 在首部位置插入元素。
40 public void addFirst(E e) {
41     linkFirst(e);
42 }
43 
44 // 在尾部位置插入元素。
45 public void addLast(E e) {
46     linkLast(e);
47 }
48 
49 // 在首部位置插入元素。
50 private void linkFirst(E e) {
51     final Node<E> f = first;
52     final Node<E> newNode = new Node<>(null, e, f);
53     first = newNode;
54     if (f == null)
55         last = newNode;
56     else
57         f.prev = newNode;
58     size++;
59     modCount++;
60 }
61 
62 // 在尾部位置插入元素。
63 void linkLast(E e) {
64     final Node<E> l = last;
65     final Node<E> newNode = new Node<>(l, e, null);
66     last = newNode;
67     if (l == null)
68         first = newNode;
69     else
70         l.next = newNode;
71     size++;
72     modCount++;
73 }
74 
75 // 在指定元素前插入元素。
76 void linkBefore(E e, Node<E> succ) {
77     // 断言指定节点不为null。
78     final Node<E> pred = succ.prev;
79     final Node<E> newNode = new Node<>(pred, e, succ);
80     succ.prev = newNode;
81     if (pred == null)
82         first = newNode;
83     else
84         pred.next = newNode;
85     size++;
86     modCount++;
87 }



删除方法



1 // 删除首部位置元素,并返回删除的元素。
  2 public E pop() {
  3     return removeFirst();
  4 }
  5 
  6 // 删除首部位置元素,并返回删除的元素。
  7 public E poll() {
  8     final Node<E> f = first;
  9     return (f == null) ? null : unlinkFirst(f);
 10 }
 11 
 12 // 删除首部位置元素,并返回删除的元素。
 13 public E pollFirst() {
 14     final Node<E> f = first;
 15     return (f == null) ? null : unlinkFirst(f);
 16 }
 17 
 18 // 删除尾部位置元素,并返回删除的元素。
 19 public E pollLast() {
 20     final Node<E> l = last;
 21     return (l == null) ? null : unlinkLast(l);
 22 }
 23 
 24 // 删除首部位置元素,并返回删除的元素。
 25 public E remove() {
 26     return removeFirst();
 27 }
 28 
 29 // 删除指定位置元素,并返回删除的元素。
 30 public E remove(int index) {
 31     checkElementIndex(index);
 32     return unlink(node(index));
 33 }
 34 
 35 // 删除指定元素。
 36 public boolean remove(Object o) {
 37     if (o == null) {
 38         for (Node<E> x = first; x != null; x = x.next) {
 39             if (x.item == null) {
 40                 unlink(x);
 41                 return true;
 42             }
 43         }
 44     } else {
 45         for (Node<E> x = first; x != null; x = x.next) {
 46             if (o.equals(x.item)) {
 47                 unlink(x);
 48                 return true;
 49             }
 50         }
 51     }
 52     return false;
 53 }
 54 
 55 // 删除首部位置元素,并返回删除的元素。
 56 public E removeFirst() {
 57     final Node<E> f = first;
 58     if (f == null)
 59         throw new NoSuchElementException();
 60     return unlinkFirst(f);
 61 }
 62 
 63 // 删除尾部位置元素,并返回删除的元素。
 64 public E removeLast() {
 65     final Node<E> l = last;
 66     if (l == null)
 67         throw new NoSuchElementException();
 68     return unlinkLast(l);
 69 }
 70 
 71 // 删除首部位置元素,并返回删除的元素。
 72 private E unlinkFirst(Node<E> f) {
 73     final E element = f.item;
 74     final Node<E> next = f.next;
 75     f.item = null;
 76     f.next = null; // help GC
 77     first = next;
 78     if (next == null)
 79         last = null;
 80     else
 81         next.prev = null;
 82     size--;
 83     modCount++;
 84     return element;
 85 }
 86 
 87 // 删除尾部位置元素,并返回删除的元素。
 88 private E unlinkLast(Node<E> l) {
 89     // assert l == last && l != null;
 90     final E element = l.item;
 91     final Node<E> prev = l.prev;
 92     l.item = null;
 93     l.prev = null; // help GC
 94     last = prev;
 95     if (prev == null)
 96         first = null;
 97     else
 98         prev.next = null;
 99     size--;
100     modCount++;
101     return element;
102 }
103 
104 // 删除指定节点元素,并返回删除的元素。
105 E unlink(Node<E> x) {
106     // 断言指定节点不为null。
107     final E element = x.item;
108     final Node<E> next = x.next;
109     final Node<E> prev = x.prev;
110 
111     if (prev == null) {
112         first = next;
113     } else {
114         prev.next = next;
115         x.prev = null;
116     }
117 
118     if (next == null) {
119         last = prev;
120     } else {
121         next.prev = prev;
122         x.next = null;
123     }
124 
125     x.item = null;
126     size--;
127     modCount++;
128     return element;
129 }



获取方法



1 // 获取首部节点。
 2 public E peek() {
 3     final Node<E> f = first;
 4     return (f == null) ? null : f.item;
 5 }
 6 
 7 // 获取首部节点。
 8 public E peekFirst() {
 9     final Node<E> f = first;
10     return (f == null) ? null : f.item;
11 }
12 
13 // 获取尾部节点。
14 public E peekLast() {
15     final Node<E> l = last;
16     return (l == null) ? null : l.item;
17 }
18 
19 // 获取指定位置的元素。
20 public E get(int index) {
21     checkElementIndex(index);
22     return node(index).item;
23 }
24 
25 // 获取首部节点。
26 public E getFirst() {
27     final Node<E> f = first;
28     if (f == null)
29         throw new NoSuchElementException();
30     return f.item;
31 }
32 
33 // 获取尾部节点。
34 public E getLast() {
35     final Node<E> l = last;
36     if (l == null)
37         throw new NoSuchElementException();
38     return l.item;
39 }



遍历方法

LinkedList同样支持两种迭代器。

LinkedList支持多种遍历方式,建议不要采用随机访问的方式去遍历LinkedList,而采用逐个遍历的方式。



1 // 通过迭代器遍历。
 2 for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
 3     System.out.println(iter.next());
 4 }
 5 
 6 // 通过普通for循环遍历,不建议使用这种方式遍历。
 7 for (int i = 0; i < list.size(); i++) {
 8     System.out.println(list.get(i));
 9 }
10 
11 // 通过增强for循环来遍历。
12 for (String string : list) {
13     System.out.println(string);
14 }
15 
16 // 通过removeFirst()方法来遍历,建议使用这种方式遍历,但是要注意可能会抛出异常。
17 String string;
18 try {
19     while ((string = list.removeFirst()) != null) {
20         System.out.println(string);
21     }
22 } catch (NoSuchElementException e) {
23     e.printStackTrace();
24 }



Set接口

Set接口继承自Collection接口,允许定义一个不重复的无序集合,集合中只允许存在一个null值。

实现Set接口的实现类主要有:HashSet、LinkedHashSet、TreeSet。

特点

不可以重复,只能插入一个空值。

无序,不能保证插入的顺序和输出的顺序一致。

没有索引。

HashSet类

特点

HashSet的底层是HashMap。

HashSet使用了一个散列集存储数据,通过元素的Hash值进行排序,不能保证插入和输出的顺序一致。

HashSet不能插入重复的元素,只能存在一个null值。

HashSet内部通过哈希表进行排序,具有很好的查找和存取功能。

HashSet是非同步的,线程不安全。

扩容机制

和HashMap相同。

LinkedHashSet类

特点

LinkedHashSet继承自HashSet,其底层是基于LinkedHashMap来实现的。

LinkedHashSet使用链表维护元素的次序,同时根据元素的hash值来决定元素的存储位置,遍历集合时候,会以元素的添加顺序访问集合的元素。

LinkedHashSet不能插入重复的元素,只能存在一个null值。

LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。

LinkedHashSet是非同步的,线程不安全。

扩容机制

和HashMap相同。

TreeSet类

特点

TreeSet的底层是TreeMap。

TreeSet基于二叉树结构,可以实现自然排序。

TreeSet通过比较方法的返回值来判断元素是否相等,因此不能添加null的数据,不能添加重复元素,只能插入同一类型的数据。

TreeSet支持两种排序方式,自然排序和定制排序。

自动排序:添加自定义对象的时候,必须要实现Comparable接口,并要覆盖compareTo方法来自定义比较规则。

定制排序:创建TreeSet对象时,传入Comparator接口的实现类。要求Comparator接口的compare方法的返回值和两个元素的equals方法具有一致的返回值。