Java-集合

1.说说List,Set,Map三者的区别

  • List(对付顺序的好帮手):存储的元素是有序的、可重复的。
  • Set(注重独一无二的性质):存储的元素是无序的、不可重复的;
  • Map(用Key来搜索的专家):使用key-value存储,,Key是无序的、不可重复的;value是无序的、可重复的。每个键最多映射到一个值。

2.ArrayList与LinkedList区别

  1. 是否保证线程安全:两者都是不同步的,也就是不保证线程安全;vector线程安全
  2. 底层数据结构:ArrayList底层使用的是Object数组,查找效率比较高;LinkedList底层使用的是双向链表,插入和删除的操作效率比较高;
  3. 内存空间占用:ArrayList的空间浪费体现在在list结尾会预留一定的容量空间,而LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

3.ArrayList扩容机制

4.HashMap和Hashtable的区别

  1. 线程是否安全:HashMap是非线程安全的,HashTable是线程安全的,因为HashTable内部方法基本都经过synchronized修饰。(如果你要保证线程安全的话就使用ConcurrentHashMap);
  2. 效率:因为线程安全问题,HashMap要比HashTable效率高一点;
  3. 对Null Key 和Null value的支持:HashMap可以存储null的key和value,但null作为键只有一个,null作为值可以有多个;HashTable不允许有null值和null键,否则会抛出NullPointerException。
  4. 初始容量大小和每次扩充容量的大小的不同:1.创建时如果不指定容量初始值,Hashtable默认的初始大小为11,每次扩充后,容量变为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩容后,容量变为原来的2倍。2.创建时如果给定了容量初始值,那么Hashtable会直接使用给定的大小,而HahMap会将其扩充为2的幂次方大小
  5. 底层数据结构:JDK1.8以后引入了红黑树结构做优化,当链表元素的个数大于等于8时,链表转换成树结构;若桶中的链表元素的个数小于等于6时,树结构还原成链表。(将链表转换成红黑树前会判断,如果当前数组的长度小于64,那么会先进行数组扩容,而不是转换成红黑树)

6.HashMap和HashSet区别

7. HashSet如何检查重复

见Java基础2Hsahcode值

8.HashMap的底层实现:

HashMap基于数组实现的,通过对key的hashcode&数组的长度得到在数组中位置,当前数组有元素,则当前元素next指向要插入的元素,这样来解决hash冲突的,形成了拉链式结构。谓的拉链法就是:所将链表与数组相结合。也就是说创建一个链表数组,数组中的每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

put时在多线程情况下,会形成环而导致死循环。数组长度一般是2n,从0开始编号,所以hashcode&(2n-1),(2n-1)每一位都是1,这样会让散列均匀。负载银子默认是0.75,2~n是为了让散列更加均匀。

DK1.8以后引入了红黑树结构做优化,当链表元素的个数大于等于8时,链表转换成树结构;若桶中的链表元素的个数小于等于6时,树结构还原成链表。还有选择6和8,中间有个差值7可以有效防止链表和树的频繁转换。

9.HashMap的长度为什么是2的幂次方

为了能让HashMap存取高效,尽量减少碰撞,也就是尽量把数据分配均匀。

散列值是不能直接拿来用的。用之前还要先做对数组长度取模运算,得到的余数才能用来存放的位置也就是对应数组的下标。这个数组下标的计算方法:(n-1)&hash(n代表数组长度).

10.ConcurrentHashMap和Hashtable的区别

两者的区别主要 体现在线程安全的方式上不同

底层数据结构:ConcurrentHashMap JDK1.7采用分段的数组+链表实现,1.8采用数据+链表/红黑树。Hashtable采用数组+链表,链表是为了解决哈希冲突而存在的.

实现线程安全的方式(重要):1.JDK1.7的时候ConcurrentHashMap(分段锁)对整个通数组进行了分段分割(Segment),每把锁只锁容器中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。JDK1.8,摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和CAS来操作。Hashtable(同一把锁)

具体实现方式

 11.比较HashSet、LinkedHashSet和TreeSet三者的异同

HashSet是Set接口的主要实现类,HashSet的底层是HashMap,线程不安全的,可以存储null值;

LinkedHashSet是HashSet的子类,能够按照添加的顺序遍历;

TreeSet底层使用红黑树,能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。

12. TreeMap底层,红黑树原理?

TreeMap的实现就是红黑树数据结构,也就是说是一棵自平衡的排序二叉树,这样就保证党需要快速检索指定节点。

红黑树的插入、删除、遍历时间复杂度都是O(lgN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树因为是排序插入的,可以按照键的值的大小有序输出。

红黑树性质:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点永远是黑色。
  3. 所有的叶节点都是空节点,并且是黑色的。
  4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)
  5. 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

13.Collection和Collections的区别?

Collection是集合类的上级接口,继承与他的接口主要有Set和List。Map不是,Map是键值对映射容器,与List和Set有明显区别。

Collections是针对集合类的一个帮助类,他提供一些列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

14.为什么集合类没有实现Clonable和Serializable接口?

克隆或者序列化的语义和含义是跟具体的实现相关的。因此应该由集合类的具体实现来决定如何被克隆或者序列化。

实现Serializable序列化的作用:将对象的状态保存在存储媒体中一边可以在以后重写创建出相同的副本;按值将对象从一个应用程序发向另一个应用程序域。

实现Serializable接口的作用就是可以把对象存储到子节流,然后恢复,所以你想如果你的对象没有序列化,怎样才能进行网络传输呢?要网络传输就必须转换成子节流,所以在分布式应用中,你就得实现序列化。

15.ConcurrentHashMap的put方法执行逻辑是什么

JDK1.7

首先,会尝试获取锁,如果获取失败,利用自旋获取锁;如果自旋重试的次数超过64次,则改为阻塞获取锁。

获取到锁后:

  1. 将当前Segment中的table通过key的hashcode定位到HashEntry
  2. 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value
  3. 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容
  4. 释放Segment的锁。

JDK1.8

  1. 根据key计算出hash值
  2. 判断是否需要初始化。
  3. 定位到Node,拿到首节点f,判断首节点f:
    1. 如果为null,则通过CAS的方式尝试添加。
    2. 如果为f.hash = MOVED = -1,说明其他线程在扩容,参与一起扩容。
    3. 如果都不满足,synchronized锁住f节点,判断是链表还是红黑树,遍历插入。
  4. 当在链表长度达到8的时候,数组扩容或者将链表转换为红黑树。

16.ConcurrentHashMap的get方法是否要加锁,为什么?

get方法不需要加锁,因为Node的元素val和指针next是用volatile修饰的,在多线程环境下线程A修改节点的val或者新增节点的时候是对线程B可见的。

17.get方法不需要加锁与volatile修饰的哈希桶有关吗

没有关系,哈希桶table用volatile修饰主要是保证在数组扩容的时候保证可见性。

18.ConcurrentHashMap不支持key或者value为null的原因

因为ConcurrentHashMap是用于多线程的,如果map.get(key)得到了null,无法判断,是映射的value是null还是没有找到对应的key而为null,这就有了二义性,

而用于单线程状态的HashMap却可以用containsKey(key)去判断到底是否包含了这个null;

19.ConcurrentHashMap的并发度是多少

JDK1.7中,并发度默认的是16,这个值可以在构造函数中设置。如果自己设置了并发度,ConcurrentHashMap会使用大于等于该值得最小2的幂指数作为实际并发度,也就是比如你设置的值是17,那么实际并发度是32.

20.ConcurrentHashMap迭代器是强一致性还是弱一致性

与HashMap迭代器是强一致性不同,ConcurrentHashMap迭代器是弱一致性。

ConcurrentHashMap的迭代器创建后,就会按照哈希表结构遍历每个元素,但在遍历过程中,内部元素可能会发生变化,如果变化发生在已遍历过的部分,迭代器就不会反映出来,而如果变化发生在未遍历过的部分,迭代器就会发现并反映出来,这就是弱一致性。

21.JDK1.7与JDK 1.8中ConcurrentHashMap的区别

  • 数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构
  • 保证线程安全机制:JDK1.7采用Segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
  • 锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)
  • 链表转换为红黑树:定位节点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
  • 查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)

22.Collection框架中实现比较要怎么做/

  1. 实体类实现Comparable接口,并实现compareTo(T t)方法,称为内部比较器
  2. 创建一个外部比较器,这个外部比较器要实现Comparator接口的compare(T t1,T t2)方法

23.Iterator和ListIterator有什么区别

  • 遍历。使用Iterator,可以遍历所有集合,但是只能向前方向上遍历集合中的元素。使用ListIterator,只能遍历List实现的对象,但是可以向前和向后遍历集合中的元素。
  • 添加元素。Iterator无法向集合中添加元素,后者可以向集合中添加元素。
  • 修改元素。前者无法修改集合中的元素;后者可以使用set()修改集合中的元素。
  • 索引。 前者无法获取集合中元素的索引;后者可以获取。

24.讲一讲快速失败(fail-fast)和安全失败(fail-safe)

快速失败:

  • 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Conturrent Modiication Exception
  • 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount值。集合在被遍历期间如果内容发生变化,就会改变modCount的值,每当迭代器使用hashNext()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历

安全失败:

  • 采用安全失败机制的集合容器,在遍历的时候不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝集合上进行遍历。
  • 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所做的修改并不能被迭代检测到,所以不会触发ConcurrentModifcation Execption
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值