java容器【数组、集合】

1 篇文章 0 订阅
1 篇文章 0 订阅

数组

概述

  • 数组是相同类型数据的有序集合。集合中的数据称作元素,每个元素可以通过一个索引(下标,从0开始)来访问

特点

  • 数组一旦进行初始化后,长度就固定下来了,不可以在改变
  • 数组存储的元素必须是相同数据类型的数据
  • 数组可以存储任何数据类型,即可以存储基本类型、也可以存储引用类型的数据
  • 数组是引用类型对象

声明

方式一【推荐使用】

格式:数据类型[] 变量名称 ;

int[]  arr;
方式二

格式:数据类型 变量名称[];

int arr[];

初始化

静态初始化
  • 指定数组中每个元素的初始值,数组长度由JVM决定
  • 格式:数组类型[] 变量名称 = new 数组类型{元素一、元素二…};
int[] arr = new int[]{1,2...}
动态初始化
  • 定义时指定数组长度,数组中每个元素的的默认值由JVM进行初始化赋值
  • 补充:JVM对数组元素默认值的初始化
    • 整数类型:默认值 0
    • 小数类型:默认值 0.0
    • 字符类型:默认值 \u0000【空格】
    • 布尔类型:默认值 false【二进制中0代表假、即false】
    • 引用类型:默认值 null
  • 格式:数组类型[] 变量名称 = new 数组类型[10];
int[] arr = new int[10]

集合

单元素集合【Collection】

特点
  • 存放独立元素的容器
有序集合【List】
特点
  • 有序集合、元素可重复
  • 使用动态数组、替换原有不可变的数组
实例一【ArrayList】
特点
  • List接口的主要实现类
  • 内部是用线性动态数组结构实现
  • 线程不安全
  • 底层使用Object[] elementData数组结构存储数据
  • 查询效率较高,增删效率较低,原因如下
    • 查询快:根据数组中下标索引查询,复杂度为O(1)级别
    • 插入、删除慢,由于底层使用的是Object[]数组存储元素,在操作非最后一个元素时,会涉及到要操作数组中i索引后的所有元素的移动、当数组长度不够时,还会涉及到扩容、数组元素复制等操作,这些操作往往会很耗时,所以操作时效率不高
源码分析

JDK7
初始化操作:ArrayList arr = new ArrayList();

public ArrayList() {
        this.elementData = new Object[10];
    }

add操作

  • 该方法的核心为扩容机制,与jdk8类似【参考JDK8的解析】

JDK8

初始化操作:ArrayList arr = new ArrayList();

	/**
     * Constructs an empty list with an initial capacity of ten.
     * 空参构造器会创建一个初始化为0的Object[]数组,这里的注释有问题,应该是jdk7的注释
     * 
     * 备注
     * 1.private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  ==> 空数组
     * 2.transient Object[] elementData; // 存储元素的数组集合
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

add操作

  • 第一次【不扩容场景】
	/**
	 * Appends the specified element to the end of this list.
	 *
	 * @param e element to be appended to this list 
	 * @return <tt>true</tt> (as specified by {@link Collection#add})
	 * 
	 * 备注:
	 * 1.private int size;   ==> 初始化为0
	 */
	public boolean add(E e) {
		// size + 1 => 0 + 1 = 1
	    ensureCapacityInternal(size + 1);  // Increments modCount!! 
	    elementData[size++] = e;
	    return true;
	}

	/**
	 * @param minCapacity  ⇒  1 
	 *  备注:
	 *  1.private static final int DEFAULT_CAPACITY = 10;
	 */
	private void ensureCapacityInternal(int minCapacity) {
		//由初始化操作可以得到下面判断为true
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //由于DEFAULT_CAPACITY = 10 ,minCapacity = 1 ==> minCapacity = 10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

	/**
	 * @param minCapacity  ⇒  10 
	 *  备注:
	 *  1.protected transient int modCount = 0;
	 */
	private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code ==> 10 - 0 > 0 成立
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

	/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity【10】 the desired【渴望】 minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length; ==0
        int newCapacity = oldCapacity + (oldCapacity >> 1); ==0
        // 0 - 10 < 0 成立 newCapacity ==》 10
        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 为 长度为10的数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

	/**
     * @param original elementData
     * @param newLength 10
     */
	public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

	/**
     * @param original elementData
     * @param newLength 10
     * @return 长度为10的数组
     */
    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,
        // original.length ==》 0 ; newLength ==》 10  ==>结果为:10
                         Math.min(original.length, newLength));
        return copy;
    }
  • 第n次【扩容场景】
	/** 前面方法都一样,这里以添加第11个元素为例
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity【11】 the desired minimum capacity
     * 
     * 备注:
     * 1.private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
     */
    private void grow(int minCapacity) {
        // overflow-conscious code 10
        int oldCapacity = elementData.length;
        // 10 + 5 ⇒ 15【这一步是扩容长度的代码实现 --》 扩容值为原数组长度的一半】
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 15 - 10 = 5 < 0 不成立
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 15 - 10 = 5 > Integer.MAX_VALUE - 8 不成立
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	//最大容量计算逻辑【最大值为整数的最大值】:
        	//return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //参数一:原数组集合、新数组的长度
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

	/** 
     * @param original 长度为10的元素组
     * @param newLength 新生产数组的长度
     */
	public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        //生产长度为15的新数组
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        //这里会将原来数组中的元素copy到新生产的数组中    
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

小结

  • jdk7中的ArrayList的对象的创建类似于单例模式中的恶汉式;
  • jdk8中的ArrayList的对象的创建类似于单例模式中的懒汉式,延迟了数组的创建、节省了内存
实例二【LinkedList】
特点
  • 内部使用Node双向链表的数据结构存储数据
  • 线程不安全
  • 查询效率低:没有下标索引的概念,查询时会按照索引数据进行前向和后向遍历,复杂度为O(n)
  • 插入、删除效率高:由于底层使用的是双向链表结构,当操作一个元素时,最多只会涉及2个元素的操作,将当前元素前后的两个节点元素引用地址变更即可,将当前元素的前一个元素Node节点的下一个节点引用当前Node地址,将当前元素的下一个元素Node节点的上一个节点引用指向当前节点即可
源码分析

初始化:List list = new LinkedList();

 	/**
     * Constructs an empty list.
     * 
     * 备注:
     * 1.LinkedList类属性一:first节点
     *  Pointer to first node. Invariant: (first == null && last == null) || (first.prev == null && first.item != null)
     * first == null && last == null ==> 只有一个元素情况
     * first.prev == null && first.item != null  ==> 有多个元素情况
     *  transient Node<E> first;
     * 
     * 1.LinkedList类属性二:last节点
     * Pointer to first node. Invariant: (first == null && last == null) || (first.next == null && first.item != null)
     * first == null && last == null ==> 只有一个元素情况
     * first.next == null && first.item != null  ==> 有多个元素情况
	 * transient Node<E> last;
     */
    public LinkedList() {
    }

	/**
     * 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;
        }
    }
  • 内部声明了Node【内部类】类型的first和last属性,默认值为null

Add()方法

	/**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     * 
     * 备注:
     * 1.实际调用的是linkLast(e)方法
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }


	#linkLast(e);方法
	/**
     * Links e as last element.
     * 备注:
     * 1.last:transient Node<E> last; ==> 添加第一个元素时为null,添加其他元素时为该集合最后一个添加进去的元素【默认情况下】
     * 2.transient int size = 0;
     * 3.protected transient int modCount = 0;
     */
    void linkLast(E e) {
    	//null
        final Node<E> l = last;
        // newNode: prev = null ; next = null ; item = e
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //添加第一个元素时成立
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
实例二【Vector】
特点
  • 内部是线性动态数组结构,查询效率高
  • 线程安全,使用synchronized对方法加锁实现,但相较于ArrayList效率会稍低
  • JDK1.0时出现,早于JDK1.2时出现的List接口
  • 底层数据结构与ArrayList相同,也是使用 Object[] elementData存储数据
源码分析

初始化:

	/**
     * Constructs an empty vector so that its internal data array has size {@code 10} and its standard capacity increment is zero.
     */
    public Vector() {
        this(10);
    }

	/**
     * Constructs an empty vector with the specified initial capacity and with its capacity increment equal to zero.
     *
     * @param   initialCapacity   the initial capacity of the vector
     * @throws IllegalArgumentException if the specified initial capacity  is negative
     */
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

	/**
     * Constructs an empty vector with the specified initial capacity and capacity increment.
     *
     * @param   initialCapacity  ==> 10     the initial capacity of the vector
     * @param   capacityIncrement  ==> 0  the amount by which the capacity is increased when the vector overflows
     * @throws IllegalArgumentException if the specified initial capacity is negative
     * 备注:
     * protected Object[] elementData;
     * protected int capacityIncrement;
     */
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //创建长度为10的Object[]数组                                       
        this.elementData = new Object[initialCapacity];
        //0
        this.capacityIncrement = capacityIncrement;
    }

add()方法

 	/**
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     * 备注:
     * 底层逻辑实现与ArrayList类型,不过与ArrayList最大的区别是该方法加了synchronized来解决多线程操作时的并发问题
     */
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

	/**
     * @desc 扩容逻辑
     * 备注:
     * 底层逻辑实现与ArrayList类型,不过与ArrayList最大的区别是该方法加了synchronized来解决多线程操作时的并发问题
     */
     private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //这里会扩容为原数组集合的2倍【与ArrayList的1.5倍不同】
        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);
    }

小结

  • 与ArrayList相比扩容大小不同:ArrayList扩容为原来的1.5倍,Vector扩容为原来的2倍
  • 初始化时实现与ArrayList的JDK7版本类似,直接创建了长度为10的数组,并没有采用‘懒加载’的方式
  • 与ArrayList相比在方法上都加了synchronized来解决并发问题
实例二【Stack】
特点
  • Stack类继承自Vector,实现的是一个栈的数据结构。Stack刚创建后是元素是空的。Stack提供5个额外的方法使得Vector可以被当作堆栈使用:
    • push()和pop()方法入栈和出栈;
    • peek()方法用于得到栈顶的元素;
    • empty()方法测试堆栈是否为空;
    • search()方法检测某元素在堆栈中的位置;
  • 由于Stack继承自Vector,自然也是由synchronized修饰过的同步容器。而除了这两个类,其他实现List接口的容器类并没有实现同步,在多线程场景下需要注意线程安全问题
源码分析

初始化

 	/**
     * Creates an empty Stack.
     * 备注:
     * 这里调用父类方法创建底层数组
     */
    public Stack() {
    }
无序集合【Set】
特点
  • 不可重复性
    • 不可重复性前提是保证添加的元素按照equals()方法判断时,不能返回true【需要重写hashCode()方法】。即相同的元素只能添加一个
  • 无序性
    • 不是随机性,存储数据时底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值经过计算添加
实例一【HashSet】
特点
  • 无序,底层内部是哈希表【本质是hashMap实例中的key】支持,不能保证Set集合中元素的迭代顺序
  • 线程不安全
  • 可以存储null值,且只能存储一个null值【验证:向空hashSet集合中添加两个null,输出hashSet的大小为1】,注意 向hashSet中添加元素,要求元素必须重新equals()和hashCode()方法;且必须保证一致性【实现对象相等规则:“相等的对象必须具有相等的散列码”】
  • 底层存储结构:数组 + 链表
源码分析

初始化

	/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has default initial capacity (16) and load factor (0.75).
     * 备注:
     * 1.private transient HashMap<E,Object> map;  ==> map为hashMap,所以hashSet底层是hashMap,元素保存在hashMap的key中
     */
    public HashSet() {
        map = new HashMap<>();
    }

add(E e)

	/**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set unchanged and returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified element
     * 备注:
     * 1.private static final Object PRESENT = new Object();
     * 2.其实是调用hashMap的put方法,参考hashMap的put代码
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
实例二【LinkedHashSet】
特点
  • hashSet的子类,对于频繁的遍历操作,LinkedHashSet效率高于HashSet
  • LinkedHashSet按照插入排序,SortedSet可排序;遍历其内部数据时,可以按照添加的顺序遍历:在添加元素的同时,每个元素还维护了两个引用,记录了此数据前一个数据和后一个数据
源码分析

初始化

	/**
     * Constructs a new, empty linked hash set with the default initial  capacity (16) and load factor (0.75).
     */
    public LinkedHashSet() {
        super(16, .75f, true);
    }

	/**
     * Constructs a new, empty linked hash set.  (This package private
     * constructor is only used by LinkedHashSet.) The backing
     * HashMap instance is a LinkedHashMap with the specified initial
     * capacity and the specified load factor.
     *
     * @param      initialCapacity   the initial capacity of the hash map
     * @param      loadFactor        the load factor of the hash map
     * @param      dummy             ignored (distinguishes this
     *             constructor from other int, float constructor.)
     * @throws     IllegalArgumentException if the initial capacity is less
     *             than zero, or if the load factor is nonpositive
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
实例三【TreeSet】
特点
  • TreeSet使用元素的自然顺序对元素进行排序【可按照添加对象的指定属性,进行排序】,或者根据创建set时提供的Comparator进行排序
  • 向TreeSet中添加的数据,要求是相同类的对象
  • 底层是红黑树
  • 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals();定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()
源码分析

初始化

	/**
     * Constructs a new, empty tree set, sorted according to the
     * natural ordering of its elements.  All elements inserted into
     * the set must implement the {@link Comparable} interface.
     * Furthermore, all such elements must be <i>mutually
     * comparable</i>: {@code e1.compareTo(e2)} must not throw a
     * {@code ClassCastException} for any elements {@code e1} and
     * {@code e2} in the set.  If the user attempts to add an element
     * to the set that violates this constraint (for example, the user
     * attempts to add a string element to a set whose elements are
     * integers), the {@code add} call will throw a
     * {@code ClassCastException}.
     */
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

add()

/**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element {@code e} to this set if
     * the set contains no element {@code e2} such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns {@code false}.
     *
     * @param e element to be added to this set
     * @return {@code true} if this set did not already contain the specified
     *         element
     * @throws ClassCastException if the specified object cannot be compared
     *         with the elements currently in this set
     * @throws NullPointerException if the specified element is null
     *         and this set uses natural ordering, or its comparator
     *         does not permit null elements
     * 备注:
     * 1.底层调用的是treeMap的put方法
     */
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

键值对集合【Map】

特点
  • 存放key-value型的entry元素;是键值对集合,存储键、值和之间的映射;Key无序,唯一;value 不要求有序,允许重复
  • jdk1.2出现
结构
key
  • 无序的、不可重复的,使用Set存储所有的key
  • 要求key所属的类重写hashcode()和equasl()方法【以hashMap为例,TreeMap是按照排序实现】
value
  • 无序的、可重复的,使用Collection存储所有的value
  • 要求value所属的类重写equasl()方法
entry
  • 一个键值对【key-value】构成一个entry对象,map是将对应的entry对象存储到map中
  • 无序的、不可重复的、使用Set存储所有的entry
实例一【HashMap】
特点
  • 有序的Map集合实现类,相当于一个栈,先put进去的最后出来,先进后出
  • map的主要实现
  • 线程不安全,效率高
  • 可以存储null的key和value
  • 底层数据结构
    • jdk7:数组 + 链表
    • jdk8:数组 + 链表 + 红黑树
源码分析
JDK7

实例化: hashMap map = new HashMap()

  • 在实例化时,底层创建了长度是16的一维数组Entry[] table

put(k1,v1):

  • 调用k1所在类的hashCode()计算k1的哈希值,使用该哈希值经过算法计算后,得到在Entry[] table数组上的存放位置
  • k1所要存放位置为空,此时将k1、v1封装成对应的Entry,并将该Entry添加到Entry[] 数组中
  • k1所要存放位置不为空【意味着此位置上存在一个或多个以链表形式存放的数据】,比较k1和已经存在该位置上数据的哈希值
  • k1的哈希值与已经存在的数据的哈希值都不相同,将k1、v1封装成Entry,并将该Entry添加到Entry[]数组中
  • k1的哈希值与已经存在数据的哈希值相同,则比较k1所在类的equals()方法
  • 如果equals()方法返回false:将k1、v1封装成Entry并保存到Entry[] 数组中
  • 如果equals方法返回true:将对应位置上Entry中的value进行替换,也就是v1替换原有的value值

扩容机制:

  • 扩容条件:存放元素的索引个数超出临界值【数组长度 * 负载因子:默认0.75】,且存放索引位置上不为空时
  • 扩容大小:默认扩容为原来的2倍,并将原有的数据复制过来
JDK8

实例化: hashMap map = new HashMap()

  • 底层没有创建长度为16的数组
  • 底层的数组为:Node[],而非Entry[]
  • 首次调用put()方法时,底层才会创建长度为16的数组
  • jdk7底层结构只有:数组+链表。jdk8底层结构:数组+链表+红黑树;当数组的某一个索引位置上的元素以链表形式存放的数据个数 > 8 且当前数组的长度 > 64时,此时该索引位置上的数据改为使用红黑树存储
  • 相关概念
    • 默认初始化容量【DEFAULT_INITIAL_CAPACITY】
      • static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    • 默认的加载因子【DEFAULT_LOAD_FACTOR】
      • static final float DEFAULT_LOAD_FACTOR = 0.75f;
    • 扩容的临界值【int threshold】
      • 容量 * 加载因子 默认值:12
    • 数组Bucket数组中链表长度大于该默认值,转化成红黑树,static final int TREEIFY_THRESHOLD = 8
    • 由红黑树转成链表的的长度:static final int UNTREEIFY_THRESHOLD = 6
    • 数组中的Node被树化时最小的hash表容量【数组长度】:64
	/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity (16) and the default load factor (0.75).
     * 备注
     * 
     * final float loadFactor; //负载因子
     * static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子默认大小
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

put(k v):
node内部类

//继承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 V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
		//重新hashCode和equals方法
		public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
        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;
        }
    }

put()方法【第一次或N次不会索引碰撞场景且不扩容情况】

  • 第一次执行put()会执行resize()方法对HashMap底层存储数据的Node[16]的数组初始化
  • 非第一次执行put()方法则不会执行resize()方法
	public V put(K key, V value) {
		//根据key求hash值
        return putVal(hash(key), key, value, false, true);
    }
	
	/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     * 备注
     * transient Node<K,V>[] table;
     * transient int modCount; ==> 0
     * transient int size; ==》 0
     * transient int modCount; ==》 0
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    	//声明Node【内部类】类型的tab、p、和初始值为0的i、n
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //第一次put table = null 赋值给tab变量 ==》 判断tab == null 成立 ==》 进入resize()方法
        if ((tab = table) == null || (n = tab.length) == 0)
        	//n ⇒ 16 ;  tab为大小为16的Node数组
            n = (tab = resize()).length;
        //第一次时 p == null 成立 ,tab[15 & key的hash] = null
        //这里是底层数组上添加时对应索引位置没有任何元素的场景
        if ((p = tab[i = (n - 1) & hash]) == null)
        	//在tab[i]的位置上创建一个Node,且下一个node元素为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;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //第一次 :modCount = 1 
        ++modCount;
        //第一次 :1 > 12 不成立 ==》不会走resize 
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

resize()方法

	/**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     * 备注
     * 1.int threshold; ==》 0
     * 2.static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
     * 3.static final float DEFAULT_LOAD_FACTOR = 0.75f;
     * 4.第一次调用会返回大小为16的Node类型数组
     */
    final Node<K,V>[] resize() {
    	//这里为null
        Node<K,V>[] oldTab = table;
        //oldCap ==》 0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //oldThr ==》 0
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
        	//第一次put值时会进入这个分支
        	//newCap ==》 16 ; newThr ==> 12
            newCap = DEFAULT_INITIAL_CAPACITY;
            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);
        }
        //这里将12赋值给扩容时临界值
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        //创建一个Node类型大小为16的数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //第一次put不会进
        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;
                        }
                    }
                }
            }
        }
        return newTab;
    }

put()方法【第N次会索引碰撞场景且不扩容情况】

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    	//声明Node【内部类】类型的tab、p、和初始值为0的i、n
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //第n次时 p = tab[i] ==> p为当前所以上数组的值
        //这里是底层数组上添加时对应索引位置没有任何元素的场景
        if ((p = tab[i = (n - 1) & hash]) == null)
        	//在tab[i]的位置上创建一个Node,且下一个node元素为null	
            tab[i] = newNode(hash, key, value, null);
        else {//这里是底层数组上添加时对应索引位置有元素的场景
            Node<K,V> e; K k;
            //情况一:数组中的元素hash 与 【数组中的元素key与要添加的key相同 或 key的equals()方法相同】 时 ,将p的值赋给e
            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 {
            //情况二:hash值不相同 或 key值不相同情况 或 hash和key值都不相同
                for (int binCount = 0; ; ++binCount) {
                	//e为p的下一个元素且为空
                    if ((e = p.next) == null) {
                    	//根据put方法参数创建一个新Node,将p的下一个节点执行新Node即可
                        p.next = newNode(hash, key, value, null);
                        //static final int TREEIFY_THRESHOLD = 8; == 条件不成立不需要转换为红黑树 ==》 打断循环
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        	//这里是链表转红黑树逻辑
                            treeifyBin(tab, hash);
                        break;
                    }
                    //e为p下一个元素不为空情况,与要添加的元素相同 == 打断循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //如果上述两种情况都不成了,将e元素赋值给p接着找下一个元素
                    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;
        //第n次 :n > 12 成立 ==》走resize 扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

扩容resize()【第一次扩容】

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        //16
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //12
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
        	//static final int MAXIMUM_CAPACITY = 1 << 30; ==》 1073741824
            if (oldCap >= MAXIMUM_CAPACITY) { //不成立
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //newCap  =  16 * 2 = 32 ;
            //oldCap << 1 ==》 24 < 1073741824 && 16 >= 16 成立
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //newThr = oldThr * 2 ; == 24
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        // 24 == 0 不成立
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        // 扩容临界值为24
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        //创建大小为32的Node数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //成立
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //将数组上的元素赋值给e
                if ((e = oldTab[j]) != null) {
                	//将原数组上对应索引位置上置为null
                    oldTab[j] = null;
                    //如果e上只有一个元素时,直接根据新的数组大小32和元素的hash求&得到新数组中所在的索引位置
                    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
                    	//如果e上有多个元素时场景
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            //取e的下一个元素
                            next = e.next;
                            //e为原数组上的第一个元素
                            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;
                        }
                    }
                }
            }
        }
        return newTab;
    }

扩容resize()转红黑树

	/**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     * 备注
     * tab hashMap存储数据的Node[]数组
     * hash 要保存数据的hash值
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //static final int MIN_TREEIFY_CAPACITY = 64;
        //tab为空 或 tab的长度的长度 < 64 ==>走扩容逻辑
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        // 数组长度>64 且 要新增的元素与65求&得到要存储数组中的位置不为空  ⇒ 将数组转换为红黑树
        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);
        }
    }
实例二【Hashtable】
特点
  • 内部存储的键值对是无序的,是按照哈希算法进行排序
  • map的早期实现类,JDK1.0就有,在map接口之前出现
  • 线程安全,效率低
  • 键或者值不能为null,为null就会抛出空指针异常
实例二【properties】
特点
  • 是Hashtable的子类
  • 常用来处理配置文件
  • key和value都是String类型
实例三【TreeMap】
特点
  • 基于红黑树(red-black tree)数据结构实现,按key排序,默认的排序方式升序
  • 保证按照添加的key-value对进行排序,按照key实现排序遍历
实例四【ConcurrentHashMap】
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

似寒若暖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值