目录
在HashMap中的元素存储过程 put()(HashMap如何处理哈希冲突)
Sort()的两种重载方式(Comparable、Comparator)
集合框架
集合框架定义:
集合框架时表示和操作集合而规定的一种统一、标准的体系结构。
集合框架的好处/用途:
- 容量自增长
- 提供了高性能的数据结构和算法,提高了程序的速度和质量
- 允许不同API之间的相互操作,API之间可以传递集合
- 可以方便地扩展和改写集合,提高代码的复用性和可操作性
- 使用jdk自带的集合类,可以降低代码的维护和学习新API的成本
Collection接口
Collection是最基本的集合接口,Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的子接口,如List和Set。
List
-
ArrayList
底层由数组实现,查找快,增删慢。
-
LinkedList
底层由链表实现,查找慢,增删快。
实现了List接口中没有定义的方法,专门操作表头和表尾,可以当做堆栈、队列和双向队列使用。
-
Vector
线程安全,底层由数组实现。
Set
-
HashSet
底层由哈希表实现,无序。
HashSet基于HashMap实现,HashSet的值存放在Hashmap的key上,HashMap的value统一为PRESENT。
有关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成。
HashSet存储数据、扩容机制等步骤参考HashMap部分。》》》HashMap
-
LinkedHashSet
底层由链表和哈希表实现,链表保证了元素顺序和存储的顺序一致,哈希表保证了元素的唯一性。
-
TreeSet
底层由二叉树实现,有序。
TreeSet通过重写hashCode()和equals()方法来保证其唯一性;通过二叉树结构保证了元素的有序性。可以参考TreeMap。
保证有序性的方法参考》》》Sort()的两种重载方式(Comparable、Comparator)
Map接口
-
HashMap
底层由数组和链表/红黑树实现,无序。
具有很快的访问速度。
HashMap底层数据结构详解
HashMap内部实现是一个 桶数组 ,每个桶中存放着一个单链表的头结点。其中,每个节点存储的是一个键值对整体(Entry),HashMap采用拉链法解决哈希冲突。
在JDK1.8,由数组和链表组成,数组为Hashmap的主体,链表的目的主要是为了用拉链法解决哈希冲突。当链表长度大于8,且数组长度大于64时,链表将转化为红黑树,以减少搜索时间。
在HashMap中的元素存储过程 put()(HashMap如何处理哈希冲突)
1.使用扰动函数
使用key.hashCode()计算hash值并将值赋给变量a;
将变量a向后移动16位,为变量b;
将变量a和变量b做异或运算(二进制相同为0,不同为1),此时得到经过扰动函数处理后的hash值。
*为什么要将得到的变量a向后移动16位并做异或运算?
如果只使用原来的hash值取余,那么相当于参加运算的只有hash值的低位;所以我们右移16位,让hashcode取值出的高位也参与运算,进一步降低哈希碰撞的概率,使得函数数据分配更加平均。
2.根据hash值计算数组下标
index=(table.length-1)&hash
*HashMap为什么不使用hashcode()处理后的hash值直接作为数组下标?
hashCode()方法返回的是int整数类型,由于整数的范围太大,而HashMap的数组容量范围远远不及hashCode()返回的值的范围。
HashMap在通常情况下,是取不到最大值的;并且设备上也难以提供这么大的存储空间,从而导致这个hash值不在数组大小范围内,进而无法匹配存储位置。
*HashMap的长度为什么是2的幂次方?
计算数组下标的方式是用一个取余(%)的方式来进行计算的。取余操作中如果除数是2的幂次,则等价于与其除数-1的与(&)操作。
如果HashMap的长度是2的幂次方,则采用的是二进制的位操作&,相比较于用%取余来说,能够大大提高效率。
3.比较此时取得的index值是否相等
如果不相等,则表示存储的两个对象一定不相等,直接存储进相应数组中;
如果相等,则将值放入相应的数组中,等待下一步比较。
4.用equals()判断两个key是否相同
如果key相同,则将其原始的value值覆盖。
如果key不同,则将当前的key-value键值对放入相应的链表中。
HashMap的扩容机制
1.涉及的参数
Capacity:HashMap的当前长度,HashMap的长度是2的幂。
LoadFactor:HashMap的负载因子,默认值为0.75.
2.扩容时机
当Map中包含的Entry(键值对整体)的数量大于等于 theshold=loadFactor(负载因子)*Capacity(桶数组长度);
且新建的Entry刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为2倍。
当size大于等于thshold的时候,并不一定会触发扩容机制。只要有一个新建的Entry出现哈希冲突,则立刻 resize()。
3.resize()步骤
扩容:创建一个新的Entry空数组(新的桶数组),长度是原数组的2倍。
rehash:
1 遍历原Entry数组,把所有的Entry重新Hash到新数组。
2 判断e.hash(oldCap-1)是否等于0。
等于0,表示e.hash&(oldCap-1)和e.hash&(newCap-1)的结果相同,则rehash后的新数组下标等于原来的数组下标。
等于1,下标等于原来下标加上旧的桶数组长度。
HashMap在JDK1.7和JDK1.8中的不同
1.resize()扩容优化
2.引入了红黑树,避免了由于单条链表过长而影响查询效率的问题。
3.解决了多线程死循环问题,但仍是非线程安全的,多线程时可能造成数据丢失问题。
*为什么HashMap中,包装类适合作为key?
1.包装类的特性能够保证hash值的不可更改性和计算准确性,有效减少哈希冲突的概率。
2.包装类都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况。
3.包装类内部已经重写了equals()、hashCode()等方法,遵守了HashMap内部的规范,不容易出现hash值计算冲突的概率。
-
LinkedHashMap
底层由HashMap实现,有序。
LinkedHashMap需要维护元素的插入顺序,所以性能略低于HashMap。但在迭代访问元素时有很好的性能,因为他使用了链表来维护内部顺序。
-
TreeMap
底层由红黑树实现,有序。
实现了SortMap接口,能够把它保存的记录根据键排序,默认按照升序排序。
保证有序性的方法参考》》》Sort()的两种重载方式(Comparable、Comparator)
-
HashTable
线程安全,和HashMap相似。
是遗留类,通过在put()、get()、size()等各种方法上加Synchronized锁来保证线程安全。这导致所有并发操作都要竞争通一把锁,其他线程只能等待,大大降低了并发操作的效率。
Iterator迭代器
Iterator迭代器主要用于遍历Collection集合中的元素。
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了iterator接口的对象。
Iterator只能用于遍历集合,Iterator本身不提供承装对象的能力。如果需要创造Iterator对象,必须有一个被迭代的集合。
-
Iterator迭代器的优缺点
优点:屏蔽内部遍历细节,访问集合只需要通过Iterator给定的方法即可。
缺点:增加新的集合类需要增加新的迭代器类,迭代器类和集合类成对增加。
-
Iterator和ListIterator的区别
1.遍历的集合种类
Iterator可以遍历List和Set。
ListIterator只能遍历List。
2.遍历方向
Iterator只能单向遍历。
ListIterator可以双向遍历(向前向后)。
3.功能
ListIterator实现了Iterator接口,添加了一些额外的功能。如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
Collections工具类
Collections是一个针对集合类的工具类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作。
-
Sort()的两种重载方式(Comparable、Comparator)
sort()的两种重载方式分别是实现Comparable、Comparator接口。
Comparable
Java提供的Comparable接口,出自java.lang包。其中只包含一个方法 compareTo(Object obj)。它会返回一个负整数、0、正整数;分别表示传入的对象小于、等于、大于已有的对象。
Comparable通过比较实体对象来调用的。但一个实体只能实现一个接口。所以扩展性不是很好,和类绑定了。
Comparator
Java提供的Comparator接口,出自java.util包,它有两个方法,compare(Object obj1,Object obj2)和 equals()。
compare方法比较两个参数,得出他们的顺序关系。它会返回一个负整数、0、正整数,分别表示第一个参数小于、等于、大于已有的对象。
equals方法有一个参数,用来确定参数对象是否等于这个comparator。这个方法仅在要比较的对象也是一个comparator,同时它的时序关系与这个comparator相同时,才会返回true。
Comparator是比较器,可以直接执行。采用了策略模式,一个实体对象可以根据需要设计多个比较器。隔离型好,方便。
快速失败机制fail-fast
快速失败机制是Java集合中的一种错误检测机制。
当多个线程对集合进行结构上改变的操作时,就可能产生fail-fast机制。
各个集合的区别
-
ArrayList和LinkedList区别
1.数据结构实现
ArrayList的底层数据结构是数组。
LinkedList的底层数据结构是链表。
2.随机访问效率(读、查找)
ArrayList效率较高,LinkedList效率较低。
LinkedList是线性的数据存储方式,需要移动指针从前向后依次查找。
3.增加和删除效率
LinkedList效率较高,ArrayList效率较低。
ArrayList 中,在非首尾的增加和删除操作会影响数组内其他元素的下标。
4.内存空间占用
LinkedList比ArrayList更占内存。
LinkedList的节点中,不仅要存储数据,还存储了两个引用,分别指向前一个元素和后一个元素。
-
ArrayList和Vector的区别
1.线程安全
Vector线程安全;ArrayList非线程安全。
Vector使用了synchronized锁来实现线程安全。
2.性能
ArrayList性能较好。
Vector性能较差。
3.扩容机制
ArrayList每次扩容增加为原数组的2倍。
Vector每次扩容增加为原数组的1.5倍。
-
HashMap和HashTable的区别
1.线程安全
HashMap线程不安全;HashTable线程安全。
HashTable中的一些方法加入了synchronized关键字来保证HashTable中的对象是性能安全的。
2.是否允许null
HashMap可以将null作为一个条目的key或value。
HashTable中不能放入空值,最多只有一个key作为null,但是可以有无数个value值作为null。
3.性能
HashMap的性能最好;HashTable的性能最差。
如果追求性能安全,建议使用ConCurrentHashMap。
4.初始容量
这里分为两种情况。
#创建时不规定初始容量值
HashTable默认初始大小为11;
HashMap默认初始大小为16。
#创建时给定初始容量值
HashTable直接使用给定容量大小;
HashMap将其扩充为其2的幂次方大小。(因为HashMap的大小是2的幂次方)
5.扩容机制
HashMap每次扩容增加为原来的2倍。
HashTable每次扩容增加为原来的2n+1倍。
-
HashSet和HashMap的区别
1.实现接口不同
HashSet实现了Set接口;
HashMap实现了Map接口。
2.存储内容类型
HashSet存储单列数据对象;
HashMap存储键值对。
3.元素添加方式
HashSet调用add()方法向Set中添加元素;
HashMap调用put()方法向Map中添加元素。
4.性能
HashSet较慢,HashMap较快。
因为HashMap使用唯一的键来获取对象。