Map接口

Map接口是有一个映射表, 存储键和值, 它提供了两个通用的接口HashMap 和 TreeMap

HashMap 是散列映射表, 对键散列; Tree是树映射表, 对键进行排序,并将其组织成搜索树

除了定义添加,删除, 视图等方法,还定义了一个子接口Entry, 用来操作键值对

HashMap

概述

HashMap是散列映射表,key-value总是会当做一个整体来处理,根据hash算法来来计算key-value的存储位置

主要属性

transient Entry<K,V>[] table;   //存储元素
transient int size; //键值对的个数
int threshold; //当元素个数超过这个域值后,就会自动扩展映射表的大小
final float loadFactor; //加载因子,表示threshold与table长度的比值

table是一个数组, Entry<K,V>表示一个键值对, Entry是Hash的内部类,定义如下

static class Entry<K,V> implements Map.Entry<K,V> {
final K key; //存储key
V value; //存储value
Entry<K,V> next; // 指向链表中下一个节点
int hash; //存储hash散列值 Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
...........
}

如何散列

Entry中存储了散列值,hashMap是如何散列的呢 ?

HashMap使用hash值确定一个Entry在table中的位置,h & (length-1)的结果,就是在table中的下标,

如果有多个hash在table中的同一个位置,那么就构成一个链表。如下图

添加

put(K key, V value):

public V put(K key, V value) {
//如果table空,初始化
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//key为空,这也是允许key为null的原因
if (key == null)
return putForNullKey(value); //计算哈希
int hash = hash(key);
//计算在table中的位置
int i = indexFor(hash, table.length); //遍历链表,如果key相同, 新value覆盖老的, 并返回老value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this); //空实现
return oldValue;
}
}
modCount++; //把Entry添加到链表中
addEntry(hash, key, value, i);
return null;
}

如何扩容

在把Entry添加到链表的过程中,可能会有扩容的操作

 void addEntry(int hash, K key, V value, int bucketIndex) {
//如果超出指定的容量上限,扩容为原来的两倍,并重新计算在table的位置
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//构造了一个Entry
createEntry(hash, key, value, bucketIndex);
}

扩容:

void resize(int newCapacity) {
//保存原table
Entry[] oldTable = table;
int oldCapacity = oldTable.length; //判断原容量
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} //初始化一个table,新的容量
Entry[] newTable = new Entry[newCapacity]; //把原table的元素, 复制到新table
transfer(newTable, initHashSeedAsNeeded(newCapacity)); //新table覆盖原table
table = newTable; //重新计算指定的容量上限
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

transfer(Entry[] newTable, boolean rehash): 复制原table元素到新table

 //把原table的元素, 复制到新table
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length; for (Entry<K,V> e : table) { //遍历原table
while(null != e) { //遍历链表
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//计算在新table中的位置
int i = indexFor(e.hash, newCapacity); //原Entry的next置为空
e.next = newTable[i]; //原Entry复制给新Entry
newTable[i] = e; //原Entry的next,指向老Entry
e = next;
}
}
}

查找

getEntry(Object key): 根据key找节点,这个方法是基本操作方法, 其他的查找方法也是调用的这个方法

final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
} int hash = (key == null) ? 0 : hash(key); //遍历链表,hash相同并且key相同 , 返回节点
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}

在取的过程中也是根据key的hashcode取出相对应的Entry对象。

Set集合 -- HashSet

Set接口是不允许元素重复的, HashSet是基于HashMap实现的

底层存储

private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();

当添加一个元素时,它是把要添加的元素当做key, PRESENT 当做value 存入map中, 如下:

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

它的其他方法也是调用的HashMap, 不再累述了

Java集合源码 -- Map映射和Set集合的更多相关文章

  1. Java集合源码学习(一)集合框架概览

    >>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...

  2. Java集合源码分析(一)——集合框架

    集合框架 集合框架如图所示 Java集合是Java提供的工具包,主要包括常用的数据结构,包括:集合.链表.队列.栈.数组.映射等. 集合的工具包位置是java.util.* 集合主要可以分为五类: L ...

  3. java集合源码分析(六):HashMap

    概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...

  4. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  5. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

  6. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  7. JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

    文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...

  8. Java 集合源码分析(一)HashMap

    目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...

  9. java集合源码分析几篇文章

    java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915

随机推荐

  1. WPF几种渐变色

      [LinearGradientBrush-- 主要属性: StartPoint 获取或设置线性渐变的二维起始坐标. EndPoint 获取或设置线性渐变的二维终止坐标. 例子: <Linea ...

  2. Asp.Net 之Jquery知识点运用

    1.先把要用的body内的代码写好. <div id="ulBox"> <h3>下面的Ulid为"ulList1"</h3> ...

  3. 阿里云服务器windows server流量不大的情况下,tomcat经常出现访问阻塞,手动ctrl+c或者点击右键又访问正常

    我被这个问题折磨了好几天,因为这两天要帮别人做推广,不能再出现这样的情况了,不然广告费就白烧了,所以特意查了一下资料,结果解决方案被我找出来了. 问题发生原因是因为打开编辑选项后,一不小心点到dos窗 ...

  4. 微服务系列(二):使用 API 网关构建微服务

    编者的话|本文来自 Nginx 官方博客,是微服务系列文章的第二篇,本文将探讨:微服务架构是如何影响客户端到服务端的通信,并提出一种使用 API 网关的方法. 作者介绍:Chris Richardso ...

  5. 【echats】echats悬浮事件频繁触发、过于灵敏、快速抖动等异常现象,适用与tooltip有关

    方案:transitionDuration设为0: 如图,发现关闭tooltip后现象消失,猜测与tooltip有关. 经过仔细观察,鼠标在快速移动时tooltip会延迟移动,就是这个时间差,让鼠标悬 ...

  6. DOM3 textInput事件

    DOM3中引入了文本事件,其中之一 textInput . 当用户再可编辑区域输入字符时触发该事件. 与keypress不同的是,该事件只会在用户输入可视字符时触发,而keypres事件则只要按下键即 ...

  7. FormData js对象的介绍和使用

    FormData js对象的介绍和使用 FormData对象,可以把所有表单元素的name与value组成一个queryString,提交到后台. 在使用ajax提交时,使用FormData对象可以减 ...

  8. GIS平台结构设计

    前言: WebGIS由于技术发展和功能定位的原因,一般在进行架构设计的时候更多地考虑是否容易实现.用户交互.数据传输方便.渲染效果等方面,对强GIS的应用考虑得少,所以架构上与桌面的GIS平台很不一样 ...

  9. golang开发不错的参考资料

    https://golangbot.com/learn-golang-series/ https://gist.github.com/ivangabriele/1c552aadc247c0a2f256 ...

  10. eclipse直接使用tomcat安装程序的webapp目录调试

    感谢此文:http://blog.csdn.net/soszou/article/details/23673133 本文很多技术及操作来源于此文 需求:因为微信方面的开发调试.为了测试方便,直接构建了 ...