二、java集合常见问题

1.常见的集合有哪些?

Collection:

  • List
    • 代表了有序可以重复的集合,可以直接根据元素的索引来访问;
  • Set
    • 代表无序的不可以重复的集合,只能够根据元素本身来访问
  • Queue
    • 是队列的集合

Map:

  • 代表着一对键值对,根据key来访问value

2.List 、Set和Map 的区别

  • list用索引来存取元素,是有序的,元素允许重复出现,可以插入多个null
  • set根据元素本顺身存取元素,无序的,不允许重复出现,一个null
  • map保存键值对
  • list有数组和链表两种形式;set map容器是基于哈希存储和红黑树两种形式。
  • set基于map实现,set里的元素值就是map的键值、

3.ArrayList 了解吗

  • arraylist的底层是动态的数组,他的容量可以动态的增长,在添加大量元素前,应该使用ensureCapacity操作增加容量。arraylist继承了abstractlist实现了list接口
  • ArrayList扩容的本质是计算出新的扩容数组的size后实例化,默认情况,扩容是原容量的1.5倍

4.怎么在遍历 ArrayList 时移除一个元素?

foreach会导致快速失败的问题,可以使用迭代器的remove方法

5.ArrayList和Vector的区别

  • ArrayList在内存不够的时候会扩容1.5倍,Vector会扩容2倍
  • Vector属于线程安全的,但是操作效率比较低

6.Arraylist 与 LinkedList的区别

  • ArrayList基于动态的数组实现;LinkedList是基于链表实现的
  • 随机的index访问的get和set方法,ArrayList速度优于LinkedList。
  • 新增和删除元素LinkedList优于ArrayList。

7.HashMap

hashmap使用数组+链表+红黑树实现的,链表长度大于8会转换为红黑树,红黑树小于6才转换为链表,防止频繁转换。

8.解决hash冲突的办法有哪些?HashMap用的哪种?

方法:开放定址法,再哈希法,链地址法。hashmap采用链地址法。

  • 开放定址法:
  • 再哈希法:再哈希法提供多个不同的hash函数,当R1=H1(key1)发生冲突时,再计算R2=H2(key1),直到没有冲突为止。 这样做虽然不易产生堆集,但增加了计算的时间。
  • 链地址法:链地址法将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。

9.为什么建议设置HashMap的容量?

HashMap有扩容机制,就是当达到扩容条件时会进行扩容。扩容条件就是当HashMap中的元素个数超过临界值时就会自动扩容

如果我们没有设置初始容量大小,随着元素的不断增加,HashMap会发生多次扩容。而HashMap每次扩容都需要重建hash表,非常影响性能。所以建议开发者在创建HashMap的时候指定初始化容量。

10.put方法流程

  1. 如果table没有初始化就先进行初始化过程
  2. 使用hash算法计算key的索引
  3. 判断索引处有没有存在元素,没有就直接插入
  4. 如果索引处存在元素,则遍历插入,有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入
  5. 链表的数量大于阈值8,就要转换成红黑树的结构
  6. 添加成功后会检查是否需要扩容

11.红黑树的特点

  • 每个节点是红色或者黑色
  • 根节点和叶子节点是黑色的
  • 如果一个节点是红的,那么他的子节点必须是黑色的
  • 从一个节点到该节点的子孙节点的所有路径包含相同的黑节点

12.在解决 hash 冲突的时候,为什么选择先用链表,再转红黑树?

因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。所以,当元素个数小于8个的时候,采用链表结构可以保证查询性能。而当元素个数大于8个的时候并且数组容量大于等于64,会采用红黑树结构。因为红黑树搜索时间复杂度是 O(logn),而链表是 O(n),在n比较大的时候,使用红黑树可以加快查询速度

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

Hash 值的范围值比较大,使用之前需要先对数组的长度取模运算,得到的余数才是元素存放的位置也就是对应的数组下标。

14.HashMap为什么线程不安全

  • 多线程下扩容死循环。
  • 在多线程环境下,会发生数据覆盖的情况。

15.HashMap和HashTable的区别?

  1. HashMap可以接受为null的key和value,key为null的键值对放在下标为0的头结点的链表中,而Hashtable则不行。
  2. HashMap是非线程安全的,HashTable是线程安全的。Jdk1.5提供了ConcurrentHashMap,它是HashTable的替代。
  3. Hashtable很多方法是同步方法,在单线程环境下它比HashMap要慢。
  4. 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

16.LinkedHashMap底层原理?

HashMap是无序的,迭代HashMap所得到元素的顺序并不是它们最初放到HashMap的顺序,即不能保持它们的插入顺序。LinkedHashMap继承于HashMap,是HashMap和LinkedList的融合体,具备两者的特性。每次put操作都会将entry插入到双向链表的尾部。

17.TreeMap

TreeMap是一个比较元素大小的Map集合,会对传入的key进行大小排序。可以使用元素的自然排序,也可以自定义比较器。

特点:

  • TreeMap是有序的key-value集合,通过红黑树实现。根据键的自然顺序进行排序或根据提供的Comparator进行排序。
  • TreeMap继承了AbstractMap,实现了NavigableMap接口,支持一系列的导航方法,给定具体搜索目标,可以返回最接近的匹配项。如floorEntry()、ceilingEntry()分别返回小于等于、大于等于给定键关联的Map.Entry()对象,不存在则返回null。lowerKey()、floorKey、ceilingKey、higherKey()只返回关联的key。

18.HashSet的底层原理

HashSet基于HashMap实现的,放入HashSet中的元素实际上由HashMap的key保存,而value则宝轮一个静态的对象。

19.HashSet、LinkedHashSet 和 TreeSet 的区别

  • HashSet是Set的接口的主要实现类,HashSet的底层是HashMap,线程是不安全的,可以储存null
  • LinkedHashSet是HashSet的子类,能够按照添加的顺序遍历
  • TreeSet底层使用红黑树,按照添加的元素顺序进行遍历,排序的方式可以自定义

20.fail fast

fail-fast是Java的一种错误机制。当多个线程对同一个集合进行操作的时候,就有可能产生。

方法:
在修改的集合内容的地方加上synchronized。

copyonwritearraylist代替arraylist。

21.fail safe

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问,而是先复制原有集合内容,再拷贝的集合上进行遍历。

22.arrayDeque

ArrayDeque实现了双端的队列,内部使用循环数组实现。

  • 在两端添加删除元素高效
  • 根据元素内容查找和删除的效率比较低
  • 没有索引位置的概念

23.哪些集合类是线程安全的?哪些不安全?

安全:

  • Vector:比ArrayList多了同步机制
  • HashTable
  • ConcurrentHashMap
  • Stack:继承vector

不安全:

  • HashMap
  • ArrayList
  • LinkedList
  • HashSet
  • TreeSet
  • TreeMap

24.迭代器Iterator是什么

Iterator模式用同一种逻辑来遍历集合。它可以把访问逻辑从不同类型的集合类中抽象出来,不需要了解集合内部实现便可以遍历集合元素,统一使用Iterator提供的接口去便利。

更加安全,在元素被更改时,会抛异常

hashNext、next、remove

25.Iterator 和 ListIterator 有什么区别?

Listiterator是Iterator的增强版

  • ListIterator遍历可以是逆向的,因为有previous()和hasPrevious()方法,而Iterator不可以。
  • ListIterator有add()方法,可以向List添加对象,而Iterator却不能。
  • ListIterator可以定位当前的索引位置,因为有nextIndex()和previousIndex()方法,而Iterator不可以。
  • ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
  • ListIterator只能用于遍历List及其子类,Iterator可用来遍历所有集合

26.如何让一个集合不能修改

可以采用Collections包下的unmodifiableMap/unmodifiableList/unmodifiableSet方法,通过这个方法返回的集合,是不可以修改的。

不能够使用final修饰

因为final修饰的关键字如果是引用类型的化,则表示地址不能够改变,但是这个引用所指的对象里面的内容还是可以改变的。

27.并发容器

这些容器大部分再java.util.concurrent包中

  • ConcurrentHashMap:线程是安全的HashMap
  • CopyOnWriteArrayList:线程安全的List,适合读多写少的场合
  • ConcurrentLinkedQueue:非阻塞队列,高效的并发,使用链表实现,可以看作一个线程安全的LinkedList
  • BlockingQueue:阻塞队列接口,JDK 内部通过链表、数组等方式实现了这个接口。非常适合用于作为数据共享的通道。
  • ConcurrentSkipListMap: 跳表的实现。使用跳表的数据结构进行快速查找。

具体见网页!!!!

28.ConcurrentHashMap

多线程环境下,使用Hashmap进行put操作会引起死循环,应该使用ConcurrentHashMap

1.8ConcurrentHashMap取消了segment分段锁,采用CAS和synchronized保证线程安全。synchronized只锁定当前链表或红黑二叉树的首节点,锁粒度更小,支持更高的并发量。

29.ConcurrentHashMap 和 Hashtable 的区别?

  • Hashtable通过使用synchronized修饰方法的方式来实现多线程同步,因此,Hashtable的同步会锁住整个数组。在高并发的情况下,性能会非常差。ConcurrentHashMap采用了更细粒度的锁来提高在并发情况下的效率。注:synchronized容器(同步容器)也是通过synchronized关键字来实现线程安全,在使用的时候会对所有的数据加锁。
  • Hashtable默认的大小为11,当达到阈值后,每次按照下面的公式对容量进行扩充:newCapacity = oldCapacity * 2 + 1。ConcurrentHashMap默认大小是16,扩容时容量扩大为原来的2倍

30.CopyOnWrite

Copy-On-Write,写时复制。当我们往容器添加元素时,不直接往容器添加,而是先将当前容器进行复制,复制出一个新的容器,然后往新的容器添加元素,添加完元素之后,再将原容器的引用指向新容器。这样做的好处就是可以对CopyOnWrite容器进行并发的读而不需要加锁,因为当前容器不会被修改。

缺点:

  • 内存占用问题。由于CopyOnWrite的写时复制机制,在进行写操作的时候,内存里会同时驻扎两个对象的内存。
  • CopyOnWrite容器不能保证数据的实时一致性,可能读取到旧数据

31.CopyOnWriteArrayList

CopyOnWriteArrayList是Java并发包中提供的一个并发容器。CopyOnWriteArrayList相当于线程安全的ArrayList,CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素add到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组

CopyOnWriteArrayList中add方法添加的时候是需要加锁的,保证同步,避免了多线程写的时候复制出多个副本。读的时候不需要加锁,如果读的时候有其他线程正在向CopyOnWriteArrayList添加数据,还是可以读到旧的数据。

优点:读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛出ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了

缺点:

一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC;

二是无法保证实时性,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。

32.ConcurrentLinkedQueue

非阻塞队列。高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,通过 CAS 操作实现。如果对队列加锁的成本较高则适合使用无锁的 ConcurrentLinkedQueue 来替代。适合在对性能要求相对较高,同时有多个线程对队列进行读写的场景

非阻塞队列中的几种主要方法:add(E e): 将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常; remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常; offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false; poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null; peek():获取队首元素,若成功,则返回队首元素;否则返回null

33.阻塞队列

阻塞队列是java.util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:

当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;

从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。

阻塞队列和一般的队列的区别就在于:

  1. 多线程支持,多个线程可以安全的访问队列
  2. 阻塞操作,当队列为空的时候,消费线程会阻塞等待队列不为空;当队列满了的时候,生产线程就会阻塞直到队列不满

七种阻塞队列:

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • PriorityBlockingQueue
  • DelayQueue
  • SynchronousQueue
  • LinkedTransferQueue
    • transfer
    • tryTransfer

34.comparable 和 Comparator 的区别

  • comparable接口出自java.lang包,有一个compareTo方法进行排序
    • Comparable 被认为是内比较器,也是自然排序,实现该接口的类,会有自身比较的功能,则依赖compareTo方法的实现

  CompareTo方法的返回值是int,有三种情况:     

      1、比较者大于被比较者,那么返回正整数

      2、比较者等于被比较者,那么返回0

      3、比较者小于被比较者,那么返回负整数

  • Comparator接口实际出自java.util,有一个compare方法用来排序
    • Comparator 被认为是外比较器,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口覆盖compare(T o1, T o2)方法,即可,然后通过这个比较器对类进行比较且排序

  compare(T o1, T o2)方法,int返回值有三种情况:

    1、o1大于o2,返回正整数

    2、o1等于o2,返回0

    3、o1小于o3,返回负整数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值