文章目录
- 算法学习10——java中的map
- map接口
- AbstractMap抽象Map
- 域
- 方法
- HashMap
- 域
- 静态类
- 方法
算法学习10——java中的map
map接口
- 定义了一个用来把keys映射到maps的对象,一个map不能包含重复key,每个key最多映射一个value的值
- 这个提供三个collection视图,允许返回keys的set,values的collection,和key-value的键值对映射。
- 如果将可变对象作为键值需要格外注意
方法 | 功能 |
int size(); | 返回大小 |
boolean isEmpty(); | 是否为空 |
boolean containsKey(Object key); | 包含键 |
boolean containsValue(Object value); | 包含值 |
V get(Object key); | 根据键获取值 |
V remove(Object key); | 根据键移除元素 |
void putAll(Map<? extends K, ? extends V> m); | 放一个map进来 |
void clear(); | 清空 |
Set keySet(); | 获取keys视图 |
Collection values(); | 获取values视图 |
Set<Map.Entry<K, V>> entrySet(); | 返回所有的键值对 |
AbstractMap抽象Map
域
**keySet:**存储键值的set
transient Set<K> keySet;
**values:**存储取值的collection
transient Collection<V> values;
方法
方法 | 介绍 |
public int size() | 返回map容器内键值的多少 |
public boolean isEmpty() | 返回容器是否为空,调用了size()方法实现 |
public boolean containsValue(Object value) | 容器内是否包含指定的值:1. 获取entry的迭代器 2. 如果查询的value是空值,迭代元素,用==判断 3. 如果不是空值,迭代元素,用equals判断 |
public boolean containsKey(Object key) | 判断是否包含对应的键,同上,遍历entrySet,根据空值和非空两种不同的处理方式 |
public V get(Object key) | 同上,遍历entrySet,每个entry元素进行判断 |
V put(K key, V value) | 未实现该方法 |
public V remove(Object key) | 利用迭代器遍历entryset,利用迭代器删除元素 |
public void putAll(Map<? extends K, ? extends V> m) | 不断调用put方法,把map里的每个entry塞进来 |
public void clear() | 清除entrySet() |
public Set keySet() | 返回keySet,根据entrySet临时构造 |
public Collection values() | 同样根据entrySet临时构造 |
public abstract Set<Entry<K,V>> entrySet() | 一个未实现的抽象方法 |
public boolean equals(Object o) | 1. ==判断;2. 判断类型,不是一个类型直接返回false;3. 判断size,大小不一致返回false;4. equals逐个判断 |
public int hashCode() | 所有元素的hashCode的叠加 |
HashMap
- hashMap是基于hashtable的map接口实现,提供所有的可选map操作,并且允许空值和空键
- 不保证映射顺序,并且顺序随着时间推移不恒定
- 容量是哈希表中桶的数量
- 负载因子是自动扩容前允许的负载程度,当存储的条目超过负载因子和容量的乘积时,容量扩展为两倍
- 实现未同步,多个线程访问hashmap时,需要在外部同步
域
域 | 作用 |
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; | 默认的初始大小 |
static final int MAXIMUM_CAPACITY = 1 << 30; | 存储的最大容量 |
static final float DEFAULT_LOAD_FACTOR = 0.75f; | 默认的负载因子 |
static final int TREEIFY_THRESHOLD = 8; | 转换成为树的阈值 |
static final int UNTREEIFY_THRESHOLD = 6; | 非树化的阈值 |
static final int MIN_TREEIFY_CAPACITY = 64; | 树化的最小容量 |
transient Node<K,V>[] table; | 桶的数组 |
transient Set<Map.Entry<K,V>> entrySet; | 存放entryset |
transient int size; | 存放的记录的多少 |
transient int modCount; | 如果迭代器的modCount和对象的modCount不一样,快速失败。 |
int threshold; | 进行resize的阈值 |
final float loadFactor; | 重载因子 |
静态类
Node<K,V>
域:
- final int hash;哈希值
- final K key;键
- V value;值
- Node<K,V> next;下一个node的指针
方法:
- Node(int hash, K key, V value, Node<K,V> next),构造器,给上面说的几个域赋值
- public final K getKey() ,get方法
- public final V getValue() ,get方法
- public final int hashCode(),调用的是key中的hashcode方法,然后异或,和上面的hash没啥关系
- public final V setValue(V newValue),set一个新的value,返回旧的value
- public final boolean equals(Object o),先==判断,然后instanceof判断,然后用equals判断key和value
方法
- static final int hash(Object key) 如果key是空,那么返回0。就是以下这段代码,首先k==null不用说,如果是null返回0。如果不是null,那么首先调用key的hashCode,放在h中。然后右移16位,再和hash进行异或。这一套操作通俗来说就是,把key的hashcode的值,高16位和低16位进行异或。说白了就是引用高位来增加扰动,使得下标更随机更均匀。添加高位异或可以增加扰动呢?hashmap的取下标函数大多数情况都是直接取低位数值,在数据量大的时候碰撞的可能性大,因此会引入高位的数字,增加随机化。
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
//等同于以下代码
int h;
if(key == null) return 0;
h = key.hashCode();
return h ^ (h>>>16);
- **static Class<?> comparableClassFor(Object x)**返回x是否是可比较类型,并且返回类型参数
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
//获取对象x的类型变量,赋值给c。如果是string,直接返回
if ((c = x.getClass()) == String.class) // bypass checks
return c;
//获取对象实现的接口,包含类型参数信息
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
//如果是一个类型接口
if (((t = ts[i]) instanceof ParameterizedType) &&
//获取接口原始类型
((p = (ParameterizedType)t).getRawType() ==
//原始的接口类型是不是Comparable
Comparable.class) &&
//获取类型参数
(as = p.getActualTypeArguments()) != null &&
//参数类型的数量是否是1,并且是c
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
- **static final int tableSizeFor(int cap)**从最高位开始全部置1
static final int tableSizeFor(int cap) {
int n = cap - 1;
//最高2位置1
n |= n >>> 1;
//最高4位置1
n |= n >>> 2;
//最高8位置1
n |= n >>> 4;
//最高16位置1
n |= n >>> 8;
//最高32位置1
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
- public HashMap(int initialCapacity, float loadFactor) 构造器
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//获取阈值
this.threshold = tableSizeFor(initialCapacity);
}
- **public HashMap(int initialCapacity)**构造器
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
- public HashMap(Map<? extends K, ? extends V> m)
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
- **final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict)**放入一个map,如果是初始化就是false,否则是true。
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
//s是输入的map的大小
int s = m.size();
if (s > 0) {
//如果table还未初始化
if (table == null) { // pre-size
//计算除以负载因子后的容量大小
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
//如果t大于阈值(初始化的时候是会大于阈值的),把t从高位开始全部置1
if (t > threshold)
threshold = tableSizeFor(t);
}
//如果table已经初始化,并且大于阈值
else if (s > threshold)
//扩容
resize();
//插入
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
- **public int size()**返回数组的大小
- **public boolean isEmpty()**是否为空
- public V get(Object key)
public V get(Object key) {
Node<K,V> e;
//调用getNode方法获取value
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
- **final Node<K,V> getNode(int hash, Object key)**根据hash和key获取node
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//如果table非空,并且table的长度大于0
if ((tab = table) != null && (n = tab.length) > 0 &&
//并且对应的桶的不为空
(first = tab[(n - 1) & hash]) != null) {
//如果第一个hash就相等
if (first.hash == hash && // always check first node
//并且key相等或者equals判断相等,那么返回first
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果first的下一个节点非空,并且是树节点,那么递归地返回treenode
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//利用链表结构返回node
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
- **public boolean containsKey(Object key)**利用getNode判断是否包含key
- **public V put(K key, V value)**利用putVal方法放置key,value键值对
- **final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)**放置键值对
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为空,或者长度为0
if ((tab = table) == null || (n = tab.length) == 0)
//进行resize
n = (tab = resize()).length;
//i为hash后的坐标,首先看看这个是不是已经为空了,如果为空就直接往里塞
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//否则判断当前节点的hash值是否相等
if (p.hash == hash &&
//key是否相等或者是equals
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果已经是树了
else if (p instanceof TreeNode)
//调用树的方法来put
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//通过链表结构,循环地遍历,如果遇上已经有key,那就返回节点node,直到遍历完,
//如果发现这个链表已经到达树化的阈值,就进行树化
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e非空,更新值
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果值太大了,进行resize
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- final Node<K,V>[] resize() 扩容函数
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//旧数组大小
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//旧的阈值
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//旧的容量扩展一倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//旧阈值也扩展一倍
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
//初始化的情况
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//新阈值的计算
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
//先把原位置置空
oldTab[j] = null;
//如果原元素的下一位是空的,直接重新hash就好了
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//如果是TreeNode
else if (e instanceof TreeNode)
//利用树重hash
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
//扩容是扩容一倍嘛,多了一位bit,扩容思路是按照这个bit位,分别分散到两个位置里去
do {
next = e.next;
//如果e的hash与旧的大小为0
if ((e.hash & oldCap) == 0) {
//尾节点为空
if (loTail == null)
//头节点就是e
loHead = e;
else
//尾节点的下一个节点就是e
loTail.next = e;
//尾节点是e
loTail = e;
}
//高位和低位类似
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
//新的链写入
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}