Java的集合有两个根接口:Collection接口、Map接口
1.Collection接口下的子接口与子类:
Collection接口常用方法:
1.1 Set接口
TreeSet:基于红黑树实现,支持有序操作。
红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。
在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 结点是红色或黑色。
性质2. 根结点是黑色。
性质3. 所有叶子都是黑色。(叶子是NIL结点)
性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
性质5. 从任一节结点其每个叶子的所有路径都包含相同数目的黑色结点。
红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。
使用TreeSet存储对象,这个对象的类要实现 Comparable接口,并重写CompareTo()方法。或者在调用TreeSet构造函数的时候,传入一个实现了Comparator接口的比较器。
TreeSet底层使用TreeMap的key来实现。
HashSet:基于哈希表实现,支持快速查找,但不支持有序操作。
hashSet底层使用“数组 + 链表/红黑树”的数据结构存储。
判断元素重复依靠两个方法:1、hashCode() 2、equals()。
HashSet底层使用的是HashMap,具体来说是使用的HashMap的key来存储的。
LinkedHashSet:具有HashSet的查找效率,并且内部使用双向链表维护元素的插入顺序。
1.2 List接口
List接口常用方法:
ArrayList:基于动态数组实现,支持随机访问。
扩容机制:初始化时是空数组,容量为0。当添加第一个元素时,容量扩展到10。当下次扩容时,容量变为原来容量的1.5倍。
List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); //list转数组 Integer[] aryIntegers = list.toArray(new Integer[0]); //数组转list,但这个list的长度固定,不能添加和删除 List<Integer> nList = Arrays.asList(aryIntegers);
Vector:和ArrayList类似,线程安全。
LinkedList:基于双向链表实现,只能顺序访问。
1.3 Queue接口
LinkedList:基于双向链表实现,只能顺序访问。可以用做栈、队列和双向队列。
PriorityQueue:基于堆结构实现,可以用来实现优先队列。
2.Map接口下的子接口与子类:
Map接口常用方法:
Map<String, Integer> map = new HashMap<String, Integer>(); System.out.println(map.size()); map.put("a", 1);//将对象存入集合中,key重复则覆盖原值 map.get("a");//根据健获取对应的值 map.keySet();//返回所有key map.values();//返回包含所有值的Collection集合 map.entrySet();//健值匹配的Set集合
TreeMap:基于红黑树实现。
HashMap:基于哈希表实现。线程不安全,允许null作为key和value。
新创建的map容量为0,当添加第一个元素时,默认容量16。
加载因子0.75,表示当元素数达到当前最大容量的75%时,进行扩容。每次扩容容量变为之前的2倍(左移1位)。
使用数组 + 链表/红黑树 实现。
如果链表的高度到8,数组的长度超过64,则将链表转化为红黑树;长度低于6,则将红黑树转为链表。(JDK1.8+)
JDK1.8之前是头插法,JDK1.8之后是尾插法。
HashTable:和HashMap类似,线程安全。已过时,建议使用ConcurrentHashMap代替。不允许使用null作为key和value。
LinkedHashMap:使用双向链表维护元素顺序,顺序为插入顺序或LRU顺序。
Collections工具列提供的方法:
线程不安全集合转成线程安全集合的方法:
3.线程安全的集合:
3.1 CopyOnWriteArrayList:
线程安全,写有锁,读无锁,读写之间不阻塞,性能优于读写锁。写入时先copy一份副本,再添加新元素,最后替换引用。
1 //演示CopyOnWriteArrayList的使用方式 2 public class demo01 { 3 public static void main(String[] args) { 4 CopyOnWriteArrayList<String> list= new CopyOnWriteArrayList<String>(); 5 ExecutorService eService = Executors.newFixedThreadPool(5); 6 for(int i=0;i<5;i++) { 7 eService.submit(new Runnable() { 8 @Override 9 public void run() { 10 for(int j=0;j<10;j++) { 11 list.add(Thread.currentThread().getName()+"|"+new Random().nextInt(100)); 12 } 13 } 14 }); 15 } 16 eService.shutdown(); 17 while(!eService.isTerminated()) { 18 } 19 20 for(String str:list) { 21 System.out.println(str); 22 } 23 System.out.println(list.size()); 24 } 25 }
3.2CopyOnWriteArraySet:
线程安全的Set,底层使用CopyOnWriteArrayList实现(有序)。使用addIfAbsent()添加元素,会遍历数组。
如存在元素,则不添加(扔掉副本)。
1 //CopyOnWriteArraySet的使用 2 public class demo02 { 3 public static void main(String[] args) { 4 CopyOnWriteArraySet<String> copyOnWriteArraySet = new CopyOnWriteArraySet<String>(); 5 copyOnWriteArraySet.add("a"); 6 copyOnWriteArraySet.add("a"); 7 copyOnWriteArraySet.add("b"); 8 copyOnWriteArraySet.add("c"); 9 10 System.out.println("元素个数:"+copyOnWriteArraySet.size()); 11 System.out.println(copyOnWriteArraySet.toString()); 12 } 13 }
3.3Queue接口:
常用方法:
抛出异常的有:
boolean add(E e); //顺序添加一个元素(达到上限后再添加会报异常)
E remove(); //获得第一个元素并移除(队列没有元素,调用会报异常)
E element(); //获得第一个元素但不移除(队列没有元素,则会报异常)
返回特殊值:(推荐使用)
boolean offer(E e); //顺序添加一个元素(达到上限后再添加会返回false)
E poll(); //获得第一个元素并移除(队列没有元素,调用会返回null)
E peek(); //获得第一个元素但不移除(队列没有元素,则返回null)
3.4ConcurrentLinkedQueue:
线程安全、可高效读写的队列,高并发下性能最好的队列。
无锁、使用CAS算法。由硬件支持,效率高。
CAS算法原理:
V:要更新的变量
E:预期值
N新值
先从V复制值到E,在更新时,判断V是否与E相等,如果V不等于E,则表示原值V被修改了,则取消当前操作。
3.5BlockingQueue接口:
阻塞队列,增加了两个方法,线程状态为无限期等待。
void put(E e); //将指定元素插入此队列中,如果没有可用空间,则等待。
E take(); //获取并移除此队列头部元素,如果没有可用元素,则等待。
可用于解决“生产者、消费者”问题。
实现类:
ArrayBlockingQueue,数组结构实现,有界队列(手动指定数组大小)
LinkedBlockingQueue,链表结构实现,有界队列(默认上限Integer.MAX_VALUE)
下面的代码使用ArrayBlockingQueue实现“生产者、消费者”问题:
1 public class demo03 { 2 public static void main(String[] args) { 3 ArrayBlockingQueue<Integer> arrayBlockingQueue = new ArrayBlockingQueue<>(6); 4 5 Runnable runnable1 = new Runnable() { 6 @Override 7 public void run() { 8 for(int i=0;i<20;i++) { 9 try { 10 arrayBlockingQueue.put(i); 11 System.out.println("生产了:"+i); 12 } catch (InterruptedException e) { 13 // TODO Auto-generated catch block 14 e.printStackTrace(); 15 } 16 } 17 } 18 }; 19 20 Runnable runnable2 = new Runnable() { 21 @Override 22 public void run() { 23 for(int i=0;i<20;i++) { 24 try { 25 Integer num = arrayBlockingQueue.take(); 26 System.out.println("消费了:"+num); 27 } catch (InterruptedException e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 31 } 32 } 33 }; 34 35 new Thread(runnable1,"生产者").start(); 36 new Thread(runnable2,"消费者").start(); 37 } 38 }
4.线程安全的Map实现类:
4.1ConcurrentHashMap:
在JDK1.8之前,使用分段锁设计。
初始容量默认为16段(Segment),。
每一段都是一个独立的Hash表。
不对整个Map加锁,而实为每个Segment单独加锁。
当多个对象存入同一个Segment时,才需要互斥。
最理想状态为16个对象分别存入16个Segment,并行数量16.
使用方式与HashMap无异。
在JDK1.8之后,使用CAS算法来实现。