Map接口及其实现类

3 篇文章 0 订阅

Map接口及其实现类

 

Map接口:

存储的是键值对形式,key-value键值对存在,key值是不能重复的,value是可以重复的

Map接口下方法:

 

 

集合中常用方法解释:

 // V put(K key, V value) 向集合中添加键值对
        hashMap.put("张杰","谢娜");
//        hashMap.put("张杰","谢娜2");
​
        System.out.println(hashMap.size());
        //void clear()  将集合元素清空处理
//        hashMap.clear();
​
        //boolean containsKey(Object key)  判断集合中是否存在指定的键 key  存在返回true  不存在返回false
        boolean key = hashMap.containsKey("战三");
//        System.out.println(key);
​
        //boolean containsValue(Object value) 判断集合中是否存在指定的值value  存在返回true  不存在返回false
        hashMap.containsValue("zhangsna");
//        System.out.println(hashMap.size());
​
        //V get(Object key) 通过键key获取value
        String s = hashMap.get("张杰");
 

Map接口下集合的遍历形式:

/通过entrySet遍历键值对
        Iterator <Map.Entry <String, String>> iterator = hashMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry <String, String> entry = iterator.next();
            System.out.println("key:"+entry.getKey()+",value:"+entry.getValue()+ " ");
        }
​
        //通过keySet遍历键
        Iterator <String> iterator1 = hashMap.keySet().iterator();
        while (iterator1.hasNext()) {
            String key1 = iterator1.next();
            System.out.print(key1+" ");
        }
​
        //通过values()遍历值
        Iterator <String> iterator2 = hashMap.values().iterator();
        while (iterator2.hasNext()) {
            String value = iterator2.next();
            System.out.print(value+" ");
        }
​

HashMap的介绍

特点:

1、存储的数据是键值对形式,key不能重复,value值可以重复

2、key和value都可以为null

3、不能保证内部元素的顺序

4、底层数据结构是哈希表

哈希表:key通过哈希函数映射到特定值的数据结构

哈希冲突:哈希函数f(x) ,f(m) =f(n) ,m不等于n

哈希函数:直接哈希,取模。

哈希冲突:链地址法,探测法(线性探测、随机探测)。

链地址法:如图所示:

HashMap源码研究

继承关系

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable   

HashMap继承自AbstractMap,该抽象类对Map接口的常用方法做了实现,方便子类复用

实现类Map接口,具有Map中提供的所有方法

实现Cloneable和Serializable接口

构造函数

SortedMap接口,SortedMap接口具有排序功能,具有比较器类Comparator

比较器类说明:

Comparable和Comparator比较

两个比较器都是接口:

Comparable接口中提供方法:

```java
public interface Comparable<T> {
    public int compareTo(T o);
}
```

该接口中提供了一个compareTo方法,

返回值分别为 -1 ,0,1

在类内部作为比较器使用,一旦确定比较属性,就不能更改





Comparator接口:

```java
public interface Comparator<T> {
    int compare(T o1, T o2);
}
```

接口中提供了compare方法,

该方法返回结果为大于0,等于0,小于0

类外部实现,自定义实现比较过程使用该接口

使用场景:对数据进行排序选择TreeMap

属性默认值

//默认初始容量 :值为16 
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
​
//默认的加载因子:0.75 
static final float DEFAULT_LOAD_FACTOR = 0.75f;
​
//默认空表
static final Entry<?,?>[] EMPTY_TABLE = {};
​
//存放数据 table属性,是entry类型数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
​
//存放的键值对个数
transient int size;
​
//扩容阈值   ,在扩容时判断  threshold =table.length()*loadFactor
int threshold;
​
//加载因子
final float loadFactor;
​
//版本控制
transient int modCount;
​
//key哈希相关
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
//key哈希相关
transient int hashSeed = 0;
​
​

底层数据结构

//存放数据位置  
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
​
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
    }

通过以上可知:HashMap底层存放数据是数据加链表形式,即是哈希表结构

扩容机制

扩容时机:在size>threshold时,会进行扩容

常用方法

插入元素:put(key,value)

    
public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            //当table数组为空时,进入初始化table数据,当第一次调用put操作会进入
            inflateTable(threshold);
        }
        
        //key为null,将数据存放在0号卡槽位置
        if (key == null)
            return putForNullKey(value);
        
        //key不为null的处理
        //通过key找到对应的存放卡槽位置
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //通过位置i找到卡槽i位置的数据链表,从头遍历,找key是否存在
            //判断条件是hash和key,相等值更新
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
       //key在i位置不存在,需要新增entry节点存放数据
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
​
private V putForNullKey(V value) {
      //key为null将哈希位置为0号位置,需要遍历0号卡槽链表、判断key为null是否存在
      //存在将entry中value进行跟新,返回旧的value中
      //不存在则新建entry实体,存放put的数据
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
​
​
//通过key哈希找到对应卡槽
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
​
        h ^= k.hashCode();
​
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
​
通过key的哈希值找到在哈希表中的位置
static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    
       容量是16
           0001 0000     16
           0000 1111     15
           1010 1001
           -------------
           0000 1001    9 
        return h & (length-1);
    }
    
​
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //当存放数据size大于扩容阈值threshold,进行扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //对HashMap进行2倍的扩容
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            //获取当前key应该插入的新卡槽位置
            bucketIndex = indexFor(hash, table.length);
        }
​
        createEntry(hash, key, value, bucketIndex);
    }
​
//扩容
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
​
       //新创建数组为原来的2倍
        Entry[] newTable = new Entry[newCapacity];
        //将原map中的每一个entry实体进行重新哈希到新的map中
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        //threshold = table.length*loadFactor
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
​
​
//采用头插法将数据插入
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++;
    }

put操作步骤:

1、先判断key是否为null,特殊处理,存放在哈希表的0号卡槽位置

2、key为null的数据是否存在,遍历0号卡槽位置的链表,如果存在(==)则更新value,返回

3、如果不存在,新建节点(addentry)

4、key不为null,通过key来计算哈希值,找到在哈希表的卡槽位置(hash,indexFor)

5、在对应卡槽获取链表,遍历找key是否存在,如果存在(hash&&equals)则更新value,返回

6、key在链表不存在,新建节点(addEntry)

7、考虑是否扩容(size>threshold),需要扩容,将新的大小为原来的2倍,然后将原哈希表中的数据

都重新哈希到新的哈希表中, 并更新当前插入节点的卡槽位置

8、 采用头插入将新entry节点插入到给定的卡槽位置

获取元素:get(key)

   
public V get(Object key) {
        //key为null,直接到0号卡槽位置获取结果
        if (key == null)
            return getForNullKey();
        //key不为null
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

private V getForNullKey() {
       //如果map为空,返回null
        if (size == 0) {
            return null;
        }
        //在0号卡槽位置,对链表遍历,查找key为null是否存在,存在则找entry中value返回,否则返回null
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }


通过key找到对应entry实例
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        //通过key的哈希找到key对应卡槽位置 
        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
            Object k;
            if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

查询过程:

1、如果key为null,在0号卡槽位置遍历链表查询,key存在则返回entry的value,否则返回null

2、如果key不为null,对key进行哈希,找到对应的卡槽,遍历链表,判断key是否存在(hash,key.equals),返回entry的value否则返回null

删除元素:remove(key)

通过key删除可以所在entry实体

    
public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
​
final Entry<K,V> removeEntryForKey(Object key) {
      //如果集合为空,直接返回null
        if (size == 0) {
            return null;
        }
       //通过key哈希找到对应卡槽(key为null卡槽为0)
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
​
       //删除节点即解决单向链表的删除问题:解决思路:给定两个指针,两指针一前一后,前指针表示要删除的节点,
    、  //通过后一个指针来将节点和前节点指针的next建立联系
        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
                    //后一个指针prex来将节点和前节点指针的next建立联系
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
​
        return e;
    }

删除过程:

1、通过key哈希来找到卡槽位置(key为null在0号卡槽)

2、对应卡槽的链表进行遍历查找,给定两个指针一前一后,前指针找到要删除节点,后指针建立和下一个节点关系

总结HashMap的特点

1、数据结构:哈希表(数组+链表)

2、默认数组大小:16

3、扩容机制:大小为原来数组长度的2倍

4、扩容因子:0.75f

5、扩容条件:0.75*数组的长度

6、线程安全:该集合是线程不安全的

7、储存数据的类型为键值对(key和value)

8、key和value都可以为null

9、储存的数据是无序的

10、key不能重复,value可以重复

11、key相同则会进行值覆盖

LinkedHashMap介绍

通过集合框架图可知:LinkedHashMap属于HashMap的子实现类

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

基本特点:

1、key不能重复、value是能重复 2、LinkedHashMap是插入有序的/访问有序 accessOrder:true 访问有序 false:插入有序 3、底层的数据结构是哈希表 4、key和value都能为null

LinkedHashMap是如何做到有序的?

数据结构

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

通过LinkedHashMap的继承关系是继承自HashMap,HashMap的属性及方法被继承

那table属性及entry类型都会被继承,其内部也是一个哈希表结构

HashTable和HashMap的异同点

相同点:

1、底层数据机构是哈希表(JDK 1.7)

2、key-value键值对中,key是不能重复的,value是可以重复的

3、HashMap和Hashtable都是插入无序的

不同点:

1、HashTable继承自Dictionary类,该类是比较早期提供的map父类,现推荐使用AbstractMap类

2、HashTable的默认初始值是11

3、HashTable是线程安全的(通过在方法上添加synchronized关键)

4、HashTable中key和value都不能为null

5、HashTable中对key的哈希过程和HashMap是不一样的

6、HashTable的扩容按照2倍加1大小扩容((oldCapacity << 1) + 1)

TreeMap集合

基本特点:

treeMap特点 1、key按照大小顺序排序,默认的是从小到大 2、key不能为null,value是可以为null 3、key不能重复、value可以重复

TreeMap是如何做到key有序的?

TreeMap底层数据结构是红黑树

时间复杂度O(log n) O(n) o(1)

TreeMap继承关系

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

treemap 实现NavigableMap接口,支持一系列的导航方法,返回具有顺序的key集合等

而NavigableMap接口声明如下:

public interface NavigableMap<K,V> extends SortedMap<K,V>

NavigableMap接口继承自

SortedMap接口,SortedMap接口具有排序功能,具有比较器类Comparator

比较器类说明:

Comparable和Comparator比较

两个比较器都是接口:

Comparable接口中提供方法:

public interface Comparable<T> {
    public int compareTo(T o);
}

该接口中提供了一个compareTo方法,

返回值分别为 -1 ,0,1

在类内部作为比较器使用,一旦确定比较属性,就不能更改

Comparator接口:

public interface Comparator<T> {
    int compare(T o1, T o2);
}

接口中提供了compare方法,

该方法返回结果为大于0,等于0,小于0

类外部实现,自定义实现比较过程使用该接口

使用场景:对数据进行排序选择TreeMap

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王不累

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值