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方法具有一致的返回值。