Java集合类

241 阅读5分钟

1. Java集合类概述

  • Java集合类主要是在java.util包中,主要包括两个顶层接口Map和Collection Java集合类.png

2. Map接口

  • Map接口的主要实现类包含HashMap、HashTable、LinkedHashMap以及TreeMap,存储的是key、map键值对类型的数据

2.1 HashMap

  • 参考:HashMap源码解析
  • 底层数据结构为数组 + 链表 + 红黑树(链表长度大于8)
  • 默认初始容量为16,默认加载因子为0.75,每次扩容都是之前的2倍
  • 存储的元素key和value均可为null
  • 无序性(迭代HashMap的顺序并不是HashMap放置的顺序)
  • 线程不安全(多线程可使用基于CAS+synchronized实现的CopyOnWriteArrayList)

2.2 HashTable

  • 底层数据结构为数组 + 链表
  • 默认初始容量为11,默认加载因子为0.75,每次扩容都是之前的2倍+1
  • 存储的元素key和value均不可为null
  • 线程安全(put和get等方法都是synchronized方法来实现)
  • 提醒:线程安全的HashMap基本都是使用java.util.concurrent包里面的ConcurrentHashMap来实现
protected void rehash() {
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code 防止溢出
    // 每次扩容都是之前的2倍+1
    int newCapacity = (oldCapacity << 1) + 1;
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    table = newMap;

    for (int i = oldCapacity ; i-- > 0 ;) {
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;
            old = old.next;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            e.next = (Entry<K,V>)newMap[index];
            newMap[index] = e;
        }
    }
}

2.3 LinkedHashMap

  • 原理参考:掘金
  • 基本继承HashMap的所有特性
  • 继承自HashMap,可以理解为HashMap + 双向链表,双向链表主要用额外的Entry来保存Node存储的顺序
  • 主要是重写了newNode和newTreeNode在里面加入linkNodeLast方法实现存储顺序的保存
  • HashMap元素的遍历顺序不一定与元素的插入顺序相同(根据数组的index排序),而 LinkedHashMap 则通过遍历双向链表来获取元素,所以遍历顺序在默认条件下等于插入顺序。
  • LinkedHashMap可以通过构造参数accessOrder来指定双向链表是否在元素被访问后改变其在双向链表中的位置,accessOrder默认为false表示顺序链表的排序按插入顺序来排列,为true时会受到读取顺序的影响,读取某元素时将其移到链表的尾部
HashMap<String, String> map = new HashMap<>();
map.put("LTP1", "LTP1");
map.put("LTP2", "LTP2");
map.put("LTP3", "LTP3");
map.put("LTP4", "LTP4");
System.out.println(map);
// hash是复制的hashMap的hash方法,15为默认capacity-1
map.forEach((key, value) -> System.out.println("index:" + (hash(key) & 15) + " ~ " + key + ":" + value));

System.out.println("------------------------");

LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(16, 0.75f, true);
linkedHashMap.put("LTP1", "LTP1");
linkedHashMap.put("LTP2", "LTP2");
linkedHashMap.put("LTP3", "LTP3");
linkedHashMap.put("LTP4", "LTP4");
System.out.println(linkedHashMap);

linkedHashMap.get("LTP2");
linkedHashMap.get("LTP4");
System.out.println(linkedHashMap);
运行结果
{LTP3=LTP3, LTP2=LTP2, LTP1=LTP1, LTP4=LTP4}
index:8 ~ LTP3:LTP3
index:9 ~ LTP2:LTP2
index:10 ~ LTP1:LTP1
index:15 ~ LTP4:LTP4
------------------
{LTP1=LTP1, LTP2=LTP2, LTP3=LTP3, LTP4=LTP4}
{LTP1=LTP1, LTP3=LTP3, LTP2=LTP2, LTP4=LTP4}

2.4 TreeMap

  • 底层数据结构为红黑树
  • 可自定义Comparator排序(构造参数),默认是根据key类型所在类中的compare方法决定
  • key不可为null,value可为null
  • 线程不安全
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(1, "LTP1");
treeMap.put(2, null);
treeMap.put(4, null);
treeMap.put(3, "LTP3");
System.out.println(treeMap);

TreeMap<Integer, String> treeMap2 = new TreeMap<>(new Comparator<Integer>() {
    @Override
    public int compare(Integer t1, Integer t2) {
        return t2 - t1;
    }
});
treeMap2.put(1, "LTP1");
treeMap2.put(2, null);
treeMap2.put(4, null);
treeMap2.put(3, "LTP3");
System.out.println(treeMap2);
执行结果:
{1=LTP1, 2=null, 3=LTP3, 4=null}
{4=null, 3=LTP3, 2=null, 1=LTP1}

3. Collection接口

  • Collection接口的主要包含List、Set以及Queue三个子接口,存储的是非键值对类型的数据

3.1 List接口

  • List接口是Collection接口的子接口,主要包含ArrayList、LinkedList以及Vector
  • List接口定义了有序性、可重复性的规则
  • 扩容多是利用了System.arraycopy这个native方法

3.1.1 ArrayList

  • 底层数据结构是数组
  • 默认初始容量为10,每次扩容都会变成之前的1.5倍
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 每次扩容都是之前的1+0.5=1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

3.1.2 LinkedList

  • 底层数据结构是一个双向链表
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
  • 有序,可重复,可存储null
  • 线程不安全

3.1.3 Vector

  • 底层数据结构是一个数组
  • 默认初始容量为10,扩容量可配置默认扩容2倍
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // capacityIncrement为自行配置的扩容大小,不配置的话每次扩容2倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 有序,可重复,可存储null
  • 线程安全(put和get等方法都是synchronized方法来实现)
  • stack类继承自Vector,当然实现栈有很多种方式,包括Stack、LinkedList、ArrayDeque(均提供了push和pop方法),效率高推荐ArrayDeque,讲究线程安全用Stack

3.2 Set接口

  • Set接口是Collection接口的子接口,主要包含HashSet、TreeSet以及LinkedHashSet
  • Set主要制定了不可重复性的规则
  • Set下的实现类基本都是基于Map下的实现类实现的

3.2.1 HashSet

  • 基于hashMap实现,只存储key,value均为new Object()
  • 基于HashMap存储key的特性,故有以下特性
    • 默认初始容量16,扩容倍数为2
    • 无序,不可重复,可为null
    • 线程不安全
  • 注意:提供了一个基于LinkedHashMap的构造器,专门供LinkedHashSet(HashSet的子类)使用
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

3.2.2 TreeSet

  • 基于TreeMap实现,只存储key,value均为new Object()
  • 基于TreeMap存储key的特性,故有以下特性
    • 可自定义Comparator排序(构造参数),默认是根据key类型所在类中的compare方法决定
    • 不可存储null
    • 线程不安全

3.2.3 LinkedHashSet

  • 继承自HashSet,基于HashSet的一个特殊的构造(new LinkedHashMap)实现的
  • 本质上是基于LinkedHashMap存储key的特性,故有以下特性
    • 有序
    • 不可存储null,值不可重复
    • 线程不安全

3.3 Queue接口

  • 双向出入的队列,可用数组或双向链表实现
  • 子接口为Deque,Deque的常见实现类为ArrayDeque

3.3.1 ArrayDeque

  • 底层数据结构是一个数组(维护了数组的头和尾指针)
  • 默认初始容量是16,容量必须为2^n,每次扩容都是之前的2倍
  • 不能存储null,可重复,有序
  • 可用于实现高效率的栈
  • 注意:JDK11中有较大的变化

4. 常见对比

4.1 HashMap与TreeMap的区别

  • HashMap的Key可为null,TreeMap的key不可为null
  • HashMap存储元素是根据hash&(capacity-1)获得的index排序,而TreeMap则可以自定义排序(构造中传递自定义的comparator实现类),默认是根据Key所在类中的compare方法排序