一、概述

  IdentityHashMap也是一类特殊的Map,根据其名字,Identity,即同一性,其表现出的具体特点便是,在判断Map中的两个key是否相等时,只通过==来判断,而不通过equals,也就是说,如果两个key相同,那么这两个key必须是同一个对象。

  除此之外,虽然这也是HashMap,即至少是Key的存储是基于Hash实现的,但其存储方式跟HashMap还是有很大的不一样。下面详细分几个方面进行说明。

二、实现分析

  1. 初始化

  可以通过三种方式来构造一个IdentityHashMap, 如下:

    public IdentityHashMap() {
init(DEFAULT_CAPACITY);//默认32
} public IdentityHashMap(int expectedMaxSize) {
if (expectedMaxSize < 0)
throw new IllegalArgumentException("expectedMaxSize is negative: " + expectedMaxSize);
init(capacity(expectedMaxSize));
} public IdentityHashMap(Map<? extends K, ? extends V> m) {
// Allow for a bit of growth
this((int) ((1 + m.size()) * 1.1));
putAll(m);
}

可以看到,以上三种方式最终都调用了init方法,而传入的参数即是容器的容量,而第二个方法中,在调用init之前会先对参数值进行处理,其目的是根据当前值构造一个最接近该数值的2的指数倍,这样,保证初始化时,容量的大小为2的指数倍。

初始化的具体过程如下:

  private void init(int initCapacity) {

        threshold = (initCapacity * 2)/3; //阈值为初始值的2/3,所以对于默认值32来说,
//其大小为32*2/3=21,这是key的个数
table = new Object[2 * initCapacity];//为什么*2,因为值也是放在这个table中的
//所以table的size不等于容量
}

     代码很简单,设置阈值为容量的2/3,并申请一个2倍于容量的数组。之所以这里要扩大一倍,是因为Map值也存储于这个数组中,所以,需要与key一一对应。

  2.存储

  对于存储来说,我们还是来看下put的实现

public V put(K key, V value) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len); Object item;
while ( (item = tab[i]) != null) {
if (item == k) {
V oldValue = (V) tab[i + 1];
tab[i + 1] = value;
return oldValue;
}
i = nextKeyIndex(i, len);
} modCount++; //新增加一个
tab[i] = k;
tab[i + 1] = value;
if (++size >= threshold)
resize(len); // len == 2 * current capacity.
return null;
} private static int nextKeyIndex(int i, int len) {
return (i + 2 < len ? i + 2 : 0);
}

  根据上面的业务逻辑,我们将其用流程图表示如下:

  根据流程图我们不难理解这个逻辑,有几点需要注意一下:

  1)key和value的值实际上都是存储在数组中的,而且val是挨着key存储的。

2)当发生冲突的时候,这里采用的方式是依次找下一个,直到有空的位置,或者找到key应有的位置。

  3)因为在超过阈值后会进行resize的操作,table的长度会被扩大一倍,所以步骤2)一定能找到一个空位置,或者找到之前设置的位置。

  如果没有自动扩容机制,则步骤2)很可能会出现死循环。

3. 查找

get方法的实现逻辑如下:

public V get(Object key) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len);//表的长度,2的n次方
while (true) {
Object item = tab[i];
if (item == k)
return (V) tab[i + 1];
if (item == null)
return null; //那么这个表示不存在该key, 所以返回null
i = nextKeyIndex(i, len);
}
}

  这个过程很简单,了解了存储机制后,这个就容易理解了,需要说明的是,如果该位置为null,表示该位置未存储key, 返回null.

  4. 删除

  删除的业务逻辑如下:

public V remove(Object key) {
Object k = maskNull(key);
Object[] tab = table;
int len = tab.length;
int i = hash(k, len); while (true) {
Object item = tab[i];
if (item == k) {
modCount++;
size--;
V oldValue = (V) tab[i + 1];
tab[i + 1] = null;
tab[i] = null;
closeDeletion(i);
return oldValue;
}
if (item == null) //未找到该key
return null;
i = nextKeyIndex(i, len);
} }

查找的逻辑还是比较好理解,删除的话,实际上就是把相应位置的值置null,实现引用的消除,以便垃圾回收。

三、总结

  上面我们大致分析了这类Map的存储机制,总的来说还是比较简单,和HashMap有类似的地方,但实现方式差别很大。主要差别整理如下 :

  1)IdentityHashMap的loadFactor固定为2/3

  2)IdentityHashMap的所有key和value都存储在数组中,key后的下一个位置即是对应的value

  3)IdentityHashMap的冲突检测方式为线性再探测,即找下一个元素再探测,没有链式结构

  4)最重要的一点,判断两个key是否相同,只根据==来判断,不使用equals

  如果我们的业务需要有第4)点的需求,则可以使用IdentityHashMap.

容器--IdentityHashMap的更多相关文章

  1. Java容器解析系列(14) IdentityHashMap详解

    IdentityHashMap,使用什么的跟HashMap相同,主要不同点在于: 数据结构:使用一个数组table来存储 key:value,table[2k] 为key, table[2k + 1] ...

  2. 【Java心得总结七】Java容器下——Map

    我将容器类库自己平时编程及看书的感受总结成了三篇博文,前两篇分别是:[Java心得总结五]Java容器上——容器初探和[Java心得总结六]Java容器中——Collection,第一篇从宏观整体的角 ...

  3. [Think In Java]基础拾遗3 - 容器、I/O、NIO、序列化

    目录 第十一章 持有对象第十七章 容器深入研究第十八章 Java I/O系统 第十一章 持有对象 1. java容器概览 java容器的两种主要类型(它们之间的主要区别在于容器中每个“槽”保存的元素个 ...

  4. Java - 容器详解

    一.ArrayList 长度可变数组,类似于c++ STL中的vector. 元素以线性方式连续存储,内部允许存放重复元素. 允许对元素进行随机的快速访问,但是向ArrayList中插入和删除元素的速 ...

  5. Java容器之旅:容器基础知识总结

    下图展示了Java容器类库的完备图,包括抽象类和遗留构件(不包括Queue的实现). 常用的容器用黑色粗线框表示,点线框表示接口,虚线框表示抽象类,实线框表示类,空心箭头表示实现关系.Produce表 ...

  6. Java集合容器简介

    Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...

  7. Java 容器相关知识全面总结

    Java实用类库提供了一套相当完整的容器来帮助我们解决很多具体问题.因为我本身是一名Android开发者,包括我在内很多安卓开发,最拿手的就是ListView(RecycleView)+BaseAda ...

  8. Java基础:容器

    转载请注明出处:jiq•钦's technical Blog 一.Collection:存放独立元素 Collection中的接口都是可选操作,事实上现类 并不一定实现了其全部接口,这是为了防止&qu ...

  9. Java容器的各种总结

    Java容器指的是List,Set,Map这些类.由于翻译的问题,问到集合,Collection这些指的都是它们几个. List ArrayList 随机访问快 LinkedList 插入删除快 这个 ...

随机推荐

  1. vs如何在C++中调用Lua

    最近Cocos2dx的学习卡壳了,一般的照抄代码我不想写上来,但想示例也想得我头晕...为了放松大脑调整状态于是开始学习Lua.Lua的语法学习还是比较简单的,学过javascript或者vbscri ...

  2. PHP实现RESTful风格的API实例(二)

    接前一篇PHP实现RESTful风格的API实例(一) Response.php :包含一个Request类,即输出类.根据接收到的Content-Type,将Request类返回的数组拼接成对应的格 ...

  3. 深入理解PHP内核(十三)类的结构和实现

    原文链接:http://www.orlion.ga/1117/ 先看一下类的结构: struct _zend_class_entry {     char type;     // „类型:ZEND_ ...

  4. struts2拦截器

    一.自定义拦截器 struts2拦截器类似于servlet过滤器 首先定义一个拦截器这个拦截器实现了Interceptor接口: package cn.orlion.interceptor; impo ...

  5. 什么才是正确的javascript数组检测方式

    前面的话 对于确定某个对象是不是数组,一直是数组的一个经典问题.本文专门将该问题择出来,介绍什么才是正确的javascript数组检测方式 typeof 首先,使用最常用的类型检测工具——typeof ...

  6. 了解HTML表单之input元素的23种type类型

    目录 传统类型 text password file radio checkbox hidden button image reset submit 新增类型 color tel email url ...

  7. Android requires compiler compliance level 5.0 or 6.0. Found '1.8' instead. Please use Android Tools>Fix project Properties.

    重装操作系统之后,或者破坏了Android的开发环境之后,需要重新配置好Android的开发环境.但是配置好后,导入原有的项目时,报错: Android requires compiler compl ...

  8. Elasticsearch Span Query跨度查询

    ES基于Lucene开发,因此也继承了Lucene的一些多样化的查询,比如本篇说的Span Query跨度查询,就是基于Lucene中的SpanTermQuery以及其他的Query封装出的DSL,接 ...

  9. 上传文件被nginx全部缓存的问题

    nginx采用1.5.6. 后端采用nodejs+formidable的方式接受上传文件,本问题的对应与采用什么样的后端没太大关系,这里只是交代一下. 问题: 在前端页面上将文件上传,nginx没有将 ...

  10. Razor语法

    1. 截取字符串  @(i.Title.Length > 18 ? i.Title.Substring(0, 18) + "" : i.Title) 2. 格式化日期  @s ...