在使用map.entryset时可以知道这是一个导出Map所有键值对的函数集合,其中entry指的是键值对基本单位条目,其实在map接口就已经定义并且有simpleentry,ImmutableEntry(不可更改)两个实现,在实现MAP接口的类中也是使用entry为单位,以数组的形式存储
主要围绕几种典型的实现HashMap,concurrentHashMap,linkedHashMap
当我们使用hashmap时,其实内部已经给我们制定了数组大小,16kb(可以指定,所以当我们使用时尽量估计其可能大小,因为扩容是一件复制过程),阀值threshold 0.75f(超过就会以2进行扩容)
this(initialCapacity, DEFAULT_LOAD_FACTOR);//默认大小
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);//2倍扩容
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
当我们put加入entry时,存放在entry[]中,大致过程是,计算其key的hash值,然后将hash与entry.size容量进行位运算,计算出其在entry[]中位置,其中我们都知道的同一key值会进行覆盖,不会增加真实存放容量大小,会返回原value值,代码如下
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
扩容过程中是对key的一次hash计算,和重新在新数组中进行定位,所以除了复制复制之后的消耗至少还有每个entry的indexfor()损耗
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
至于hashmap具有高效率的读取操作,从put()就应该能够得出,不管是增加或是读取,在entry数组中是根据key与size进行位运算后直接得出在数组中的位置,代码如下
key的hash计算
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
位运算
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
HashMap的移除操作,有点不是很不明白,但是把自己的认为写下来
hashmap内部是对entry进行了扩展的,其中增加了了一个Entry next属性,在createEntry方法内
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];//获取entry初始的数组,这里面的entry应该是在#1构造过程中entry没
//有任何值
table[bucketIndex] = new Entry<>(hash, key, value, e);//这里面把初始化的entry放进去,就相当每
//个entry都含有一个初始化的entry,#2这就相当于remove操作就是使用这个entry来替代原来的entry
size++;
}
#1
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);//初始化
}
#2
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
//替代(没有使用比如标志清除位来进行逻辑判断,或者采用其他方式,使用空间换时间,而且没有其他过多的操作,直接调用方法来进行替换,就又使entry恢初
//始状态,个人认为,其实这里没有怎么看明白,)
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
至于hashmap的其他操作,containkey,putall,clear等基本是在上面实现的
对于map的并发控制,已经做出控制的concurrenthashmap,继承AbstractMap(内部重写了HashEntry<K,V>,并没有继承Hashmap),实现ConCurrentMap接口,内部进行同步控制是使用Reentrantlock()锁机制,Segment
static final class Segment<K,V> extends ReentrantLock implements Serializable
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);//获取锁
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();//释放锁
}
return oldValue;
}
linkedHashMap继承了HashMap,实现Map接口,从字面意思上看,就是在原来具有HashMap的基础上添加了链表的特性,以维护一个双向链表来对map映射关系进行排序(规则为map.key先后顺序),那么具体表现和内部的一些知识点:
双向链表的构造具体还是对map.entry进行重构,就像hashmap中entry增加了.next通过保存初始化entry进行清除原来的entry(key),具体为像链表一样增加一个前驱节点指向和一个后驱节点指向,after,before,而保存前驱和后驱节点的当前节点用header来保存,这个做法在map的具体实现类里面(重写entry)很常见且非常有用,具体的整个实现尚未完全弄懂,有待研究
entry一部分
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;//前驱和后驱
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
super(hash, key, value, next);//next 在remove() entry使用
}
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**
* Inserts this entry before the specified existing entry in the list.
*/
private void addBefore(Entry<K,V> existingEntry) {
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
}
具体构建链表代码
void addEntry(int hash, K key, V value, int bucketIndex) {
super.addEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
}
}
/**
* This override differs from addEntry in that it doesn't resize the
* table or remove the eldest entry.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
至于HashTable直接实现map接口,不允许key,value=null,是线程安全的,使用synchronized同步机制,又实现了dictionary,顾名思义具有类似词典的映射功能,只是其key,value规定object对象,即为对象集合(jdk注:此类已过时。新的实现应该实现 Map 接口,而不是扩展此类),虽然map中k-v需要进行装箱和拆箱,而且类型检查,类型擦除,损耗性能,但是更为灵活,我想这可能是尽管hashtable与hashmap(保证线程安全之后)比较效率有较小差异,而且步骤少,但是大家却喜欢使用hashmap的原因。
TreeMap是一种二叉树结构的存储方式,并没有使用数组形式,可以看出查询效率应该没有以hashmap高,但是因为在存储过程中。可以进行key的compare所以treemap是可以理解为有序的结构,其中remove操作,应该是将关联节点置为null,添加即创建entry,关联节点,这种方式在设计模式上可以理解为组合模式,使用数组的确不合适,处理麻烦,我试过实现。在lucene为例的索引中倒排列表里面的单词的有一种方式是采用这种形式进行存储的。具体如何实现,详细的东西,源码神马的看了都非常清晰和透彻
以上总结为对map的数据类型的一些认知,个人学习总结使用,虽然大部分以源代码形式,而且可能有些还不正确,讲个大概的数据类型的实现思路,但是这个对于使用数据类型的不同场景的选取有一些帮助,了解这些数据类型之间的关系和性能问题的出现与解决有一定提升。
附1:
线程安全一般3重办法,具体网上资料比较多:
1.信号量semaphore
2.锁机制lock(同步synchronized,在jvm中也是锁机制,只是具体指令不一样)
3.CAS(比较和交换算法,jdk有提供一些此算法的实现类型在java.util.concurrent.atomic中,或者使用第三方AMINO框架提供的数据类型)
附2:
查看源码可以下载open-jdk,如果找不到可以使用jd-gui反编译class,非常好用,或者直接在krugle里面搜索,基本可以找到相关的类和扩展信息。
附2:
查看源码可以下载open-jdk,如果找不到可以使用jd-gui反编译class,非常好用,或者直接在krugle里面搜索,基本可以找到相关的类和扩展信息。
相关推荐
这里,我们将JSON字符串解析为`Map, City>`,其中键是城市ID,值是`City`对象。然后,通过遍历Map的`entrySet()`,我们可以访问每个城市的ID和名称。 ### 3. Gson解析JSON的优势 - **简单易用**:Gson提供了简洁的...
本主题主要探讨的是如何使用Gson库处理JSON数据与Java中的Bean、Array、List、Map以及简单类型的相互转换。 首先,我们来看JSON与Java Bean之间的转换。Java Bean是一种具有特定属性和方法的对象,它们通常用来封装...
在这个项目中,开发者使用易语言编写代码,实现了MAP文件读取、解析以及图像绘制的功能。易语言的易用性和丰富的库函数使得这一转换过程变得相对简单。 转换过程中,程序首先需要读取TSK-MAP文件,解析其中的二进制...
以下是一个简单的Map转字符串的例子: ```java Map, String> map = new HashMap(); map.put("key1", "value1"); map.put("key2", "value2"); String mapAsString = map.entrySet().stream() .map(entry -> entry....
在实际使用`EasyXmlUtil`时,只需简单调用`xmlToMap`和`mapToXml`方法,即可完成XML和Map之间的转换。例如: ```java Map, String> map = EasyXmlUtil.xmlToMap(xmlString); String xmlString = EasyXmlUtil....
`xmlToMap`方法首先使用SAXReader解析XML字符串,然后递归地遍历XML文档的元素,将它们转换为Map结构。 `mapToXml`方法则将Map转换成XML字符串: ```java import org.dom4j.Document; import org.dom4j....
Java中的Map集合简单汇总解析 Map集合是Java中的一种常用的集合类型,它提供了键值对的存储机制,可以根据键快速地查找对应的值。Map集合的主要特点是键唯一,值可以重复,每个键都对应一个值。 Map接口简介 Map...
### Map.toString()后转换成Map类型的实现方法及解析 在Java编程中,有时我们需要将一个`Map`对象转换为字符串形式进行存储或传输,而在接收端又需要将该字符串重新转换回`Map`对象以便进一步处理。本篇将详细介绍...
<title>Google Map简单运用 <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" async defer></script> <!-- 这里放置地址列表 --> <div id="map"> ...
以上三种库各有优缺点,Gson和Jackson性能较好,org.json库轻量级但功能相对简单。在实际开发中,应根据项目需求和性能要求选择合适的库。同时,理解这些库的工作原理和使用方式对于提升编程效率至关重要。
本文将详细解析如何利用Google Map API实现"基于Google Map API的简单地图"的功能,包括显示用户所在地区、地图操作以及标记地点等核心知识点。 首先,我们要了解Google Map API的基本用法。它是Google提供的...
在这个简单的示例中,我们只触及了C++协议解析的基本概念。实际应用中,还需要考虑性能优化、内存管理、错误恢复等方面。"HighwayMain.ncb"、".sln"和".suo"文件通常是Visual Studio项目文件,用于编译和管理代码;...
以下是一个简单的示例,展示如何使用Properties类加载文件并将其内容转换为Map: ```java import java.io.*; import java.util.*; public class PropertyHandler { public static Map, String> loadProperties...
然而,对于简单的JSON解析,我们可以编写自定义的简单函数来实现。本篇文章将介绍一种基于Java的基本JSON解析方法,通过提供的`JsonUtil.java`、`BeanUtil.java`和`AjaxResponse.java`三个文件,我们可以看到如何...
- 如果XML结构简单,没有复杂的嵌套,可以使用JAXB将XML解析为Java对象,然后手动将其转换为Map。例如,如果你有一个只有一个根元素且没有子元素的XML,你可以创建一个对应的Java类,并使用JAXB的`Unmarshaller....
《GEOMAP3.5安装指南及应用解析》 GEOMAP3.5是一款功能强大的地理信息系统(GIS)软件,其简洁的安装流程和无需许可证的特性,使其在用户群体中备受青睐。本文将详细阐述如何进行GEOMAP3.5的安装,并探讨其主要功能...
在Java世界中,JAXB(Java Architecture for XML Binding)是一个...这个过程涉及XML的解析、节点遍历以及Map的构建。虽然JAXB不直接提供这个功能,但结合其他Java工具和库,我们可以实现高效且准确的XML到Map的转换。
首先,创建一个Map非常简单。例如,`def map = [a:1,b:2,c:3]`定义了一个Map,其中包含三个键值对,`a`对应1,`b`对应2,`c`对应3。这个Map实际上是一个`HashMap`实例,可以通过`instanceof`操作符进行验证,如`...
本文将深入解析m_map的用法,帮助你更好地掌握这一强大工具。 一、m_map工具箱简介 m_map是由Jiro Doke博士开发的MATLAB地图绘图工具箱,它包含了用于地图投影、数据读取、地图要素绘制等功能的函数。m_map不仅...