Java集合框架主要分为两大类:Collection(集合)和Map(映射),集合作为数组的补充,能够存储多种数据类型,并支持动态扩容
Map(键值对集合)
- key全局唯一,允许键为null(TreeMap除外)
通用场景:首选HashMap
并发环境:ConcurrentHashMap
需要保持插入顺序:LinkedHashMap
需要排序:TreeMap
HashMap
- 基于哈希表实现,不保证元素的顺序
- 内部查询原理:
JDK1.8之前,采用Node(单链表)数组来存储元素
当hash碰撞的元素较多时,通过key查找value时遍历Node链表的时间复杂度为O(N),查询效率越来越低
Node(单链表)是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对),其本身包含了:KEY、VALUE、键的hash、下一个Node节点
JDK1.8开始,采用单链表+红黑树(切换阈值8)数组来存储元素。
当链表长度超过8时,将单链表转换为红黑树,通过红黑树查询元素的时间复杂度为O(logN),大大提升查找效率
TreeNode(红黑树)是HahsMap的一个内部类,实现了LinkedHashMap.Entry接口,其本身包含:父节点、左子树、右子树、上一个同级节点、属性颜色
- 扩容原理:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// 默认初始容大小:1的二进制向左位移4位,即00000001向左位移4位后00010000,转化成十进制为1乘以2的4次方等于16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 加载因子,当存储的元素个数大于数组长度乘以加载因子时触发扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 链表转成红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;
// 红黑树转为链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
// 最小树形化容量阈值:当哈希表中的容量 > 该值时才允许树形化链表(即将链表转换成红黑树)
// 否则,若桶内元素太多时,则直接扩容,而不是树形化
static final int MIN_TREEIFY_CAPACITY = 64;
// key的哈希算法
static final int hash(Object key) {
int h;
// key的hash值对容器大小进行取模运算,运算结果决定了当前key所属数组的下标
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
加载因子是HashMap在其容量自动增加之前可以达到多满的一种尺度。
负载因子越大表示散列表的装填程度越高,对空间的利用更充分,但查找效率会降低;
负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费,但查询效率会提升
当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值(数组的长度乘以加载因子)就要自动扩容,大小为原来的2倍
LinkedHashMap
- 继承自HashMap,内部维护一个双向链表来记录元素的插入顺序
TreeMap
- 基于红黑树实现,按照键的自然顺序或指定的比较器顺序进行排序
- 提供了丰富的导航方法(如firstKey, lastKey, headMap, tailMap等)
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
- 自定义排序
TreeMap<String,Object> treeMap = new TreeMap<String,Object>(Comparator.reverseOrder());
treeMap.put("a", "a");
treeMap.put("b", "b");
treeMap.put("c", "c");
// {"c":"c","b":"b","a":"a"} 倒叙排列
System.out.println(JSON.toJSONString(treeMap));
TreeMap<String,Object> treeMap = new TreeMap<String,Object>(Comparator.naturalOrder());
treeMap.put("b", "b");
treeMap.put("a", "a");
treeMap.put("c", "c");
// {"a":"a","b":"b","c":"c"} 正序排列
System.out.println(JSON.toJSONString(treeMap));
Collection
List 接口(有序、可重复)
ArrayList
- ArrayList类内部使用Object数组存储元素,Vector和ArrayList类似,只是其内部方法都添加了synchronized关键字
- ArrayList实现了RandomAccess接口,该接口提供了通过索引快速查找元素的能力【适合做查询操作】
- 自定义排序
List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("c");
stringList.add("b");
// TODO 默认升序排列 ["a","b","c"]
stringList.sort(Comparator.naturalOrder());
// TODO 倒序排列 ["c","b","a"]
stringList.sort(Comparator.reverseOrder());
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("张三", 20));
studentList.add(new Student("李四", 22));
studentList.add(new Student("王五", 24));
studentList.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getAge() - o1.getAge(); // 倒序
}
});
LinkedList
- LinkedList类内部使用Node链表存储元素,每个节点除了包含存储自身数据外,还存储前、后两个节点的引用
- LinkedList实现了Deque接口,该接口提供了从集合两端进行队列操作的能力【适合做添加、删除操作】
Set接口
HashSet(无序、不重复)
- HashSet是通过HashMap实现元素去重,存储的元素是
LinkedHashSet(有序、不重复)
- LinkedHashSet继承HashSet类,内通过一个单链表来记录添加元素时的顺序,所以存储的元素是
TreeSet
- 自定义排序
TreeSet<String> treeSet = new TreeSet<String>(Comparator.naturalOrder());
treeSet.add("c");
treeSet.add("a");
treeSet.add("b");
// ["a","b","c"] 正序排列
System.out.println(JSON.toJSON(treeSet));
TreeSet<String> treeSet = new TreeSet<String>(Comparator.reverseOrder());
treeSet.add("c");
treeSet.add("a");
treeSet.add("b");
// ["c","b","a"] 倒序排列
System.out.println(JSON.toJSON(treeSet));
TreeSet<Student> treeSet = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按照年龄倒叙
return o2.getAge() - o1.getAge();
}
});
treeSet.add(new Student("张三", 20));
treeSet.add(new Student("李四", 22));
treeSet.add(new Student("王五", 24));
// [{"name":"王五","age":24},{"name":"李四","age":22},{"name":"张三","age":20}] 倒序排列
System.out.println(JSON.toJSON(treeSet));