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方法流程
- 如果table没有初始化就先进行初始化过程
- 使用hash算法计算key的索引
- 判断索引处有没有存在元素,没有就直接插入
- 如果索引处存在元素,则遍历插入,有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入
- 链表的数量大于阈值8,就要转换成红黑树的结构
- 添加成功后会检查是否需要扩容
11.红黑树的特点
- 每个节点是红色或者黑色
- 根节点和叶子节点是黑色的
- 如果一个节点是红的,那么他的子节点必须是黑色的
- 从一个节点到该节点的子孙节点的所有路径包含相同的黑节点
12.在解决 hash 冲突的时候,为什么选择先用链表,再转红黑树?
因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。所以,当元素个数小于8个的时候,采用链表结构可以保证查询性能。而当元素个数大于8个的时候并且数组容量大于等于64,会采用红黑树结构。因为红黑树搜索时间复杂度是 O(logn),而链表是 O(n),在n比较大的时候,使用红黑树可以加快查询速度
13.HashMap 的长度为什么是 2 的幂次方?
Hash 值的范围值比较大,使用之前需要先对数组的长度取模运算,得到的余数才是元素存放的位置也就是对应的数组下标。
14.HashMap为什么线程不安全
- 多线程下扩容死循环。
- 在多线程环境下,会发生数据覆盖的情况。
15.HashMap和HashTable的区别?
- HashMap可以接受为null的key和value,key为null的键值对放在下标为0的头结点的链表中,而Hashtable则不行。
- HashMap是非线程安全的,HashTable是线程安全的。Jdk1.5提供了ConcurrentHashMap,它是HashTable的替代。
- Hashtable很多方法是同步方法,在单线程环境下它比HashMap要慢。
- 哈希值的使用不同,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提供了线程安全的队列访问方式:
当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;
从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。
阻塞队列和一般的队列的区别就在于:
- 多线程支持,多个线程可以安全的访问队列
- 阻塞操作,当队列为空的时候,消费线程会阻塞等待队列不为空;当队列满了的时候,生产线程就会阻塞直到队列不满
七种阻塞队列:
- 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,返回负整数