Map接口的实现类:HashMap、HashTable、TreeMap

Map接口的具体实现类:HashMap、HashTable、Properties。其中HashMap是使用最多的类。

HashMap:

通过HashMap讲解Map类的特点,也可以知道HashMap类的特点:

        1.HashMap是以key-value健值对的形式存储数据的(HashMap$Node类型)

        2.HashMap中的key不能重复,而value可以重复,允许两者为null

        3.如果向HashMap中存储相同的key,则会覆盖原来的key-value,等同于修改

        4.HashMap无法保证映射顺序,底层使用哈希表进行存储(数组+链表+红黑树)

HashMap源代码:

HashMap hashMap = new HashMap();
hashMap.put("java", 10);
hashMap.put("php", 10);
hashMap.put("java", 20);
System.out.println(hashMap);

        首先,执行构造器,初始化加载因子loadFactor=0.75,此时HashMap$Node[] table = null

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

        向表中添加元素,执行put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

        继续执行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; // 辅助变量
    // table为null,执行resize方法进行扩容:Node<K,V>[] newTab = new Node[newCap];
    // table变为length=16的空表
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 判断所在索引位置是否为空
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 索引位置为null,直接将数据封装成Node类型放在该位置
        tab[i] = newNode(hash, key, value, null);
    // 该索引位置不为null
    else {
        Node<K,V> e; K k;
        // 索引位置key与加入的key哈希值相同并key是同一个对象或者equals方法返回真
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            // 索引位置节点还是原来的Node,不做改变
            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) {
                    // 将新数据封装成Node插入到链表最后
                    p.next = newNode(hash, key, value, null);
                    // 判断链表长度是否进行树化
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                 }
                 // 比较到链表某个位置,key哈希值相等,是同一个对象或equals相同
                 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; // 没增加一个Node,size++
     // 数组table到达临界值,完成数组扩容
     if (++size > threshold)
         resize();
     afterNodeInsertion(evict);
     return null;
}

        链表树化

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // 如果table表为null,或者没有达到64,不会直接树化,而是进行扩容
    // 树化后,数据减少到一定程度也会发生剪枝(树结构转为链表结构)
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
}

HashTable:

HashTable特点:

        1.存放的元素是键值对key-value

        2.HashTable中的键和值都不能为null,否则会抛出空指针异常

        3.HashTable的使用与HashMap相同

Hashtable hashtable = new Hashtable();
hashtable.put("lucy", 100);
hashtable.put("lic", 100);
hashtable.put("lic", 80); // 替换
hashtable.put("hello1", 1);
hashtable.put("hello2", 2);
hashtable.put("hello3", 3);
hashtable.put("hello4", 4);
hashtable.put("hello5", 5);
hashtable.put("hello6", 6);
hashtable.put("hello7", 7);

底层实现:

        1.HashTable中存在数组HashTable$Entry[],数组初始化大小为11

        2.数据封装为Entry类型存储在数组table中

        3.临界值threshold:11*0.75 =8

        4.按照自己的扩容机制进行扩容

HashTable的扩容机制:

        执行addEntry()方法

addEntry(hash, key, value, index);

        判断数组当前大小是否大于临界值threshold,执行rehash扩容

if (count >= threshold) {
    // Rehash the table if the threshold is exceeded
    rehash();

        将数组扩容为原先数组的二倍

int newCapacity = (oldCapacity << 1) + 1;
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]

        因此,数组大小变化:11(8)-> 23(17) -> 47(35) ->.......

TreeMap:

        TreeMap实现了Map接口,存储数据的同时,可以实现数据排序

使用默认构造器创建TreeMap对象,按照默认顺序进行排序

TreeMap treeMap1 = new TreeMap();
treeMap1.put("jack", "杰克");
treeMap1.put("tom", "汤姆");
treeMap1.put("kristina", "克瑞斯提娜");
treeMap1.put("smith", "史密斯");
System.out.println(treeMap1); // {jack=杰克, kristina=克瑞斯提娜, smith=史密斯, tom=汤姆}

向构造器中传递参数(实现了Comparator的匿名内部类),按照指定的顺序进行排序

TreeMap treeMap2 = new TreeMap(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        // 按照传入的key的大小进行比较(使用的是String类中实现的Compare方法)
        //return ((String) o1).compareTo(((String) o2));
        // 按照传入的key的长度进行比较
        return ((String) o1).length() - (((String) o2).length());
    }
});
treeMap2.put("jack", "杰克");
treeMap2.put("tom", "汤姆");
treeMap2.put("kristina", "克瑞斯提娜");
treeMap2.put("smith", "史密斯");
treeMap2.put("mary", "玛瑞"); // jack=玛瑞
System.out.println(treeMap2); // {tom=汤姆, jack=杰克, smith=史密斯, kristina=克瑞斯提娜}

源码解读:

        调用构造器,将实现comparator接口的匿名内部类传给comparator属性

public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

        添加元素,调用add方法

return m.put(e, PRESENT)==null

        继续调用put方法

public V put(K key, V value) {
    Entry<K,V> t = root;
    // 第一次添加,直接添加
    if (t == null) {
        compare(key, key); // type (and possibly null) check
        // 调用compare方法,对同一个key进行比较,不会对添加元素产生影响,仅仅检查是否为null
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;//添加成功
     }
     // 之后添加,启用比较器
     int cmp;
     Entry<K,V> parent;
     // split comparator and comparable paths
     Comparator<? super K> cpr = comparator; 
     if (cpr != null) {
         do {
             parent = t;
             cmp = cpr.compare(key, t.key);
             if (cmp < 0)
                 t = t.left;
             else if (cmp > 0)
                 t = t.right;
             // 通过compare得到的两个key相等时,替换value
             else
                 return t.setValue(value);
          } while (t != null);
    }
    Entry<K,V> e = new Entry<>(key, value, parent);
    // 根据cmp的取值确定新元素加入的位置     
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null; // 添加成功
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值