java集合

本文详细介绍了Java集合框架中的List、Set、Map三者区别及其常用实现类,如ArrayList、LinkedList、HashSet、HashMap等。分析了ArrayList和LinkedList在插入、删除和随机访问上的性能差异,以及HashMap与Hashtable的不同,强调了线程安全和效率问题。还探讨了TreeMap的有序特性以及多线程环境下HashMap的安全问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、说一下java集合,各自特点与适用情况。List、set、map三者的区别
java集合容器分为collection和map两大类,collection集合的子接口有set、list、queue三种子接口,常用的是set,list。
(1)List(对付顺序的好帮手):存储的元素是有序的,可重复的,常用的实现类有ArrayList,linkedlist和vector。
可以使用Collections.sort(list)进行排序
(2)set(注重独一无二的性质):存储的元素是无序的,不可重复的。常用的实现类是hashset,treeset和linkedhashset。
(3)map(用key搜索的专家):使用键值对存储,key是无序的,不可重复的,value是无序的,可重复的,每个键最多映射一个值,常用的有hashmap,treemap,hashtable,concurrenthashmap。

2、ArrayList和LinkedList的区别
(1)是否保证线程安全:两个都是线程不同步的,也就都不保证线程安全。
(2)底层数据结构:ArrayList底层使用的是Object数组,LinkedList底层用的是双向链表数据结构,JDK1.6用的是循环列表,1.7之后取消了。
(3)插入和删除是否受元素位置影响
-ArrayList:采用数组存储,所以插入和删除元素的时间复杂度受位置影响。
默认将指定的元素追加到列表的末尾,这样时间复杂度是o(1),如果要在指定位置i加入或删除元素的话,时间复杂度为o(n-i),因为第i和第i个之后的元素要执行移一位的操作。
-LinkedList采用链表存储,因此在表头或者表尾插入或者删除元素时间复杂度为o(1),如果指定位置插入,那么时间复杂度是o(n),因为要先移动到指定位置。
(4)是否支持快速随机访问:
LinkedList不支持,ArrayList支持
(5)内存占用空间:
ArrayList的空间浪费主要体现在list列表的结尾会预留一定容量的空间,而LinkedList的空间花费则体现在他每个元素都要消耗比arraylist更多的空间,因为要存放直接后继和直接前驱以及数据。

3、说一说ArrayList
(1)ArrayList底层是object数组,默认长度是10,扩容长度为1.5倍。集合扩容时会创建更大的数组,把原有数组放入到新数组内,支持对数组的快速随机访问,但是插入与删除速度较慢。
(2)elementData是ArrayList的数据域,被transient修饰。
应用场景
ArrayList适合于频繁进行查找,并且插入和删除操作比较少的场景,而LinkedList适用于查询比较少但是插入和删除操作比较多的场景。

4、说一说linkedList
(1)LinkedList是双向链表,比ArrayList的插入和删除速度更快,但是随机访问元素较慢,内部类Node(val,next,pre)是链表的节点。
(2)它可以当做堆栈、队列进行操作。

5、HashMap和Hashtable的区别
(1)线程是否安全:hashmap是非线程安全的,hashtable是线程安全的
(2)效率:hashmap效率高
(3)对null key 和null value的支持:hashmap支持,但null作为键只能有一个,作为值可有多个。
(4)底层:hashmap是数组链表或红黑树,在解决哈希冲突的时候,当链表长度超过8,就将链表转化为红黑树(JDK1.8),而JDK1.7之前是数组加链表
(5)初始容量和每次扩容容量大小的不同:hashtable的默认初始值为11,之后每次扩容,容量大小变为原来的2n+1,。hashmap默认初始化大小为16,之后每次扩容容量为原来2倍。如果给定初始值,hashtable就是这么大,hashmap总会使用2的幂次大小。

**6、TreeMap:**基于红黑树实现,与 HashMap 相比,其是有序的,因为红黑树是有序的,比如:获取到的 key 和 value 是有序的。而哈希表则是无序的

6、hashset实现原理
(1)hashset是基于hashmap实现的,hashset的值存放在hashmap的key上,hashmap的value统一为PRESENT,因此hashset的实现比较简单,相关hashset的操作,基本上都是直接调用hashmap的相关方法完成,hashset不允许重复的值。

7、hashmap的遍历方法
使用entryset遍历,调用一个迭代器,然后把entryset中得到的key和value取出来

6、Hashset和hashmap的区别
(1)hashmap实现map接口,存储了键值对,调用put方法向map中添加元素,hashmap使用key来计算hashcode。
(2)hashset实现了set接口,仅存储了对象,调用add方法在set中添加对象,hashset使用成员对象计算hashcode值,如果两个对象的hashcode值相等,用equals方法来判断两个对象是否相等。

7、HashMap 有什么特点?
(1)JDK8之前底层是用数组和链表,JDK8之后用的是数组+链表/红黑树,节点类型从entry变为node,主要成员变量包括存储数据的table数组,元素个数size,加载因子loadfactor。
(2)table数组记录hashmap的数据,每个下表对应一个链表,链表中存放着哈希冲突的数据,每个node节点中存放着key,value,next和hash值。
(3)hashmap中数据以键值对的方式存在,如果两个元素key的hash值相同就会发生hash冲突。
(4)hashmap的默认初始值为16,扩容容量必须是2的幂次方,最大容量为2^30,默认加载因子是0.75.

7.5、hashmap的实现原理
hashmap是基于hash算法实现的
(1)当我们向hashmap中put元素时,利用key的hashcode值重新hash计算出当前对象元素在数组的下标。
(2)存储时,如果出现hash值相同的key,如果key相等,则覆盖原始数据。如果key不同,就把当前数据放到链表中。
(3)获取数据时,直接找hash值对应的数组下标,然后判断key值是否相同,从而找到对应值。

8、为什么扩容是2的幂次方
2的幂次方的优点 数组长度为2的幂次方的话,hash值与数组长度减一的与运算相当于取模,但是与运算的效率要快得多。并且数组长度是2的幂次方的话,数组减一的二进制位都是1,这样hash值发生变化得到的散列值也会变化,减少发生哈希冲突的概率。

8、HashMap 相关方法的源码?
JDK8
hash:计算元素key的散列值

(1)如果key为mull返回0,否则就将key的hashcode方法返回值高低16位异或,让尽可能多的位参与运算。
put:添加元素
(1)调用putVal方法添加元素 /final V putVal
(2)如果table为空或者长度为0就进行扩容,否则计算元素下标位置,不存在就调用newCode创建一个节点。
(3)如果存在且是链表,如果首节点和待插入元素的hash和key都相同,就更新节点的value。
(4)如果首节点是个TreeNode类型,调用putTreeVal方法增加一个树节点,然后插入元素。
(5)如果都不满足,遍历链表,根据hashcode和key判断是否重复,决定是更新value还是添加新的节点。如果遍历到了链表末尾就添加节点,如果链表长度大于8,就调用treeifyBin方法把链表变为红黑树。
(6)存放元素后将modCount加1,如果++size>threshold,调用resize扩容。
get:获取元素的value值
(1)调用getNode方法获取Node节点,如果不是null就返回其value值,否则返回null
(2)getNode方法中如果数组不为空且存在元素,先比较第一个节点和要查找的元素的hash值和key,如果都相同则直接返回。
(3)如果第二个节点是TreeNode类型,调用getTreeNode方法进行查找,否则遍历链表用hash和key查找,如果没有返回null
resize:扩充数组
重新规划长度和阈值,如果长度发生了变化,部分数据节点也要重新排列。
(1)如果当前容量oldCap>0且达到最大容量,将阈值设为Integer最大值,终止扩容。
(2)如果未达到最大容量,且oldcap<<1不超过最大容量,就扩大为2倍。
(3)如果都不满足且当前扩容阈值oldThr>0,使用当前扩容阈值为新容量。
(4)否则将新容量置为默认初始容量16,新扩容阈值为12

9、JDK1.8做了哪些优化
(1)resize扩容优化
(2)引入了红黑树,避免了单链条过长影响查找效率
(3)解决了多线程死循环问题,但线程依旧是非线程安全的

10、快速失败(fail–fast)
**(1)本质:**是java集合的一种错误检查机制,当多个线程对集合在结构上进行更改,有可能会产生fail–fast操作。
**(2)原因:**迭代器在遍历集合时直接访问集合中的内容,并且在遍历时使用一个modCount变量。集合在遍历期间如果内容发生变化(add,move,clear等改变list元素个数),就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否等于expectedmodConut,如果相等就返回遍历,否则就抛出异常,终止遍历。
(3)解决方法
(1)在遍历过程中,所有涉及到改变modCount的地方全部加上synchronized
(2)使用CopyOnWriteArrayList来替换ArrayList
CopyOnWriterArrayList所代表的核心概念就是:任何对array在结构上有所改变的操作(add、remove、clear等),CopyOnWriterArrayList都会copy现有的数据,再在copy的数据上修改,这样就不会影响COWIterator中的数据了,修改完成之后改变原有数据的引用即可。同时这样造成的代价就是产生大量的对象,同时数组的copy也是相当有损耗的。)

11、安全失败(fail–safe)
(1)采用安全失败机制的集合容器,在遍历时不时直接在集合内容上,而是先复制原有的集合内容,在拷贝的集合上进行遍历。
(2)java.util.concurrent包下的容器都是安全失败的,可以在多线程并发下使用,
fail-safe机制有两个问题
(1)需要复制集合,产生大量的无效对象,开销大
(2)无法保证读取的数据是目前原始数据结构中的数据

12、多线程put可能发生的问题
(1)当多个线程同时执行addEntry时,如果产生哈希碰撞,导致两个线程得到同样的backetIndex去存储,就可能发生元素覆盖丢失的情况。
(2)在JDK1.7以前,向hashmap中put元素时,先检查hashmap的容量是否足够,如果不够时就建一个比原来容量大2倍的hash表,然后把数组从原来的hash表中迁移到新的hash表中,迁移的过程就是一个rehash的过程,多个线程操作就可能形成循环列表。

13、为何HashMap线程不安全
(1)在JDK1.7中,hashmap采用头插法插入元素,因此并发情况下会导致环形链表,产生死循环。
(2)在JDK1.8中虽然采用尾插法解决了这个问题,但是并发下put操作也会使前一个key被后一个key覆盖。
(3)由于hashmap中扩容机制的存在,也存在A线程进行扩容后,B线程之后get方法出现失误的情况。

14、简述java的TreeMap
(1)TreeMap是底层用红黑树实现的map结构,底层实现是一颗平衡的二叉排序树,由于红黑树的插入、删除、遍历的时间复杂度都是o(logN),所以性能上低于哈希表,但是哈希表无法提供键值对的有序输出,红黑树可以按照建值得大小有序输出。

15、ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
JDK1.7
(1)ConcurrentHashMap采用分段锁的概念,在初始化时,默认产生16个段(segment),每个段内部又是一个数组,数组中每个元素又是一个链表。
(2)Segment的结构和hashmap类似,是一种数组和链表结构。当有元素要添加进来,首先进行一次hash计算,计算出该元素会被分配到哪个段中,然后再进行第二次哈希计算,确定在段中数组的哪个位置。
在这里插入图片描述
JDK1.8
(1)在JDK1.8中,放弃了segment设计,采用node+CAS+synchronized来保证并发安全的实现,synchronized只锁定当前链表或者红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发。

在这里插入图片描述
16、ConcurrentHashMap 和 Hashtable 的区别?
区别主要体现在实现线程安全的方式上不同
(1)底层数据结构:JDK1.7的ConcurrentHashMap采用的是分段的数组+链表实现,JDK1.8采用的是数组+链表/红黑二叉树。hashtable采用的是数组+链表实现
(2)实现线程安全的方式:
①JDK1.7,ConcurrentHashMap将整个表分成多段segment,每个segment有一把锁,多线程访问容器中不同segment中的数据,就不会存在锁竞争。默认有16个segment。
②JDK1.8,直接使用Node+数组,链表/红黑树的数据结构,并发控制使用synchronized和CAS实现
③hashtable使用同一把锁来保证线程安全,效率低下。当一个线程访问同步方法时,其他线程访问同步方法就可能进入阻塞或轮询状态。

17、Hashmap链表要转红黑树,为什么长度超过8
(1)因为红黑树的平均查找长度为log(n),长度为8,查找长度为3,链表的平均查找长度为n/2,这样当长度为8时,查找长度为4,才有转换成树的必要。
treemap是基于红黑树实现的一个保证有序的map,基于红黑树,所以treemap的时间复杂度是o(logn)

18、简述一下hashmap用哪些方法解决哈希冲突
(1)使用链地址法来连接有相同哈希值的数据
(2)使用2次扰动函数来降低哈希冲突的概率,使得数据分布平均。
(3)引入红黑树进一步降低遍历的时间复杂度,使得遍历更快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值