集合大杂烩

集合大杂烩

  • 本篇博客内容主要是集合内容的介绍,包括:
  • Collection下的List,Set相关的集合
  • Map的相关集合
  • 关于TreeSet和TreeMap内部实现(红黑树)的部分有时间再补上。
  • 整理不易,可累死仙鱼我了,看完有收获的话记得点赞欧~

Collection

1 List

1.1 ArrayList

1.1.1 类说明
  • ArrayList是继承AbstractList抽象类,实现ListRandomAccessCloneablelava.io.Serializable接口的数组队列,相当于动态数组,容量可以动态增长,地址空间连续。

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    
  • 实现RandomAccess接口使ArrayList支持快速随机访问查询

    如下,RandomAccess接口中是没有任何实现的(Cloneable,Serializable也是),就相当于给ArrayList做了一个可以快速随机访问的标识,因为ArrayList的底层实现是Object数组才支持快速随机访问。

    public interface RandomAccess {
    }
    
  • ArrayList不是线程安全的,建议在单线程中使用,多线程情况下可以选择使用Vector(这个都快弃用了)或者CopyOnWirteArrayList。

1.1.1 内部结构
1.1.1.1 初始化
  • 初始化(构造时),默认的数组初始化容量大小为10

    调用无参的构造方法进行只是进行了数组的初始化,这个时候并没有分配容量,当添加第一个元素,调用add(E e)方法时,会初始化数组容量大小为10;

    在这里插入图片描述

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //0
    // Object数组
    transient Object[] elementData;
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    /***************************************************************************************************/
    private static final int DEFAULT_CAPACITY = 10; //10
    private int size;
    // 将指定元素追加到此列表的末尾
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    // 得到最小扩容量
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //小于10则返回默认容量
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            //调用grow方法进行扩容,调用此方法代表已经开始扩容了
            grow(minCapacity);
    }
    
1.1.1.2 扩容
  • 扩容:如当前数组长度为10,来了第十一个元素,就要就行扩容

    // minCapacity 所需的最小容量
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    // 判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            // 扩容的核心方法
            grow(minCapacity);
    }
    // 需要分配的最大数组的大小
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    // ArrayList扩容的核心方法
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        // newCapacity为旧容量oldCapacity的1.5倍,oldCapacity >> 1 等价于 oldCapacity/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 新容量小于最小需求量,则最小需求量为数组的新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 新容量是否大于ArrayList所定义的最大容量
        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);
    }
    // 新容量是否大于ArrayList所定义的最大容量时,比较最小需求量和MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }
    
1.1.1.3 删除元素
  • 删除该列表中指定位置的元素。 将任何后续元素向左移动一位(从其索引中减去一个元素)。

    在这里插入图片描述

    public E remove(int index) {
        rangeCheck(index);
    
        modCount++;
        E oldValue = elementData(index);
    
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    
        return oldValue;
    }
    
1.1.1.4 copyOf()和arraycopy()
  • Arrays.copyOf()和System.arraycopy():

    • 联系:看两者源代码可以发现 copyOf() 内部实际调用了 System.arraycopy()方法

      public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
          @SuppressWarnings("unchecked")
          T[] copy = ((Object)newType == (Object)Object[].class)
              ? (T[]) new Object[newLength]
              : (T[]) Array.newInstance(newType.getComponentType(), newLength);
          System.arraycopy(original, 0, copy, 0,
                           Math.min(original.length, newLength));
          return copy;
      }
      
    • 区别arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 。copyOf() 是系统自动在内部新建一个数组,并返回该数组。

1.2 Vector

1.2.1 类说明
  • Vector继承了AbstractList抽象类,实现了ListRandomAccessCloneablejava.io.Serializable接口,底层实现也是动态数组,和ArrayList不同的是Vector是线程的,Vector中的很多方法使用synchronized关键字进行了同步加锁操作。这个类现在使用的很少了。

    public class Vector<E>
        extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    
  • 其他的内容和ArrayList差不多,不再赘述。

1.2.2 内部结构
1.2.2.1 初始化
  • 调用Vector的无参构造方法,默认的初始化大小为10:注意ArrayList调用无参的构造方法时并没有分配大小,在第一次add的时候,初始化大小为10,而Vector调用无参的构造方法时,直接初始化数组大小为10,注意区分。

    public Vector() {
        this(10);
    }
    
1.2.2.2 扩容
  • Vector的扩容和ArrayList的扩容基本一致

    // 调用add(E e)/add(int index, E element)都会进行是否要扩容的判断,这里仅以add(E e)为例
    public synchronized boolean add(E e) {
        modCount++;
        // 判断是否普需要扩容
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    // 判断是否需要扩容
    private void ensureCapacityHelper(int minCapacity) {
        // 若最小期望容量比实际的数组容量大,说明数组长度不够,就需要扩容
        if (minCapacity - elementData.length > 0)
            // 扩容的核心方法
            grow(minCapacity);
    }
    // 扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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);
    }
    
1.2.2.3 删除元素
  • 删除元素和ArrayList基本一致

    public synchronized E remove(int index) {
        modCount++;
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        E oldValue = elementData(index);
    
        int numMoved = elementCount - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--elementCount] = null; // Let gc do its work
    
        return oldValue;
    }
    

1.3 LinkedList

1.3.1 类说明
  • LinkedList是一个继承了AbstractSequentialList抽象类实现了ListDequeCloneablejava.io.Serializable双端链表(双向链表)。

    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
    
  • LinkedList底层的链表结构(双向链表,长度没有限制,在JDK1.7之前底层为双向循环链表,在JDK1.7的时候去掉了循环,双向循环链表和双向链表的区别就在于首尾节点是否相连)使它支持高效的插入和删除操作,另外它实现了Deque接口(Deque接口继承了Queue接口),使得LinkedList类也具有队列的特性;

  • LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:

    List list=Collections.synchronizedList(new LinkedList(...));
    
1.3.2 内部结构
  • 底层是双向链表,如下图:

    在这里插入图片描述

  • LinkedList源码中有一个内部私有类,描述了双端链表的节点Node,可知Node包含三个属性:前驱节点节点值后继节点,如下:

    private static class Node<E> {
        E item;//节点值
        Node<E> next;//后继节点
        Node<E> prev;//前驱节点
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
    
  • 链表的操作头插啊尾插啊,中间插入啊,删除啊等就不介绍了,都是比较基础的数据结构的知识。

2 Set

2.1 HashSet

2.1.1 类说明
  • HashSet继承AbstractSet抽象类,实现SetCloneablejava.io.Serializable接口。

    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    
  • 存放的元素是无序的,不可重复的,它维持它自己的内部排序,所以随机访问没有任何意义。允许存放null值,底层封装HashMap(是基于HashMap实现的,最终依然使用数组存储数据)所以也是不同步的线程不安全的。

    // 保障线程安全
    Collections.synchronizedSet(new HashSet(...));
    
2.1.2 内部结构
  • 就是一个元素不重复的HashMap(HashMap的详细介绍在下面),可以看到HashSet的构造方法是直接new的HashMap。

    private transient HashMap<E,Object> map;
    public HashSet() {
        map = new HashMap<>();
    }
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        // 看到LinkedHashMap莫慌,LinkedHashMap的构造也是调用父类HashMap的构造实现的
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
    
  • 那么,是如何保障元素的不重复呢?

    很简单,HashMap中的key保障了唯一,使所有的value对应一个内部的对象PRESENT即可。

    // 定义value对应的唯一内部对象
    private static final Object PRESENT = new Object();
    // HashMap的put方法如果该位置已经存在一个一样的Key(==或者equals相等),会用新的value替换原来的旧的value,并且返回旧的value,所以对于HashSet而言第一次插入返回null就代表成功,以后再次插入同样的元素,返回的是一个对象,表示已经存在这样的元素了,插入失败!
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    

2.2 LinkedHashSet

2.2.1 类说明
  • LinkedHashSet继承了HashSet类,实现了SetCloneablejava.io.Serializable接口。

    public class LinkedHashSet<E>
        extends HashSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    
  • 因为实现了Set接口,所以具有Set集合元素不重复的特点,同时具有可预测的迭代顺序,也就是我们插入的顺序(默认)。

  • LinkedHashSet源码中的方法非常简单,是基于LinkedHashMap实现的(LinkedHashMap下面有详细的介绍)。

  • LinkedHashSet是有序的,它是按照插入的顺序排序的。

2.2.2 内部结构
2.2.2.1 初始化
  • 调用LinkedHashSet默认的构造方法(无参),可以得知,初始化大小为16。

    public LinkedHashSet() {
        super(16, .75f, true);
    }
    
  • 基于LinkedHashMap实现的证据:

    打开LinkedHashSet的源码你可能首先会有一点懵逼,因为里面一共就一个静态常量,四个构造方法和一个重写的切分的方法。而且构造方法上是super,也看不出是基于什么父类实现的,如下:

    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }
    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }
    public LinkedHashSet() {
        super(16, .75f, true);
    }
    public LinkedHashSet(Collection<? extends E> c) {
        super(Math.max(2*c.size(), 11), .75f, true);
        addAll(c);
    }
    

    但是别慌,按住Ctrl,点进super,你会发现都是基于同一个方法实现的,如下,可以看出,是直接new了一个LinkedHashMap,所以现在知道为什么说是基于LinkedHashMap实现了的不。

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
    

    同时,基于好奇心我又点进了LinkedHashMap,看到了下面的类:

    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }
    

    看到了一句很关键的话:accessOrder = false,这里写死了就让accessOrder为false,也就是说,LinkedHashSet是不支持按访问顺序对元素排序的,只能按插入顺序排序

  • 后面会介绍LinkedHashMap,就不赘述了,好,下一个!

2.3 TreeSet

2.3.1 类说明
  • TreeSet继承了AbstractSet抽象类,实现了NavigableSetCloneablejava.io.Serializable接口,就像HashSet是基于HashMap实现的一样,TreeSet是基于TreeMap实现的(后面有TreeMap的介绍),可以说TreeSet的底层是自平衡的排序二叉树(红黑树)。

    public class TreeSet<E> extends AbstractSet<E>
        implements NavigableSet<E>, Cloneable, java.io.Serializable
    
  • TreeSet实现了NavigableSet接口,而NavigableSet接口继承SortedSet接口,所以 是一个有序的集合,它的作用是提供有序的Set集合。

  • TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序

2.3.2 内部结构

Map

1 HashMap

1.1 类说明

  • HashMap继承AbstractMap抽象类,实现MapCloneableSerializable接口,主要用于存放键值对。

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable
    
  • 在JDK1.8之前的HashMap是由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(拉链法解决冲突),在JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认是8)时,将链表转化为红黑树,可以减少搜索时间。

  • HashMap是非线程安全的,若要保证线程安全,可以使用 ConcurrentHashMap

  • HashMap可以存储一个Key为null,多个value为null的元素。

1.2 内部结构:

1.2.1 初始化
  • 先看一下HashMap的无参构造方法,如下:

    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    

    乍一看HashMap的无参构造方法只是简单的指定了哈希表的加载因子,并没有定义初始化容量,但是这个构造方法上面的注释说:初始化容量大小为16,于是找啊找,终于找到了,原来是直接定义了一个常量,默认为16。

    /**
     * 1 << 4 :表示1左移4为,即2的4次方,那可不就是16嘛
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    
1.2.2 hash()的变化
  • hash():先说一下hash()方法的变化,从下面的代码可以看出,1.8之后的性能更高,毕竟只有一次>>>

    JDK1.8之前的hash():

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    

    JDK1.8之后的hash():

    // key.hashCode()返回散列值,即hashcode
    // >>>:无符号右移,忽略符号位,空位都以0补齐
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
1.2.3 存储结构
  • 1.8之后的存储结构:数组+链表+红黑树,大致如下图:

    在这里插入图片描述

  • 数组:Node<K,V>[] table,即哈希桶数组,存储了头节点的table数组,HashMap就是使用哈希表实现的:

    transient Node<K,V>[] table;
    // 实现了Map.Entry接口,本质上就是一个键值对
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
    
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
    
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
    	// 
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
    
1.2.4 put()方法
  • 以向HashMap中添加数据为例介绍以下几个操作:插入是在链表的尾部插入

    在这里插入图片描述

// 调用put()方法
  public V put(K key, V value) {
      return putVal(hash(key), key, value, false, true);
  }
  // 调用putVal()方法之前调用了hash(),获取key对应的hashcode
  static final int hash(Object key) {
      int h;
      // 可以看到 取到hashcode后 和自己本身的高16位做了亦或操作,是为了使hashcode的生成更加散列
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }
  // 接下来调用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是否进行了初始化
      if ((tab = table) == null || (n = tab.length) == 0)
          // table初始化,即扩容
          n = (tab = resize()).length;
      // 将新生成的hashcode和table的长度n-1做&操作,获取该节点在数据中的下标
      // 如果数组中该位置没有数据,则根据传入的值生成新node节点
      if ((p = tab[i = (n - 1) & hash]) == null)
          tab[i] = newNode(hash, key, value, null);
      else { // 该位置上已经存在其他节点(上面if操作已经对p进行了赋值,为tab[(n-1)&hash])
          Node<K,V> e; K k;
          // 判断已经存在的节点数据和新传入的节点key和hash值是否相同
          if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
              e = p;
          // 判断是否是一个树节点,若是,直接在红黑树中插入键值对
          else if (p instanceof TreeNode)
              e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
          else { //  已存在节点的key和新增的key不相同,发生hash冲突
              // 将新节点放入该节点链表的最后
              for (int binCount = 0; ; ++binCount) {
                  if ((e = p.next) == null) { // 查找到链表的最后一个节点后,进行赋值操作
                      p.next = newNode(hash, key, value, null);
                      if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                          // 在链表长度超过一定长度时,将链表转红黑树存储
                          treeifyBin(tab, hash);
                      break;
                  }
                  // 在将新节点放置在链表最后位置后,终止循环
                  if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                      break;
                  p = e;
              }
          }
          // 定位到put数据位置,进行value赋值操作
          if (e != null) { // existing mapping for key
              V oldValue = e.value; // 获取key 对应节点的原来数据
              if (!onlyIfAbsent || oldValue == null)
                  e.value = value; // 重新赋值
              afterNodeAccess(e);
              return oldValue; // 返回节点原数据
          }
      }
      // 没有发生hash冲突,数组的某个位置被占用,hashMap 结构修改次数 
      ++modCount;
      // 数组已经被占用数量++,自增后判断当前数组是否需要扩容
      if (++size > threshold)
          resize();
      afterNodeInsertion(evict);// LinkedHashMap 方法
      return null;
  }
  // TreeNode节点
  static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
      TreeNode<K,V> parent;  // red-black tree links
      TreeNode<K,V> left;
      TreeNode<K,V> right;
      TreeNode<K,V> prev;    // needed to unlink next upon deletion
      boolean red;
      TreeNode(int hash, K key, V val, Node<K,V> next) {
          super(hash, key, val, next);
      }
      /**
           * Returns root of tree containing this node.
           */
      final TreeNode<K,V> root() {
          for (TreeNode<K,V> r = this, p;;) {
              if ((p = r.parent) == null)
                  return r;
              r = p;
          }
      }
  }
1.2.5 get()方法
  • 那么又是如何获取数据的呢?

    // 调用get()方法,通过key获取
    public V get(Object key) {
        Node<K,V> e;
        // 计算key对应的hashcode值,去获取节点
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    // 获取节点
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 数组不为空,长度部位0并且有元素
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            // 若为第一个元素,直接返回
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 不是第一个元素
            if ((e = first.next) != null) {
                // 判断是否为树节点
                if (first instanceof TreeNode)
                    // 若是,直接获取节点并返回
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                // 不是树节点的话,遍历下面的节点,匹配则返回
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        // 否则,没有这个节点,返回null
        return null;
    }
    // 获取树节点,源码就放到这里,不再深入了
    final TreeNode<K,V> getTreeNode(int h, Object k) {
        return ((parent != null) ? root() : this).find(h, k, null);
    }
    

2 Hashtable

2.1 类说明

  • Hashtable继承了Dictionary抽象类,实现了MapCloneablejava.io.Serializable接口,虽然和HashMap同样都属于散列表,但使用的情况很少。

    public class Hashtable<K,V>
        extends Dictionary<K,V>
        implements Map<K,V>, Cloneable, java.io.Serializables
    
  • 唯一一点优于HashMap是:Hashtable是线程安全的。但是想在多线程下使用散列表的话还是建议使用上面说过的ConcurrentHashMap来替换Hashtable。

  • Hashtable不允许key或者value为null。

2.2 内部结构

2.2.1 初始化
  • 初始化(无参构造方法):Hashtable默认capacity是11,默认负载因子是0.75f。

    public Hashtable() {
        this(11, 0.75f);
    }
    
2.2.2 存储结构
  • Hashtable的底层是:Entry数组+链表实现的,结构如下图:

    private transient Entry<?,?>[] table;
    private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Entry<K,V> next;
    
        protected Entry(int hash, K key, V value, Entry<K,V> next) {
            this.hash = hash;
            this.key =  key;
            this.value = value;
            this.next = next;
        }
    
        @SuppressWarnings("unchecked")
        protected Object clone() {
            return new Entry<>(hash, key, value,
                               (next==null ? null : (Entry<K,V>) next.clone()));
        }
    
        // Map.Entry Ops
    
        public K getKey() {
            return key;
        }
    
        public V getValue() {
            return value;
        }
    
        public V setValue(V value) {
            if (value == null)
                throw new NullPointerException();
    
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
    
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
    
            return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
                (value==null ? e.getValue()==null : value.equals(e.getValue()));
        }
    
        public int hashCode() {
            return hash ^ Objects.hashCode(value);
        }
    
        public String toString() {
            return key.toString()+"="+value.toString();
        }
    }
    
2.2.3 put()方法
  • 与HashMap不同的是这里会对value进行是否为空的校验,节点为Entry节点,没有红黑树(看着很像JDK1.8之前的HashMap)

  • 并且HashMap在链表的尾部插入元素,而Hashtable在链表的头部插入元素,可能是因为没有HashMap的红黑树机制,如果要在尾部添加元素的话可能需要遍历大量的数据(链表的长度没有限制)。

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }
        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
        addEntry(hash, key, value, index);
        return null;
    }
    private void addEntry(int hash, K key, V value, int index) {
        modCount++;
    
        Entry<?,?> tab[] = table;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();
    
            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }
    
        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }
    
2.2.4 get()方法
  • 根据key获取数据的方法如下,是线程安全的:

    public synchronized V get(Object key) {
        // 获得hashtable数组
        Entry<?,?> tab[] = table;
        // 获得key对应的hashcode
        int hash = key.hashCode();
        // 根据当前key的hashcode计算出在hashtable数组中对应的下标
        int index = (hash & 0x7FFFFFFF) % tab.length;
        // 获取对应的链表,遍历找到匹配的hash取值返回
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        // 否则返回null
        return null;
    }
    

3 LinkedHashMap

3.1 类说明

  • LinkedHashMap继承了HashMap类,实现了Map接口。可以说LinkedHashMap是基于HashMap和双向链表来实现的

    public class LinkedHashMap<K,V>
        extends HashMap<K,V>
        implements Map<K,V>
    
  • LinkedHashMap是线程不安全的。

  • 我们知道HashMap是无序的,内部是数组+单链表,而LinkedHashMap由于底层是数组+双向链表,从而保障了LinkedHashMap是有序的,且默认为插入顺序(无参构造默认accessOrder = false)。

3.2 内部结构

3.2.1 初始化
  • LinkedHashMap使用父类的构造方法初始化,我们在上面说过,其父类HashMap调用无参的构造方法进行初始化的时候,默认的初始化大小为16,那么LinkedHashMap调用无参方法进行初始化的时候的初始大小也是16

    public LinkedHashMap() {
        super();
        // true表示访问顺序,false表示插入顺序
        accessOrder = false;
    }
    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    // 多了一个前驱后继的Entry
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    
  • 上面的初始化过程可用下面的图表示:

    在这里插入图片描述

3.2.2 存储结构:
  • 先看一下HashMap的存储结构:

    在这里插入图片描述

  • 再看一下LinkedHashMap的存储结构对比一下区别:

    在这里插入图片描述

3.2.3 put()方法
  • LinkedHashMap并没有重写自己的put()方法,而是直接调用父类HashMap中的put方法,只是重写了newNode()方法,代码如下:

    // 父类HashMap中的方法
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    // 父类HashMap中的方法
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            // LinkedHashMap中重写了newNode()方法
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else { 
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        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);// LinkedHashMap 方法
        return null;
    }
    // 父类HashMap中的newNode()方法
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
        return new Node<>(hash, key, value, next);
    }
    // LinkedHashMap中重写了此方法
    Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }
    // LinkedHashMap中实现
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            // 将新节点 p 接在链表尾部
            p.before = last;
            last.after = p;
        }
    }
    
3.2.4 get()方法
  • LinkedHashMap重写了get方法,默认是按照插入顺序查询

    // LinkedHashMap 中覆盖
    public V get(Object key) {
        Node<K,V> e;
        // 默认是按照插入顺序查询的
        if ((e = getNode(hash(key), key)) == null)
            return null;
        // 如果为true表示按照访问顺序查询
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }
    // 父类HashMap中的方法
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
    // LinkedHashMap中覆盖 
    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }
    

4 TreeMap

4.1 类结构

  • TreeMap继承了AbstractMap抽象类,实现了NavigableMapCloneablejava.io.Serializable接口。

    public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable
    
  • 是一个有序的,非同步的,基于红黑树实现,该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

4.2 内部结构

这里是小仙鱼?~记得给孩子点赞哇!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值