HashMap、LinkedHashMap、TreeMap使用及原理

HashMap

特点

Collection(存储单值)和Map(存储双值)是集合框架库的两个顶级接口。
Map<K,V>,以key–value键值对的形式存储数据。
key不重复,元素的存储位置由key决定,即可以通过key寻找键值对的位置,从而得到value的值。

继承的接口

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
  1. Map.Entry:存储key–value的具体数值。
  2. Cloneable:可以使用clone方法。
  3. Serializable:可以被序列化。

方法

public static void main(String[] args) {
    HashMap<String,Integer> map = new HashMap<>();
    //增
    map.put("Alice",5);
    map.put("Tom",6);
    map.put("Tom",7);//key值一样时,会替换value值
    
    //删
    map.remove("Alice");
    
    //改
    map.replace("Tom",10);//修改该key值下的value
    
    //查
    System.out.println(map.get("Tom"));//获取该key值下的value
    
	//遍历1
    Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
    while(iterator.hasNext()){
        Map.Entry<String, Integer> next = iterator.next();
        System.out.println("Name:"+next.getKey()+"  Age:"+next.getValue());
    }
    
    //遍历2  同理可将keySet换为valueSet
    Iterator<String> iterator1 = map.keySet().iterator();
    while(iterator1.hasNext()){
        String next = iterator1.next();
        System.out.println("Name:"+next);
    }
    
    //遍历3  同理也可将entrySet换为keySet和valueSet
    for(Map.Entry<String,Integer> entry:map.entrySet()){
        System.out.println("key:"+entry.getKey()+"  value:"+entry.getValue());
    }
}

源码分析

构造器

//双参构造
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
	//初始容量最大不能超过2的30次方
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
	//显然加载因子不能为负数  || 判断是不是一个数字
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();
}

//指定初始容量,加载因子为默认
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//无参构造,调用双参构造器
public HashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

put方法

public V put(K key, V value) {
	//第一次添加时,初始化数组
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);//threshold  阈值
    }
    
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key); //扰动处理后的key的hashcode
    int i = indexFor(hash, table.length);//值的位置
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //先判断哈希值,哈希值不一样key一定不相同
        //如果引用地址一样,key一定是一样的
        //最后比较key
        //这样做提高了效率,前两步判断速度快,且可以筛选掉大部分比较
        //直接对象比较的话速度较慢
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  //当有重复的key插入的时候就会替换掉之前的
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
        //如果重写equals方法,那么一定要重写hashcode方法:
        //重写了equals后,可能是两个对象按照编写者自己的逻辑相等,
        //如果这样直接会判断hashcode不相等,到不了equals比较
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
//key为null时的添加
private V putForNullKey(V value) {
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null) {  //本身已经存放一个key为null
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
//本身没有存放一个key为null
    modCount++;
    addEntry(0, null, value, 0);
    return null;
}

void addEntry(int hash, K key, V value, int bucketIndex) {
	//当数据量大于加载因子乘原数组大小并且数组该位置以及有元素了,扩容
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);   //2倍扩容
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);//计算下标
}

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

扩容方法

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];//32
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;//将新数组覆盖原数组
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//转移数据的方法
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {//遍历数组
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {//默认为false
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];//头插
            newTable[i] = e;//将元素放入新数组中
            e = next;
        }
    }
}

remove方法

final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);//计算哈希值和下标
    Entry<K,V> prev = table[i];//前驱
    Entry<K,V> e = prev;//e为当前元素
	//单链表的删除
    while (e != null) {
        Entry<K,V> next = e.next;
        Object k;
        //与添加方法中同理
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)  //删除的是头一个节点
                table[i] = next;
            else
                prev.next = next;//链接
            e.recordRemoval(this);
            return e;
        }
        prev = e;//更新
        e = next;
    }
    return e;
}

迭代器

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

private abstract class HashIterator<E> implements Iterator<E> {
    Entry<K,V> next;        // 下一个结点
    int expectedModCount;   // 快速失败
    int index;              // 当前数组下标
    Entry<K,V> current;     // 当前节点

    HashIterator() {
        expectedModCount = modCount;
        if (size > 0) {
            Entry[] t = table;
            //找到数组中第一个不为空的结点
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    }

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

    final Entry<K,V> nextEntry() {
        if (modCount != expectedModCount) //快速失败
            throw new ConcurrentModificationException();
        Entry<K,V> e = next;
        if (e == null)
            throw new NoSuchElementException();

        if ((next = e.next) == null) { //这一步相当于指针向后移动
            Entry[] t = table;
            //找到数组下一个不为空的结点
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
        current = e;
        return e;
    }

    public void remove() {
        if (current == null)
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        Object k = current.key;
        current = null;
        HashMap.this.removeEntryForKey(k);
        expectedModCount = modCount;
    }
}

LinkedHashMap

特点和使用与HashMap基本相同。
在Entry中引入before和after属性,构成双向链表,维护插入顺序。

但如果要维护大小顺序,则引入TreeMap集合。

TreeMap

特点

存储key—value键值对的集合,根据key的大小来排序。底层数据结构为红黑树key不能为空

继承的接口

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

使用

常用方法

// 找到第一个比指定的key小的值
Map.Entry<K,V> lowerEntry(K key);

// 找到第一个比指定的key小的key
K lowerKey(K key);

// 找到第一个小于或等于指定key的值
Map.Entry<K,V> floorEntry(K key);

// 找到第一个小于或等于指定key的key
K floorKey(K key);

//  找到第一个大于或等于指定key的值
Map.Entry<K,V> ceilingEntry(K key);

//找到第一个大于或等于指定key的key
K ceilingKey(K key);

// 找到第一个大于指定key的值
Map.Entry<K,V> higherEntry(K key);

//找到第一个大于指定key的key
K higherKey(K key);

// 获取最小值
Map.Entry<K,V> firstEntry();

// 获取最大值
Map.Entry<K,V> lastEntry();

// 删除最小的元素
Map.Entry<K,V> pollFirstEntry();

// 删除最大的元素
Map.Entry<K,V> pollLastEntry();

//返回一个倒序的Map
NavigableMap<K,V> descendingMap();

// 返回一个Navigable的key的集合,NavigableSet和NavigableMap类似
NavigableSet<K> navigableKeySet();

// 对上述集合倒序
NavigableSet<K> descendingKeySet();

Demo

Student类和Score类

//省略构造方法、getter方法、setter方法和toString方法
public class Student{
    private String name;
    private int age;
    private String sex;
}

public class Score {
    private int chineseScore;
    private int mathScore;
    private int englishScore;
}

主函数

TreeMap<Student,Score> map = new TreeMap<>();
    Student student1 = new Student("Tom",18,"male");
    Score score1 = new Score(78,88,98);
    Student student2 = new Student("Jack",18,"male");
    Score score2 = new Score(77,87,97);
    Student student3 = new Student("Alice",18,"female");
    Score score3 = new Score(76,86,96);
    map.put(student1,score1);
    map.put(student2,score2);
    map.put(student3,score3);

    Iterator<Map.Entry<Student,Score>> iterator = map.entrySet().iterator();
    while(iterator.hasNext()){
        Map.Entry<Student,Score> next = iterator.next();
        System.out.println("key:"+next.getKey()+"  value:"+next.getValue());
    }
}

结果
在这里插入图片描述
自定义类作为key时,无法自动比较,应提供比较方法。

内部比较器

Student类

@Override
public class Student implements Comparable<Student>{
	...
	...
    public int compareTo(Student o) {
        return this.name.compareTo(o.name);
    }
}

结果
在这里插入图片描述

外部比较器

主函数

public static void main(String[] args) {
    Student student1 = new Student("Tom",18,"male");
    Score score1 = new Score(78,88,98);
    Student student2 = new Student("Jack",18,"male");
    Score score2 = new Score(77,87,97);
    Student student3 = new Student("Alice",18,"female");
    Score score3 = new Score(76,86,96);
    Comparator<Student> comparator = new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getName().compareTo(o2.getName());
        }
    };
    TreeMap<Student,Score> map = new TreeMap<>(comparator);

    map.put(student1,score1);
    map.put(student2,score2);
    map.put(student3,score3);

    Iterator<Map.Entry<Student,Score>> iterator = map.entrySet().iterator();
    while(iterator.hasNext()){
        Map.Entry<Student,Score> next = iterator.next();
        System.out.println("key:"+next.getKey()+"  value:"+next.getValue());
    }
}

结果

在这里插入图片描述

比较

外部比较器可以提供多个比较原则
创建类时,确定排序规则时使用内比较器。
无法改变类时,使用外比较器。

总结

  1. 如果需要维护key–value结构的大小顺序可以选择TreeMap。
  2. TreeMap底层采用红黑树进行排序。
  3. 使用时一定要使用比较器。
  4. TreeMap和HashMap使用方法基本相同。
  5. put时key不能为空,提供了一系列和大小有关的方法。
  6. 查找时间复杂度:O(log2N)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值