集合随笔
⚫ 谈一谈对集合的理解?
实现Collection接口的集合是单列集合,常用的有:set(集)、list(列表)。
list:list中有ArrayList,LinkedList,Vector。ArrayList,Vector底层都是基于array数组的,不同的是ArrayList是不同步的,线程不安全。Vector是同步的,加synchronized锁,是线程安全的。所以在性能上ArrayList优于Vector。LinkedList不同于前面两种List,不是基于array的,它是基于Node节点的,当LinkedList增删数据时,不需要像基于Array的List一样,必须进行大量的数据移动,只需要更改nextNode就行了,这也是LinkedList的优势。
set:
HashSet:虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是 在HashMap的基础上来实现的,这个就是Set和List的根本区别。HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。看看 HashSet的add(Object obj)方法的实现就可以一目了然了。
public boolean add(Object obj){
return map.put(obj, PRESENT) == null;
}
这个也是为什么在Set中不能像在List中一样有重复的项的根本原因,因为HashMap的key是不能有重复的。
LinkedHashSet:HashSet的一个子类,一个链表。 TreeSet:SortedSet的子类,它不同于HashSet的根本就是TreeSet是有序的。它是通过SortedMap来实现的。(HashSet和TreeSet均为有序的顺序由小到大)
实现Map接口的Map是双列集合,常用的有:HashMap、LinkedHashMap,TreeMap不常用。HashMap初始容量16,默认加载因子0.75
⚫ 集合体系
单列集合:顶层Collection接口
Collection衍生出:
List接口:有序,有索引,可以重复存取
Set接口:无序,无索引,不可以重复
List衍生出:
ArrayList类(常用):底层数组实现,查询快,增删慢,不安全,效率高
LinkedList类:底层链表实现,查询慢,增删快,不安全,效率高
Vector类:底层数组实现,增删改查慢,安全
Set衍生出的类:
HashSet类(常用):底层hashmap,子类LinkedHashSet底层链表实现
TreeSet类:可以排序,底层二叉树算法实现
双列集合:顶层map接口
Map衍生出:
HashMap类(常用):底层哈希算法,针对键,允许null键和值,线程不安全,效率高,键不能重复存取,值可以
TreeMap类:底层是二叉树算法,针对键,可以排序
HashMap衍生出:
LinkedHashMap:底层哈希算法,和HashSet一样键唯一,值可以改变
HashTable类:继承自dictionary,实现map接口,不允许null键和值,线程安全,效率低
ArrayList、Vector 默认初始容量为10
Vector加载因子1(元素个数超过容量长度),扩容增量为:原容量的1倍;10—>20—>40
ArrayList原容量的0.5倍+1,10—>16
HashSet,HashMap默认初始容量为16,加载因子0.75(元素长度超过容量长度的0.75倍),扩容增量1倍;16—>32
补充:
数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难
链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易
那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便,其中一种实现链表的数组
哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置
Set保证元素唯一底层依赖的两个方法(Hashcode,equals)
Hashset底层实现是哈希表,即存储一系列哈希值的表,当执行添加操作时,会将元素的哈希值与集合中的哈希值相比较,不一样的话就存入,一样的话,就会继续判断equals方法,如果返回true就不添加,否则就添加,通常基本类型值不一样,哈希值也不一样,没有影响,引用类型内容一样,地址不一样,此时哈希值也不一样,就会被添加进去,而我们是要达到不添加的效果的,此时我们要重写hashcode方法,通常将其属性值的哈希值加起来,此时内容相同的对象的hashcode就相同,就会继续判断equals方法,重写equals方法,通常判断hashcode相同的两对象的内容是否相同,相同返回true,此时就不会添加对象了,就达到我们要的效果了
⚫ 集合和数组的区别
数组是固定长度的;集合可变长度的。 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存 储引用数据类型。 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同 数据类型。
**⚫ HashSet和TreeSet有什么区别? **
HashSet
HashSet有以下特点
不能保证元素的排列顺序,顺序有可能发生变化
不是同步的
集合元素可以是null,但只能放入一个null
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
TreeSet类
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法。
最重要:
1、TreeSet 是二差树实现的,Treeset中的数据是自动排好序的,不允许放入null值。
2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。
3、HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例 。
⚫ HashSet的底层实现是什么?
通过看远吗知道,HashSet的实现是依赖HashMap的,HashSet的值都是储存在HashMap中的。在HashSet的构造方法中会初始一个HashMap对象,HashSet不容许值重复,因此,HashSet的值是作为HashMap的key储存在HashMap中的,当储存的值已经存在时返回false。
⚫ LinkedHashMap的实现原理?
⚫ Iterator和ListIterator的区别是什么?
区别:1、Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。2、Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。3、ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
⚫ 数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList
1、存储内容比较:
Array 数组可以包含基本类型和对象类型,
ArrayList 却只能包含对象类型。
Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。
2、空间大小比较:
Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。
ArrayList 的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
3.方法上的比较:
ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。
适用场景:
如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。
如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。
**⚫ Comparable和Comparator接口是干什么的?列出他们的区别? **
⚫ TreeMap和TreeSet在排序时如何比较元素?Collections工具类的sort()方法如何计较元素?
**⚫ List,Set,Map三者的区别? **
List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重 复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。 Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素, 只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、 LinkedHashSet 以及 TreeSet。
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不 要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元 素时,只要给出键对象,就会返回对应的值对象。 Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、 ConcurrentHashMap
**⚫ List、Map、Set三个接口,存取元素时,各有什么特点? **
Java容器分为Collection和Map两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List。Map接口不是Collection的子接口。
Collection集合主要有List和Set俩大接口
List:有序,可重复、可以插入多个NULL元素,元素都有索引。常用的实现类有ArrayList、LinkedList、Vector。
Set:无序、不可重复、只容许储存一个NULL元素,必须保证元素唯一性。Set接口常用的实现类是HashSet、LinkedHashSet以及TreeSet。
Map:它是一个键值对集合,存储键、值和之间的映射。key无序,唯一;Value不要求有序,容许重复。
Map常用的实现类:HashMap、TreeMap、HashTable、LinkHashMap、ConcurrentHashMap
⚫ 集合框架底层数据结构
list
ArrayList:Object数据
Vector:Object数据
LinkedList:双向链表
2、Set
HashSet(无序,唯一):底层是HashMap实现
LinkedHashSet:LinkedHashSet继承HashSet,并且内部通过LinkedHashMap来实现的。
TreeSet(有序,唯一):红黑数
3、Map
HashMap:JDK1.8之前采用数组+链表,数据是HashMap的主体,链表则主要为了解决哈希冲突。JDK1.8之后解决哈希冲突有了较大的变化,当链表的长度大于阈值(默认是8),将链表转化为红黑树,减少搜索时间。
LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。
HashTable:数组+链表组成。
TreeMap:红黑树实现
⚫ Hashset如何检查重复
当你把对象加入HashSet时,HashSet会先计算hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode做比较,如果没有相符的hashcode,HashSet会假设对象没有重复 出现,但是如果发现有相同hashcode的对象,这时会调用equals方法来检查hashcode相等的对象是否真的相同,如果两者相同,hashset就不会让其加入操作成功,如果不同的话,就会重新散列到其他位置。这样我们就大大减少equals的次数,相应就大大提高了执行效率。
⚫ List、Set、Map 是否继承自 Collection 接口?
List、Set 是,Map 不是。Map 是键值对映射容器,与 List 和 Set 有明显的区别, 而 Set 存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List
是线性结构的容器,适用于按数值索引访问元素的情形。
⚫ 阐述 ArrayList、Vector、LinkedList 的存储性能和特性。
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
LinkedList也是线程不安全的,LinkedList提供了一些方法,使得LinkedList可以被当作堆栈和队列来使用
⚫ Collection 和 Collections 的区别?
Collection 是一个接口,它是 Set、List 等容器的父接口;Collections 是个一个
工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、
排序、线程安全化等等。
⚫ ArrayList和LinkedList 的区别?
数组结构:ArrayList是动态数组的数据结构实现,而LinkedList是双向链表的数据结构实现;
随机访问效率:ArrayList比LinkedList在随机访问的时候效率要高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找;
增加和删除效率:在非首位的增加和删除操作,LinkedList要比ArrayList效率要高,因为ArrayList增删操作要影响到数组内其他数据的下标;
内存空间占用:LinkedList比ArrayList更占用内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素;
线程安全:ArrayList和LinkedList都是不同步的,都不保证线程安全;
**⚫ Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用还是equals()? 它们有何区别? **
Set里的元素是不能重复的,元素重复与否是使用equals()方法进行判断的。
equals()和方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。
⚫ ArrayList 内部⽤什么实现的?
⚫ 并发集合和普通集合如何区别?
⚫ List 的三个⼦类的特点?
答:ArrayList 底层结构是数组,底层查询快,增删慢。
LinkedList 底层结构是链表型的,增删快,查询慢。
voctor 底层结构是数组 线程安全的,增删慢,查询慢。
⚫ List、Map、Set 三个接口存取元素时,各有什么特点?
List 以特定索引来存取元素,可以有重复元素。Set 不能存放重复元素(用对象的
equals()方法来区分元素是否重复)。Map 保存键值对(key-value pair)映射,
映射关系可以是一对一或多对一。Set 和 Map 容器都有基于哈希存储和排序树的
两种实现版本,基于哈希存储的版本理论存取时间复杂度为 O(1),而基于排序树
版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达
到排序和去重的效果。
⚫ List 和 Map、Set 的区别
结构特点 :
答:List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集
合;List 中存储的数据是有顺序,并
且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是
可以有重复的,Set 中存储的数据是⽆
序的,且不允许有重复,但元素在集合中的位置由元素的 hashcode 决定,位置
是固定的(Set 集合根据 hashcode 来
进⾏数据的存储,所以位置是固定的,但是位置不是⽤户可以控制的,所以对于
⽤户来说 set 中的元素还是⽆序的);
实现类 :
List 接⼝有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每⼀
个元素存储本身内存地址的同时还
存储下⼀个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,⾮线
程安全的,效率⾼,便于索引,但不
便于插⼊删除;Vector:基于数组实现,线程安全的,效率低)。
Map 接⼝有三个实现类(HashMap:基于 hash 表的 Map 接⼝实现,⾮线程安
全,⾼效,⽀持 null 值和 null
键;HashTable:线程安全,低效,不⽀持 null 值和 null 键;LinkedHashMap: 是 HashMap 的⼀个⼦类,保存了
记录的插⼊顺序;SortMap 接⼝:TreeMap,能够把它保存的记录根据键排序,
默认是键值的升序排序)。
Set 接⼝有两个实现类(HashSet:底层是由 HashMap 实现,不允许集合中有
重复的值,使⽤该⽅式时需要重
写 equals()和 hashCode()⽅法;LinkedHashSet:继承与 HashSet,同时⼜基
于 LinkedHashMap 来进⾏实现,底
层使⽤的是 LinkedHashMp
⚫ 数组和链表的区别 ?
答:数组是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内
存地址连续,所以在查找数据的时候效
率⽐较⾼;它的缺点:在存储之前,我们需要申请⼀块连续的内存空间,并且在
编译的时候就必须确定好它的空间的⼤
⼩。在运⾏的时候空间的⼤⼩是⽆法随着你的需要进⾏增加和减少⽽改变的,当
数据两⽐较⼤的时候,有可能会出现
越界的情况,数据⽐较⼩的时候,⼜有可能会浪费掉内存空间。在改变数据个数
时,增加、插⼊、删除数据效率⽐较低
链表是动态申请内存空间,不需要像数组需要提前申请好内存的⼤⼩,链表只需
在⽤的时候申请就可以,根据需
要来动态申请或者删除内存空间,对于数据增加和删除以及插⼊⽐数组灵活。还有就是链表中数据在内存中可以在任
意的位置,通过应⽤来关联数据(就是通过存在元素的指针来联系)
⚫ TreeMap 和 TreeSet 在排序时如何比较元素?
Collections 工具类中的 sort()方法如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比
较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。
TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元
素进行排序。Collections 工具类的 sort 方法有两种重载的形式,第一种要求传入
的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;第二
种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是
Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于
一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对
回调模式的应用(Java 中对函数式编程的支持)。
⚫ Java 中的 TreeMap 是采用什么树实现的?(答案)
Java 中的 TreeMap 是使用红黑树实现的。
⚫ Hashtable 与 HashMap 有什么区别?
1、线程安全:HashMap是非线程安全的,Hashtable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。(如果需要多线程则使用ConcurrentHashMap)
2、效率:因为线程安全的问题,HashMap要比HashTable效率高一点。HashTable基本被淘汰。
3、HashMap的key值支持NULL,HashTable不支持NULL;
4、1)初始容量大小和每次扩充容量大小的不同:创建时如果不指定容量初始值,HashTable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的二倍。
2)创建时如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说HashMap总是使用2的幂作为哈希表的大小。
5、底层数据结构:JDK1.8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。HashTable没有这样的机制。
⚫ HashMap的长度为什么是2的幂次方?
⚫ 如何决定使用Hashmap还是TreeMap?
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序的key遍历。
⚫ HashMap和ConcurrentHashMap的区别?
1、ConcurrentHashMap对整个桶数组惊进行了分割分段,然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有琐机制,不是线程安全的。
2、HashMap的键值对容许有NULL,但是ConcurrentHashMap都不容许。
⚫ HashTable和ConcurrentHashMap的区别?
⚫ ConcurrentHashMap底层具体实现知道吗?实现原理是什么?
⚫ 集合删除底层?
List底层时数组,删除时数字元素下标会改变
迭代器调用next()方法时,会检测是否有修改过
如果要修改集合中的元素一定要用迭代器的remove()方法
⚫ Java 中的 HashSet,内部是如何工作的?
HashSet 的内部采用 HashMap 来实现。由于 Map 需要 key 和 value,所以
所有 key 的都有一个默认 value。类似于 HashMap,HashSet 不允许重复的
key,只允许有一个 null key,意思就是 HashSet 中只允许存储一个 null 对象。
⚫ ArrayList 和 HashMap 的默认大小是多数?
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是
16 个元素(必须是 2 的幂)。
⚫ 有没有可能两个不相等的对象有有相同的 hashcode?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在
hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必
须有相同的 hashcode 值,但是没有关于不相等对象的任何规定。
⚫ 两个相同的对象会有不同的的 hash code 吗?
不能,根据 hash code 的规定,这是不可能的。
⚫Collection常用功能
⚫Iterator迭代器
⚫List集合
⚫Collections类
public static void shuffle(List<?> list) :打乱集合顺序。
public static
public static
⚫Set集合
⚫Map集合
map常用方法:public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。
public V remove(Object key) : 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的 值。
public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
public Set
public Set<Map.Entry<K,V>> entrySet() : 获取到Map集合中所有的键值对对象的集合(Set集合)。
public boolean containKey(Object key) :判断该集合中是否有此键。
⚫ 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
⚫ hashMap的工作原理是什么?
Java中的hashMap是以键值对(key-value)的形式储存元素的。HashMap需要一个hash函数,它使用hashcode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法时,hashMap会计算key的hash值,然后把键值对储存在集合合适的索引上。如果key已经存在了,value会被更新成新值。hashMap的一些重要的特性时它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。
⚫ hashMap什么时候进行扩容呢?
当hashmap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashMap中的元素个数超过160.75=12的时候,就把数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashMap中元素的个数,那么预设元素的个数能够有效的提高hashMap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。
⚫ hashMap是线程安全的吗?为什么不是线程安全的?
不是线程安全的:如果有两个线程A和B,都进行插入数据,刚好这两条不同的数据经过哈希计算后得到的哈希吗是一样的,且该位置还没有其他的数据,所以这两个线程都会进入我在上面标记为1的代码中。假设一种情况,线程A通过if判断,该位置没有哈希冲突,进入了if语句,还没有进行数据插入,这时候cpu就把资源让给了线程B,线程A停在了if语句里面,线程B判断该位置没有哈希冲突(线程A的数据还没有插入),也进入了if语句,线程B执行完后,轮到线程A执行,现在线程A直接在该位置插入而不需要在做判断。这时候你就会发现A线程把B线程插入的数据覆盖了。发生了线程不安全情况。本来HashMap中,发生哈希冲突是可以用链表或者红黑树来解决的,但是多线程中,可能就直接给覆盖了。
⚫ HashMap的扩容过程?HashMap的扩容操作是怎么实现的?
当向容器添加元素时,会判断当前容器的元素的个数,如果大于等于阈值--即当前数组的长度乘以加载因子的值的时候,就要自动扩容了。
扩容就是重新计算容量,向hashMap对象中不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便装入更多的元素。当然java里数组时无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组。
⚫ 在使用Hashmap的时候,用String做key有设么好处?
HashMap内部试下是通过key的hashcode来确定value的存储位置,因为字符串是不可变的,所以当创建字符串时,它的hashcode被缓存下来,不需要再次计算,所以相比于其他对象更快。
⚫ 说一下HashMap的实现原理?
⚫ HashMap的put方法的具体流程?
1、hash()过程,经历了rehash()
2、执行put流程前的初始化定义,如初始化桶,数组长度等等
3、执行开始,验证桶是否初始化过,如果没有则扩容并重新赋值数组长度
4、通过桶数组长度、hash值的&运算,获取对应的数组下标
5、验证对应链表上的头节点,如果为空,就不用走接下来的逻辑,直接创建节点即可
6、如果不为空,那么确定当前节点已经处于链表上了,那么对头节点进行比较,如果头节点正好是当前值的节点,那么替换值即可.
7、如果头节点不是,需要确认当前链表结构是否为树形结构,即头节点是否为树形节点,如果是树形节点,调用putTreeVal()即可.
8、如果头节点也不是树形节点,那么就有趣了,hashMap会直接来个死循环遍历当前链表,知道获取到相同的节点为止,如果匹配到了,那么就将值进行替换.
9、如果直到尾结点也没有匹配上,说明当前节点在之前不存在,那么hashMap就会为当前值创建一个节点,并且判断是否需要进行树化,如果满足树化条件,那么会对当前链表进行树化.
⚫ HashMap的是怎么解决哈希冲突的?
⚫ 为什么HashMap中String、integer这样的包装类适合作K?