看情况,如果只是用get,那么就没有线程安全问题。
如果put完之后,又get,在极端情况下,就有线程安全问题。因为hashmap有扩容机制,key的存储位置是通过hashcode在取模得到的,取模值取决于数组长度,因此当数组触发扩容的时候,取模值也要进行相应的增量,key的位置可能就要发生改变,取模值改变和get方法同时发生就可能会出现问题
红黑树是一种数据结构,属于二叉树的一种,叫平衡二叉树,他的特点是非叶子节点用来存储key值,叶子节点全用来存储数据,因此相比于其他树他的查询速率会快,而且支持区间查找。jdk8中新增的红黑树主要用于解决链表遍历的问题,我们都知道,链表插入快查询慢,如果在hashmap中一直在某个位置增加数据,恰巧不触发扩容的话,链表就会很长,查询就会变的很慢,当然没有上述情况也有这个问题。这样的话就需要引入红黑树,原先的数据结构链表就像是二叉树只有又节点呈线性,查询的效率就相比红黑树慢很多,红黑树可以通过自旋,减少树的高度,增快查询速率。但是红黑树并不是直接替换链表的,它设置了链表大小到8时才会转为红黑树,因为,链表还有插入快这一优点。
1.7数据+链表
1.8数据+链表+红黑树
hashmap数据结构:1.7之前数组+链表 1.8后增加了红黑树
特点:数组插入慢查询快,链表插入删除快查询慢
原理:put》通过哈希算法以key算出hashcode,再通过取模得到存储位置,如果相同的存储位置就发生哈希碰撞并变成链表形式;get》前面也是想put一样通过计算得到下标存储位置,如果找不到,就遍历当前位置的链表,先比较key、hash,如果没有就查看next,继续遍历直到有为止或到底。
哈希算法(散列):就是把任意长度值(key)通过散列算法编号成固定长度
的key(hashcode),通过这个地址进行访问的数据结构。
哈希碰撞:当某个key得出的存储位置上已经有key存储过了,就会发生哈希碰撞,即不会覆盖,而是在那一列变成链表,新的key的next属性值就指向旧key
可以的,因为java会自动提供一个单独的hash值 给空值,且重复null只会覆盖
实现的是1.7的没有链表的hashmap,也没增加扩容机制
package hashmaptest;
/**
* @author whongf
* @create 2020-07-19-14:09
*/
public interface Map<K,V> {
public V put(K k,V v);
public V get(K k);
public int size();
public interface Entry<K,V>{
public K getkey();
public V getvalue();
}
}
package hashmaptest;
/**
* @author whongf
* @create 2020-07-19-14:13
*/
public class HashMap<K,V> implements Map<K,V> {
private Entry[] table=null;
int size=0;
public HashMap(){
table=new Entry[16];
}
@Override
public V put(K k, V v) {
int index =hash(k);
Entry<K,V> entry=table[index];
if (entry==null){
table[index]=new Entry(k,v,index,null);
size++;
}else{
table[index]=new Entry(k,v,index,entry);
}
return (V) table[index].getvalue();
}
private int hash(K k){
return Math.abs(k.hashCode()%16);
}
@Override
public V get(K k) {
int index=hash(k);
Entry<K,V> entry=table[index];
if (null==entry){
return null;
}else{
return find(k,entry);
}
}
private V find (K k,Entry entry) {
if(k==entry.getkey()||k.equals(entry.getkey())){
return (V) entry.getvalue();
}else{
if (entry.next!=null){
return find(k,entry.next);
}
}
return null;
}
@Override
public int size() {
return size;
}
class Entry<K,V> implements Map.Entry<K,V>{
K k;
V v;
int hash;
Entry<K,V> next;
public Entry(K k, V v, int hash, Entry<K, V> next) {
this.k = k;
this.v = v;
this.hash = hash;
this.next = next;
}
@Override
public K getkey() {
return k;
}
@Override
public V getvalue() {
return v;
}
}
}
package hashmaptest;
/**
* @author whongf
* @create 2020-07-19-14:56
*/
public class Test {
public static void main(String[] args) {
HashMap<String,String> map=new hashmaptest.HashMap<String, String>();
map.put("学习","学习使我快乐");
map.put("游戏","游戏有什么好玩");
System.out.println(map.get("学习"));
System.out.println(map.get("游戏"));
for (int i=0;i<1000;i++){
map.put("测试"+i,"结果"+i);
}
for (int i=0;i<1000;i++){
System.out.println(map.get("测试" + i));
}
}
}
前面讲过1.7之前原理put》通过哈希算法以key算出hashcode,再通过取模得到存储位置,如果相同的存储位置就发生哈希碰撞并变成链表形式;get》前面也是想put一样通过计算得到下标存储位置,如果找不到,就遍历当前位置的链表,先比较key、hash,如果没有就查看next,继续遍历直到有为止或到底。
我们发现如果put过多,链表有可能会变的非常长,上面又提到了数组和链表的差异,链表插入删除快,查询很慢。因此为了解决链表遍历问题,在jdk1.8是引入了红黑树,红黑树又称是平衡二叉树,原先是用的链表,链表的存储在二叉树就如同线性的,查询效率为o(n),通过红黑树的变色和自旋可以改变树的高度,大大减少了查询时长。
但是jdk1.8不是直接引入红黑树的,它是红黑树和链表的组合,默认用链表,当链表长度为8时,就会转换为treenode红黑树。为什么这样呢,原因上面有提过,链表插入删除很快,为o(1),而红黑树在插入的时候还需要有自旋这个操作,效率会变慢点,因此两者需要有个临界点进行转换权衡。
先是用的链表,链表的存储在二叉树就如同线性的,查询效率为o(n),通过红黑树的变色和自旋可以改变树的高度,大大减少了查询时长。
但是jdk1.8不是直接引入红黑树的,它是红黑树和链表的组合,默认用链表,当链表长度为8时,就会转换为treenode红黑树。为什么这样呢,原因上面有提过,链表插入删除很快,为o(1),而红黑树在插入的时候还需要有自旋这个操作,效率会变慢点,因此两者需要有个临界点进行转换权衡。