Map接口及其实现类![](https://img-blog.csdnimg.cn/fad41d39e4f148728ff3f3da1cafccf1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP546L5LiN57Sv,size_20,color_FFFFFF,t_70,g_se,x_16)
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