散列链表,在JDK中的API实现是 HashMap 类。

  为什么HashMap被称为“散列链表”?这与HashMap的内部存储结构有关。下面将根据源码进行分析。

  首先要说的是,HashMap中维护着的是一个数组: transient Node<K,V>[] table; ,数组中的每个元素都是一个 Node 对象。这里的Node是HashMap的一个内部类,代码如下:

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
} public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; } public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}

  可以看出来,这个Node类明显是一个链表的结构。也就是说,HashMap是一个链表的数组,这样一来,HashMap作为“链表”的部分就清楚了。

  那么HashMap为什么还被定义为“散列”呢?我们来看HashMap中的 put() 方法。put()方法中直接调用了 putVal() 方法,putVal()方法代码如下:

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) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

  通过上面的代码可以看到,HashMap分析某个节点应该存储到哪条链中的依据是:先通过HashMap中的 hash() 方法获取到当前元素的key的哈希码,然后通过 (n - 1) & hash 运算,确定该节点具体应该位于哪条链上。这样一来,就相当于在HashMap中创建了一个“索引”,如果要取元素,只需要通过key的哈希值,就可以轻松地索引到节点所在的链,从而遍历链中的所有元素,最终得到目标节点,大大提高了查找的效率。通过这种方式,可能会导致数组中某些下标没有存储任何数据,HashMap的“散列”就体现在这里。

  HashMap的这种“散列”的思想,使得HashMap的查询效率比其他的任何数据结构都要高。但是,我们都知道,最好的散列,就是数组中的每个元素都是一个只有一个节点的链,即数组中的每个下标都只对应一个节点,这样,就可以保证HashMap的查询效率达到最高。当然,这种情况是很难实现的,但是HashMap给我们提供了一个能极大限度地达到这种效果的机制,那就是HashMap的扩容机制。

  HashMap的扩容机制体现在putVal()方法中: if (++size > threshold) resize(); 。 resize() 方法就是HashMap的扩容方法,后面详述; threshold 是HashMap的扩容临界值,默认是当前HashMap中数组长度的0.75倍; size 是当前HashMap中存储的节点的数量。也就是说,当HashMap中存储的节点数量超过扩容临界值的时候,就要开始扩容。通过这样方式,使HashMap能够极大地实现最佳散列效果。简单来说,如果不扩容,那么所有元素都会挤在那个数组中,难免会导致数组中某个下标对应的链过长;而通过扩容,增大了 (n - 1) & hash 操作中的基数n,因此各个元素可以重新散列,铺得更散。HashMap就是通过这种方式达到散列的。

【数据结构】之散列链表(Java语言描述)的更多相关文章

  1. 【数据结构】之二叉树(Java语言描述)

    有关树的一些基础知识点请参考[这篇文章]. 本文主要记录Java语言描述的二叉树相关的一些操作,如创建.遍历等. 首先,我们需要一个表示树中节点的数据结构TreeNode,代码如下: public c ...

  2. 【数据结构】之队列(Java语言描述)

    在[这篇文章]中,我简单介绍了队列的基本数据结构及操作方式,并用C语言代码描述了队列的基本功能实现. JDK中默认为我们提供了队列的API—— Queue . Queue是一个接口,其中提供了处理队列 ...

  3. 【数据结构】之栈(Java语言描述)

    在前面的[这篇文章]中,我简单介绍了栈这种数据结构的操作功能,并使用C语言对其进行了代码的编写. Java的JDK中默认为我们提供了栈这种数据结构的API—— Stack . Java中的Stack类 ...

  4. 数据结构(java语言描述)

    概念性描述与<数据结构实例教程>大同小异,具体参考:http://www.cnblogs.com/bookwed/p/6763300.html. 概述 基本概念及术语 数据 信息的载体,是 ...

  5. 数据结构与抽象 Java语言描述 第4版 pdf (内含标签)

    数据结构与抽象 Java语言描述 第4版 目录 前言引言组织数据序言设计类P.1封装P.2说明方法P.2.1注释P.2.2前置条件和后置条件P.2.3断言P.3Java接口P.3.1写一个接口P.3. ...

  6. 数据结构(Java语言描述)-第一章:概述

    第一章 概述 1.0 序言 自己为啥要学数据结构嘞,我觉得主要有以下三个原因: 前段时间在看并发编程时,发现aqs,corrunthashmap等底层都用到了数据结构,主要的有队列,还有链表,学习数据 ...

  7. 《数据结构与算法分析-Java语言描述》 分享下载

    书籍信息 书名:<数据结构与算法分析-Java语言描述> 原作名:Data Structures and Algorithm Analysis in Java 作者: 韦斯 (Mark A ...

  8. 三元组表压缩存储稀疏矩阵实现稀疏矩阵的快速转置(Java语言描述)

    三元组表压缩存储稀疏矩阵实现稀疏矩阵的快速转置(Java语言描述) 用经典矩阵转置算法和普通的三元组矩阵转置在时间复杂度上都是不乐观的.快速转置算法在增加适当存储空间后实现快速转置具体原理见代码注释部 ...

  9. 利用栈实现算术表达式求值(Java语言描述)

    利用栈实现算术表达式求值(Java语言描述) 算术表达式求值是栈的典型应用,自己写栈,实现Java栈算术表达式求值,涉及栈,编译原理方面的知识.声明:部分代码参考自茫茫大海的专栏. 链栈的实现: pa ...

随机推荐

  1. jupyter qtconsole 的安装

    Jupyter qtconsole最近开始研究人工智能算法,发现了一款基于python的科学计算的神器,jupyter qtconsole,简直就是ipython的加强版,每个命令都直接显示帮助信息, ...

  2. Vue入坑第一篇

    写在前面的话:文章是个人学习过程中的总结,为方便以后回头在学习.文章中会参考官方文档和其他的一些文章,示例均为亲自编写和实践,若有写的不对的地方欢迎大家和我一起交流. 一.前言 本篇作为vue入门的一 ...

  3. Python中xml和dict格式转换

    在做接口自动化的时候,请求数据之前都是JSON格式的,Python有自带的包来解决.最近在做APP的接口,遇到XML格式的请求数据,费了很大劲来解决,解决方式是:接口文档拿到的是XML,在线转化为js ...

  4. beanFactory 设计模式 Bean 生命周期的胡言乱语,哈哈

    写在前面的话 适用读者:有一定经验的,本文不适合初学者,因为可能不能理解我在说什么 文章思路:不会一开始就像别的博客文章那样,Bean 的生命周期,源码解读(给你贴一大堆的源码).个人觉得应该由问题驱 ...

  5. Nmap的一些常用的nse脚本

    转自freebuf.com/  点击跳转 在这篇文章中,我们将研究最著名的渗透工具之一 Nmap 一款标志性的跨平台扫描器.它的原意为Network Mapper(网络映射器),具有相当强大的扫描功能 ...

  6. 前端组件用 Scope 发布 npm 包的方法

    1.引言 多人.多组织或多组件发布 npm 包到同一个仓库时,可能出现命名冲突问题. 为了解决这个问题,npm 引入了“scope”(范围)概念. 在 Angular 项目中,我们通常可以看到“@an ...

  7. [考试反思]1109csp-s模拟测试107:低能

    诶一看这不是水题AK场吗?然后80分钟就拿到了285分. 然后,对拍?还是卡T2常数?还是想T2正解? 于是上述三项我依次进行了. 前两项让我的分数丝毫不变但是吃掉了我一个多小时的时间. 卡常卡的也不 ...

  8. NOIP模拟 37

    啊哈这次没什么智障低错丢rank什么的托词了STO 发现好像110我就拿满了.. 水平不行..只会简单题qaq T1 可以树上启发式合并水过(普通分治也行) T2 我连那么显然的 一劳永逸的容斥都没想 ...

  9. 我跟上家老板说过的最后一句话:转.NET Core吧

    最近几天浩子终于刚刚脱离了令人发指工作,一者是年底了,一者是不要向生活低头,就在这时我选择了第二者. 上家是做物联网的,人数不多,七八名开发人员,感觉都还可以,都很年轻没有秃顶,糊里糊涂就选择了入职. ...

  10. Flink中发送端反压以及Credit机制(源码分析)

    上一篇<Flink接收端反压机制>说到因为Flink每个Task的接收端和发送端是共享一个bufferPool的,形成了天然的反压机制,当Task接收数据的时候,接收端会根据积压的数据量以 ...