Java集合框架-HashMap
目录
HashMap
1 HashMap引入
Map接口专门处理键值映射数据的存储,可以根据键实现对值的操作。
最常用的实现类是HashMap。
2 HashMa数据结构
1、HashMap概述
HashMap是基于哈希表的Map接口实现的,它存储的是内容是键值对<key,value>映射。此类不保证映射的顺序,假定哈希函数将元素适当的分布在各桶之间,可为基本操作(get和put)提供稳定的性能。
2、HashMap在JDK1.8以前数据结构和存储原理
链表散列
通过数组和链表结合在一起使用,就叫做链表散列。这其实就是hashmap存储的原理图。
HashMap的数据结构和存储原理
HashMap内部有一个entry的内部类,其中有四个属性,我们要存储一个值,则需要一个key和一个value,存到map中就会先将key和value保存在这个Entry类创建的对象中。
通过entry对象中的hash值来确定将该对象存放在数组中的哪个位置上,如果在这个位置上还有其他元素,则通过链表来存储这个元素。
Hash存放元素的过程
- 通过key、value封装成一个entry对象,然后通过key的值来计算该entry的hash值,通过entry的hash值和数组的长度length来计算出entry放在数组中的哪个位置上面,
- 每次存放都是将entry放在第一个位置。在这个过程中,就是通过hash值来确定将该对象存放在数组中的哪个位置上。
3、JDK1.8后HashMap的数据结构
上图展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率。
4、HashMap的属性
HashMap的实例有两个参数影响其性能。
初始容量:哈希表中桶的数量
加载因子:哈希表在其容量自动增加之前可以达到多满,的一种尺度
当哈希表中条目数超出了当前容量*加载因子(其实就是HashMap的实际容量)时,则对该哈希表进行rehash操作,将哈希表扩充至两倍的桶数。
Java中默认初始容量为16,加载因子为0.75。
capacity译为容量代表的数组的容量,也就是数组的长度,同时也是HashMap中桶的个数。默认值是16。
一般第一次扩容时会扩容到64,之后好像是2倍。总之,容量都是2的幂。
通过一张HashMap的数据结构图来分析:
3 HashMap的源码分析
1、HashMap的层次关系与继承结构
【HashMap继承结构】
【实现接口】
- Map<K,V>:在AbstractMap抽象类中已经实现过的接口,这里又实现,实际上是多余的。但每个集合都有这样的错误,也没过大影响
- Cloneable:能够使用Clone()方法,在HashMap中,实现的是浅层次拷贝,即对拷贝对象的改变会影响被拷贝的对象。
- Serializable:能够使之序列化,即可以将HashMap对象保存至本地,之后可以恢复状态。
2、HashMap类的属性
3、HashMap的构造方法
【HashMap()】:
【HashMap(int)】
【HashMap(int,float)】
【HashMap(Map<? extends K, ? extends V> m)】
4、常用方法
【put(K key,V value)】
【putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)】
HashMap并没有直接提供putVal接口给用户调用,而是提供的put函数,而put函数就是通过putVal来插入元素的。
【get(Object key)】
【getNode(int hash,Pbject key)】
HashMap并没有直接提供getNode接口给用户调用,而是提供的get函数,而get函数就是通过getNode来取得元素的。
【resize方法】
进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,要尽量避免resize。
在resize前和resize后的元素布局如下:
4 总结
1. 要知道hashMap在JDK1.8以前是一个链表散列这样一个数据结构,而在JDK1.8以后是一个数组加链表加红黑树的数据结构。
2. 通过源码的学习,hashMap是一个能快速通过key获取到value值得一个集合,原因是内部使用的是hash查找值得方法。
迭代器
所有实现了Collection接口的容器类都有一个iterator方法用以返回一个实现Iterator接口的对象
Iterator对象称作为迭代器,用以方便的对容器内元素的遍历操作,Iterator接口定义了如下方法:
- boolean hashNext();//判断是否有元素没有被遍历
- Object next();//返回游标当前位置的元素并将游标移动到下一个位置
- void remove();//删除游标左边的元素,在执行完next之后该操作只能执行一次
方法1:通过迭代器Iterator实现遍历
- 获取Iterator :Collection 接口的iterator()方法
- Iterator的方法:
- boolean hasNext(): 判断是否存在另一个可访问的元素
- Object next(): 返回要访问的下一个元素
方法2:增强for循环
泛型
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
通过泛型 , JDK1.5使用泛型改写了集合框架中的所有接口和类
? 通配符: < ? >
Collections工具类
Java提供了一个操作Set、List和Map等集合的工具类:Collections,该工具类提供了大量方法对集合进行排序、查询和修改等操作,还提供了将集合对象置为不可变、对集合对象实现同步控制等方法。
这个类不需要创建对象,内部提供的都是静态方法。
1、Collectios概述
2、排序操作
3、查找、替换操作
4、同步控制
Collectons提供了多个synchronizedXxx()方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
5、Collesction设置不可变集合
Vevtor和Stack
锁机制:对象锁、方法锁、类锁
对象锁就是方法锁:就是在一个类中的方法上加上synchronized关键字,这就是给这个方法加锁了。
类锁:锁的是整个类,当有多个线程来声明这个类的对象的时候将会被阻塞,直到拥有这个类锁的对象被销毁或者主动释放了类锁。这个时候在被阻塞住的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住。例如:在类A上有关键字synchronized,那么就是给类A加了类锁,线程1第一个声明此类的实例,则线程1拿到了该类锁,线程2在想声明类A的对象,就会被阻塞。
现在使用的是方法锁。
1 Vector
1、Vector概述
1. Vector是一个可变化长度的数组
2. Vector增加长度通过的是capacity和capacityIncrement这两个变量,目前还不知道如何实现自动扩增的,等会源码分析
3. Vector也可以获得iterator和listIterator这两个迭代器,并且他们发生的是fail-fast,而不是failsafe,注意这里,不要觉得这个vector是线程安全就搞错了
4. Vector是一个线程安全的类,如果使用需要线程安全就使用Vector,如果不需要,就使用arrayList
5. Vector和ArrayList很类似,就少许的不一样,从它继承的类和实现的接口来看,跟arrayList一模一样。
2、Vector源码分析
Vector的继承关系和层次结构和ArrayList中的一模一样
构造方法作用:
1. 初始化存储元素的容器,也就是数组,elementData,
2. 初始化capacityIncrement的大小,默认是0,这个的作用就是扩展数组的时候,增长的大小,为0则每次扩展2倍
【Vector():空构造】
【Vector(int)】
【ector(int,int)】
【Vector(Collection<? extends E> c)】
3、核心方法
这个就是在每个方法上比arrayList多了一个synchronized,其他都一样。
2 Stack
Vector的子类Stack,我们学过数据结构都知道,这个就是栈的意思。那么该类就是跟栈的用法一样
3 总结Vector和Stack
【Vector总结】
1. Vector线程安全是因为它的方法都加了synchronized关键字
2. Vector的本质是一个数组,特点能是能够自动扩增,扩增的方式跟capacityIncrement的值有关
3. 它也会fail-fast,还有一个fail-safe两个的区别在下面的list总结中会讲到。
【Stack的总结】
1. 对栈的一些操作,先进后出
2. 底层也是用数组实现的,因为继承了Vector
3. 也是线程安全的