Java 集合 HashMap & HashSet 拾遗
Java 集合 HashMap & HashSet 拾遗
@author ixenos
摘要:HashMap内部结构分析
Java HashMap采用的是冲突链表方式
- 从上图容易看出,如果选择合适的散列函数,
put()和get()方法可以在常数时间内完成,因为较好的散列减少了散列冲突,使时间主要花在对桶寻址上(数组),而较少去遍历桶中的链表。但在对HashMap进行迭代时,需要遍历整个table以及后面跟的冲突链表。因此对于迭代比较频繁的场景,不宜将HashMap的初始大小设的过大。 - 有两个参数可以影响HashMap的性能:初始容量(inital capacity)和负载系数(load factor)。初始容量指定了初始
table的大小,负载系数用来指定自动扩容的临界值。当entry的数量超过capacity*load_factor时,容器将自动扩容并重新哈希。对于插入元素较多的场景,将初始容量设大可以减少重新哈希的次数。 - 将对向放入到HashMap或HashSet中时,有两个方法需要特别关心:
hashCode()和equals()。hashCode()方法决定了对象会被放到哪个bucket里,当多个对象的哈希值冲突时,equals()方法决定了这些对象是否是“同一个对象”。所以,如果要将自定义的对象放入到HashMap或HashSet中,需要@OverridehashCode()和equals()方法。
由Value得Key
- many-to-one ( 多Key映射一Value ):遍历整个Map的entry然后得到所要求的key
public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
Set<T> keys = new HashSet<T>();
for (Entry<T, E> entry : map.entrySet()) {
//判断当前entry是否含有value
if (Objects.equals(value, entry.getValue())) {
//通过含有value的entry得到对应的key
keys.add(entry.getKey());
}
}
return keys;
}public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
return map.entrySet()
.stream()
.filter(entry -> Objects.equals(entry.getValue(), value))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}In Java 8: Lambda
- one-to-one ( 一Key映射一Value ):
- 同样遍历,但一遇到Key直接return
public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
for (Entry<T, E> entry : map.entrySet()) {
if (Objects.equals(value, entry.getValue())) {
//一找到就return
return entry.getKey();
}
}
return null;
}- 如果需要大批量,则直接把Key和Value对调存放在Map中,再getValue就好
还可以不用Java集合框架,用Google的开源框架Guava,其中的BiMap可以由value得key
BiMap<Token, Character> tokenToChar =
ImmutableBiMap.of(Token.LEFT_BRACKET, '[', Token.LEFT_PARENTHESIS, '(');
Token token = tokenToChar.inverse().get('(');
Character c = tokenToChar.get(token);
put方法对重复键的处理
找到key对应的entry,如果非空,则添加时(只是)覆盖value
//e是一个node<K,V>对象,也就是一个entry
//value是put进来的
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//从这里可以看出是找到对应entry然后改变值
e.value = value;
afterNodeAccess(e);
return oldValue;
}put源代码片段
- 同样hashTable也是(只是)覆盖value
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
} // Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
//先判断哈希是因为比equals快,而且用的逻辑与
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
//可以看出还是替换掉了旧有的值
entry.value = value;
return old;
}
} addEntry(hash, key, value, index);
return null;
}HashTable的put源码
从containsValue的源码看数据结构
/**
* Returns <tt>true</tt> if this map maps one or more keys to the specified value.
* 即“如果这个映射表有一个或多个key映射到一个值上时,返回true”
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value
*/
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
//遍历每一个桶bucket(同样hash值的entry(Node)在一个桶中),一个桶存放一个双链表
for (int i = 0; i < tab.length; ++i) {
//遍历桶中的链表,把所有映射value的key揪出来
for (Node<K,V> e = tab[i]; e != null; e = e.next) { //后继结点e.next指向的是另一个同hash的entry的前驱结点key(Node是双链表结点)
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
零碎知识
尽量返回接口而非实际的类型,如返回List、Set、Map而非ArrayList、HashSet、HashMap,便于更换数据结构,而客户端代码不用改变。这就是针对抽象编程
Map.entrySet 方法返回Map映射的 Set 视图Set<Map.Entry<K,V>>,维护entry键值对
该 set 受Map映射支持,所以对Map映射的更改可在此 set 中反映出来,反之亦然!
如果对该 set 进行迭代的同时修改了Map映射,外部modCount改变而内部的xxxmodCount还在自己的节奏(通过迭代器自己的 remove 操作,或者通过对迭代器返回的映射项执行 setValue 操作除外),则迭代结果是不确定的;简单来说就是iterator的对象不受Map支持,Map自行修改的时候不会通知到他,modCount异常,会发生如NullPointException之类的异常
- 该set 支持元素移除,通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear这几种操作操作可从映射中移除相应的映射关系,但它不支持 add 或 addAll 操作
- 欲深入理解HashMap可前往
- skywang12345的博文:http://www.cnblogs.com/skywang12345/p/3310835.html#a21
- chenssy的博文:http://www.cnblogs.com/chenssy/p/3521565.html
Java 集合 HashMap & HashSet 拾遗的更多相关文章
- Java 集合:HashSet 与 ArrayList
Set 集合是无序不可以重复的的.List 集合是有序可以重复的. Java 集合:HashSet 与 hashCode.equals 博客里面已经说到这个问题,但是解释的还是不够清楚. 看一个小例子 ...
- 【Java集合】HashSet源码解析以及HashSet与HashMap的区别
HashSet 前言 HashSet是一个不可重复且元素无序的集合.内部使用HashMap实现. 我们可以从HashSet源码的类注释中获取到如下信息: 底层基于HashMap实现,所以迭代过程中不能 ...
- 死磕 java集合之HashSet源码分析
问题 (1)集合(Collection)和集合(Set)有什么区别? (2)HashSet怎么保证添加元素不重复? (3)HashSet是否允许null元素? (4)HashSet是有序的吗? (5) ...
- Java集合:HashSet的源码分析
Java集合---HashSet的源码分析 一. HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该 ...
- Java集合--HashMap分析
HashMap在Java开发中有着非常重要的角色地位,每一个Java程序员都应该了解HashMap. 本文主要从源码角度来解析HashMap的设计思路,并且详细地阐述HashMap中的几个概念,并深入 ...
- Java集合---HashMap源码剖析
一.HashMap概述二.HashMap的数据结构三.HashMap源码分析 1.关键属性 2.构造方法 3.存储数据 4.调整大小 5.数据读取 ...
- [转载] Java集合---HashMap源码剖析
转载自http://www.cnblogs.com/ITtangtang/p/3948406.html 一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射 ...
- 1.Java集合-HashMap实现原理及源码分析
哈希表(Hash Table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常 ...
- Java集合之HashSet/TreeSet原理
Set集合 1.HashSet 只去重复, 没有顺序 HashSet的add方法会调用hashCode和equals, 所以存储在HashSet中的对象需要重写这两个方法. 2.TreeSet ...
随机推荐
- configure, make, make install都做了什么
1. 我的理解./configure: 确保接下来的make以及make install所依赖的文件没有问题make: build编译连接生成可执行程序make install: 将编译好的可执行 ...
- python3中str的函数
# 转换# 'capitalize',# 'lower',# 'upper',# 'casefold',# 'swapcase', 大小写互换# 'title',# 'strip',# 'rstrip ...
- redis单主机多实例
假设我们服务器上面已经安装好了redis: 可参看:http://zlyang.blog.51cto.com/1196234/1834700 下面我们来配置redis单主机多实例: 我们首先拷贝两份文 ...
- myeclipse的一些优化设置
注:本文内容复制于little_paper的博客,具体链接为http://little-paper.iteye.com/blog/1670745,感谢little_paper的分享,为便于能随时学习, ...
- C语言 · FJ的字符串
问题描述 FJ在沙盘上写了这样一些字符串: A1 = "A" A2 = "ABA" A3 = "ABACABA" A4 = "AB ...
- 在ubuntu下设置eclipse开发STM32等嵌入式设备
之前为了能够让ROS与底层能够顺利通讯,我采用可开源开发板arduino ,因为arduino有ROS的库,能够按照ROS wiki上所给的教程就可以顺利的开发,但由于arduino的局限性,我觉得是 ...
- 2014年蓝桥杯预选赛 C/C++ 本科A组试题--切面条
//主要是要找到f(n)=2*f(n-1)-1的规律. #include <stdio.h> #include <math.h> int f(int n) { if(n==0) ...
- git 克隆到本地linux目录的2种方式
登录到gitlab查看2种不同的地址 ssh 类型 地址 git@inc.xxxx:shiwf/xxxAdmin.git http类型 地址 http://inc.xxxx:8000/shiwf/xx ...
- Egret 学习之 入口函数 及开始编写程序(三)
1,Egret的程序入口: C和java是以一个main函数作为入口,但egret类似于ActionScript 是以一个文档类作为入口,确切的说是以这个文档类的构造函数作为入口: 2,文档类的构造函 ...
- ios发布笔录
需要一张1024x1024的icon 发布尺寸4.7英寸 1334x7505.5英寸 2208-12424英寸 1136-6403.5英寸 960-640ipad 2048x1536 视频 ip ...
