java ArrayList、LinkedList、Set、Map等扩容方式,及其源码分析

java集合 (超详细)

    • List接口:
      • ArrayList集合:
          • 扩容机制:
      • LinkedList集合:
          • 存储方式及扩容:
          • LinkedList常用方法:
      • Vector集合:
    • Set接口:
      • HashSet集合
      • LinkedHashSet集合:
      • TreeSet集合:
    • Map集合:
        • HashMap:
          • hash值的计算方法:
          • 存储过程
          • HashMap常用方法:
          • HashMap的扩容机制:
          • HashMap底层典型属性的属性说明:
        • LinkedHashMap和TreeMap的使用

Java中已经有了数组为什么还要引入集合这个概念?回答这个问题前我们不妨先来看看数组的特点:

①数组一旦初始化,长度、类型就不能再改变了,只能按照初始化的数据类型和长度进行存储数据了。

②数组中提供的方法比较有限,对于数据的增删改查操作不方便,并且效率也不太高。

③可以存储重复的值,并且有序特点单一。

数组在存储和操作中的存在诸多不便,而集合恰巧能够解决上述问题,闲话少叙,直接进入重点。

在接口中继承和实现关系如下图所示:

Collection接口的的子接口为List和Set;其中List的实现类为ArrayList、Linked List、Vector;Set的实现类为HashSet和TreeSet等。

Map的主要实现类:HashMap、Hashtable等

java ArrayList、LinkedList、Set、Map等扩容方式,及其源码分析_第1张图片

//源码如下
public interface Collection<E> extends Iterable<E> {

java ArrayList、LinkedList、Set、Map等扩容方式,及其源码分析_第2张图片

Collection接口:单列集合,用来存储一个一个的对象。实现的子接口有两个:①List接口,用于存储有序的、可重复的数据(可以想象成动态的数组)②Set接口,存储无序的、不可重复的数据(类似于高中的集合)

List接口:

ArrayList集合:

ArrayList底层实现源代码:

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

可以看出ArrayList底层是使用Object[] elementData存储的。从字面上可以看出有Array指定和数组有点瓜葛,不错,许多性质和数组也类似,比如,存在索引,可以根据索引取元素等。

ArrayList的初始化方式有两种,直接整源码:

//使用空参构造器创建集合:
ArrayList list = new Arraylist();
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
//其中DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//使用带参构造器创建集合:(initialCapacity:指定集合的长度)
ArrayList list = new Arraylist(3);
public ArrayList(int initialCapacity) {
    //如果初始化长度大于0则根据传入的初始化长度创建一个数组	
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
    //如果长度等于0就和空参构造器的情况一样
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
	//否则就抛出一个异常
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
    }
扩容机制:

分为两种第一种,为空参构造器初始化。第二种,调用带参构造器初始化。

一、空参构造器初始

//情况一:(不想看源码,可以直接跳过代码段直接看结论)
//①:根据上述源码可以看出使用空参构造器创建集合时,初始化长度为0;

//从add方法中可以看出第一次调用时会调用ensureCapacityInternal方法进行初始化长度
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
//ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
    //如果第一次调用的的话,就会将长度初始化为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
//DEFAULT_CAPACITY常量为第一次添加元素的初始化10
private static final int DEFAULT_CAPACITY = 10;

//如果第一次的10不够用,或者后续的扩容不够用就会调用grow方法
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

//grow扩容方法:
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
    //使用位运算将原数组扩容为原容量的1.5倍。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果扩容后依旧不够的话就按照实际需求的大小进行扩容
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
    //判断数组容量是否超过规定的嘴的容量(不可能无限制扩容)
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

//MAX_ARRAY_SIZE能够扩容的最大容量
 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

至此,使用空参构造器初始化集合的扩容结束;作以总结:

当使用空参构造器进行初始化时,起始长度为0,当开始添加进第一个元素后,底层就会分配长度为10的空间,当存储第十一个时就会按照原来的长度的1.5倍进行扩容,也就是15,以后每次扩容都是按照之前按的1.5倍进行扩容,数组的最大容量在Integer.MAX_VALUE-8至Integer.MAX_VALUE之间,如果超出,则抛出OutOfMemoryError错误。上述结论是基于JDK1.8版本,而在JDK1.7中略有不同ArrayList list = new ArrayList();底层初始化时就创建了长度为10的Object[]数组elementData,而扩容方式与JDK1.8无区别。jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

二、调用带参构造器初始化

//情况二
//使用含参构造器:(上面有解析)
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);
        }
    }
//扩容的方式和空参一样,扩容为原来的1.5倍

带参构造器和空参构造器的区别就是在初始化时,在扩容的时候是一样(没有一个猿想多写一行代码,有的话就不是合格的猿)含参总结:当使用有参构造器时,指定的长度就是起始长度,后面扩容也是按照之前的1.5倍进行扩容,也就是说100第一次扩容那个为150.

性质决定用途:ArrayListde 适合应用于数据连续性遍历,读多写少的场景

ArrayList常用方法:

方法 含义
list.add(" "); 添加元素:默认添加至集合尾部
list.add(2," "); 添加元素:添加至指定位置
List lists = Arrays.asList(" “,” “,” “,” ");
list.addAll(listx);默认添加到集合尾部
list.addAll(0,lists);//添加至指定位置
添加元素:添加指定集合中的所有元素
lists.clear(); 清空列表
boolean b = list.contains(); 判断集合中是否存在指定元素
int i = list.indexOf(); 查找指定集合内元素的下标
ArrayList cloneList = (ArrayList) list.clone(); 集合克隆;克隆出来的是独立存在的两个集合,但是两个集合内容相同,计算出来的hashCode值相等,因此equals判断也相等,如果向克隆后添加内容,不会影响原来的集合。
boolean isEquals = list.equals(cloneList); 判断两个集合内元素是否相同
list.remove(0); 按照下标删除元素
boolean b2 = list.removeAll(Arrays.asList(“李白”, “杜甫”)); 按照指定集合的元素进行删除
list1.retainAll(list2); 交集
list3.set(list3.size() - 1,“h鄠邑”); 替换
list3.sort(new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
排序;需要重写compare方法
List retList = musucList.subList(0, 2); 截取子集合
Object[] array1 = musucList.toArray(); 集合转化数组,如果数组初始化长度小于集合长度,则将其复制进一个等大的数组,如果初始化长度大,则后面多余的部分用null填充

LinkedList集合:

LinkedList是基于双向链表实现,链表中的每个节点都是一个Node类型的对象,Node对象由item、prev、next三部分组成。对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储。

//初始化方式;只有空参初始化
LinkedList list = new LinkedList();

public LinkedList() {
}
存储方式及扩容:

由于采用链表结构,每次添加元素,都会创建新的Node节点并分配空间,所以不存在扩容,存储结构在关于ArrayList和LinkedList中做了详细的介绍。

public boolean add(E e) {
        linkLast(e);
        return true;
    }

//其中,Node定义为:体现了LinkedList的双向链表的说法
      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;
            }
        }

//向集合中添加元素(默认在最后节点进行添加)
void linkLast(E e) {
        final Node<E> l = last;
    //将需要存的元素中的l指向前一个节点,e存储当前内容,null代表后面没有节点因此为空
    //Node<>(指向上一个元素,当前元素内容,指向下一个元素)
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

//这里将向集合头部添加元素的源码也作以解释,和上述向尾部添加做以对比
private void linkFirst(E e) {
        final Node<E> f = first;
    //将需要存的元素中的null代表后面没有节点因此为空,e存储当前内容,f指向下一个节点,
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

//向指定节点前插入元素
 void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
     //指向更改就行,具体操作和上述操作无区别
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

特点:LinkedList底层是使用双向链表,因此在插入和元素时只需将指向更改就行,不存在ArrayList需要将插入或删除后面的元素进行后移或前移,但也存在弊端就是无法像ArrayList那样直接访问集合中的元素,也就是说LinkedList适合于频繁进行插入或删除操作,而不适合频繁访问,因为访问操作只能从头部或尾部开始。

LinkedList常用方法:
方法 含义
list.add() 在尾部插入元素
list.addFirst(" "); 在头部插入元素
list.addLast(" "); 在尾部插入元素
list.getFirst(); 获取头部元素
list.getLast(); 获取尾部元素
list.get() 根据下标获取元素
list.removeFirst(); 删除头部元素
list.removeLast(); 删除尾部元素
list.remove() 删除头部元素
list3.sort(new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
排序;需要重写compare方法
list.toArray(T[ ] a) 将链表转换为数组

Vector集合:

Vector集合属于远古’物件‘现在使用不多,因此不过多赘述。通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。线程安全,性能较差。

//创建时初始化长度就为10
public Vector() {
        this(10);
    }

Set接口:

特点:无序、值唯一;无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。值唯一:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。

HashSet集合

在创建HashSet时,其构造方法创建维护了一个HashMap(下文会做出解释)来进行存储,元素值保存在HashMap的key位置,而value位置是由系统进行同意分配的PRESET值。是线程不安全的,可以存储null值。

//创建HashSet集合的方式:
//使用空参构造器
HashSet<String> set = new HashSet<String>();
//使用带参构造器
HashSet<String> set = new HashSet<String>(4);

//构造器
public HashSet() {
        map = new HashMap<>();
    }

//添加方法
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

private static final Object PRESENT = new Object();

HashSet中常用的方法:

方法 解释
set.add() 添加新元素
set.clerar() 清空集合
set.contains() 判断集合中是否包含指定元素
set.isEmpty() 判断集合是否为空
set.remove() 删除元素
set.size() 获取集合长度
set.iterator 获取迭代器
元素的添加过程:

1、向集合中添加元素时,首先调用元素的hashCode()方法,计算元素的哈希值。然后计算出在集合中的存放位置。

2、判断此位置上是否有其他元素:

​		(1):位置上没有其他元素,则元素添加成功 。

​		(2):位置上已经存在其他元素,或以其他的方式存在(链表),则比较待插入
元素和原来元素的hash值

​					①:如果hash值不相同,则元素添加成功。

​					③:如果hash值相同,则继续需要调用元素所在类的equals方法

​                            A:equals方法返回true,则元素添加失败

​                            B:equals方法返回false,则元素添加成功

另外需要说明的是数组上已经存在需要放入数组时的情况在JDK1.7和JDK1.8的情况还
是有所区别的:JDK1.7是将元素放入到数组上,然后指向原来的元素(头插法);
JDK1.7是将元素存入链表尾部,原来元素指向新元素(尾插法);简单记就是”七上八
下“,7上数组,8下数组。

LinkedHashSet集合:

LinkedHashSet作为HashSet的子类是在HashSet的基础上增加了双向链表维护的两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet。创建LinkedHashSet时,其构造方法调用父类构造方法,在内部创建维护了一个LinkedHashMap,用于维持元素的有序性。缘于继承关系。因此,与HashSet的特点几乎一致,不过多赘述。

LinkedHashSet和HashSet存储对象所在类的要求(原因在上文”元素的添加过程“已做出解释):

①:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()

②:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码

③:对象中用作 equals() 方法比较的字段,都应该用来计算 hashCode 值。

TreeSet集合:

可以照添加对象的指定属性,进行排序。但在排序时有两点需要注意:
①:向TreeSet中添加的数据,要求是相同类的对象。
②:两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator)

​ A.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
​ B.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

//方式一:自然排序
@Test
    public void test1(){
        TreeSet set = new TreeSet();

        //失败:不能添加不同类的对象
//        set.add(123);
//        set.add(456);
//        set.add("AA");
//        set.add(new User("Tom",12));

            //举例一:
//        set.add(34);
//        set.add(-34);
//        set.add(43);
//        set.add(11);
//        set.add(8);

        //举例二:
        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

//方式二:定制排序
    @Test
    public void test2(){
        TreeSet set = new TreeSet(new Comparator() {
            //照年龄从小到大排列
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User)o1;
                    User u2 = (User)o2;
                    return Integer.compare(u1.getAge(),u2.getAge());
                }else{
                    throw new RuntimeException("输入的数据类型不匹配");
                }
            }
        });

        set.add(new User("Tom",12));
        set.add(new User("Jerry",32));
        set.add(new User("Jim",2));
        set.add(new User("Mike",65));
        set.add(new User("Mary",33));
        set.add(new User("Jack",33));
        set.add(new User("Jack",56));

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

Map集合:

Map键值对集合,集合中按Key-Value键值对方式存储,类似于高中的函数:y = f(x);

HashMap:

作为Map的主要实现类;线程不安全,但效率高;无序、key唯一、value允许重复、key和value允许为null。

HashMap在jdk8中相较于jdk7在底层实现方面的不同:

  1. new HashMap():底层没创建一个长度为16的数组
  2. jdk 8底层的数组是:Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
hash值的计算方法:

//        哈希值计算方法及冲突处理方法
        String s = "hahaha";
        int h;
        System.out.println(s.hashCode());
//        计算哈希值
        int hash = (h = s.hashCode()) ^ (h >>> 16);
        System.out.println(hash);

        int n = 16;//table 数组长度

//        方式一
//        通过%运算,效率太低(已经被抛弃了)
        int index1 = hash % n;
        System.out.println("下标" + index1);

//        方式二
//        通过&运算,效率高
        int index2 = (n - 1) & hash;
        System.out.println("下标" + index2);
存储过程

源码做以简单分析(乍一看挺长,稳住别慌奥)存储方式:

//将键值对传入后调用putVal方法:
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

//首先对Node源码进行分析:

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


//putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
    //对数组的初始化,按照默认长度16创建
        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;
    //判断哈希是是否相等,key是否相等,也就是判断key是否已经存在了,如果已经存在了,就将新的value值替换旧的value值。
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
    //判断是否为树Node节点(下文有解释)
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    //非数组非红黑树节点,就只剩下能存放进链表了
            else {
    //遍历链表
                for (int binCount = 0; ; ++binCount) {
    //判断是否为为尾节点(JDK1.8的尾节点)
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
    //链表不为空则判断是否长度大于7(TREEIFY_THRESHOLD是定义长度为8的常量),超过就将其转化为树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

HashMap常用方法:
 HashMap<String, String> map = new HashMap<>();
        map.put("010","刘二");
        map.put("029","张三");
        map.put("020","李四");
        map.put("0912","王五");
        map.put("0998","赵六");

//        清空所有KV键值对
          map.clear();

//        根据key删除键值对
          map.remove("0998");

//        根据key+value删除键值对

//        获取KV键值对
          map.remove("0998","赵六");

//        根据key替换value
          map.replace("0998","田七");

//        判断key或value是否存在
        System.out.println("val 赵六 ?" + map.containsKey("赵六"));
        System.out.println("kay 0998 ?" + map.containsKey("0998"));

//        根据key获取value
        String city1 = map.get("010");
        System.out.println("010=>" + city1);

        String city2 = map.get("0755");
        System.out.println("0755=>" + city2);

//        指定默认值(如果存在对应的值就获取,如果不存在就返回后面的内容)
        String city3 = map.getOrDefault("0755", "未知人名");
        System.out.println("0755=>" + city3);

//        获取所有的KV键值对
//        1.获取所有的key
        Set<String> keySet = map.keySet();
        System.out.println("所有的key" + keySet);
        System.out.println(keySet.getClass());

//        2.获取所有的value
        Collection<String> values = map.values();
        System.out.println("所有的val" + values);
        System.out.println(values.getClass());

        System.out.println(map);

//        获取每个KV键值对(Entry类型的对象)
        Set<Map.Entry<String, String>> set = map.entrySet();
        for (Map.Entry<String,String> entry: set) {
            System.out.println(entry.getKey() + "\t" + entry.getValue());
        }


HashMap<String, Integer> map = new HashMap<>();
        map.put("Mate60",8000);
        map.put("K60",4000);
        map.put("iphone",10000);

//依次获取Map中的每个【key】和【value】后,传入【BiFunction接口的实现类对象】进行替换操作后,将【替换结果】重新存入Map;
        map.replaceAll(new BiFunction<String, Integer, Integer>() {
            @Override
            public Integer apply(String s, Integer i) {
                return i - 1;
            }
        });

//根据key,将获取到的value传入【BiFunction接口实现类】中进行计算,并将【计算结果】,重新存入map;如果key不存在,则value默认为null
        map.compute("K60", new BiFunction<String, Integer, Integer>() {
            @Override
            public Integer apply(String s, Integer i) {
                return i * 2;
            }
        });

//如果key不存在,则重新计算value,并存入map;如果key存在,则不计算;      
        map.computeIfAbsent("K60",new Function<String, Integer>() {
            @Override
            public Integer apply(String key) {
                return 1;
            }
        });

//如果key存在,则重新计算value,并存入map;如果key不存在,则不计算;
        map.computeIfPresent("k60",new BiFunction<String,Integer,Integer>(){
           @Override
            public Integer apply(String t,Integer u){
               System.out.println(t+":"+u);
               return u+10;
           }
        });

//如果key存在,则将【原value值】与【新value值】传入【BiFunction接口实现类】中进行计算,并将【计算结果】,存入map;如果key不存在,则不进行任何计算,直接存入map;
        map.merge("K60", 4000, new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer i, Integer i2) {
                System.out.println(i + ":" + i2);
                return i + i2;
            }
        });

        map.merge("k80", 4000, new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(Integer i, Integer i2) {
                System.out.println(i + ":" + i2);
                return i + i2;
            }
        });

//forEach方法:
        map.forEach(new BiConsumer<String, Integer>(){
            @Override
            public void accept(String key, Integer val) {
                System.out.printf("%d:%d",key,val);
            }
        });
        System.out.println(map);
HashMap的扩容机制:
 
/*
DEFAULT_LOAD_FACTOR加载因子:加载因子实际上通俗来说就是集合的总容量与
集合中元素的比值,通常情况,设置为0.75也就是说,当初始16的容量达到12时
就会扩容。
*/
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

static final float DEFAULT_LOAD_FACTOR = 0.75f
    
        
 //resize()方法;
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {  //static final int MAXIMUM_CAPACITY = 1 << 30
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)//static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
                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);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            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;
                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;
    }

代码放出来就是吓唬吓唬大家,能看懂的就尽量看懂,毕竟懂底层没啥错,是在看不懂就直接上结论:

HashMap在jdk8中相较于jdk7在底层实现方面的不同:

  1. new HashMap():底层没创建一个长度为16的数组
  2. jdk 8底层的数组是:Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 大于 8 且当前数组的长度 大于 64时,此时此索引位置上的所数据改为使用红黑树存储。
HashMap底层典型属性的属性说明:

DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

LinkedHashMap和TreeMap的使用

向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要照key进行排序:自然排序 、定制排序:和上面一样。

LinkedHashMap的底层实现原理:LinkedHashMap底层使用的结构与HashMap相同,因为LinkedHashMap继承于HashMap。区别就在于:LinkedHashMap内部提供了Entry,替换HashMap中的Node。按照键值对方式存储,在HashMap的基础上,多维护了一条双向链表,用于存储顺序。事实上面的内容如果理解了,就基本懂了。

//HashMap中的内部类:Node
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    
//LinckedHashMap内部类: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);
        }
    }

报以学习心态写,欢迎大家评论区批评指正!!!

你可能感兴趣的:(java,java,开发语言)