zoukankan      html  css  js  c++  java
  • HashMap实现原理总结

    前言:
    我们常见的有数据结构有三种结构:1、数组结构 2、链表结构 3、哈希表结构。



    1. 数组结构

    public static void main(String[] args) {
    	// 数组:采用一段连续的存储单元来存储数据。
    	// 特点:指定下标0(1)  删除插入0(N)   查询快 插入慢
    	Integer[] integers = new Integer[10];
    	integers[0] = 0;
    	integers[1] = 1;
    	integers[2] = 2;
    	
    	System.out.println(integers[4]);
    }
    
    
    数组结构: 存储区间连续、内存占用严重、空间复杂度大
    
    优点:随机读取和修改效率高,原因是数组是连续的(随机访问性强,查找速度快)
    缺点:插入和删除数据效率低,因插入数据,这个位置后面的数据在内存中都要往后移动,且大小固定不易动态扩展。
    


    2. 链表结构

    public class Node {
    	private Node next;
    	private Object data;
    	
    	public Node(Object data) {
    		this.data = data;
    	}
    	
    	// 链表:一种物理存储单元上非连续,非顺序的存储结构
    	// 特点:插入,删除时间复杂度0(1)   查找遍历时间复杂度0(N)   插入快    查找慢
    	public static void main(String[] args) {
    		Node node = new Node("张三");
    		node.next = new Node("李四");
    		
    		System.out.println(node.data);
    		System.out.println(node.next.data);
    	}
    }
    
    链表结构:存储区间离散、占用内存宽松、空间复杂度小
    
    优点:插入删除速度快,内存利用率高,没有固定大小,扩展灵活
    缺点:不能随机查找,每次都是从第一个开始遍历(查询效率低)
    


    3. HashMap的底层实现原理

    HashMap的默认初始容量:16 必须是2的幂次方
    HashMap的默认加载因子:0.75

    
      当我们创建一个HashMap时,默认为其指定了容量大小。大小为16。可以理解成16个 “桶”。每个桶存的就是 K-V 的数据。
      
      在put(key,value)元素时,会根据key的值得到他的hashCode值【hashCode是一个int类型 -2147483648——2147483647 】。在得到hashCode值后,怎么确定把这个元素放到哪个桶呢?
          HashMap采用了一种算法,   index = (n - 1) & hash     n:表示初始容量     hash:该元素key的hashCode值  index:表示得出的该元素要存放到哪个桶的位置。
    
              为什么初始容量必须是 2的幂次方呢?
                  下面我们以值为“book”的Key来演示整个过程:
                  1.计算book的hashcode(ps: "book".hashCode() ),结果为十进制的3029737,二进制的101110001110101110 1001。
                  2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。
                  3.把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。
                  可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。
                  总之一句话,反正初始容量必须是2的幂次方,人家这个算法才能生效。
    
              那么,既然是2^n ,为啥一定要是16呢?为什么不能是4、8或者32呢?
                  关于这个默认容量的选择,JDK并没有给出官方解释。  
                  这应该就是个经验值(Experience Value),既然一定要设置一个默认的2^n 作为初始值,那么就需要在效率和内存使用上做一个权衡。这个值既不能太小,也不能太大。
                  太小了就有可能频繁发生扩容,影响效率。太大了又浪费空间,不划算。
                  所以,16就作为一个经验值被采用了。
    
    
              初始容量是不是不能自动变呢?
                  初始容量是会自动变的,如果16个 “桶” 都占满了,一直在这16个桶里面操作,难免会影响一定的效率。
                  初始容量会在桶被占用了  初始容量*默认加载因子=12  个桶数时进行扩容,扩容一倍的大小。
                  
                  Hashmap的扩容需要满足两个条件:当前数据存储的数量(即size())大小必须大于等于阈值;当前加入的数据是否发生了hash冲突。 //============来源于网上的说法,不确定。=============//
                  因为上面这两个条件,所以存在下面这些情况
                    (1)、就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。
                    (2)、当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,所以不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象。
    
    
              在确认了存在哪个位置之后,我们就可以把该元素存放到相应的桶里面。如果存在某2个或多个元素的key经过算法后,他们要存放到桶的位置相同怎么处理呢?
                  当出现这种情况,我们称为Hash碰撞。
                  在JDK1.8之前 ========链表来解决的=========
                  在JDK1.8之后 ========链表个数达到默认值8时将其改为红黑树=========
                  
    

  • 相关阅读:
    centos7安装kafka
    Qt——透明无边框Widget的bug
    Qt——浅谈样式表
    Qt——QLineEdit使用总结
    Qt——信号槽连接:基于字符串与基于函数的连接之间的不同
    Qt——树的搜索实现源码
    Qt——树结点的搜索
    Qt——鼠标拖动调整窗口大小
    Qt——右键菜单
    Qt——正则表达式
  • 原文地址:https://www.cnblogs.com/itlihao/p/14948189.html
Copyright © 2011-2022 走看看