java集合专题
[### java集合专题
list
-
Arraylist(不安全) 底层是Object类型的数组 , 可以放null , 调用无参构造器,默认从0扩容到10,之后每次扩容1.5倍 ; 调用有参构造器,先按指定大小扩容,之后按照1.5倍扩容
-
Vector(线程安全) , 底层依然是数组,内部每个操作都带synchronized , 如果是无参构造器,默认10, 满后就按2倍扩容,有参构造器,就按照指定大小,之后按照2倍扩容
-
LinkedList (不安全) 底层是双向链表,可放重复元素,可存储null值
set --不重复元素,底层都是map
-
hashSet(无序),底层是hashmap,可存放null,索引位置取决于hash ,不保证存入顺序和取出顺序一致 , 扩容机制见hashmap , 键值是一个静态对象P1RESENT,不可改变
-
LinkedHashSet(有序) 继承hashSet , 底层是LinkedHashMap,维护了数组加双向链表,插入顺序和遍历顺序一致
-
treeSet (支持排序) , 底层是treeMap,使用无参构造器创建TreeSet时,元素无序存放,想要有序需要传入一个比较器,重写其compare方法
TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { // 下面调用String的compareTo方法进行字符串大小比较 return ((String) o2).compareTo((String) o1); // 规则1 return ((String) o2).length() - ((String) o1).length(); // 规则2 } });
双列集合map
-
hashMap
1.8及之后的hashmap扩容机制
-
HashSet底层是HashMap,map里维护了一个Node类型的table数组 , 第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75 = 12
-
意思就是如果table 数组使用到了临界值12,虽然还剩下四个,但是table数组就会扩容到16* 2 =32,新的临界值就是32*0.75 = 24,依次类推
-
这里需要注意,临界值指的是节点数量,即使只占用table的两个位置,即拉了两条链 , 当总节点数到达12的时候, table数组的长度就会扩容2倍
-
在java8中 ,满足以下两个条件链表就会树化成红黑树
-
- 一条链表的元素个数已经到8个了
- table数组长度达到64了
-
如果仅仅是链表元素个数到8 , 数组长度还没到64 , 那么就不会树化,而是数组长度会扩容为2倍 , 然后重新哈希,当然链表的长度可能超过8
-
但是如果红黑树的元素个数小于6 那么就会还原成链表, 当红黑树的元素个数不小于32的时候才会再次扩容。
// 添加的核心方法,putVal final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { // 这是一些辅助变量 Node<K,V>[] tab; Node<K,V> p; int n, i; //table是hash数组表 ,如果table长度为0,就执行第一次扩容到16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; /* * 根据key值和hash算法(h >>> 16无符号右移),计算该key值应该放在table表的哪一个位置,并且把这个位置的对象赋给p * 判断p是否为空,如果是空的,说明table的对应位置还没有存放元素, 那么就创建一个node放在这个位置 */ if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; /* * p.hash : 当前索引位置对象的hash值 ; hash : 要添加的key的hash * 如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样 * 并且满足 下面两个条件之一 ,就不能加入 * ① 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象 * ② p指向的node元素的equals()和准备加入的key的equals()相同 */ if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 判断 p 是不是一颗红黑树 , 如果是就调用putTreeVal添加节点 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { 如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较(死循环) for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 若果该链表的长度达到8了,就调用treeifyBin方法树化成红黑树 ,转之前还需要判断 table长度是否超过64 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 比较过程中,如果有hash和equals()相同的情况,就直接 break if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
hashmap1.7和1.8的区别
-
底层数据结构不一样,1.7是数组+链表,1.8则是**数组+链表+红黑树结构
-
JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法
那么他们为什么要这样做呢?
因为JDK1.7是用单链表进行的纵向延伸,当采用头插法就是能够提高插入的效率,但是在并发情况下也会容易出现循环链表问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现循环链表的问题。
循环链表问题:假设同时有两个线程同时执行触发了同一个HashMap对象的resize()过程,他们同时进入到了transfer()方法中。。。
-
1.7中hash值是可变的,1.8中hash是final修饰,不可变
-
扩容机制:1.7中是先扩容后插入,1.8中是先插入再扩容
-
-
hashTable(线程安全) ,底层方法都加了synchronized , 底层有数组 Hashtable$Entry[] 初始化大小为 11 , 临界值 threshold 8 = 11 * 0.75 , 扩容机制:左移1位再加1 , 键和值都不能为null
-
treeMap(支持排序) ,使用默认的构造器创建treeMap是无序的 , 想要有序的话,和treeSet的机制相同(比较的依然是key值)
-
propryties , Properties类继承Hashtable类井且实现了Map接口,也是使用一种键值对的形式来保存数据。
collections
java.util.Collections 是一个包装类(工具类/帮助类)。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于Java的Collection框架。
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对 List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
- sort(List, Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List, int, int):将指定 list 集合中的i 处元素和j 处元素进行交换