目录
3.1.1、 ArrayList类、LinkedList类、Vector类
3.2.1、HashSet类、TreeSet类、LinkedHashSet类
3.4、Collections工具的一个功能:线程安全地(synchronized)使用非线程安全的集合
4、Concurrent包下的阻塞队列(都是线程安全的,因为使用了Lock)
4.5.2、 看看DelayQueue的源码(这个源码不难,耐心点看吧)
1、集合框架
下面这个图基本包括了java中核心的集合结构,个人觉得还可以。
2、Iterable 接口
这个接口是集合系列的根接口,都要实现它,因为 Iterable 接口里面有个iterator()方法,用于遍历集合里的每一个元素,所以记着:凡是集合类,都可以通过iterator来遍历。
3、 Collection接口
下面是Collection接口的源码,定义了集合最基本的使用规范。不仅是java,其它的开源代码也是这样层层继承和实现,我们可能会比较疑惑,为什么要搞得这么复杂,把代码写在一起不好吗,干嘛弄出这么多的接口文件。层层分开的目的是让框架解耦,这样可以更加灵活地扩展和使用,你把代码都写到一堆,一旦需要更改或者扩展,会让你改得头疼。
public interface Collection<E> extends Iterable<E> {
// 返回集合的元素个数
int size();
// 集合是否为空
boolean isEmpty();
// 集合是否包含该元素
boolean contains(Object o);
// 返回迭代器,用于遍历集合
Iterator<E> iterator();
// 返回一个包含集合所有元素的数组
Object[] toArray();
// 同上,只是能够运行指定类型
<T> T[] toArray(T[] a);
// 添加一个元素,成功返回true,失败返回false
boolean add(E e);
// 删除一个元素,成功返回true,失败返回false
boolean remove(Object o);
// 集合是否包含该集合c(c是否是集合的子集)
boolean containsAll(Collection<?> c);
// 添加一个集合,成功返回true,失败返回false
boolean addAll(Collection<? extends E> c);
// 删除一个集合,成功返回true,失败返回false
boolean removeAll(Collection<?> c);
// 删除满足指定条件的元素
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
// 仅保留包含在指定集合c中的元素
boolean retainAll(Collection<?> c);
// 删除所有元素
void clear();
// 判等
boolean equals(Object o);
// 返回哈希码
int hashCode();
// 返回集合的Spliterator
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
// 返回以此集合作为源的Stream
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
// 返回可能并行的以此集合作为源的Stream
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
上面是Collection接口的源码,重点说一下:removeIf()。
removeIf(Predicate<? super E> filter):删除满足指定条件的元素,那怎么去设置条件呢?下面举个例子,删除集合中大于24的元素,其实就是对集合的每个元素进行遍历,然后判断每个元素是否满足条件,如果满足,就删除。很多地方建议removeIf()里面用lambda表达式,确实,如果条件比较简单的话,用lambda表达式更简洁,但是对于复杂的条件,还是用java方式吧。
ArrayList<Integer> collection = new ArrayList<>();
collection.add(22);
collection.add(40);
collection.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer > 24;
}
});
3.1、 List接口
下面是List接口的源码,除去了从Collection接口继承的方法,只简单介绍了扩展的方法,熟悉这些方法的目的是让我们知道List集合框架下的结构可以有哪些属性和操作。
public interface List<E> extends Collection<E> {
// 在指定位置处添加集合
boolean addAll(int index, Collection<? extends E> c);
// 将所有元素替换,具体的替换操作代码就写在参数位置即可
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
// 给list排序,前提是元素能够比较(实现了Comparator接口)
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
// 返回index处的元素
E get(int index);
// 设置index处的元素为element
E set(int index, E element);
// 在index处添加元素element
void add(int index, E element);
// 删除index处的元素
E remove(int index);
// 返回指定元素的位置
int indexOf(Object o);
// 返回指定元素在list中最后一个的位置
int lastIndexOf(Object o);
// 迭代器
ListIterator<E> listIterator();
// 迭代器,从index处开始迭代
ListIterator<E> listIterator(int index);
// 截取list的一段出来
List<E> subList(int fromIndex, int toIndex);
}
3.1.1、 ArrayList类、LinkedList类、Vector类
ArrayList类:线程不安全。底层是一个数组,new ArrayList(); 创建时默认为空数组(长度为0),当真正要用的时候,再扩展容量为10,其实也可以理解为ArrayList 的默认容量为10。之后的扩容机制:扩容后的大小= 原始大小*1.5。。
优点:能够随机访问,相对于数组而言,能够扩容。
缺点:容易造成空间浪费,扩展容量的话,也是创建一个新的更大容量的数组,让后将旧数组的数据复制到新数组,麻烦;另外,不方便插入、删除操作。
LinkedList类:线程不安全。底层是一个双向链表。
优点:插入,删除操作非常方便。
缺点:遍历,查询不方便。
Vector类:线程安全。和ArrayList差不多,区别在于:Vector是线程安全的,因为用了synchronized关键字;Vector的扩容机制是扩展1倍,而ArrayList是扩展0.5倍。
3.2、Set接口
Set接口的源码没有什么可说的,因为都是继承自Collection接口的。
3.2.1、HashSet类、TreeSet类、LinkedHashSet类
HashSet类:线程不安全。底层是HashMap的,元素就是HashMap的key,value固定是PRESENT。此处暂不做详细介绍,看下面的HashMap即可。
TreeSet类:线程不安全。底层是TreeMap实现的。
LinkedHashSet类:线程不安全。既有hash功能,以时间复杂度O(1)读取,又有Linked 链表性质,保证元素的先后顺序,底层原理和HashSet差不多,只不过每个元素还有链表指针,维护着先后顺序。
都是用Map来实现的,那Set系列存在的意义呢?其实Set系列都用的少,替我们封装了一层,让我们用起来更方便;另外,Set里的元素必须不重复,这也是Set的优点。
3.3、Map接口
3.3.1、HashMap类
下面的图画得很好理解,所以引用一下,请原作者别喷。
线程不安全。HashMap是由数组+链表实现的,添加一个元素A时,首先要确定插入的位置:先计算其hashCode值,再根据hashCode计算出位置,如果该位置已经有元素了,那就比较两个元素是否是同一个,如果是,则替换,如果不是,即出现哈希冲突,将两个元素形成链表,然后表头插入插入数组的该位置处,新来的元素A放入表头,就形成了链表。当链表的长度超过8时,就自动将链表转为红黑树结构。
默认情况下,数组的初始长度为16。默认的负载因子是0.75。当元素的个数超过 数组长度 * 负载因子 时,就扩容,数组长度翻倍。
负载因子:比如一个哈希数组长度为16时,里面存的元素越多,那么下一次存入元素时发生哈希冲突的可能性就越大,如果数组都满了,再存入一个元素,那哈希冲突概率就是100%。一旦冲突了,就会用链表的方法去解决,你要知道哈希的查询是O(1),但是链表的查询是要从表头挨个挨个地去找的,所以链表的大量存在会影响HashMap的性能。因此,不能等到数组都存满了再扩容,所以设置了一个负载因子,负载因子表明一个数组最多存储的元素比例,比如,因子 = 0.75时,长度为16的HashMap最多存入12个元素,然后就得扩容为32。
扩容:扩容比较麻烦,大小从16变为了32,之前元素的哈希位置需要重新计算;另外还得重新创建数组,将元素复制过去。因此,如果能提前预料到HashMap的最大容量是最好的,就不会出现扩容了。
负载因子的选择需要选好,大了,哈希冲突多,链表多,影响性能;小了,浪费空间。
如果要自己设置hashMap 的大小,最好设置为2的幂,为了让哈希计算出来的索引位置均匀地分布在数组里。如果不是2的幂,那么在二进制取模运算时,有些index结果的出现几率会更大,而有些index可能永远不会出现。
顺便说一下HashMap的哈希算法,hashCode方法是将对象的地址进行处理,高16位与低16位进行异或,再将异或的结果与(数组的长度 - 1)做 & 运算,得出的结果就是对象应该插入在HashMap 数组中的位置。
常用方法:
// 添加键值对
public V put(K key, V value);
// 添加很多键值对
public void putAll(Map<? extends K, ? extends V> m);
// 获取key对于的值
public V get(Object key);
// 是否包含该key
public boolean containsKey(Object key);
// 是否包含该value
public boolean containsValue(Object value);
// 删除key对应的键值对
public V remove(Object key);
// 获取所有value的集合
public Collection<V> values();
// 判断是否为空
public boolean isEmpty();
// 返回一个由所有的key组成的一个Set
public Set<K> keySet();
// 返回一个由所有键值对组成的一个Set
public Set<Map.Entry<K,V>> entrySet();
// 清空
public void clear();
// 获取大小
public int size();
3.3.2、 HashTable类
线程安全。虽然HashTable已经被弃用了,还是介绍一下吧,HashTable的结构与HashMap的一样,两者的主要区别在于:
1、HashTable是线程安全的(sychronized);
2、HashTable不允许key,value为null;
3、HashTable的默认大小为11,扩容时,变为原来大小的2倍+1。
HashTable被弃用了,肯定是有新的类替代它,下文会介绍ConcurrentHashMap。
3.3.3、TreeMap类
线程不安全。TreeMap类的底层结构是一颗红黑树,即自平衡二叉排序树,因此,查询操作的时间复杂度为O(log n)。如果说仅仅查询,添加,删除操作,那TreeMap的性能是不如HashMap的,但是TreeMap是能够自动排序的,这也是TreeMap存在的意义。
每添加/删除一个元素时,TreeMap都会自动调整自己的结构以再次让自己成为平衡二叉排序树,当我们要使用TreeMap的自动排序特性时,需要将所有元素/所有key导出为一个集合,然后这个集合里的内容是有序的(默认按照key升序),当然我们可以指定排序的规则,使得获得的集合按照我们自定义的规则来排序,因此,就需要让key实现Comparable接口。
常用方法:
1、public TreeMap():默认的构造方法,按key自然排序。
2、public TreeMap(Map<? extends K, ? extends V> m):将Map里的键值对直接构造成红黑树,key按照自然排序。
3、public TreeMap(Comparator<? super K> comparator):指定比较器,key按照比较器排序。
4、public V get(Object key):根据key获取value。
5、public V put(K key, V value):添加key-value。
6、Map.Entry<K,V> ceilingEntry(K key):返回大于等于此key 的元素。
7、K ceilingKey(K key):返回大于等于此key的key。
8、void clear():清空。
9、Object clone():返回此 TreeMap实例的浅拷贝。
10、Comparator<? super K> comparator():返回比较器,如果是自然排序,即返回null。
11、boolean containsKey(Object key):是否包含指定key。
12、boolean containsValue(Object value):是否包含指定value。
13、NavigableSet<K> descendingKeySet():以反序的方式返回所有的key
14、NavigableMap<K,V> descendingMap():以反序的方式返回所有键值对。
15、Set<Map.Entry<K,V>> entrySet():返回包含所有键值对的集合。
16、Map.Entry<K,V> firstEntry():返回最小键相关联的键值对 。
17、K firstKey():返回第一个(最低)键。
18、Map.Entry<K,V> floorEntry(K key):返回小于等于此key的键值对。
19、K floorKey(K key):返回小于等于此key 的key。
20、Set<K> keySet():返回包含所有key的集合。
21、Map.Entry<K,V> lastEntry():返回最大key对应的键值对。
22、K lastKey():返回最大key。
23、void putAll(Map<? extends K,? extends V> map):将map添加进TreeMap中。
24、V remove(Object key):删除key。
25、int size():返回键值对的总数量。
26、Collection<V> values():返回包含所有值的集合。
3.3.4、 WeakHashMap类
这是一个和HashMap类似的哈希结构类,常用的操作方法就是map那套,因为平时也没有用过,所以此处暂不作详细介绍,只是这个类是一个弱引用的哈希映射,在垃圾回收时,不管当前内存是否够用,都会回收掉弱引用指向的内存空间的。