集合——Map

1、Map接口介绍

在这里插入图片描述

  1. Map:双列数据,存储 key-value 对的数据(类似于函数 y=f(x))
    1. HashMap :作为 Map 的主要实现类,线程不安全,效率高。可以存储null的key和value
      1. LinkedHashMap:是HashMap的子类。
        ① 保证在遍历元素时,可以按照添加元素的顺序实现遍历
        ② 在原有的HashMap底层结构上,添加了一对指针,指向添加顺序的前一个元素和后一个元素
        ③ 所以对于频繁的遍历操作,此类执行效率高于HashMap
    2. TreeMap:可以按照添加的 key-value 键值对数据进行排序,实现排序遍历
      ① 注意:其中排序是根据 key 中的属性进行排序,自然排序或定制排序。所以里面存放的 key 必须是同一个类的对象
      ② 有排序的特点,底层使用红黑树存储(排序二叉树的一种)
      ③ 采用红黑树的存储结构,是有序的,查询速度比List快
    3. Hashtable:作为古老实现类,线程安全,效率低。不可以存储null的key和value
      1. Propertise:常用来处理配置文件。key 和 value 都是 String类型

HashMap的底层结构:

数组 + 链表 (JDK 7 及之前)
数组 + 链表 + 红黑树 (JDK 8 )

本节学习问题:
HashMap的底层实现原理?
HashMap的与 Hashtable的异同?
HashtableCurrentHashMap的异同?
谈谈对HashMapput / get方法的认识?
谈谈HashMap的扩容机制?
什么是负载因子(填充比)?
什么是吞吐临界值(阈值、threshold)?

2、理解key-value双列数据

key-value键值对之间的关系类似于高中的函数

  1. Map中的key:无序的、不可重复的,使用Set存储所有的key
    key所在类要重写hashSet()方法和equals()方法(以HashSet为例)
  2. Map中的value:无序的、可重复的,使用Collection存储所有的value
    value所在类要重写equals()方法
  3. 一个键值对:key-value 构成了一个 Entry 对象
  4. Map中的 entry:无序的、不可重复的,使用Set存储所有的 entry

在这里插入图片描述

3、HashMap的底层实现原理

3.1、以 JDK 7 为例说明

实例化操作:
HashMap h = new HashMap();
——实例化之后创建了一个长度为 16 的数组Entry[] table

Entry(int h,K k,V v,Entry<K,V> n){
	hash = h;
	Key = k;
	value = v;
	next = n;   //链表指向的后一个元素
}

添加数据操作:
h.put(key1,value1)
① ——先调用key1的hashCode(),计算得到扰动后的哈希值,此哈扰动后的希值经过某种算法计算之后,得到在Entry数组中的存放位置。若此位置上为空,此时的 key1-value1 添加成功
② —— 若此位置上不为空,存放着一个或多个数据(若是多个数据就会以链表形式存在),比较 key1 和已经存在的数据的哈希值。若 key1 的哈希值与已经存在的数据的哈希值不同,此时的 key1-value1 添加成功
③ —— 若 key1 的哈希值和其中的某一个数据( key2-value2 )的哈希值相同,继续比较,调用 key1所在类的 equals(key2)方法,如果方法返回是 false,此时的 key1-value1 添加成功
④ —— 如果返回是 true ,使用 value1 去替换已经存在的 value2 (修改操作,鸠占鹊巢)(在HshSet中是添加失败,属于财富数据,在HashMap中是替换操作)

注意:关于添加成功的情况②和情况③, key1-value1 与原来的数据以链表的方式存储

public V put(K key, V value){
	//说明HashMap可以存储null,与HashSet的区别之一
	if(key == null)
	    return putForNullKey(Value);
	    
	//计算得到扰动后的哈希值
	int hash = hash(key);
	//根据扰动后的哈希值计算得到存储的索引值位置
	int i = indexFor(hash, table.lenght);
	//判断该位置上是否有元素,若无,直接跳过,执行循环外代码
	//若有元素,则需要一个一个的比较
	for(Entry<K,V> e = table[i]; e != null;e = e.next){
	    Object k;
	    //存入的元素与已经存在的元素进行哈希值和equals比较
	    //若二者有其一不相同,直接跳过,执行循环外代码
	    //若都相同,则新的value值替代旧的value值
	    if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
    	    V.oldValue = e.value;
        	e.value = value;
        	e.recordAccess(this);
        	return oldValue;
    	}
	}
	//添加成功
	modCount ++;
	//在这里添加元素可能涉及到扩容操作,下面会说
	addEntry(hash, key, value,i);
	return null;
}

扩容操作:
在不断的添加过程中,会涉及到扩容问题

当数组的长度 >= 扩容阈值,且要存放的位置为空,则会扩容

默认扩容为原来的2倍,并将原来的数据复制过来

void addEntry(int hash,K key,V value,int bucketIndex){
	//判断长度是否达到扩容阈值,并且当前位置是否为空。两个条件都满足才会去扩容
	//仅仅是数组长度达到扩容阈值不一定会扩容
    if((size >= threshold) && (null != table[bucketIndex])){
    	//扩容为原来的2倍
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.lenght);
    }
    
    //添加元素
    createEntry(hash, key, value, bucketIndex);
}

对于同一个位置存放多个元素,JDK 7 的存放方式是将新的元素放入数组中,将旧的元素以链表方式挂在外面

void createEntry(int hash,K key,V value,int bucketIndex){
	Entry<K,V> e = table[bucketIndex];
	table[bucketIndex] = new Entry<>(hash, key, value, e);
	size++;
}

3.2、以 JDK 8 与 JDK 7 的不同之处:

HashMap h = new HashMap(); ——实例化之后底层没有创建数组
JDK 8 底层的数组不是 Entry[] 类型,而是 Node[] 类型

首次调用put()方法,底层才会创建长度为 16 的数组Node[]

JDK 7 底层结构只有 数组 + 链表,JDK 8 中底层结构 数组 + 链表 + 红黑树
——当数组的某一个索引位置上有多个元素,这多个元素以链表形式存在,这些数据的个数 > 8 ,且当前数组的长度 > 64 时,此时这个索引位置上的数据改为使用红黑树存储
(因为对于同一个位置上的元素,要添加新的元素就要一个一个的比较,比较耗时,而采用红黑树方式存储,数据是有序的,进行比较的效率较高)
在这里插入图片描述
JDK 8 中HashMap中一些重要的默认值以及属性

//默认主数组table的长度:16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   // aka 16

// 主数组table最大长度为:2^30=1,073,741,824
static final int MAXIMUM_CAPACITY = 1 << 30;

// 默认的负载因子loadFactor = 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//注意:以下两个条件都满足时才会进行树化

//链表树化阈值1:链表的长度超过8
static final int TREEIFY_THRESHOLD = 8;
//链表树化阈值2:table的长度超过64
static final int MIN_TREEIFY_CAPACITY = 64; 

//红黑树退化成链表的阈值:红黑树中的元素少于6时
//扩容时,元素重新分配过程中,会进行分链处理,此时红黑树有可能会少于6个元素
static final int UNTREEIFY_THRESHOLD = 6;

//主数组 table
transient Node<K,V>[] table;
//当前HashMap中的元素的个数
transient int size;
//HashMap结构修改次数,不包含相同key的覆盖操作
transient int modCount;

//扩容阈值,当size > threshold进行扩容
int threshold;
//负载因子
final float loadFactor;
/*
	 HashMap中的每个元素都被封装成 Node<K, V> 对象 
	 Node中的属性:
		1. K key
		2. V value
		3. 扰动后的hash值 hash
		4. Node<K, V> next
*/

当链表的长度超过8,但是table的长度不超过64的时候,执行树化操作

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

为什么要提前扩容?为什么不等满了再扩容?
首先这个底层的16数组不一定会满,再者为了尽可能避免链表过长。假如要链表尽可能的少,就可以让加载因子尽可能的小一些,更早一些去扩容

4、LinkedHashMap的底层实现原理

集合遍历输出可以按照数据添加的顺序输出

LinkedHashMap内部的构造器和put()方法都是继承了父类HashMap中的更准确和方法,但是不同的是在put()方法中,数据添加成功之后的创建底层数组的操作,在LinkedHashMap中被重写,因为增加了可以记录数据添加顺序的属性

父类HashMap中的内部类:Node

这里的next时链表中的下一个,是数据存放的顺序,这里是单向链表

static class Node<K, V> implements Map.Entry<K, V> {
	final int hash;
	final K key;
	V value;
	HashMap.Node<K, V> next;
}

子类LinkedHashMap中的内部类:Entry

能够记录元素的先后顺序

这里的beforeafter只是记录数据添加的顺序,不是链表顺序,after未必等于next,不是双向链表

static class Entry<K, V> extends HashMap.Node<K, V> {
	LinkedHashMap.Entry<K, V> before;
	LinkedHashMap.Entry<K, V> after;

	Entry(int hash, K key, V value, Node<K, V> next) {
		super(hash, key, value, next);
	}
}

此处可以提以下HashSet中的add()方法,底层的源码其实就是调用了Map中的put(key, value)方法

将要传入的数据放在 key 的位置,而 value 的位置放了一个常量值PRESENT

这个常量值为了避免后续使用会出现空指针异常,就不是放null,而是创建了一个静态的对象,这样存放多个数据之后,它们的 value 都是指向一个同对象

public boolean add(E e) {
	return this.map.put(e, PRESENT) == null;
}
private static final Object PRESENT = new Object();

5、Map中的常用方法

5.1、增加、删除、修改操作:

Object put(Object key, Object value):将指定的key-value添加或修改当前的map对象中

Object putAll(Map p):将p中的所有key-value对存放到当前的map中

Object remove(Object key):移除指定key对应的value值,并返回该值。假如该key不存在,返回null

void clear():清空当前map中的所有数据,与 p = null操作不同

    @org.junit.Test
    public void Test02(){
        Map map = new HashMap();

        //Object put(Object key, Object value):将指定的key-value添加或修改当前的map对象中
        //添加操作
        map.put("AA",12);
        map.put("BB",444);
        map.put("CC",555);
        //修改操作
        map.put("AA",333);
        System.out.println(map);   //{AA=333, BB=444, CC=555}

        //Object putAll(Map p):将p中的所有key-value对存放到当前的map中
        Map p = new HashMap();
        p.put("DDD",1111);
        p.put("EEE",1111);
        p.put("BB",1111);
        map.putAll(p);
        System.out.println(map);   //{AA=333, BB=1111, CC=555, EEE=1111, DDD=1111}

        //Object remove(Object key):移除指定key对应的value值,并返回该值。假如该key不存在,返回null
        System.out.println(map.remove("AA"));   //333
        System.out.println(map);   //{BB=1111, CC=555, EEE=1111, DDD=1111}

        //void clear():清空当前map中的所有数据,与 p = null操作不同
        p.clear();
        System.out.println(p);          //{}
        System.out.println(p.size());   //0
    }

5.2、元素查询的操作:

Object get(Object key):获取指定 key 对应的value

boolean containsKey(Object key):判断是否包含指定的 key
boolean containsValue(Object Value):判断是否包含指定的 Value

int size():返回 map 中 key-value 对的个数

boolean isEmpty():判断当前 map 是否为空

boolean equals(Object obj):判断当前 map 和参数对象 obj 是否相等。要想返回true,就要当前对象与参数对象的元素都相同

    @org.junit.Test
    public void Test03() {
        Map map = new HashMap();
        map.put("AA", 11);
        map.put("BB", 22);
        map.put("CC", 11);

        //Object get(Object key):获取指定 key 对应的value若没有对应的key,返回null
        System.out.println(map.get("AA"));

        //boolean containsKey(Object key):判断是否包含指定的 key
        //是通过key的哈希值计算得到位置,照这个位置上的元素,用equals()进行判断
        System.out.println(map.containsKey("DD"));
        //boolean containsValue(Object Value):判断是否包含指定的 Value
        System.out.println(map.containsValue(11));

        //int size():返回 map 中 key-value 对的个数
        System.out.println(map.size());

        //boolean isEmpty():判断当前 map 是否为空
        System.out.println(map.isEmpty());
    }

5.3、元视图的操作:

Set keySet():返回所有 key 构成的 Set 集合

Collection values():返回所有 value 构成的 Collection 集合

Set entrySet():返回所有 entry(key-value对) 构成的 Set 集合

    @org.junit.Test
    public void Test04(){
        Map map = new HashMap();
        map.put("AA", 11);
        map.put(45, 1234);
        map.put("CC", 11);

        //Set keySet():返回所有key构成的Set集合
        Set set = map.keySet();
        //用迭代器遍历Set集合
        //遍历的顺序是根据key排列的顺序
        Iterator iterator = set.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());  //AA   CC   45
        }
        
//遍历所有 key-value
        //也可以获取其中的kry值,然后通过get(Object key)方法得到value值
        Iterator i = set.iterator();
        while (i.hasNext()){
            Object keyNext = i.next();
            Object o = map.get(keyNext);
            System.out.println(keyNext + "," + o);   //AA,11   CC,11   45,1234
        }

        //Collection values():返回所有value构成的Collection集合
        Collection values = map.values();
        //用for循环遍历Collection集合
        //遍历的顺序是根据key排列的顺序
        for (Object value : values) {
            System.out.println(value);   //11   11   1234
        }
        
//遍历所有 key-value
        //Set entrySet():返回所有entry(key-value对)构成的Set集合
        Set entrySet = map.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            //entrySet里面的元素都是entry,可以进行强转
            Object next = iterator1.next();
            Map.Entry e = (Map.Entry) next;
            //然后就可以通过getKey()、getValue()方法获取key和value值
            System.out.println(e.getKey() + "," + e.getValue());    //AA,11   CC,11   45,1234
        }
    }

总结:

  1. 添加:
    Object put(Object key, Object value):将指定的key-value添加或修改当前的map对象中
  2. 删除:
    Object remove(Object key):移除指定key对应的value值,并返回该值。假如该key不存在,返回null
  3. 修改:
    Object putAll(Map p):将p中的所有key-value对存放到当前的map中
  4. 查询:
    Object get(Object key):获取指定 key 对应的value
  5. 长度:
    int size():返回 map 中 key-value 对的个数
  6. 遍历:
    Set keySet():返回所有 key 构成的 Set 集合
    Collection values():返回所有 value 构成的 Collection 集合
    Set entrySet():返回所有 entry(key-value对) 构成的 Set 集合

6、TreeMap

(学习 TreeMap 可以参照 TreeSet )

  1. 采用红黑树的存储结构,是有序的,查询速度比List快
  2. TreeMap中添加key-value对,要求key必须是由同一个类创建的对象
  3. 因为要按照key进行排序:自然排序、定制排序

对于自定义类而言,若TreeMap中数据都是自定义类Student,则类Student中需要实现Comparable接口,重写compareTo(Object obj)方法,否则报错。
如下所示,抛异常:java.lang.ClassCastException

    @org.junit.Test
    public void Test05(){
        TreeMap treeMap = new TreeMap();
        Student s1 = new Student("AA",11);
        Student s2 = new Student("BB",22);
        Student s3 = new Student("CC",33);
        treeMap.put(s1,1111);
        treeMap.put(s2,2222);
        treeMap.put(s3,2222);
    }

在这里插入图片描述

对于TreeMap有两种排序方式:

  1. 自然排序
    1. 实现Comparable接口,重写compareTo(Object o)方法
    2. 自然排序中,比较两个对象是否重复的标准:compareTo()返回0,(不再是根据equals()方法)
  2. 定制排序
    1. 定义Comparator对象,作为参数传入TreeMap有参构造器
    2. 比较两个对象是否重复的标准:compareTo()返回0,(不再是根据equals()方法)

6.1、自然排序

Student中重写compareTo(Object o)方法,按照姓名从小到大排序,按照年龄从小到大排序

    @org.junit.Test
    public void Test05(){
        TreeMap treeMap = new TreeMap();
        Student s1 = new Student("Tom",99);
        Student s2 = new Student("Ben",18);
        Student s3 = new Student("Angel",33);
        treeMap.put(s1,1111);
        treeMap.put(s2,2222);
        treeMap.put(s3,2222);

		//遍历
		Set entrySet = treeMap.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            //entrySet里面的元素都是entry,可以进行强转
            Object next = iterator1.next();
            Map.Entry e = (Map.Entry) next;
            //然后就可以通过getKey()、getValue()方法获取key和value值
            System.out.println(e.getKey() + "———value:" + e.getValue());    //AA,11   CC,11   45,1234
        }
    }
}
class Student implements Comparable{
    private String name;
    private int age;

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }


    //按照姓名从小到大排序,按照年龄从小到大排序
    @Override
    public int compareTo(Object o) {
        if(o instanceof Student){
            Student s = (Student)o;
            int i = this.name.compareTo(s.name);
            if (i == 0){
                return Integer.compare(this.age,s.age);
            }
            return i;
        }
        throw new RuntimeException("传入数据类型不一致");
    }
}

执行结果如下
在这里插入图片描述

6.2、定制排序

在这种排序下,传入的数据所在类也可以不实现Comparable接口和重写compareTo(Object o)方法。这样也不会报错

假如实现Comparable接口和重写compareTo(Object o)方法。也只会根据定制排序来实现排序

比如以下代码中,Student类中未实现Comparable接口,直接使用定制排序

            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Student && o2 instanceof Student){
                    Student s1 = (Student)o1;
                    Student s2 = (Student)o2;
                    return -s1.getName().compareTo(s2.getName());
                }
                throw new RuntimeException("传入数据类型不一致");
            }
        });
        Student s1 = new Student("Tom", 99);
        Student s2 = new Student("Ben", 18);
        Student s3 = new Student("Angel", 33);
        treeMap.put(s1, 1111);
        treeMap.put(s2, 2222);
        treeMap.put(s3, 2222);

        //遍历
        Set entrySet = treeMap.entrySet();
        Iterator iterator1 = entrySet.iterator();
        while (iterator1.hasNext()){
            //entrySet里面的元素都是entry,可以进行强转
            Object next = iterator1.next();
            Map.Entry e = (Map.Entry) next;
            //然后就可以通过getKey()、getValue()方法获取key和value值
            System.out.println(e.getKey() + "———value:" + e.getValue());    //AA,11   CC,11   45,1234
        }
    }

执行结果如下
在这里插入图片描述

7、Propertise

Hashtable的子类,常用来处理配置文件。key 和 value 都是 String类型

Propertise的创建涉及到反射,需要用到类加载器

先在module下右击,新建 resource bundle,自动创建Propertise文档

在这里插入图片描述

读取配置文件方式一:

使用流

读取的文件默认是在当前module下

public class PropertiseTest{
	@Test
	public void test1() throws Exception{
		//读取的文件默认是在当前module下
		//读取配置文件方式一:
		Propertise prop = new Propertise();
		FileInputStream fis = new FileInputStream("jdbc.propertise");
		prop.load(fis);
		
		String user = prop.getProperty("user");
		String password = prop.getProperty("password");
		System.out.println("user = "+user+ ", password = "+password);
	}
}

读取配置文件方式二:

使用类加载器

读取的文件默认是在当前module的src文件夹下
在这里插入图片描述

public class PropertiseTest{
	@Test
	public void test1() throws Exception{
		//读取的文件默认是在当前module的src文件夹下
		//读取配置文件方式二:
		Propertise prop1 = new Propertise();
		ClassLoader cl = PropertiseTest.class.getClassLoader();
		InputStream is = cl.getResourceAsStream("jdbc1.propertise")
		prop1.load(is);
		
		String user1 = prop1.getProperty("user1");
		String password1 = prop1.getProperty("password1");
		System.out.println("user1 = "+user1+ ", password1 = "+password1);
	}
}

8、Collections

是操作Collection、Map的工具类

Collections 工具类中提供了多个synchoronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题

	List l = new ArrayList();
	l.add(34);
	l.add(99);
	l.add(11);
	l.add(-10);
	l.add(99);
	
	//返回的l1即为线程安全的List
	List l1 = Collections.synchoronizedList(l);
	
	Map m = new HashMap();
	m.put("AA",11);
	m.put("CC",22);
	m.put("BB",11);

	Map map = Collections.synchronizedMap(m);

reverse(List):将集合l进行反转,修改的是当前集合

shuffle(List):将集合进行随机排序,每次执行的结果不一定相同

sort(List):对集合进行自然排序
sort(List,Comparator):对集合进行定制排序

swap(List, int i, int j):交换指定索引位置的元素

Object max(Collection):根据自然排序,返回集合中的最大值
Object max(Collection,Comparator):根据定制排序,返回集合中的最大值
Object min(Collection):根据自然排序,返回集合中的最小值
Object min(Collection,Comparator):根据定制排序,返回集合中的最小值

    @org.junit.Test
    public void Test01(){
        List l = new ArrayList();
        l.add(34);
        l.add(99);
        l.add(11);
        l.add(-10);
        l.add(99);
        System.out.println(l);     //[34, 99, 11, -10, 99]
        //reverse(List):将集合l进行反转,修改的是当前集合
        Collections.reverse(l);
        System.out.println(l);     //[99, -10, 11, 99, 34]

        //shuffle(List):将集合进行随机排序,每次执行的结果不一定相同
        Collections.shuffle(l);
        System.out.println(l);     //[34, 11, 99, 99, -10]

        //sort(List):对集合进行自然排序
        //sort(List,Comparator):对集合进行定制排序
        Collections.sort(l);
        System.out.println(l);     //[-10, 11, 34, 99, 99]

        //swap(List, int i, int j):交换指定索引位置的元素
        Collections.swap(l,3,4);
        System.out.println(l);     //[-10, 11, 34, 99, 99]
    }

int frequency(Collection, Object):返回集合中指定元素的出现次数,若不存在,返回0

void copy(List l1, List l2):将集合l2中的内容复制到l1中

boolean replaceAll(List l, Object oldVal, Object newVal):使用新值替换List对象的多有旧值

    @org.junit.Test
    public void Test01(){
        List l = new ArrayList();
        l.add(34);
        l.add(99);
        l.add(11);
        l.add(-10);
        l.add(99);
        System.out.println(l);     //[34, 99, 11, -10, 99]

        //int frequency(Collection, Object):返回集合中指定元素的出现次数,若不存在,返回0
        System.out.println(Collections.frequency(l, 99));

        //注意会抛异常:IndexOutOfBoundsException
        //因为 dest.size() 小于 l.size()
//        List dest = new ArrayList();
//        Collections.copy(dest,l);

        //void copy(List l1, List l2):将集合l2中的内容复制到l1中
        List<Object> dest = Arrays.asList(new Object[l.size()]);
        System.out.println(dest.size());
        Collections.copy(dest,l);
        System.out.println(dest);
    }

9、小结

HashMap的与 Hashtable的异同?

  1. Hashtable是古老的Map实现类,JDK 1 就提供了。线程安全,效率较低
    ——HashMap是主要的实现类,线程不安全,效率高
  2. Hashtable不允许使用 null 作为 key 和value
    —— HashMap可以使用 null 作为 key 和value
  3. Hashtable实现原理和HashMap相同,功能相同,底层都是用哈希表结构,查询速度快
  4. HashtableHashMap相同,都不能保证 key - value对 的顺序
  5. HashtableHashMap判断两个 key 相等、判断两个 value 相等 的标准相同

HashMap的底层实现原理

  1. JDK 8 实例化之后底层没有创建数组,首次调用put()方法,底层才会创建长度为 16 的数组Node[]
    —— JDK 7 实例化之后创建了一个长度为 16 的数组Entry[] table
  2. JDK 8 底层的数组不是 Entry[] 类型,而是 Node[] 类型
  3. JDK 7 底层结构只有 数组 + 链表,JDK 8 中底层结构 数组 + 链表 + 红黑树
    JDK 8 中当以链表形式存在的这些数据的个数 > 8 ,且当前数组的长度 > 64 时,此时这个索引位置上的数据改为使用红黑树存储
  4. 扩容操作:当数组的长度 >= 扩容阈值,且要存放的位置为空,则会扩容。默认扩容为原来的2倍,并将原来的数据复制过来
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值