JAVASE面试题-集合

  1. Java中的常用集合有哪些?

单列集合顶层父接口Collection,子接口有List、Set、Queue;List中常用的集合实现类有ArrayList和LinekdList(Vector以及(线程安全的)Vector的子类Stack(栈数据结构,先进后出))Set中常用的集合实现类有TreeSet和HashSet(以及HashSet(存取无序)的子类LinkedHashSet(存取有序))Queue不怎么用,实现类有PriorityQueue(优先级高的先出)、ArrayDeque(基于数组双端队列,可以队头队尾都出队入队)、LinekdList(LinekdList当队列来用,先进先出)(ArrayDeque和LinekdList)都实现于Queue的子接口Deque;

双列集合顶层父接口是Map,常用集合的实现类有HashMap、TreeMap以及HashTable和其子类Properties

JAVA中的集合分为两种,一种的单列集合Collection,另一种是双列集合Map,在Collection有三个子接口,一个是List一个是Set,还有一个是Queue,List下常用的实现类有Vector,ArrayList,LinekdList;Set常用的实现类有两个,一个是HashSet和TreeSet,TreeSet有一个子类叫LinkedHashSet,它是一个存取有序的Set集合;还有一个Queue队列,常见的队列有PriorityQueue,ArrayDeque还有LinekdList,LinekdList也能当队列来用。Map的常见实现类有HashMap,TreeMap,HashTable和其子类Properties。

  1. Collection 和Collections 的区别?

Collection是单列集合的接口,Collections是工具类,Collections工具类提供了一些方法,可以实现排序、二分查找、还有获取线程安全的集合,主要就是这三大功能。

Collection是单列集合的顶层父接口,接口中有很多常用的实现类,其直接继承的接口有List、Set、Queue;Collection的意义是为集合提供最大化的统一操作的方法。

Collections是集合的工具类,提供了很多静态方法用于对集合的操作,例如元素排序、搜索等。

  1. HashMap是线程安全的吗?如何得到一个线程安全的Map?

不是线程安全的,可以选择的方案有两种,可以利用Collections中的synchronizedMap()传入一个线程不安全的Map,它返回一个线程安全的Map,底层使用的是装饰者设计模式,除了这个方式之外还可以使用本身就是线程安全的Map,比如HashTable和ConcurrentHashMap,ConcurrentHashMap是比较推荐的,因为HashTable的并发度太低了。

HashMap线程不安全,可以使用Collections工具类中的synchronizedMap(map)利用装饰者设计模式,增强方法。也可以直接使用ConcurrentHashMap(方法被final修饰)或Hashtable(方法被synchronized修饰)这两个都是线程安全的Map。

  1. List和Set的区别?

是否有序、是否有索引、是否数据不重复;

List元素可重复,存取有序,有索引;Set元素不能重复,存取无序,没有索引。而且底层的数据结构也不同,List用的是线性结构,比如说数组链表,而Set底层用的是红黑树或哈希表。

List:元素有序,可以通过索引获取元素,允许重复元素;底层通常是通过线性表(数组、链表)实现。

Set:元素无序,不可以通过索引获取元素,不允许重复;底层是通过二叉查找树、hash表实现。

  1. HashMap 和Hashtable 的区别?

HashMap线程不安全Hashtable线程安全,HashMap允许存空键和空值,Hashtable不允许。

  1. HashMap线程不安全,Hashtable的方法被synchronized修饰,线程安全

  1. HashMap允许null键和null值,Hashtable不允许。

  1. ArrayList和LinkedList的区别?

数据结构不同,ArrayList底层是数组,LinkedList底层是双向非循环链表,这两种数据结构优点也不一样,数组在根据索引在查找元素的时候时间复杂度是O(1)级别,所以速度比较快;双向非循环链表根据索引在查找元素的时候时间复杂度是O(N)级别的,速度较慢;不管数组还是链表他们在添加元素时,时间复杂度都是O(N)级别的,数组如果我们在尾部添加或删除元素时,时间复杂度是比较快的,是O(1)级别,链表在头部和尾部添加都快都是O(1)。

结构不同:ArrayList是动态数组的数据结构,LinkedList是双向非循环链表的数据结构。

效率不同:ArrayList查询快,增删慢;LinkedList查询慢,增删快。

ArrayList数组结构,根据索引在查找的时候时间复杂度是O(1)级别,所以速度比较快;LinkedList双向非循环链表结构,根据索引在查找的时候时间复杂度是O(N)级别的,速度较慢,不管数组还是链表他们在添加元素时,时间复杂度都是O(N)级别的,数组如果我们在尾部添加或删除元素时,时间复杂度是比较快的,是O(1)级别,链表在头部和尾部添加都快都是O(1)。

  1. HashSet的实现原理

HashSet底层是依赖于HashMap来实现的,利用HashMap中的key来存储数据,HashMap的value存了一个常量(统一采用private static final Object PRESENT = new Object();)

  1. 如何实现数组和List之间的转化

数组转List用的是Arrays里的asList方法,List转数组用的是list中的toArray方法。

List转数组调用list中的方法:toArray()方法来实现

数组转List使用Arrays工具类中的asList()方法来实现

  1. ArrayList和Vector的区别

ArrayList是线程不安全的Vector是线程安全的;扩容机制不同,ArrayList扩容1.5倍;Vector扩容2倍。

底层都是基于数组实现

不同点1:线程安全方面,ArrayList线程不安全,Vector有synchronized锁,线程安全;如果需要一个线程安全的ArrayList,一般推荐CopyOnWriteArrayList

不同点2:扩容,ArrayList扩容1.5倍;Vector默认扩容2倍

  1. Queue中的peek()和poll()方法有什么区别

peek()是查看队列头部元素、poll()出队

  1. Iterator和ListIterator的区别

Iterator可以迭代list也可以得带set,ListIterator只可以迭代list。ListIterator是Iterator的子接口,除此之外ListIterator中的方法更丰富,除了可以往后迭代也可以往前迭代,还可以替换和添加元素。

Iterator和ListIterator都是迭代器接口,不同的集合底层数据结构不同,所以有不同的实现;Iterator是ListIterator的父接口。

Iterator可以遍历List也可以遍历Set,因为List和Set接口中都有 iterator()方法;Iterator<E> iterator();

ListIterator不能遍历Set,只有List接口中有对应的方法;ListIterator<E> listIterator();

Iterator只能向后遍历(hasNext,next),ListIterator不仅可以向后遍历,也可以向前遍历(hasPrevious,previous)

Iterator中对集合元素的操作只有删除,ListIterator提供了获取前一个元素索引,后一个元素索引,添加元素,替换元素等方法

HashMap相关问题

  1. HashMap底层数据结构是什么?1.7和1.8有什么区别?

HashMap底层数据结构就是哈希表,哈希表本身就是个数组,1.7哈希表里面装的是链表,1.8里面装的是链表或者红黑树;

1.7 数据结构:数组+链表

1.8数据结构:数组 + 链表|红黑树

  1. HashMap存储元素的流程

当使用put方法存储键值对数据的时候,它是分为四步来存,第一步:首先先获取key的哈希code,第二步:对哈希code进行二次哈希(就是用原hashCode的高16位和低16位进行异或运算),二次哈希后得到一个新的哈希值;第三步:用新的哈希值取模数组的长度得到索引;第四部:得到索引后将键和值封装成一个节点,要么是一个链表节点要么是一个红黑树节点,要么存到链表中要么存到红黑树中。之所以HashMap要使用哈希表这种方案来存储数据,是因为哈希表它的存储和查询的时间复杂度可以达到O(1)级别,可以提高查询效率。

当使用put方法存储key value数据时,首先会获取key的hashCode,再对hashCode进行二次hash,对得到的hash值取模数组的长度,得到数组的索引再将key value存入到指定索引的链表或红黑树中。这样的存储方案可以提高查询效率,时间复杂度可以达到O(1)级别。

  1. HashMap链表什么时候转化红黑树

达到一定条件的时候,链表会转化成红黑树;有2个条件:1.当数组的长度大于等于64(默认长度是16);2.并且链表中的节点个数超过8,这个时候就会把链表转为红黑树;

  1. HashMap什么时候扩容

链表长度超过8并且数组长度没有超过64它会扩容,除了这种情况外还有当hashMap中的元素个数超过阈值,也会触发扩容,每次扩容一倍。阈值=数组的容量*默认加载因子0.75。当我们创建一个hashMap之后,这个hashMap的数组长度默认是16,加载因子默认是0.75,16*0.75=阈值,那么就12,也就是说当hashMap中的键值对个数超过12时,就会触发扩容,扩容长度就是原长度16的2倍,也就是32。

  1. HashMap桶中为什么不直接存放红黑树

有以下原因,1.按照查询效率来看,链表长度较短的时候查询效率与红黑树差不多,而且链表比红黑树简单;2.在数据结构的角度来说,红黑树一个节点占用的内存要比链表一个节点占用的内存大(红黑树不仅要存元素的数据,还要存左节点、右节点、父节点和颜色;而单链表只需要存元素的数据和下一个节点的指针就行了);3.还有一个原因就是,如果这个hash值很随机,按照罗松分布(统计学相关知识),链表超过8的概率为0.00000006,这个值几乎为0了,也就是说链表的超度几乎不会超过8。之所以引入红黑树是为了防止DOS攻击。(所谓DOS攻击就是可以伪造一些数据让hashCode的值一样)。

  1. 红黑树什么时候退化成链表

两种情况,第一种情况,数组的扩容导致树的拆分并且拆分后的节点小于等于6;第二种情况,红黑树中的元素被删除也有可能导致红黑树退化成链表。(如果根节点的左子树、右子树、左孙子、右孙子有一个为null,再次删除一个节点,退化成链表)

  1. hashMap的索引如何计算

1.先拿到key的原哈希code;2.对原哈希code进行二次哈希;3.将二次哈希的哈希值取模数组的长度得到元素应存储的对应索引。(底层并不是取模运算而是使用哈希值&按位与数组的长度-1,提高效率,这个运算和取模运算是等价的,有个要求就是数组的长度必须是2的n次幂)

  1. 索引计算为何要进行二次hash的结果计算索引

将添加的元素,能更平均的分配到hash表中;不至于某些桶中元素过多;让哈希值分布的更均匀。

  1. HashMap数组长度为何是2的n次幂

①、计算索引的时候,可以不用取模运算,而采用位运算提高效率;②、跟扩容相关,扩容后元素要进行迁移(如果数组的长度是2的n次幂,那么数组扩容后,元素在新数组中的位置会有个规律:数组在扩容的时候如果元素的hash值&原始的容量=0的时候,它是留在原位置上,否则它的位置就是原始位置再加上原始数组的容量)

  1. HashMap1.7和1.8添加元素的区别
  1. 扩容因子为何是0.75

①大于该值节省空间,但是链表长度可能会比较长,②小于该值,扩容比较频繁,占用内存空间较大

  1. 多线程下操作hashMap会引发什么问题

会数据丢失(多个线程如果都去往同一个索引位置存储元素的话,就有可能导致后面的元素把前面的元素覆盖掉)

  1. HashMap为什么要求key重写hashCode和equals方法

map的key是不允许重复的,重写了hashCode和equals方法可以判断key是否产生了重复。(不重写或只重写一个的话就会产生两个相同属性的对象都可以作为键存到同一个hashMap中,这就违背了hashMap键不重复的原则)重写hashCode和equals要遵守规则,如果两个对象是equals的,那么他们的hashCode一定是相同的;两个对象的hashCode相同,他们不一定equals;

之所以hashCode和equals两个方法都要重写就要了解hashMap存储数据的流程,hashMap在存储数据时会用到元素的hashCode进行二次哈希再取模数组长度获取索引位置,如果只重写了hashCode没有重写equals,那么两个相同属性的对象会产生相同的哈希值也就是哈希冲突,同时也会指向相同的索引,那么底层会进行equals判断两个对象是否相同,因为没有重写equals方法,底层判断的就是对象的地址引用是否相同,那肯定是不同的,这就导致hashMap中存入了相同的键;

如果只重写了equals没有重写hashCode方法,那么两个属性相同的对象哈希值不一定相同,hashMap存储元素又是根据哈希值来进行存储的,不同哈希值计算得到的索引也不相同,这也就导致两个属性相同的对象都作为键存储到同一个hashMap中。

如果hashCode和equals方法都重写了,又出现了哈希冲突的情况,就会将前存入的值替换为后存入的值也就是键相同的话将旧的值替换为新的值。

  1. Integer,Dubble,Float,String这些类的hashCode是如何重写;

判断当前Integer的value和你传入的Integer的value的int值是不是相同的;

Dubble,Float和Integer是一样的。

String是判断当前对象的字符和传进来的字符串的每一个字符是不是相等;

Integer的哈希值就是它封装的int值;

Dubble:在底层是8个字节,64位,先将内存中的64位二进制数据右移32位,再和原64位的二进制数据进行异或运算,得到的结果强转称整数就是Dubble的hashCode。

Float:和Dubble一样,内存中是32位,先将内存中的32位二进制数据右移16位,再和原32位的二进制数据进行异或运算,得到的结果强转称整数就是Float的hashCode。

String:字符在内存中存的是它的编码,它的hashCode就是拿到每一个字符的编码乘以31的n次幂(比如“abc”就是97*31^2+98*31^1+99*31^0=96354)

  1. 谈谈你对Map集合的理解以及Map中有哪些常用实现类,他们的区别是什么?
Map 是 Java 集合框架中的一员,用于存储一组具有映射关系的键值对。它是基于哈希表实现的,提供了快速的 key-value 存储和查询操作,并且支持各种类型的键和值。
常用的 Map 实现类包括:
1. HashMap:基于哈希表实现的 Map,通过 key 的 hashCode 映射到具体的值存放位置以实现 quick get。HashMap 允许空键空值,非同步,不保证元素的顺序,查找和插入速度快,适用于大部分 key-value 映射,但是不适合于对元素顺序有要求的场景。
2. LinkedHashMap:具有插入顺序的 HashMap,通过双向链表维护插入顺序。LinkedHashMap 允许空键空值,非同步,在迭代 Map 时能保证元素的顺序,适用于需要字典顺序的情况。
3. TreeMap:基于红黑树实现的 Map,它里面的元素是有序的。TreeMap 不允许空键,非同步,保证元素按键顺序排列,查找速度较慢,但是可以通过 TreeMap 提供的排序方法实现自定义排序,适用于对元素顺序有要求的场景。
4. ConcurrentHashMap:线程安全的 HashMap,使用分离锁来实现各个段的并发访问。ConcurrentHashMap 允许空键空值,提供了各种不同级别的并发性,适用于多线程并发访问的场景。
5. Hashtable:早期的 Map 实现,被 ConcurrentHashMap 所取代。Hashtable 不允许空键空值,非同步,安全性较差,对于大部分场景不再推荐使用。
总之,HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap 和 Hashtable 是常用的 Map 实现类。它们在内部实现、同步性、并发性能和不同排序方式等方面存在一些区别,在选择使用时应根据具体的业务需求进行选择。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值