目录
1.添加一组元素
Collection 和 Collections的区别
2 迭代器
2.1 Iterator
2.2 ListIterator
2.3 Foreach与迭代器
2.4 快速失败(fail-fast)和安全失败(fail-safe)的区别
3 List
3.1 ArrayList
3.2 LinkedList
4 Stack
5 Queue
5.1 PriorityQueue
6 Set
7 Map
7.1 HashMap
7.2 HashMap和Hashtable的区别
7.3 ConcurrentHashMap
7.3 TreeMap
7.4 LinkedHashMap
8 补充
8.1 List、Map、Set三个接口存取元素时各自的特点
8.2 集合类没有实现Cloneable和Serializable接口
Collections.addAll():接受一个Collection对象,以及一个数组或是一个用逗号分割的列表,将元素添加到Collection中。
Collection.addAll():只能接受另一个Collection对象作为参数。
Arrays.asList():接受一个数组或是一个用逗号分隔的元素列表(使用可变参数),并将其转换为一个List对象。注意,该list对象底层是数组,因此不能调整尺寸。
Iterator提供了统一遍历集合元素操作的统一接口, Java的Iterator只能单向移动,可用来遍历Set和List集合:使用方法iterator()要求容器返回一个Iterator,Iterator将准备好返回序列的第一个元素;使用next()获得序列中的下一个元素;使用hasNext()检查序列中是否还有元素;使用remove()将迭代器新返回的元素删除(调用remove()方法之前必须先调用next()方法)。
注:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出ConcurrentModificationException异常. 但是可以通过Iterator接口中的remove()方法进行删除.
ListIterator可以双向移动,只用来遍历List集合:通过调用listIterator()方法产生一个指向List开始处的ListIterator,并且还可以通过调用ListIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator;可以使用set()方法替换它访问过的最后一个元素。
foreach语法主要用于数组,也可以用于任何Collection对象。任何实现了Iterable的类,都可以用于foreach语句中。但是数组本身并不是一个Iterable。
用于随机访问元素,但是在List的中间插入和移除元素时较慢。
ArrayList自动扩容机制:ArrayList的默认初始容量为10,也可以通过ArrayList(int initialCapacity)构造一个具有指定初始容量的空列表,随着动态的向其中添加元素,其容量可能会动态的增加,每次扩充至原有基础的1.5倍。例如初始化容量为20,总共有50个元素,扩容次数依次为:20->30->45->67。另外,ArrayList并发add()可能出现数组下标越界异常。这是因为ArrayList在扩容的过程中,内部的一致性被破坏,但由于没有锁的保护,另外一个线程访问到了这个不一致的内部状态,导致出现越界问题。
ArrayList和Vector的区别:都使用数组方式存储数据,索引数据快但增删慢,Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。
使用双向链表实现存储,通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList还添加了可以使其用作栈、队列或双端队列的方法。
注:1.ArrayList和LinkedList都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用
2. 如果一直在list的尾部添加元素,当数据量小的时候,ArrayList需要扩容,所以LinkedList的效率就会比较高;当数据量很大的时候,new对象的时间大于扩容的时间,那么ArrayList的效率比LinkedList高。
可以直接将LinkedList作为栈使用,程序中应避免使用java.util.Stack(已过时)。
public class Stack{
private LinkedList list = new LinkedList<>();
public void push(T v){
list.addFirst(v);
}
public T peek(){
return list.getFirst();
}
public T pop(){
return list.removeFirst();
}
public boolean empty(){
return list.isEmpty();
}
public String toString(){
return list.toString();
}
}
LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口。peek()和element()都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常。poll()和remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常。
PriorityQueue调用offer()方法来插入一个对象时,这个对象会在队列中被排序,默认为自然排序,我们可以通过提供自己的Comparator来修改这个顺序。PriorityQueue可确保调用peek()、poll()和remove()方法时获取的元素是队列中优先级最高的元素。
HashMap用来快速访问,是基于数组+链表+红黑树实现的,它的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12
就会进行扩容。
transient Node[] table;
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
}
HashMap初始容量为2^n及每次扩容为原来的2倍:
hash方法原理:
static final int hash(Object key) { //jdk1.8
int h;
// h = key.hashCode() 第一步 取hashCode值
// h ^ (h >>> 16) 第二步 高位参与运算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
put方法:首次put元素需要进行扩容为默认容量16,每次put,先根据key的hash值得到插入的数组索引i,如果索引i中有值,看key是否存在(equals()方法),如果存在就直接覆盖,否则插入链表或者红黑树中。当链表元素个数大于等于8时,链表转换成树结构;若桶中链表元素个数小于等于6时,树结构还原成链表。因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。还有选择6和8,中间有个差值7可以有效防止链表和树频繁转换。
注:使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。
扩容机制:首次put元素需要进行扩容为默认容量16,之后达到阈值就扩容为原来的两倍,接下来就是进行扩容后table的调整:假设扩容前的table大小为2的N次方,而元素的table索引为key的hash值的后N位确定,那么扩容后元素的table索引为其hash值的后N+1位确定,比原来多了一位,因此,若元素hash值第N+1位为0,则不需要进行位置调整,反之如果为1则调整至原索引的两倍位置。扩容或初始化完成后,resize方法返回新的table。注意:JDK1.8之后是先插入后扩容
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment,一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组:
static final class HashEntry {
final K key; // 声明 key 为 final 型
final int hash; // 声明 hash 值为 final 型
volatile V value; // 声明 value 为 volatile 型
final HashEntry next; // 声明 next 为 final 型
HashEntry(K key, int hash, HashEntry next, V value) {
this.key = key;
this.hash = hash;
this.next = next;
this.value = value;
}
}
在ConcurrentHashMap 中,在散列时如果产生“碰撞”,将采用“分离链接法”来处理“碰撞”:把“碰撞”的 HashEntry 对象链接成一个链表。由于 HashEntry 的 next 域为 final 型,所以新节点只能在链表的表头处插入。 下图是在一个空桶中依次插入 A,B,C 三个 HashEntry 对象后的结构图:
图1. 插入三个节点后桶的结构示意图:
注意:由于只能在表头插入,所以链表中节点的顺序和插入的顺序相反。
Segment 类继承于 ReentrantLock 类,从而使得 Segment 对象能充当锁的角色。每个 Segment 对象用来守护其(成员对象 table 中)包含的若干个桶。
TreeMap是一个有序的key-value集合,基于红黑树实现。红黑树的插入、删除、遍历时间复杂度都为O(lgN)。该映射根据其键的自然顺序进行排序,或根据创建映射时提供的 Comparator进行排序,具体取决于使用的构造方法。TreeMap的特性(黑根黑叶路同黑,红黑二色红生黑)
保持元素出入的顺序,同时通过散列提供了快速访问能力。
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。实现Serializable序列化的作用:将对象的状态保存在存储媒体中以便可以在以后重写创建出完全相同的副本;按值将对象从一个从一个应用程序域发向另一个应用程序域。 实现 Serializable接口的作用就是可以把对象存到字节流,然后可以恢复。所以你想如果你的对象没有序列化,怎么才能进行网络传输呢?要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化。如果你不需要分布式应用,那就没必要实现实现序列化。