java集合

2.2.1. 说说List,Set,Map三者的区别?

储存元素的角度看:
List:存储的元素是有序的(有顺序),可以重复。
Set:存储的元素是无序的,不可以重复。
Map:存储的是<key,value>的键值对,key无序的,不可重复value无序的,可以重复。一个key对应一个value。


List:

2.2.2. ArrayList 与 LinkedList 区别?ArrayList 与 LinkedList的使用场景是什么?

1.是否线程安全
它们都不是线程安全的
2.它们的底层结构
ArrayList的底层结构是Object数组,而LinkedList的底层结构是双向链表。(在jdk1.6前时循环的,jdk1.7后是不循环的。为什么会这样改动?有什么区别?
3.它们获得某个元素的时间复杂度——get()(是否能随机访问):
ArrayList是借助Object数组实现的,所以可以在O(1)时间查找(因为是元素的有序的)。而LinkedList是借助双向链表实现的,所以查找需要从头遍历到要查找的元素,时间为O(n),不能快速查找
4.它们插入和删除的时间复杂度——add()/remove()
ArrayList的add()由于用的是Object数组,所以除了在末尾添加一个元素是O(1)但一般在中间添加的话需要先把该index后面的元素都往后移一位,所以时间复杂度为O(n)
而LinkedList的add()是用双向链表,当知道插入的位置时时间复杂度为O(1),但是通常需要从头遍历直到找到要插入的位置,所以时间复杂度也为O(n)
5.它们占用的内存空间
ArrayList通常元素都占不满所申请的数组,而申请的数组即使没有存放元素,也占着内存空间。而且当ArrayList的空间刚要被占满时,它会以原来1.5倍的空间进行扩容。所以会占用一些用不到的空间
LinkedList则是因为每个Node节点不只存储数据元素,而且还存储前后节点等信息。每个节点占的空间要大于ArrayList的每个节点。

ArrayList适用于频繁查询的场景(因为支持随机访问,实现了标志接口RandomAccess),LinkedList适用于频繁修改的场景(因为实现是双向链表方便元素的修改)。.

2.2.2.1补充:RandomAccess接口

RandomAccess接口里什么都没有,它只是个标志性接口,表示实现该接口的类支持随机访问

ArrayList就实现了RandomAccess接口,而LinkedList就没有实现该接口。说明ArrayList支持随机访问,而LinkedList不支持随机访问

2.2.3. ArrayList 与 Vector 区别呢?为什么要⽤Arraylist取代Vector呢?

ArrayList和Vector最主要的区别是ArrayList不是线程安全的,而Vector是线程安全的。其它的有细节上的区别。(如扩容方式)

Vector实现线程安全的方式把基本所以的操作上直接加上synchronized关键字(加锁)。

如果不需要保证线程安全的话,要尽量用ArrayList。因为不必要的加锁会降低速度。

2.2.4. 说⼀说 ArrayList 的扩容机制

ArrayList是动态数组。当它的空间满的时候会自动扩容

1.当ArrayList不指定初始长度时,默认创建一个长度为0的Object数组。当元素add时,会扩容为默认长度的数组,默认为10。再add直到元素大于10时会再次扩容,扩容是以old+old>>1的方式扩的(如果是偶数则是1.5倍,如果是奇数的话就会小一点)。
2.当ArrayList指定初始长度创建时,add时不会扩容,直到元素数量大于初始长度时再进行扩容。

扩容用的是**Arrays.copyOf(oldList,newLength),**它会把oldList里的元素拷贝到以newLength长度创建的数组里,然后把新数组返回。

扩到最满的时候抛出OOM内存溢出异常


Map:

HashMap里的元素是<k,v>。

2.2.5. HashMap 和 Hashtable 的区别

1.从线程安全角度。
HashMap不是线程安全的,而Hashtable是线程安全的。(Hashtable不是驼峰命名,Hashtable的操作都直接加了synchronized来保证线程安全)
2.从性能角度。
HashMap不需要保证线程安全所以性能要更好。
3.从易用性角度。
HashMap现在应用广泛,而Hashtable现在已经被废弃了(1.没有遵循驼峰命名2.它继承的Dictionary类已经废弃)。当需要高并发需要线程安全时则推荐使用ConcurrentHashMap
4.对null键和null值的支持角度。
HashMap支持null作为key和value,null只能作为唯一的key,但null值可以有多个。
Hashtable不支持null作为key和value,当检测到为null时会抛出空指针异常。(Hashtable不支持null是因为想让keys都实现hashCode和equals方法,这样才能方便从容器中存和取
5.默认初始大小和扩容方式不同
HashMap默认是16,默认装载因子为0.75,每次扩容为原来的2倍。而且需要保证大小为2的次幂
Hashtable默认是11,默认装载因子为0.75,每次扩容为原来的2倍+1。不需要保证什么条件,直接就可以用。
6.底层结构不同
jdk1.8前HashMap的实现是数组+链表(数组的每个元素是一个链表的头结点)
jdk1.8后的HashMap实现是数组+链表+红黑树

某个链表的长度大于阈值(默认为8),而且数组中的Node节点小于64时,会进行扩容操作(不把链表转化为红黑树)。
某个链表的长度大于阈值(默认为8),而且数组中的Node节点大于64时,才会把该链表转化为红黑树

而Hashtable没有这样的底层结构。

在这里插入图片描述
之所以要用链表是为了处理冲突,当放入的<k,v>中的k的hashcode(已重写)与里面已经存在的Node <k,v>一样k还不相等时,就发生了冲突(k相等时直接把v替换就行了),所以要采用拉链法(用链表方式)来处理冲突,也就是把该<k,v>放到链表后边。

HashMap里的元素为<k,v>类型。

参考链接:
https://blog.csdn.net/qq_40574571/article/details/97612100

2.2.6. 说说HashSet?说说HashMap 和 HashSet区别

HashSet的底层是用HashMap实现的。HashSet可以保证添加的数据不重复。HashSet底层HashMap的key用的是要添加的对象value是一个临时创建的Object对象(无意义)。即是<E e,Object>。

1.一个实现Map接口,一个实现Set接口
2.一个存储**<K,V>键值对**,一个存储对象
3.都是以key来计算hashCode(都在HashMap中实现)的。但是HashSet是把要加入的对象作为key,拿这个对象来计算hashCode。不同对象的hashCode可能相等,所以再用equals()方法判断是否相等来确定是否要添加。

2.2.7. HashSet如何检查重复?==与equals()有什么区别?

HashSet加入一个对象时(作为底层HashMap的key),会计算它的hashCode是否已经在HashSet里出现了。如果没有重复,则添加。如果hashCode有重复的话,就使用equals()方法判断,如果返回true说明是同一个对象,则不添加。如果为false,则添加。

关于hashCode()和equals():
1.如果对象相等(是同一个对象),则hashCode和equals都是相等的。
2.但是hashcode相等的两个对象不一定相等。(虽然在hashcode()方法没有重写时,如果hashcode相等则对象一定相等,hashCode()没有重写时表示的是对象在内存中的地址),是因为hashCode()重写后就有可能重复了。

==如果用在基本类型上时,比较的是值是否相等。如果用在引用类型上时,比较的是两个引用的地址是否相等

equals()方法如果没有重写,比较的是引用的地址是否相等,因为调用的是==来比较的。如果重写了,按重写的规则比较。一般是比较内容是否相同。

2.2.8. HashMap的底层实现

jdk1.8之前为 数组+链表实现,每个元素储存的为一个<k,v>

过程为:
扰动函数hash(Object key),为HashMap的一个方法通过key来获得一个hash值。然后通过 (n-1) & hash 作为数组下标(n是数组长度)。如果该下标对应<k,v>为null(因为每个元素为<k,v>,默认创建时元素为null),则就把该<k,v>放到这。如果有<k,v>在这,则判断k是否相等,如果k相等则直接把新的v替换。如果k不相等则说明发生冲突,使用拉链法处理,就是把该元素<k,v>放到链表的后边。

使用扰动函数hash(Object key)是为了减少发生冲突的几率,减少碰撞

jdk1.8后实现为 数组+链表+红黑树。当某条链表的长度大于阈值(默认为8)且数组大小小于64(默认值),先给数组扩容。当某条链表的长度大于阈值(默认为8)且数组大小大于64(默认值)时,转化链表为红黑树

当链表节点数目变多时,使用红黑树加快查找速度。

2.2.8.1 HashMap中数组的下标怎么计算的?(是怎么由key得到的)

首先用hash(Object key)这个扰动函数(该hash函数里面使用了hashCode()方法),得到hash值。
然后下标为: hash%n ,n为数组长度(hashMap容量)。
hash%n = (n-1)&hash————n必须为2的幂次。
所以HashMap中的下标为(n-1)&hash。

这样理解:

hash%n,n为2的幂次。所以把hash化为2进制的话,大于n的2的幂次都被消掉了。结果应该是hash中小于n的2的幂次的部分
而**(n-1)再与hash做&运算**,也正好是获得了hash中小于n的2的幂次的部分
所以n为2的幂次时,hash%n = (n-1)&hash。

2.2.9. HashMap 的⻓度为什么是2的幂次⽅

hash的范围是int的范围,负数到正数一共40多亿。声明这么大的数组是放不到内存当中的。所以<k,v>的下标要用hash对数组长度n进行取模得到。

正常来说下标应为hash%n,但是当n为2的次幂时,hash%n等于hash&(n-1),使用**&运算来说速度快的多**,所以要求HashMap的数组长度n应该为2的次幂

2.2.10. HashMap 多线程操作导致死循环问题

HashMap不是线程安全的,如果在多线程场景下,需要保证线程安全,则要用ConcurrentHashMap.

ConcurrentHashMap在jdk1.8之后,与hashMap的底层结构一样,都是用数组+链表+红黑树实现的。用部分synchronized+CAS操作来保证线程安全。

2.2.11. ConcurrentHashMap 和 Hashtable 的区别

1.底层结构不同:
在jdk1.8前,ConcurrentHashMap的底层结构是分段数组(Segment数组)+链表数组(HashEntry数组,每个HashEntry是一个链表),jdk1.8之后,ConcurrentHashMap的底层结构和jdk1.8的HashMap结构一样是数组+链表/红黑树
而Hashtable的底层结构是数组+链表
2.实现线程安全的方式不同:
在jdk1.8之前ConcurrentHashMap实现线程安全的方式:
使用分段锁把<k,v>数组分段,一个锁只负责一部分区域,当多线程去访问不同区域时,获得不同的锁,就不会存在锁竞争,就可以提高效率。
在jdk1.8之后ConcurrentHashMap实现线程安全的方式:
不再使用Segment分段锁,通过部分使用synchronized和CAS操作来保证线程安全。

而Hashtable保证线程安全的方式是:把所有的操作前面加上synchronized关键字,这样如果很多线程同时使用该方法时,效率很低(因为一个线程拿着锁,剩下线程就只能等待该线程释放锁,就很慢)

在jdk1.8后ConcurrentHashMap中仍保留了Segment数据结构(是为了兼容之前的代码),但是里面的属性都被简化了。

2.2.12. ConcurrentHashMap线程安全的具体实现⽅式/底层具体实现

在jdk1.7时(jdk1.8之前),ConcurrentHashMap底层结构是分段数组(Segment数组)+链表数组(HashEntry数组,每个HashEntry是一个链表)。

一个ConcurrentHashMap里有一个Segment数组每个Segment元素有一个HashEntry数组每个HashEntry是一个链表

在这里插入图片描述

由于Segment继承ReentrantLock,所以作为锁的角色。
当线程想获得一个Segment区域中的元素,则先需要获得该Segment的锁

jdk1.8时,ConcurrentHashMap使用部分synchronized+CAS操作来保证线程安全。
synchronized用的是代码块锁的是链表或红黑树的首节点,也就是**<k,v>数组的某一个元素(链表/红黑树首节点)
这样
当没有发生hash冲突时**,就可以多线程进行高并发操作

在这里插入图片描述

2.2.13. ⽐较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

HashSet是Set接口的主要实现类,底层由HashMap实现,和HashMap一样,都可以存储null值

LinkedHashSet是HashSet的子类,可以按顺序来进行遍历元素(用的链表)。

TreeSet底层使用红黑树(Set用tree的方式实现),可以按顺序来进行遍历

如何使用集合?

如果想保证集合里元素唯一,则使用Set

如果想从一个key获得一个value,有这个对应关系,用Map:
需要排序则使用TreeMap不需要排序则使用HashMap,需要并发保证线程安全则使用ConcurrentHashMap。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值