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