【java集合】超详细知识点+源码分析

集合

1、集合概述

1.1 集合的概念

集合可以看作是一种容器,用来从出对象信息。所有集合类都为于java.util.concurrent包下。

1.2 常用集合的继承关系

java集合都是由Collection接口、Map接口派生出的,通过各层抽象类、接口的扩展可实现不同特性的集合子类,在实际业务中需要通过实际情况选择使用哪种集合。

1.3 集合与数组区别
  • 数组是静态的,实例化时就声明了其长度,之后无法扩容。而集合可以根据需求动态的改变容量,操作更加灵活、方便。
  • 数组的存放类型只能是一种(基本类型/引用类型),而集合存放的类型是Object,在不加泛型的情况下可以存放不同类型的对象(实际上也都属于Object)。
  • 数组是java内置的数据类型,是线性排列的,执行效率更高。

2、Collection接口

存储一组Object对象,实现了Iterable接口,其子类通过迭代器进行遍历。

2.1 接口方法

添加元素:boolean add(E e);

添加集合:boolean addAll(Collection<? extends E> c);

删除某个对象:boolean remove(Object o);

删除参数集合中的所有对象:boolean removeAll(Collection<?> c);

清空集合:void clear();

Iterable迭代器进行遍历:Iterator iterator();

Iterator iterator = arrayList.iterator();//获取迭代器
while (iterator.hasNext()){//从-1开始,判断是否还有下一个
    //遍历输出元素
    System.out.println(iterator.next());
}

获取元素个数:int size();

判断是否为空:boolean isEmpty();

判断是否包含某个元素:boolean contains(Object o);

集合转为数组:Object[] toArray();

删除不属于Collection对象的所有元素:boolean retainAll(Collection<?> c);

判断对象引用是否相等:boolean equals(Object o);

2.2 List接口

有序集合,此接口的具体实现类是有序的,可以通过索引的位置获取、插入元素

2.2.1 接口方法

获取指定位置的元素:E get(int index);

更新指定位置元素:E set(int index, E element);

更新指定位置元素:E set(int index, E element);

获取子类迭代器:ListIterator listIterator();(支持向前遍历,不常用)

2.2.2 接口实现类
(1)ArrayList

特点底层为数组;查询速度快;增删速度慢(涉及创建新数组、复制数组等工作);效率高但线程不安全

构造函数

/**
 * Constructs an empty list with the specified initial capacity.
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity
 *         is negative
 */
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);
    }
}

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
  • public ArrayList();初始化存储数组长度为0;添加第一个元素时初始化为10;当添加的对象要超过10时,以1.5倍容量扩容
  • public ArrayList(int initialCapacity);初始化数组长度为initialCapacity;当添加的对象要超过initialCapacity时,以1.5倍容量扩容

扩容逻辑:

//扩容机制分析,执行add方法时:
public boolean add(E e) {
//1、调用ensureCapacityInternal方法,size为当前list对象中存储的对象个数
public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//2、返回要添加这个新元素的最小长度。elementData长度为0时,返回10,否则返回当前存入数据的数量+1
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //判断elementData是不是空数组,即是不是第一次添加
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //返回长度10
         return Math.max(DEFAULT_CAPACITY, minCapacity);//private static final int DEFAULT_CAPACITY = 10;
     }
    //后面添加返回需要的长度
    return minCapacity;
 }
//3、比较最小长度和当前数组的总长度,最小长度大于现有长度时,需要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//elementData为当前list对象
    	grow(minCapacity);
}
//4、传入最小长度对现有数组进行扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//当前数组长度
    int newCapacity = oldCapacity + (oldCapacity >> 1);//获取当前数组长度的1.5倍数
    if (newCapacity - minCapacity < 0)//当前数组长度的1.5倍数 比 期望长度小的情况(初始化未指定长度第一次添加时)
    	newCapacity = minCapacity;//新数组长度为 期望长度
    if (newCapacity - MAX_ARRAY_SIZE > 0) //新数组长度为大于允许的最大长度情况下
    	newCapacity = hugeCapacity(minCapacity);//设置新数组长度为MAX_ARRAY_SIZE、newCapacity中大的值。
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);//按照新数组的长度创建新的数组,并将之前保存的数据复制进来
}
(2)Vector

特点:线程安全。其他与ArrayList类似

构造函数

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

/**
 * 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 so that its internal data array
 * has size {@code 10} and its standard capacity increment is
 * zero.
 */
public Vector() {
    this(10);
}

/**
 * Constructs a vector containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this
 *       vector
 * @throws NullPointerException if the specified collection is null
 * @since   1.2
 */
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);
}
  • public Vector();初始化存储数组长度为10;当添加的对象要超过10时,以2倍容量扩容
  • public Vector(int initialCapacity) ;初始化存储数组长度为initialCapacity;当添加的对象要超过数组长度时,以2倍容量扩容
  • public Vector(int initialCapacity, int capacityIncrement); 初始化存储数组长度为initialCapacity;容量增量为:capacityIncrement;当添加的对象要超过数组长度时,容量扩容为:数组原长度+capacityIncrement

扩容逻辑

//elementCount为当前vector对象中存储的对象个数
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}
//比较最小长度和当前数组的总长度,最小长度大于现有长度时,需要进行扩容
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}
//扩容,默认扩容为原来数组长的2倍
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //capacityIncrement再初始化时,默认设置为0;故默认情况下
    //newCapacity = oldCapacity+oldCapacity,即默认扩容两倍
    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);
}
(3)Stack

特点:是Vector的子类。栈,实现了标准的先进先出

特殊方法

堆栈是否为空:boolean empty() ;

查看堆栈顶部的对象,但不从堆栈中移除它:Object peek( );

移除堆栈顶部的对象,并作为此函数的值返回该对象:Object pop( );

把项压入堆栈顶部:Object push(Object element);

查找对象在堆栈中的位置:int search(Object element); 栈顶为1,栈底为站的高度

Tips:Stack继承了Vector类,在初始化时,会执行Vector的无参构造,底层与Vector一样将对象保存在数组中,默认长度是10,长度不够时,扩容机制与Vector一致。

(4)LinkedList

特点:底层使用双链表实现,增删效率快,但查询需要遍历寻找效率较慢。线程不安全。对象维护first、last两个指针,分别指向两表的头、尾节点;

构造函数:

/**
 * Constructs an empty list.
 */
public LinkedList() {
}

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection's
 * iterator.
 *
 * @param  c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

特殊方法:(头尾的增删查)

链表头增加节点:public void addFirst(E e) ;

链表尾增加节点:public void addLast(E e);

链表头删除节点:public E removeFirst() ;

链表尾删除节点:public E removeLast() ;

链表头获取节点:public E getFirst();

链表尾获取节点:public E getLast() ;

增加逻辑

//实质为双向链表
public boolean add(E e) {
    linkLast(e);
    return true;
}
//往链表中添加的逻辑
void linkLast(E e) {
    //类last指针指向的节点,也就是未添加之前的最后一个节点,如果是第一次添加则指向null
    final Node<E> l = last;
    //通过添加的对象生成新的节点,前置节点是l(第一个添加的元素也是null),后置节点为null
    final Node<E> newNode = new Node<>(l, e, null);
    //将last指针指向新的节点
    last = newNode;
    if (l == null)//如果l为null(即添加第一个节点)first指针也指向新节点
    	first = newNode;
    else//l不为空将l的next指向新节点
    	l.next = newNode;
    size++;//数量加1
    modCount++;
}

删除逻辑

public E remove(int index) {
    checkElementIndex(index);//判断index有没有越界
    return unlink(node(index));
}
//通过index位置尽可能块的找到要删除的Node(判断从头找或从尾找,这里必须一个一个找,所以链表的查询比较慢)
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;
    }
}
//删除操作
E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;//获取要删除节点的值
    final Node<E> next = x.next;//得到要删除节点的后置节点
    final Node<E> prev = x.prev;//得到要删除节点的前置节点

    if (prev == null) {//前置节点为空,即删除的时第一个节点
    	first = next;//将first指针指向下一个节点即可
    } else {//不为空时,前置接点的后直接点修改为当前节点的后置节点;当前节点的前直接点置为null
        prev.next = next;
        x.prev = null;
    }
    if (next == null) {//后直接点为空,即删除的时最后一个节点
   		last = prev;//last指针指向要删除节点的前置接点
    } else {//不为空时,后直节点的前置节点指向要删除节点的前置节点;当前节点的后置节点置为null
        next.prev = prev;
        x.next = null;
    }
    x.item = null;//当前节点的值设置为null,后由垃圾回收器回收
    size--;
    modCount++;
    return element;//返回删除节点的值
}
2.2.3 选择那个实现类?
  • 对线程安全有要求:Vector;
  • 增删操作较多时使用LinkedList,查询较多使用ArrayList。原因:ArralyList删除元素后会导致数组不连续,底层数组要进行复制操作,添加在数组容量溢出时产生扩容+数组复制的开销;
  • 一般情况下使用ArrayList即可;
2.3 Set接口

元素唯一、无序(不能使用索引获取元素)。

2.3.1 接口方法

获取set大小:int size();

判断是否为空:boolean isEmpty();

判断是否包含某个元素:boolean contains(Object o);

转为数组:Object[] toArray();          T[] toArray(T[] a);

增加元素:boolean add(E e);

删除元素:boolean remove(Object o);

判断是否包含一个Collection的所有元素:boolean containsAll(Collection<?> c);

增加整个Collection对象的所有元素:boolean addAll(Collection<? extends E> c);

删除不属于Collection对象的所有元素:boolean retainAll(Collection<?> c);

删除属于Collection对象中的所有元素:boolean removeAll(Collection<?> c);

清空对象:void clear();

2.3.2 接口实现类
(1)HashSet

特点

  • 底层为HashMap;
  • 无序性。由于HashMap底层存储方式通过hashCode计算才能得出落在数组的那个位置,故存取对象的顺序不是一致的,即是无序的;
  • 唯一性。存储的对象实际存储在HashMap中key的位置,故存入的元素不可以重复,可以有一个null;

构造函数

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<>();
}

/**
 * Constructs a new set containing the elements in the specified
 * collection.  The <tt>HashMap</tt> is created with default load factor
 * (0.75) and an initial capacity sufficient to contain the elements in
 * the specified collection.
 *
 * @param c the collection whose elements are to be placed into this set
 * @throws NullPointerException if the specified collection is null
 */
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * 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
 * @throws     IllegalArgumentException if the initial capacity is less
 *             than zero, or if the load factor is nonpositive
 */
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * the specified initial capacity and default load factor (0.75).
 *
 * @param      initialCapacity   the initial capacity of the hash table
 * @throws     IllegalArgumentException if the initial capacity is less
 *             than zero
 */
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

/**
 * 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);
}
  • public HashSet();

构造函数内操作:map = new HashMap<>();

  • public HashSet(Collection<? extends E>);

构造函数内操作:map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));

addAll(c);

创建HashMap对象传入初始容量,再将Collection的值循环添加到这个对象,及将Collection的值复制到这个set中

  • public HashSet(int,float);

构造函数内操作:map = new HashMap<>(initialCapacity, loadFactor);

创建HashMap对象传入初始容量、负载因子(负载因子可看作一个百分比,当map的容量到达当前容量*负载因子的值时,map进行扩容。默认负载因子的值时0.75f)

  • public HashSet(int);

构造函数内操作:map = new HashMap<>(initialCapacity);

  • public HashSet(int float,boolean);

构造函数内操作:map = new LinkedHashMap<>(initialCapacity, loadFactor);

创建一个LinkedHashMap对象,loadFactor是什么??

特殊方法

源码增加逻辑:

public boolean add(E e) {
    //add方法内部实际上是对map这个对象添加一项,添加的对象作为map的key,map的值都是PERRESENT(一个空的Object:private static final Object PRESENT = new Object();)
    return map.put(e, PRESENT)==null;
}

由于HashSet底层是通过HashMap实现的,所以其他关于扩容等源码在HashMap介绍章节再详细介绍

(2)TreeSet

特点:

  • TreeSet是一个有序的集合,它的作用是提供有序的Set集合。
  • TreeSet是基于TreeMap实现的
  • TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。

构造函数:

/**
 * Constructs a set backed by the specified navigable map.
 */
TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

/**
 * 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>());
}

/**
 * Constructs a new, empty tree set, sorted according to the specified
 * comparator.  All elements inserted into the set must be <i>mutually
 * comparable</i> by the specified comparator: {@code comparator.compare(e1,
 * 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, the
 * {@code add} call will throw a {@code ClassCastException}.
 *
 * @param comparator the comparator that will be used to order this set.
 *        If {@code null}, the {@linkplain Comparable natural
 *        ordering} of the elements will be used.
 */
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

/**
 * Constructs a new tree set containing the elements in the specified
 * collection, sorted according to the <i>natural ordering</i> 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.
 *
 * @param c collection whose elements will comprise the new set
 * @throws ClassCastException if the elements in {@code c} are
 *         not {@link Comparable}, or are not mutually comparable
 * @throws NullPointerException if the specified collection is null
 */
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}

/**
 * Constructs a new tree set containing the same elements and
 * using the same ordering as the specified sorted set.
 *
 * @param s sorted set whose elements will comprise the new set
 * @throws NullPointerException if the specified sorted set is null
 */
public TreeSet(SortedSet<E> s) {
    this(s.comparator());
    addAll(s);
}
  • public TreeSet();
  • TreeSet(NavigableMap<E,Object> m);
  • public TreeSet(Comparator<? super E> comparator);
  • public TreeSet(Collection<? extends E> c)
  • public TreeSet(SortedSet s)
(3)LinkedHashSet

3、Map接口

3.1 概述

1.是一个双列集合,赋值的时候必须同时给key和value赋值

2.是一个无序的集合(存入和取出元素的顺序可能不一致)

3.key值不能重复,value可以重复

4.一个key只能对应一个vlaue

5.定义集合时,数据类型key和value可以使用相同的数据类型,也可以使用不同的数据类型

3.2接口方法

添加元素:V put(K key, V value);

删除元素:V remove(Object key);

清空整个集合:void clear();

修改元素:V put(K key, V value);

判断更新值与原值不一致时更新:default boolean replace(K key, V oldValue, V newValue) ;

包含此key就直接更新:default V replace(K key, V value);

通过key获取对应的值:V get(Object key);

通过key获取值,为空时返回默认值:default V getOrDefault(Object key, V defaultValue)

获取键值对数量:int size();

判断是否为空:boolean isEmpty();

判断是否包含此key:boolean containsKey(Object key);

判断是否包含此value:boolean containsValue(Object value);

复制整个Map:void putAll(Map<? extends K, ? extends V> m);

返回key的set集合(此set不支持add、addAll方法):Set keySet();

返回value集合(同样不支持add|、addAll方法):Collection values();

返回Set<Map.Entry<K, V>>:Set<Map.Entry<K, V>> entrySet();

返回hash值:int hashCode();

3.3接实现类
3.3.1HashMap

特点:

  • 并允许使用 null 值和 null 键。
  • 除实现了Map接口外还实现了Cloneable,Serializable,继承了AbstractMap抽象类
  • 此类不保证映射的顺序
  • (k,v)结构实际上是一个Node,实现了Map.Etry<K,V>
  • jdk7.0的HashMap底层实现是:数组+链表;jdk8.0更新为:数组+链表+红黑树

构造方法:

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and load factor.
 *
 * @param  initialCapacity the initial capacity
 * @param  loadFactor      the load factor
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 */
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and the default load factor (0.75).
 *
 * @param  initialCapacity the initial capacity.
 * @throws IllegalArgumentException if the initial capacity is negative.
 */
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * 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
}

/**
 * Constructs a new <tt>HashMap</tt> with the same mappings as the
 * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
 * default load factor (0.75) and an initial capacity sufficient to
 * hold the mappings in the specified <tt>Map</tt>.
 *
 * @param   m the map whose mappings are to be placed in this map
 * @throws  NullPointerException if the specified map is null
 */
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
  • public HashMap(int initialCapacity, float loadFactor)

通过初始化大小(initialCapacity需要是2的幂次方,不是也无妨底层代码会对初始化容量进行修正)、加载因子(loadFactor,默认0.75f,控制是否需要扩容数组)参数进行初始化;

Tip:loadFactor参数大于1并不会影响其正常存储和读取,只是超过长度后新加的Node会以链表的形式追加到相同的HahCode对应的位置,增加了hash碰撞的几率,会导致存储、读取效率低。

  • public HashMap(int initialCapacity)

通过初始化大小进行初始化;

  • public HashMap()

使用默认的initialCapacity(16)、loadFactor参数(.75f);

  • public HashMap(Map<? extends K, ? extends V> m)

示例化的同时复制一个Map的内容;

方法解析

增加(put)方法源码

//调用putVal方法,通过hash()方法处理key对象的hash值
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
//处理hash值
static final int hash(Object key) {
        int h;
        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是hashMap的数组,判断其是否为空或长度是0,即判断是不是第一次添加
        if ((tab = table) == null || (n = tab.length) == 0)
            //如果是空的话,调用resize()方法进行初始化,设置数组长度为默认的16、临界值为16*0.75=12
            n = (tab = resize()).length;
    	//通过 (n - 1) & hash 计算具体落在数组的哪一项上。如果是空,表示数组此位置还没有存储Node对象,添加的对象可以直接存储到数组中,可直接返回。否则需要进一步判断。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            //判断要加入的节点与上述数组位置的key值是否,相同的话将e对象指向 数组原有的节点 可进入下一个判断:if(e != null)
            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) {
                    //到达链表的尾节点,p的next指针指向此节点
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //判断是否达到树化的临界值,大于等于临界值(8)需要进行树化处理(此处也不一定会树化、还得判断数组的长度),这里-1是因为binCount是从0开始的
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果遇到key相同的,直接退出循环,进入下一个判断:if(e != null)
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //每次循环后,将p指向判断过的节点上,依次往后
                    p = e;
                }
            }
            //e保存的是数组旧节点
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                //onlyIfAbsent源码传参是false,或者旧节点的值为null,则将就节点的value置为要增加的节点的值,相当于值的更新,更新后直接返回更新后的Node节点。
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
    	//增加元素的情况(非更新),会执行以下代码
    	//更新次数++
        ++modCount;
    	//判断元素的数量大于临界值时,进行数组扩容处理
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

//树化方法,
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
    	//判断数组为空或数组长度小于MIN_TREEIFY_CAPACITY(64)时,先进行数组的扩容
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            //从数组上的节点开始全部更新为TreeNode节点
            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);
        }
    }
3.3.2LinkedHashMap

特点:

  • 是HashMap的子类
  • 与HashMap相比LinkedHashMap是有序
  • LinkedHashMap可以认为是HashMap+LinkedList,即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。

LinkedHashMap中并没有什么操作数据结构的方法,也就是说LinkedHashMap操作数据结构(比如put一个数据),和HashMap操作数据的方法完全一样,无非就是细节上有一些的不同罢了。

LinkedHashMap只定义了两个属性:

/**
 * The head of the doubly linked list.
 * 双向链表的头节点
 */
private transient Entry<K,V> header;
/**
 * The iteration ordering method for this linked hash map: true
 * for access-order, false for insertion-order.
 * true表示最近最少使用次序,false表示插入顺序
 */
private final boolean accessOrder;

构造方法:

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

public LinkedHashMap() {
    super();
    accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

特殊方法

添加元素时,使用HashMap的putVal()方法,那么怎么实现记录元素顺序的呢?通过查看对添加元素时调用的newNode方法可以看出改方法已经被LinkedHashMap重写了,除next属性外,LinkedHashMap.Entry还拥有head、tail、last、after属性来维护双向链表的结构,具体代码如下:

//LinkedHashMap.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);
    }
}
//头节点
transient LinkedHashMap.Entry<K,V> head;
//尾节点
transient LinkedHashMap.Entry<K,V> tail;

//再添加元素时,重写了newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    //LinkedHashMap.Entry对象,继承于HashMap.Node
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    //将新创建的节点p作为尾结点tail,
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    //若原tail指向为空,即没有元素,那么可以认为它即是head节点,也是tail节点,此时节点p的before和after都为null
    if (last == null)
        head = p;
    else {//否则,将新元素链接到链表的尾部。将当前尾节点p的前一个节点(before)设置为上一次的尾结点last,将上一次尾节点last的后一个节点(after)设置为当前尾结点p。通过此方法实现了双向链表功能,完成before,after,head,tail的值设置
        p.before = last;
        last.after = p;
    }
}

其次:关于afterNodeAccess()方法,在HashMap中没给具体实现,而在LinkedHashMap重写了,目的是保证操作过的Node节点永远在最后,从而保证读取的顺序性,在调用put方法和get方法时都会用到。

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    //accessOrder在初始化时可以定义,为true时会将操作变更过的元素移动到链表的的尾部
    if (accessOrder && (last = tail) != e) {
        //p时变更的节点、b时p的前置节点、a时p的后置节点
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        //p的after指向空
        p.after = null;
        //如果前置节点是空的,即变更的是头节点,则将头节点指向p节点的后置节点
        if (b == null)
            head = a;
        else//否则,p节点前置节点的after指向p节点的后置节点(可以是null)
            b.after = a;
        if (a != null)//如果p的后置节点不为空,p的后置节点的before指向p的前置节点
            a.before = b;
        else//p的后置节点为null,表示p是尾节点,此时last指向p的前置节点。到此p节点被剔除到链表之外
            last = b;
        if (last == null)//如果last为null即b为null,表示链表中只有p一个元素,head指向p
            head = p;
        else {//否则将p节点链接到链表尾部
            p.before = last;
            last.after = p;
        }
        tail = p;//将尾指针指向p
        ++modCount;
    }
}

get方法片段:

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);//查询完元素后,会执行afterNodeAccess方法,将查找的元素放到链表的尾部
    return e.value;
}

put方法片段:

//插入元素时,发现key已存在时会运行此片段将旧的value覆盖掉
if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

afterNodeInsertion方法:HashMap中没有具体实现

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    //获取头节点。removeEldestEntry方法默认直接返回false,可重写此方法,当满足条件时调用HashMap的removeNode方法,删除头节点。
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}
//HashMap类删除节点的方法
final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    //通过hash值查看落在hash表的位置是否为空,即是否有可能找到要删除的节点
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        //判断是否就是hash表上的这个节点,是的话node直接指向p
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            //从红黑树中去找
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                //通过node节点的next属性循环查找该元素
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    //p指向的是查找节点的上一个节点
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        //删除节点操作
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)//正好落在hash表上的情况
                tab[index] = node.next;
            else//在链表上的情况
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);//LinkedHashMap对删除后的节点进行指针的处理,HashMap改方法是空的。
            return node;
        }
    }
    return null;
}
//LinkedHashMap重写了afterNodeRemoval方法,处理伤处节点后的前后指针
void afterNodeRemoval(Node<K,V> e) { // unlink
    	//获取删除节点、删除节点的前置节点、后置节点
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    	//当前节点的前置、后置节点指向null
        p.before = p.after = null;
        if (b == null)//删除的节点是头节点
            head = a;
        else//正常情况
            b.after = a;
        if (a == null)//删除的节点是尾节点
            tail = b;
        else//正常情况
            a.before = b;
    }
3.3.4HashTable

特点:

  • 存放的匀速是键值对,即K-V
  • hashTable的键、值都不可以为空(会抛出空指针异常)
  • 使用方法与HashMap一致
  • hashTable时线程安全的,HashMap线程不安全
  • 结构:数组+链表。不会树化,每次插入新节点都是插在链表头。

构造方法:

  • 通过initialCapacity初始化大小、loadFactor加载因子实例化。构造过程中初始化的值包含:loadFactor、table、threshold(临界值,整个Hashtable中存放的节点到达临界值时会触发table的扩容)
public Hashtable(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load: "+loadFactor);

    if (initialCapacity==0)
        initialCapacity = 1;
    this.loadFactor = loadFactor;
    table = new Entry<?,?>[initialCapacity];
    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
  • 通过initialCapacity初始化大小实例化,默认加载因子是0.75f
public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
}
  • 无参构造,默认数组初始化大小为11、加载因子为0.75f
public Hashtable() {
    this(11, 0.75f);
}
  • 通过Map接口的实例化对象来构造hashtable,数组长度在Map对象的长度*2和11中去较大值,默认加载因子为0.75f。构造完成后调用putAll方法将Map对象的键值对存入这个Hashtable对象。
public Hashtable(Map<? extends K, ? extends V> t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);
}

方法解析

put方法:通过put方法添加新节点,其逻辑代码先获取key的hash值,判断这个key是否已经存在。存在时直接更新,否则通过addEntry()方法进行节点的添加

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();
    //通过hash值计算在tab的位置
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    //entry指向对应的位置,通过next循环遍历有无hash和key值相同的,相同的直接更新value即可
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }
	//增加entry节点
    addEntry(hash, key, value, index);
    return null;
}

addEntry方法:添加元素的方法会判断节点数量到达临界值时对数组进行扩容,而后再将新节点添加到Hashtable中。此处需要注意的是添加的新节点会直接放到数组上,充当这个位置链表的头节点。

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;//重新计算hash值
    }
    // Creates the new entry.
    @SuppressWarnings("unchecked")
    //获取index对应的entry节点
    Entry<K,V> e = (Entry<K,V>) tab[index];
    //将新节点插入到index处,同时通过构造方法将next指向e。这里与HashMap不同,Hashtable每次将新节点添加到tab上,即链表头上,通过next将原有的链表连接在其后。而HashMap是将新节点添加到链表尾部
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

rehash方法:节点数量到达临界值时对数组进行扩容。产生新数组后需要将旧数组原有的数据迁移过来。源码逻辑的实现方式采用了两层循环,第一层对数组进行循环遍历,第二层是对对应数组位置的链表进行遍历。与上述添加逻辑一致,调整节点位置时会将要调整的节点直接放在数组的位置,也就是链表头的位置,依次类推。

protected void rehash() {
    int oldCapacity = table.length;//旧hashtable长度
    Entry<?,?>[] oldMap = table;//旧hashtable

    // overflow-conscious code
    int newCapacity = (oldCapacity << 1) + 1;//2倍+1
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
            // Keep running with MAX_ARRAY_SIZE buckets
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    //定义新的map数组
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

    modCount++;
    //重新设置临界值
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    table = newMap;

    //遍历table的每个位置
    for (int i = oldCapacity ; i-- > 0 ;) {
        //遍历table后的链表
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
            Entry<K,V> e = old;//e指向当前节点
            old = old.next;//old指向当前节点的下一个节点

            //重新计算每个节点的index值
            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            //当前节点next指向新的table位置
            e.next = (Entry<K,V>)newMap[index];
            //将当前节点保存在新的table位置,插入到链表的表头
            newMap[index] = e;
        }
    }
}
3.3.5Properties

特点

  • 继承HashTable,是Hashtable的子类
  • 可通过load方法通过文件流等方式加载properties、xml文件到Properties对象中

构造函数

  • 空构造,但是注意实例化时会调用父类HashTable的空构造,初始化table等属性。
public Properties() {
    this(null);
}
  • 传入Properties对象,设置defaults属性
public Properties(Properties defaults) {
    this.defaults = defaults;
}
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值