Java集合框架(Collection\List\Set\Map等) -- 基于jdk8源码分析

集合

概念

对象的容器,定义了对多个对象进行操作的常用方法,可以实现数组的功能。存在于java.util.*

集合和数组的区别

  • 数组的长度固定,集合的长度不固定
  • 数组可以存储基本类型和引用类型,而集合只能存储引用类型

集合体系图(Collection\Map)

单列集合(Collection)
在这里插入图片描述
双列集合(Map)
在这里插入图片描述

一、Collection接口

Collection接口实现类的特点

  1. collection实现子类可以存放多个元素,每个元素可以是Object
  2. Collection的实现类,有些可以存放重复元素,有些不可以
  3. Collection的实现类,有些是有序的(List),有些是无序的(Set)
  4. Collection接口没有直接的实现子类,是通过他的子接口 List 和 Set 来实现的

Collection接口的常用方法

在这里插入图片描述
补充Iterator接口,用于迭代遍历集合元素
在这里插入图片描述

二、List接口

List接口的特点

  1. List集合类中元素有序(即添加和取出顺序一致)、且可以重复
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引
  3. 常有的实现类有:ArrayList、LinkedList、Vector

List接口的常用方法

在这里插入图片描述

(一) ArrayList底层结构和源码分析(重点)

注意事项

  1. ArrayList可以加入null值,没有数量限制
  2. ArrayList是由数组来实现数据存储的
  3. ArrayList是线程不安全的,如果再多线程的情况下,可以考虑使用Vector。

ArrayList扩容机制(重点)

  1. ArrayList中维护了一个Object类型的数组elementData transient Object[] elementData;(transient 表示当前属性不会被序列化)
  2. 当创建了ArrayList对象时,如果使用的是无参构造器,则初始的elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
  3. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容为elementData的1.5倍

首先重点对下面要用到的一些变量做解释:
elementData:存放当前ArrayList集合的数组,其大小就是当前ArrayList集合中元素的数量
size:表示当前ArrayList集合中元素的数量
DEFAULT_CAPACITY:默认的初始容量,值为10
minCapacity:当前ArrayList所需要的最小容量,当大于集合中元素数量时,说明所需大于已有,就要进行扩容
newCapacity:扩容之后的新数组的大小
modCount:对当前ArrayList进行修改的次数
MAX_ARRAY_SIZE :数组可容纳的最大的数量

当调用无参构造器时,首先会初始化容量为0的空数组

	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	...... 
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

当调用有参构造器时,会指定数组大小

	public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

当调用add方法的时候,会进入下面一系列的操作

	public boolean add(E e) {
		// 首先判断在当前长度基础上再加上一个新元素后,集合容量是否还够
		ensureCapacityInternal(size + 1);  // Increments modCount!!
		elementData[size++] = e;
		return true;
	}
	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    // 判断是否是第一次调用add方法,即判断当前数组是否为空,如果为空则初始化容量为10,否则直接返回当前所需最小容量
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
		// 如果是第一次调用add方法,即此时数组为空数组,则初始化此时容量为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
 	private void ensureExplicitCapacity(int minCapacity) {
 		// 修改次数加一
        modCount++;

        // overflow-conscious code
        // 判断此时所需最小容量和当前集合长度的关系
        // 如果所需最小容量大于了当前集合的长度值,则说明此时的数组已经无法容纳更多元素加入,所以要进行扩容
        // 如果所需最小容量小于当前集合的长度之,说明此时数组可以存放更多的元素,则不需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
	private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 扩容为原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果是第一次调用add方法,此时数组大小为0,常规扩容无效
       	// 或者扩容后还是不能满足元素的存放需求
       	// 此时则需要直接将所需最小容量复制给新容量
        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);
    }

(二) Vector底层结构和源码分析

Vector与ArrayList对比

在这里插入图片描述

Vector构造方法

在这里插入图片描述
在这里插入图片描述

	public Vector() {
        this(10);
    }

	public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

	public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }


	public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }

Vector的add方法(扩容)

	public synchronized void addElement(E obj) {
        modCount++;
        // 首先判断数组的长度是否足够加入新元素
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }
	private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        // 如果所需最小容量比当前数组的长度值大,说明不够存储新元素,所以扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
	private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 如果构造方法中capacityIncrement值大于0,则扩容为原容量+capacityIncrement
        // 如果构造方法中capacityIncrement为默认0值,则扩容为原来的两倍
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        // 第一次扩容,数组大小为0,避免扩容失败,所以直接将所需最小容量赋值给新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

(三) LinkedList底层结构和源码分析(重点)

LinkedList特点

  1. LinkedList底层实现了双向链表和双端队列特点
  2. 可以添加任意元素(元素可重复),包括null
  3. 线程不安全,没有实现同步

LinkedList底层结构

  1. LinkedList底层维护了一个双向链表
  2. LinkedList中维护了两个属性first和last分别指向 首节点和尾节点
  3. 每个节点都是一个Node对象,里面维护了prev、next、item三个属性,其中通过prev指向前一个节点,通过next指向后一个节点,最终实现双向链表
	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;
        }
    }
  1. 因为此时增加、删除元素不需要进行扩容和元素复制,所以效率较高

LinkedList源码分析(CRUD)

调用无参构造器, first 和 last 都为null

	 public LinkedList() {
     }
1. add()方法
	public boolean add(E e) {
		// 调用尾插法
        linkLast(e);
        return true;
    }
	// 尾插法
	void linkLast(E e) {
		// 得到尾结点
        final Node<E> l = last;
        // 创建要加入的新节点,并设置前驱节点为last(因为是尾插,插到last之后)
        final Node<E> newNode = new Node<>(l, e, null);
        // 将尾节点指向新节点
        last = newNode;
        // 如果之前不存在尾节点,即第一次插入,则直接让头结点指向新结点(只有一个节点,头尾都指向它)
        // 否则,让上一个尾节点的后继节点指向当前新的尾节点(即加入的新节点)
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        // 链表长度加一
        size++;
        modCount++;
    }
2. removeLast()方法
	public E removeLast() {
		// 直接得到最后一个元素值,传入unlinkLast方法进行删除操作
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
	private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        // 得到最后一个元素值,最后作为返回值
        final E element = l.item;
        // 先创建节点指向要删除的尾结点的前驱节点
        final Node<E> prev = l.prev;
        // 将尾结点的值和前驱节点置为空
        l.item = null;
        l.prev = null; // help GC
        // 尾结点指向其前驱节点,即此时尾结点为上一个节点
        last = prev;
        // 如果prev为空,说明此时链表为空,则首节点也置为空(头尾都指向null)
        // 否则,说明链表不为空,就让现在新的尾节点的后继节点指向null,之前的尾结点没有任何指向
        if (prev == null)
            first = null;
        else
            prev.next = null;
        // 长度减一
        size--;
        modCount++;
        return element;
    }
3. removeFirst()方法
	public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
	private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }
4. set()方法
	public E set(int index, E element) {
		// 首先判断索引是否合法
        checkElementIndex(index);
        // 根据索引获取要修改的节点
        Node<E> x = node(index);
        // 保存旧的节点值
        E oldVal = x.item;
        // 将节点值覆盖为新的值
        x.item = element;
        // 返回旧的节点值
        return oldVal;
    }
	private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
	Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
5. get()方法
	public E get(int index) {
		// 判断索引是否合法
        checkElementIndex(index);
        // 根据索引查询节点,并返回其值
        return node(index).item;
    }
 	Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
6. getFirst()方法
	public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
7. getLast()方法
	public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

ArrayList和LinkedList比较

在这里插入图片描述
选择方面:

  1. 如果修改,查询的操作较多,则选择ArrayList
  2. 如果增加,删除的操作较多,则选择LinkedList
  3. 一般来说,在程序中,大部分操作都是查询,所以大部分情况下都会使用ArrayList
  4. 在一个项目中,根据业务灵活选择。

三、Set接口

Set接口特点

  1. 无序(添加和取出的顺序不一样),没有索引
  2. 不允许重复元素,故最多包含一个null
  3. 在这里插入图片描述

Set接口常用方法

在这里插入图片描述

Set接口遍历方式

  1. 可以使用迭代器
  2. 增强for循环
  3. 不能使用索引方式来遍历

(一) HashSet底层结构和源码分析(重点)

Hash特点

  1. HashSet实现了Set接口

  2. HashSet底层实际上是HashMap

  3. 可以存放null值,但是只能存放一个null

  4. HashSet不保证元素是有序的,取决于hash后,在确定索引的结果

  5. 不能有重复元素\对象。

HashSet扩容机制

  1. HashSet底层是一个HashMap
  2. 当一个元素要进行添加时,先获取他的哈希值,并将这个哈希值转换为索引值
  3. 找到数据存储表table,查看当前获取的索引位置是否已经存放有元素
  4. 如果不存在元素,则直接加入
  5. 如果存在了元素,则调用equals方法依次与当前位置及其后面的链表元素作比较,如果发现相同,则不会加入;如果比较之后发现都不相同,则将这个新元素添加到最后
  6. 在Java8及以后,如果一条链表中的元素个数到达TREEIFY_THRESHOLD(默认为8 static final int TREEIFY_THRESHOLD = 8;),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认为64static final int MIN_TREEIFY_CAPACITY = 64;)时,就会转化为红黑树

add()方法 (重点,难点)

	public boolean add(E e) {
	    // private static final Object PRESENT = new Object();
	    // 为value占位用的,没有实际意义
	    // 返回null 说明添加成功
        return map.put(e, PRESENT)==null;
    }
	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

	......

	// 首先 算出这个值应该所在的哈希值作为其在table中的下标
	static final int hash(Object key) {
        int h;
        // 先将key的hashcode值赋给h,在和h的高16位进行异或
        // 这样做的目的是尽量避免哈希冲突
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

重点代码:

	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就是 Node<K,V>[]类型的变量,是HashMap的一个数组(数据域)
        // 如果当前table是null 或 大小为0, 就为第一次扩容,默认为16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 根据key,获取hash值,计算该key应该存放到table表的哪一个索引位置
        // 并且把这个位置的对象赋值给P
        // 如果P为空,表示这个位置没有任何元素数据,就创建一个Node直接存放到这个位置
        if ((p = tab[i = (n - 1) & hash]) == null)
        	// 将key,value(PRESENT),还有hash(为将来比较hash做准备)放进Node中
            tab[i] = newNode(hash, key, value, null);
        else {
        	// 当前位置不为空,进去此语句块
            Node<K,V> e; K k;
            // 当前索引位置对应链表的第一个元素和准备添加的key的hash值一样
            // 并且两者的key相同 或者 两者euqals结果相同
            // 理解为判断两者是否完全相同,为同一个对象,如果完全相同,记录该节点,不做添加
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
           	// 判断 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);
                        // 将元素添加到链表后,就会立即判断链表是否已经到达8个节点
                        // 如果达到了,就调用treeifyBin() 对当前链表进行红黑树化
                        // 在转换红黑树过程当中,还会进行一个判断(即table长度要大于等于64)
                       	// 如果不满足则只会进行table的扩容
                        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 = p.next
                    p = e;
                }
            }

			// 有相同元素的情况下,上面的代码会给e赋值,然后返回这个e携带的value,说明添加不成功
            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();
        // 对于HashMap是空方法,此方法目的是为其子类进行更多操作做准备的
        afterNodeInsertion(evict);
        // 返回空代表成功
        return null;
    }
	final Node<K,V>[] resize() {
		// 旧的table
        Node<K,V>[] oldTab = table;
        // 旧的容量, 如果旧的table为空,则为0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        // 旧的临界值
        int oldThr = threshold;
        // 定义新容量和新的临界值
        int newCap, newThr = 0;
      	// oldCap > 0 说明不是第一次扩容
        if (oldCap > 0) {
        	// 旧的容量大于默认最大容量,就讲临界值置为最大整数
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 如果正常情况下,(旧的容量*2)之后小于最大容量,并且旧的容量大于默认容量
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 新容量也扩大为2倍
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
        	// 如果旧的容量为0且旧的临界值为0,说明是第一次添加数据
        	// 进入这里,设置新的容量为默认容量16
            newCap = DEFAULT_INITIAL_CAPACITY;
            // 负载因子(DEFAULT_LOAD_FACTOR)默认为0.75,
            // 设置新的临界值为负载因子*默认容量 此时为12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        // 将新的临界值赋值给成员变量threshold,方便全局访问
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        // 定义新的Node<K,V>[] 数据域,容量为新容量
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // 将新的数据域赋值给table,方便全局访问
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        // 将新的table返回
        return newTab;
    }
	final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        // table数据域的大小只有大于等于64才会进行红黑树转换,否则只对table扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

(二) LinkedHashSet底层结构和源码分析(理解)

LinkedHashSet特点

  1. LinkedHashSet是HashSet的子类
  2. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组 + 双链表
  3. LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素次序,使得元素看起来是以插入顺序保存的
  4. LinkedHashSet不允许添加重复元素

借鉴韩顺平老师的图来说明
在这里插入图片描述

LinkedHashSet源码分析

	// 无参构造
	public LinkedHashSet() {
		// 使用父类的构造方法
        super(16, .75f, true);
    }
	// 父类构造方法,调用LinkedHashMap创建
	HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

双向链表的底层数据结构

	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);
        }
    }

添加方法走的还是HashMap中的set方法,但是其中子类LinkedHashMap重新实现了newNode方法,里面调用了前后节点链接的方法linkNodeLast (before和after的指向)
在这里插入图片描述

	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;
    }


	// link at the end of list
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

HashMap$Node[] 类型的数组里面放了LinkedHashMap $Entry类型的值 ,上面的代码里说明了Entry是LinkedHashMap 的静态内部类,before和after就是双向链表的根本
在这里插入图片描述
在这里插入图片描述

四、Map接口

Map接口特点

  1. Map与Collection并列存在,用于保存具有映射关系的数据Key-Value
  2. Map中的key和value可以是任意引用类型的数据,会保存在HashMap$Node对象中
  3. Map中的key不能重复,和Set中原因一致
  4. Map中的value可以重复
  5. Map中的key和value都可以为null
  6. 常用String类作为Map的key
  7. key和value之间存在单向一对一的关系,即可以通过指定的key找到其对应的value
  8. 一对k-v是放在一个HashMap$Node中的,右因为Node实现了Entry接口,所以一对k-v就是一个Entry

理解Map通过entrySet()遍历 (重难点)

  1. 增强性for循环内部是使用的iterator方法(我们最终都会使用迭代器方式去遍历,自然会调用iterator方法,而具体这个被便利的集合中iterator是如何实现的,是由他自己确定的,所以下面的entrySet()、keySet()、values()方法中都有自己特定的iterator()实现方法,并且他们都调用到了HashIterator类中的nextNode()实现,最终引用到Map自己的table上,这也就解释了为什么明明没有看到向entrySet变量中添加值,但是却能够最终遍历到结果)
  2. EntrySet类中覆写了iterator方法。返回的是一个new EntryIterator();
  3. EntryIterator类里就只有一个方法next()。发现它继承了HashIterator类;
  4. 可以看出这个HashIterator迭代器的默认构造器中,会初始化一个next的变量,这个变量是在table数组中取得(所以在entrySet中,并没有真正存放k-v值,而只是引用了table中的值,这也就解释了entrySet()方法中entrySet变量即使为空,也可以遍历到结果的原因,因为最终的取值还是会到table中去找的),索引是从0递增的。构造初期会从0开始找有值的索引位置,找到后将这个Node赋值给next;
  5. 然后要遍历的时候是调用nextNode()方法,这个方法是先判断next.next是否为空,如果为空继续往上找有值的索引位置,如果不为空就找next.next。这样就能都遍历出来了,是从索引0到table.length去一个个寻找遍历的。
	transient Set<Map.Entry<K,V>> entrySet;
	public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }
	public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

    final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        // ================重写iterator方法返回EntryInterator==================
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }
 	abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

	// 只返回key
    final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }
    }

	// 只返回value
    final class ValueIterator extends HashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

	// 返回Entry
    final class EntryIterator extends HashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

类似的还有keySet()方法和values()方法,内部都重写了iterator()方法,可以做到只返回key或者value,但内部操作nextNode()操作都是与上面一致的

	public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }

    final class KeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<K> iterator()     { return new KeyIterator(); }
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator() {
            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super K> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.key);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }
	public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
    }

    final class Values extends AbstractCollection<V> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<V> iterator()     { return new ValueIterator(); }
        public final boolean contains(Object o) { return containsValue(o); }
        public final Spliterator<V> spliterator() {
            return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super V> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

在这里插入图片描述

Map接口常用方法

在这里插入图片描述

(一) HashMap底层结构和源码分析

HashMap底层结构

jdk1.7及以前,HashMap底层实现为[数组+链表];jdk1.8及之后,HashMap底层实现为[数组+链表+红黑树]

相同key情况下替换value的逻辑

	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
	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)
            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;
                }
            }

			//==========上面key发现有相同值,此时e就等于原来的key对应的键值对============
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                	// 这里使用传入的value替换掉原来的value
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

扩容机制和add方法与HashSet基本一致,可以看之前HashSet内容

(二) Hashtable底层结构和源码分析

Hashtable特点

  1. 存放的元素是键值对(key-value)
  2. hashtable的键和值都不能为null,否则会抛出NullPointException
  3. hashtable使用方法基本上和HashMap一样,都实现了Map接口
  4. hashtable是线程安全的(synchronized),hashMap是线程不安全的

Hashtable源码分析

hashtable底层就是一个Entry数组
	private transient Entry<?,?>[] table;
调用无参构造方法,默认初始大小为11,负载因子为0.75
	public Hashtable() {
        this(11, 0.75f);
    }
put()方法以及扩容机制
	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) {
        	// 当发现有相同的键存在时,就用新的value替换旧的value
            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,hash,index转换为扩容后的值
            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        // 记录当前索引位置元素,赋值给e(可以为空)
        Entry<K,V> e = (Entry<K,V>) tab[index];
        // 创建新的Entry元素,并将next设置为e,放置到table当前索引位置,相当于插入链表头部(头插法)
        // Entry构造函数可以看下面代码
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }
	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;

		// 将原先数组中的元素重新进行hash等操作放到新的数组当中
        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;
            }
        }
    }

Entry构造函数

	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;
        }

		......
	}

(三) Properties(了解使用即可)

Properties特点

  1. Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来存储数据的
  2. 它的使用方法类似于Hashtable
  3. Properties可以从xxx.properties文件中,加载数据到Properties类对象中,从而进行读取和修改

五、TreeMap(TreeSet)底层结构和源码分析

这里传入的键(TreeSet)或者键值对(TreeMap),是无序的(这里的无序指的是传入顺序如取出顺序不一致,但是底层会根据自定义的比较方法进行自然排序)

在这里插入图片描述

	// 无参构造方法
	public TreeMap() {
      	comparator = null;
    }

	// 有参构造(传入比较器)
	public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

注意:这里如果显示传入比较器的话,就必须保证加入的key实现了Comparable接口,并且实现了compareTo()方法,因为TreeMap底层中如果没有发现显示传入的比较器(Comparator),就会去找键所对应的比较方法进行比较 Comparable<? super K> k = (Comparable<? super K>) key; ... cmp = k.compareTo(t.key);,如果这时发现键没有实现Comparable接口,就会抛出类型转换异常; 如果显示传入了比较器,则会根据传入的构造器逻辑进行比较

	public V put(K key, V value) {
        Entry<K,V> t = root;
        // 树为空则直接插入
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        // 获取一下变量构造器,看是否显示传入过自己的比较器
        Comparator<? super K> cpr = comparator;
        // 如果有显示传入的比较器,则使用这个比较器进行比较操作
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                // 这里内部是一个二叉排序树,根据自己的比较逻辑会找到要插入的位置
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else // 如果发现相同,则只替换值
                    return t.setValue(value);
            } while (t != null);
        }
        // 如果没有显示传入比较器,则会找键对应的比较方法compareTo()进行比较
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                // 这里内部是一个二叉排序树,根据自己的比较逻辑会找到要插入的位置,与上面效果相同
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else // 如果发现相同,则只替换值
                    return t.setValue(value);
            } while (t != null);
        }

		// 创建要插入的新节点,判断cmp比较结果,确定最终插入的位置,父节点为parent
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

六、集合类型选型

在这里插入图片描述

七、Collections工具类

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值