Java小李备春招|集合

Java小李备春招|集合

Java小李备春招系列文章:
1. Java小李备春招|JVM
2. Java小李备春招|面向对象
3. Java小李备春招|常用API
4. Java小李备春招|异常
5. Java小李备春招|集合
6. Java小李备春招|IO
7. Java小李备春招|计算机网络

  1. Java集合的基础接口有哪些?
    1)Collection接口是最基本的集合接口;
    2)List接口是有序的Collection,由Collection派生出来,使用此接口能够精确的控制每个元素插入的位置,List支持使用索引访问元素,类似于数组。List集合允许存在相同元素(有序可重复);
    3)Set接口是不包含重复元素的无序的Collection(无序无重复);
    4)Map接口没有继承Collection接口,Map提供键(key)值(value)对映射关系,每个Map中不能包含重复的key,每个key只能映射一个value。

  2. 为什么Map接口不继承Collection接口?
    Map提供的是键值对映射,而Collection提供的是一组数据,这就导致了两种接口的数据结构不同,所以Map继承Collection也就毫无意义,而且Map如果继承Collection接口的话还违反了面向对象的接口分离原则。

  3. 刚才你提到了接口分离原则,请简单说一下Java的接口分离原则。
    接口分离原则是指客户类不应该依赖它不需要的接口,也就是说,类间的依赖关系应该建立在最小的接口上。接口隔离原则将非常庞大、臃肿的接口拆分成为更小、更具体的接口,这样客户将只会知道他们所需要的方法。接口隔离原则的目的是降低系统耦合,便于重构、更改和重新部署系统,让客户端依赖的接口尽可能的小。

  4. List接口的主要实现类有哪些?他们的特点是什么?
    List一共有三个实现类:ArrayList、LinkedList和Vector。
    ArrayList 和Vector都是使用数组方式存储数据,都允许直接通过索引快速查找元素,但是插入元素要涉及重新计算数组长度、元素移动等内存操作,所以随机查找和遍历数据快而插入删除数据慢,Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上比ArrayList差。LinkedList使用双向链表实现存储,按索引查找数据需要进行前向或后向遍历,但是插入数据时只需要记录修改项的前后项即可,所以LinkedList很适合数据的随机插入和删除,查找和遍历速度较慢。

  5. ArrayList和LinkedList的区别。
    ArrayList和LinkedList都实现了List接口,他们有以下的不同点:(1)ArrayList是基于数组的数据接口,它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素连接在一起,在这种情况下,查找某个元素的时间复杂度是O(n),比ArrayList慢;(2)相对于ArrayList,LinkedList的插入、删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引;(3)LinkedList比ArrayList更占内存,因为LinkedList为每一个节点都存储了前后节点引用;(4)LinkedList将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按索引查找的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高。

  6. Array和ArrayList有何区别?什么时候更适合用Array?
    区别:(1)Array可以容纳基本类型和对象,而ArrayList只能容纳对象;(2)Array是指定大小的,而ArrayList大小是动态的。
    适用于Array的场景:(1)元素数量确定,且大多数情况下用于存储和遍历操作;(2)对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢;(3)使用多维数组比List<List<>>更方便。

  7. ArrayList是否会产生数组索引越界?
    当并发执行add()时,可能出现数组索引越界。

  8. Set接口的主要实现类有哪些?他们存储数据的特点是什么?
    Set集合的实现类包括HashSet、TreeSet和LinkedHashSet。HashSet底层由哈希表实现,按照哈希值来存取数据。HashSet先通过元素的hashCode方法获取元素哈希值,若两个元素的哈希值相同再使用equals方法进行比较,如果结果为true则证明两个元素为相同元素,如果结果为false就会在相同的哈希值下顺延,也就是会将哈希值相同的元素放在一个哈希桶中;TreeSet 使用元素的自然顺序对元素进行排序,或者实现通过Comparator接口下的compareTo方法进行排序;LinkedHashSet通过维护一条双向链表来解决HashSet不能保持遍历顺序和插入顺序一致的问题。

  9. 在JDK7和JDK8中HashMap分别采用了什么形式存储数据?相较于之前形式的改变有什么优点?有什么缺点?
    在JDK7中采用了数组+链表的存储形式,即总体上看HashMap是一个数组,数组中的每个元素是一个单向链表。通过对key的哈希值& 数组的长度-1得到在数组中位置,如当前位置有元素,则数组当前元素的next属性指向待插入的元素,形成链式结构解决hash冲突。在JDK8中,引入了红黑树结构做优化,当链表元素个数大于等于8时,链表转换成树结构;若桶中链表元素个数小于等于6时,树结构还原成链表。这种改变的优点在于当定位到数组下标向下查询元素时,链表向下遍历查询的时间复杂度为O(n)而红黑树可降低时间复杂度为O(logN);缺点在于使用红黑树时新增元素的成本较高。

  10. 既然HashMap本质上以数组的形式存储数据,那么它的索引是如何计算的?
    当调用HashMap的put()方法时,首先调用hash()方法将键的hashCode值(key.hashCode())二次哈希,再调用indexFor(hash,table.length)方法传入二次哈希的值和当前数组容量,在indexFor方法中将hash值与table.length-1进行&运算得到数组索引。
    这里有几个需要注意的问题:(1)二次哈希的意义在于保证元素初始值尽量不同,降低哈希冲突;(2)数组容量为2n是因为2n-1的二进制形式为n个1,进行&可以保证结果最大程度上的差异,让散列更加均匀;(3)进行&运算得目的在于,得到的结果一定小于length-1且一定为正数;(4)HashMap的初始容量为16,负载因子0.75,阈值为容量*负载因子,当超过阈值时HashMap会使用更大的容量对map中已有的元素重新哈希。

  11. HashMap和HashTable有何不同?
    1)HashMap允许key和value为null,而HashTable不允许。
    2)HashTable是同步的,而HashMap不是。所以HashMap适合单线程环境,HashTable适合多线程环境。
    3)HashMap提供对key的Set集合进行遍历,因此它是fail-fast的,但HashTable提供对key的Enumeration进行遍历,它不支持fail-fast。
    4)HashTable被认为是个遗留的类,不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。

  12. 为什么HashMap允许key和value为null,而HashTable不允许?
    HashTable的 put方法,首先会对put方法中的值value进行null值判断,为null抛出空指针异常,然后会对key值进行Object类的hashCode方法计算hash值,为null值也会抛出空指针异常,故不能在HashTable键、值中放入null值;而HashMap在调用put方法时,其实是调用了putVal方法,而该方法的第一个参数hash值的获取是通过HashMap中自定义的hash方法得到,hash方法中明确指出当key值为null时返回hash值为0,并不会报错,且putVal方法中也没有对value值进行任何约束,故HashMap中的键值都能存放null值。

  13. ConcurrentHashMap的特点。
    ConcurrentHashMap与HashMap相似,但是ConcurrentHashMap支持并发操作。简单理解就是,ConcurrentHashMap是一个Segmet数组,Segment通过继承ReentrantLock加锁,所以每次锁操作锁住的是一个Segment,这样保证每个Segment是线程安全的,也就实现了全局的线程安全。ConcurrentHashMap有16个Segments,所以只要操作分别分布在不同的Segment上,理论上最多可以同时支持16个线程并发操作。这个值可以在初始化的时候设置为其他值,但是一经初始化就不可扩容。

  14. HashMap和ConcurrentHashMap的区别?
    1)HashMap是非线程安全的,ConurrentHashMap是线程安全的。
    2)ConcurrentHashMap将整个Hash桶进行了分段,也就是将整个大的数组分成了若干个小的segment片段,同时每个segment片段上都有锁的存在,因此在插入元素时需要先确定要操作的segment片段,获取锁后在进行相应操作。
    3)ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。

  15. HashTable和ConcurrentHashMap的区别?
    ConcurrentHashMap提供了与HashTable不同的锁机制。HashTable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能有一个线程对其进行操作;而ConcurrentHashMap中则是使用分段锁,一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样就从单线程操作提升至最多可以同时有16个写线程执行,并发性能大幅提升。

  16. 请你说明一下TreeMap的底层实现
    TreeMap 底层使用的是红黑树,也就是一棵自平衡的排序二叉树,这样保证了能够快速检索指定节点。红黑树插入、删除、遍历的时间复杂度都为O(lgN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树因为是排序插入的,可以按照键的值的大小有序输出。

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

  18. 如何决定选用HashMap还是TreeMap?
    对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。但当需要遍历一个有序的key集合时,TreeMap是更好的选择。

  19. Collection和Collections的区别?
    Collection是集合类的上级接口,继承与他的接口主要有Set 和List;Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

  20. 简单说明一下什么是迭代器?迭代器的优点是什么?
    Iterator提供了统一遍历操作Collection元素的统一接口。每个Collection集合都可以通过iterator()方法获取Iterator接口实例, 然后对集合的元素进行迭代操作。注意:在迭代元素的时候不能通过集合的方法修改元素, 否则会抛出ConcurrentModificationException ,但是可以通过Iterator接口中的remove()方法进行删除。所以说,使用迭代器更加线程安全。

  21. Iterator和ListIterator之间有什么区别?
    1)Iterator可以遍历Set和List集合,而ListIterator只能遍历List。
    2)Iterator只能向前遍历,而LIstIterator可以双向遍历。
    3)ListIterator继承Iterator接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

  22. Iterator的fail-fast属性有什么含义?是如何实现的?
    在多线程中,当一个线程修改正在被其他线程所遍历的集合时,会抛出ConcurrentModificationException,并放弃数据修改,以保证数据的安全性。实现原理是:每次创建Iterator对象时,会将当前时刻集合的modCount(用于记录集合修改次数,每修改一次+1)属性值赋给迭代器的expectedModCount属性,在迭代器执行hasNext()/next()方法时,都会对modCount和expectedModCount的值进行比较,如果相同则继续迭代,若不同则抛出异常,产生fail-fast事件。

  23. fail-fast和fail-safe的区别是什么?哪些集合支持fail-fast?哪些支持fail-safe?
    java.util包下面的所有的集合类都是快速失败的,不支持多线程下的并发修改,而java.util.concurrent包下面的所有的类都是安全失败的,可以在多线程下并发使用。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

  24. 为什么Iterator接口没有具体的实现?
    Iterator接口定义了遍历集合的方法,但它的实现则是各个集合实现类的责任。每个能够返回Iterator实例的集合类都有它自己的Iterator实现内部类。这就允许了集合类去选择迭代器是fail-fast还是fail-safe的。

  25. 我们如何从给定集合那里创建一个synchronized的集合?
    我们可以使用Collections.synchronizedCollection(Collection c)根据指定集合来获取一个synchronized(线程安全的)集合。

  26. 当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它?
    在作为参数传递之前,使用Collections.unmodifiableCollection(Collection c)方法创建一个只读集合,这样就使得改变集合的任何操作都会抛出UnsupportedOperationException异常。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值