Java集合源码 -- Map映射和Set集合
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集合的更多相关文章
- Java集合源码学习(一)集合框架概览
		
>>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...
 - Java集合源码分析(一)——集合框架
		
集合框架 集合框架如图所示 Java集合是Java提供的工具包,主要包括常用的数据结构,包括:集合.链表.队列.栈.数组.映射等. 集合的工具包位置是java.util.* 集合主要可以分为五类: L ...
 - java集合源码分析(六):HashMap
		
概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...
 - Java集合源码分析(四)Vector<E>
		
Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...
 - Java集合源码分析(三)LinkedList
		
LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...
 - Java集合源码分析(二)ArrayList
		
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
 - JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)
		
文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...
 - Java 集合源码分析(一)HashMap
		
目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...
 - java集合源码分析几篇文章
		
java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915
 
随机推荐
- centos关闭selinux
			
SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux历史上最杰出的新安全子系统.在这种访问控制体系的限制下,进程只能访问那 ...
 - log4j2配置文件
			
项目里面经常用到日志,Java开发一般用log4j.slf4j这些框架,看着配置文件有点懵.这几天看公司代码的时候,也有用到log4j,感觉要复杂一点.在本地打log,也有打到hive里面存的.看了一 ...
 - Web安全相关(三):开放重定向(Open Redirection)
			
简介 那些通过请求(如查询字符串和表单数据)指定重定向URL的Web程序可能会被篡改,而把用户重定向到外部的恶意URL.这种篡改就被称为开发重定向攻击. 场景分析 假设有一个正规网站http://ne ...
 - 使用fastjson 获取json字符串中的数组,再转化为java集合对象
			
最近,使用layui做一个导出功能,尽管有插件提供导出,但是呢,由于数据中有状态是数字,例如1显示是已支付,但是导出时也希望这样显示,但是导出的还是1: lz没有找到改下这个插件的办法,只能利用服务端 ...
 - 【数据库】10.0 MySQL常用语句(一)
			
显示数据库语句: SHOW DATABASES 只是显示数据库的名字 显示数据库创建语句: SHOW CREATE DATABASE db_name 数据库删除语句: DROP DATABASE ...
 - 搭建VueMint-ui框架
			
搭建VueMint-ui框架 2018年01月07日 22:29:58 阅读数:2660 vueweb vue project 检查项目环境 一.检查是否安装node.js # 检查node版本 $ ...
 - SQL-字符串运算符和函数
			
COALESCE(columnname,string) 函数 将 NULL 值作为字符串(用空字符串或其他字符串替换 NULL)- 接受一列输入值(字段)如果该字段为 NULL,则返回后面替换的字符串 ...
 - HTTP(S)网络框架的设计
			
0.麻烦 操作系统提供的网络接口都会令人不爽,要么太接近底层而使用不便,要么层次过高又不提供底层点的接口供设置参数.但是我们不能期望系统API做得很高级,因为没有绝对合适的网络库,必须定制化从而达到适 ...
 - IPtables中SNAT和MASQUERADE的区别
			
问题 iptables中snat和MASQUERADE的区别 解决方案 iptables中可以灵活的做各种网络地址转换(NAT) 网络地址转换主要有两种:snat和DNAT snat是source n ...
 - Android 获取SD卡的图片资源
			
首先我先获得SD卡下的根目录路径: privateString isSdcard(){ File sdcardDir=null; boolean isSDExist=Environment.getEx ...