HashMap总结
hashmap的基于数组和链表及红黑树实现的哈希表。采用数组作为键值对的容器,数组中存放的是单链表的头节点。hashmap采用链表的方式(链地址法)处理hash冲突,当发生冲突时,将冲突的元素链接到链表上去。hashmap的key和value都支持null值,而hashtable则不支持。它不是同步的,但是可以通过适当的方法变为支持同步的。
哈希表
1、 散列表,是根据关键码值(Key)进行访问的数据结构,也就是说,通过将Key映射到表中一个位置来获取记录,加快查找的速度,这个映射函数叫做撒案列函数,存放记录的结构 称之为散列表 寻址容易,删除插入也容易的数据结构 张三 15376467384 ZS 90+83 = 173 //key value 李四 18548787753 LS 76+83 = 159 王五 19847875837 WW 87+87 = 174 张帅 14338477838 ZS 90+83 = 173 class People{ private String name; private String number; } 2、 查找的时间复杂度: 链表 O(N) 二叉排序树 O(log2 N) 散列表 key - value O(1) key -》 hash函数 -》 name所对应的首字母的ASCII值 -> 散列码 3、哈表冲突 通常以下几种方式构造哈希函数 1)直接定址法 key address(key) = a*key + b 2)平方取中法 key 108 109 110 = 108^2 109^2 110^2 3)折叠法 key -》 拆分为几部分 区域号-书架号-图书编号 4)除留取余法 key -》 hash表的最大长度m 取不大于m的质数 key%质数 4、解决冲突 1)开放地址法 12 13 25 23 hash表的长度14 hash函数key%13 2)链地址法
HashMap基本用法
static void main(String
[] args
) {
Map
<String, Integer> map
= new HashMap<>();
map
.put("zhangsan", 98);
map
.put("lisi", 100);
map
.put("wangwu", 102);
map
.put("zhangsan", 1000);
System
.out
.println(map
.get("lisi"));
System
.out
.println(map
.remove("lisi"));
System
.out
.println(map
.size());
System
.out
.println(map
.containsKey("zhangsan"));
System
.out
.println(map
.containsKey("xiaohua"));
System
.out
.println(map
.isEmpty());
map
.clear();
System
.out
.println(map
.isEmpty());
}
HashMap迭代器的使用:
public static void main(String
[] args
) {
HashMap
<String, String> map
= new HashMap<>();
map
.put("a", "tulun1");
map
.put("b", "tulun2");
map
.put("c", "tulun3");
map
.put("aa", "tulun4");
Iterator
<Map
.Entry
<String, String>> itr
= map
.entrySet().iterator();
while(itr
.hasNext()){
Map
.Entry
<String, String> entry
= itr
.next();
System
.out
.println(entry
.getKey() + ":: "+entry
.getValue());
}
}
迭代后的结果
自定义HashMap
class MyHashMap<K,V>{
private Node
<K, V>[] table
;
private int size
;
class Node<K,V>{
protected K key
;
protected V value
;
protected int hash
;
protected Node
<K, V> next
;
public Node(K key
, V value
, int hash
) {
this.key
= key
;
this.value
= value
;
this.hash
= hash
;
}
}
public MyHashMap(int capacity
){
table
= new Node[capacity
];
}
public Iterator
<Node
<K, V>> iterator(){
return new Itr();
}
class Itr implements Iterator<Node
<K, V>>{
private int currentIndex
;
private Node
<K, V> nextNode
;
private Node
<K, V> currentNode
;
public Itr(){
if(MyHashMap
.this.isEmpty()){
return;
}
for(int i
=0; i
< table
.length
; i
++){
currentIndex
= i
;
Node
<K, V> firstNode
= table
[i
];
if(firstNode
!= null
){
nextNode
= firstNode
;
return;
}
}
}
@Override
public boolean hasNext() {
return nextNode
!= null
;
}
@Override
public Node
<K, V> next() {
currentNode
= nextNode
;
nextNode
= nextNode
.next
;
if(nextNode
== null
){
for(int i
=currentIndex
+1; i
<MyHashMap
.this.table
.length
; i
++){
currentIndex
= i
;
Node
<K,V> firstNode
= MyHashMap
.this.table
[currentIndex
];
if(firstNode
!= null
){
nextNode
= firstNode
;
break;
}
}
}
return currentNode
;
}
@Override
public void remove() {
if(currentNode
== null
){
return;
}
K currentKey
= currentNode
.key
;
MyHashMap
.this.remove(currentKey
);
currentNode
= null
;
}
}
public void resize(){
Node
<K, V>[] newTable
= new Node[table
.length
* 2];
for(int i
=0; i
<table
.length
; i
++){
rehash(i
, newTable
);
}
this.table
= newTable
;
}
public void rehash(int index
, Node
<K,V>[] newTable
){
Node
<K, V> currentNode
= table
[index
];
if(currentNode
== null
){
return;
}
Node
<K, V> lowListHead
= null
;
Node
<K, V> lowListTail
= null
;
Node
<K, V> highListHead
= null
;
Node
<K, V> highListTail
= null
;
while(currentNode
!= null
){
int newIndex
= newTable
.length
-1 & hash(currentNode
.key
);
if(newIndex
== index
){
if(lowListHead
== null
){
lowListHead
= currentNode
;
lowListTail
= currentNode
;
}else{
lowListTail
.next
= currentNode
;
lowListTail
= lowListTail
.next
;
}
}else{
if(highListHead
== null
){
highListHead
= currentNode
;
highListTail
= currentNode
;
}else{
highListTail
.next
= currentNode
;
highListTail
= lowListTail
.next
;
}
}
currentNode
= currentNode
.next
;
}
if(lowListTail
!= null
){
newTable
[index
] = lowListHead
;
lowListHead
.next
= null
;
}
if(highListTail
!= null
){
newTable
[index
+this.table
.length
] = highListHead
;
highListHead
.next
= null
;
}
}
public int hash(Object key
) {
int h
;
return (h
= key
.hashCode()) ^ (h
>>> 16);
}
public void put(K key
, V value
){
int hash
= hash(key
);
int index
= table
.length
-1 & hash
;
Node
<K, V> firstNode
= table
[index
];
if(firstNode
== null
){
table
[index
] = new Node(key
, value
, hash
);
size
++;
}else{
if(firstNode
.key
.equals(key
)){
firstNode
.value
= value
;
}else{
Node
<K,V> tmp
= firstNode
;
while(tmp
.next
!= null
&& !tmp
.key
.equals(key
)){
tmp
= tmp
.next
;
}
if(tmp
.next
== null
){
if(tmp
.key
.equals(key
)){
tmp
.value
= value
;
}else{
tmp
.next
= new Node(key
, value
, hash
);
size
++;
}
}else{
tmp
.value
= value
;
}
}
}
}
public boolean remove(K key
){
int hash
= hash(key
);
int index
= table
.length
-1 & hash
;
Node
<K, V> firstNode
= table
[index
];
if(firstNode
== null
){
return false;
}else{
if(firstNode
.next
== null
){
if(firstNode
.key
.equals(key
) && firstNode
.hash
== hash
){
table
[index
] = null
;
return true;
}
}
Node
<K,V> tmpPrev
= firstNode
;
Node
<K, V> tmp
= firstNode
.next
;
while(tmp
.next
!= null
){
if(tmp
.key
.equals(key
) && tmp
.hash
== hash
){
tmpPrev
.next
= tmp
.next
;
size
--;
}else{
tmpPrev
= tmp
;
tmp
= tmp
.next
;
}
}
}
return false;
}
public Node
<K,V> get(K key
){
int hash
= hash(key
);
int index
= table
.length
- 1 & hash
;
Node
<K, V> firstNode
= table
[index
];
if (firstNode
== null
) {
return null
;
}
if (hash
== firstNode
.hash
&& key
== firstNode
.key
) {
return firstNode
;
} else {
Node
<K, V> currentNode
= firstNode
.next
;
while (currentNode
!= null
) {
if (currentNode
.hash
== hash
&& currentNode
.key
== key
) {
return currentNode
;
}
currentNode
= currentNode
.next
;
}
}
return null
;
}
public boolean isEmpty(){
return size
== 0;
}
}
HashMap源代码分析
继承的类与实现的接口
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable
, Serializable
{
继承了AbstractMap类; 实现了Map<K,V>, Cloneable, Serializable三个接口。
类的属性
初始化容量
static final int DEFAULT_INITIAL_CAPACITY
= 1 << 4;
初始化容量为1左移4位(即二进制00001左移4位变为10000=16)
最大容量
static final int MAXIMUM_CAPACITY
= 1 << 30;
最大容量为2^30
桶中结构转化为红黑树对用的table的最小大小
static final int MIN_TREEIFY_CAPACITY
= 64;
默认加载因子
static final float DEFAULT_LOAD_FACTOR
= 0.75f
由链表转换为树的阈值
static final int TREEIFY_THRESHOLD
= 8;
桶上的节点个数大于8时会转为红黑树
由树转换为链表的阈值
static final int UNTREEIFY_THRESHOLD
= 6;
桶上的绩点个数小于6时会转为链表
存储元素的数组
transient Node
<K,V>[] table
;
大小总是2的幂次方
哈希表中节点的集合
transient Set
<Map
.Entry
<K,V>> entrySet
;
类的构造函数
HashMap有4种构造方法:
1、指定初始化容量和加载因子
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
);
}
2、指定初始容量
public HashMap(int initialCapacity
) {
this(initialCapacity
, DEFAULT_LOAD_FACTOR
);
}
3、使用默认值
public HashMap() {
this.loadFactor
= DEFAULT_LOAD_FACTOR
;
}
4、从其他Map实例创建HashMap对象
public HashMap(Map
<? extends K, ? extends V> m
) {
this.loadFactor
= DEFAULT_LOAD_FACTOR
;
putMapEntries(m
, false);
}
常用的方法
put操作
final V
putVal(int hash
, K key
, V value
, boolean onlyIfAbsent
,
boolean evict
) {
Node
<K,V>[] tab
; Node
<K,V> p
; int n
, i
;
if ((tab
= table
) == null
|| (n
= tab
.length
) == 0)
n
= (tab
= resize()).length
;
if ((p
= tab
[i
= (n
- 1) & hash
]) == null
)
tab
[i
] = newNode(hash
, key
, value
, null
);
else {
Node
<K,V> e
; K k
;
if (p
.hash
== hash
&&
((k
= p
.key
) == key
|| (key
!= null
&& key
.equals(k
))))
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
) {
p
.next
= newNode(hash
, key
, value
, null
);
if (binCount
>= TREEIFY_THRESHOLD
- 1)
treeifyBin(tab
, hash
);
break;
}
if (e
.hash
== hash
&&
((k
= e
.key
) == key
|| (key
!= null
&& key
.equals(k
))))
break;
p
= e
;
}
}
if (e
!= null
) {
V oldValue
= e
.value
;
if (!onlyIfAbsent
|| oldValue
== null
)
e
.value
= value
;
afterNodeAccess(e
);
return oldValue
;
}
}
++modCount
;
if (++size
> threshold
)
resize();
afterNodeInsertion(evict
);
return null
;
}
大致的流程图: 大致思路流程: 首先判断hashtable是否为null,如果为null,则先初始化容量;根据key计算处于桶中的索引位置,然后判断头节点是否为null,如果为null则插入节点;如果头节点不为空,判断key是否存在,如果是新值覆盖旧值,如果不存在,先判断是按照链表存储还是按照树的结构存储,如果是按照树的结构存储则就按照树的插入节点的方法插入,否则按照链表的方式插入。在按链表的方式插入时,移动引用,找到链尾,插入节点。在移动过程中会判断当前链表的容量与初始阈值的大小(判断链表长度是否大于8,如果大于8,则传化为红黑树;检查key是否存在,key不存在插入新节点,存在新值覆盖旧值),以此来决定要不要转化为树形结构存储。最后,插入成功返回旧的值,插入失败返回null。 参数: hash : key的hash值 key : 键 value : 值 onlyIfAbsent : 仅当键不存在才添加 evict : 是否回收
判断key是否存在
public boolean containsKey(Object key
) {
return getNode(hash(key
), key
) != null
;
}
resize();get();remove()方法的分析差不多一样这里就不分析了
HashMap和Hashtable对比