Java集合相关

1、ArrayList 和 Vector 的区别

ArrayList和Vector底层都是基于可动态改变数组大小的数据结构。最主要区别是Vector是线程安全的,底层所有操作元素的方法都使用了synchronized关键字,而ArrayList则是非线程安全的,因此在效率上ArrayList要好于Vector。扩容时ArrayList会扩容至原来容量的1.5倍,而Vector如果指定了自增量capacityIncrement,则每次容量增加指定的自增量,否则每次扩容至原来的2倍。

2、说说 ArrayList,Vector, LinkedList 的存储性能和特性。

ArrayList底层是基于可动态改变数组大小的数据结构,可以存储所有类型的数据,包括null值,存储的元素是线性存储的并且是可重复的。默认初始化容量是10,每次扩容时,会扩容至原来容量的1.5倍。实现了RandomAccess接口,可通过数组下标进行随机访问,插入和删除元素时,由于需要移动数组元素,因此查询效率较高,插入和删除效率较低。

Vector与ArrayList类似,底层也是基于可动态改变数组大小的数据结构,如果指定了自增量capacityIncrement,则每次扩容时容量增加指定的自增量,否则每次扩容时,容量增加至原来的2倍。Vector是线程安全的,底层操作元素的所有方法都使用了synchronized关键字所修饰,因此效率上不如ArrayList。

LinkedList底层是基于双向链表的数据结构,不支持随机访问,只支持顺序访问,因此查询效率相对较低,插入和删除元素时,只需要移动指针即刻,因此插入和删除效率较高。

3、快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?

快速失败:遍历一个集合时,当一个集合的结构被修改,就会抛出ConcurrentModificationException

安全失败:对集合结构的修改都会在一个复制的集合修改,因此不会抛出ConcurrentModificationException

4、hashmap 的数据结构。

JDK1.7中是采用数组+链表的数据结构;JDK1.8中式采用数组+链表+红黑树的数据结构。

5、HashMap 的工作原理是什么?

HashMap是通过键值对的形式存储对象,通过put和get方法来存储和获取对象。当获取一个对象时,将key/value传递到put方法中,若key为null,则将value存储到table数组下标为0的位置的链表上,然后计算key的哈希值,并结合table数组长度计算要存储的元素的位置。若该位置无链表,则直接存储,否则遍历该链表通过equals方法来比较是否存在相同的key,若存在则覆盖旧值,并直接返回新址。否则将value构造成新节点添加到链表的表头作为头结点。

当获取对象的时,将key传递到get方法,若key为null,则从table数组下标为0的链表中遍历查找,否则计算key的哈希值,并结合数组下标计算要获取的元素的数组下标,接着遍历该位置上的链表通过equals方法进行比较,找到则返回,否则返回null。

HashMap在JDK1.8与JDK1.7有什么区别?

JDK1.8相比1.7来说改动是比较大的

JDK1.8中HashMap是采用数组+链表+红黑树的数据结构

发生哈希冲突时采用链地址法+尾插法+红黑树,当一条链表的长度个数到达8时,会调用转红黑树的方法,判断当前HashMap大小是否小于64,如果小于则直接进行扩容处理,否则链表转为红黑树,当链表个数小于等于6时,红黑树会转换为链表

计算哈希值的时候,JDK1.7用了9次扰动处理(4次位运算+5次异或),JDK1.8中用了2次扰动处理(1次位运算+1次异或)

JDK1.8用Node代替了Entry

拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

二叉查找树在特殊情况下会变成一个线性结构,会造成遍历查找慢。而红黑树属于平衡二叉树、通过左旋、右旋和变色操作来保持平衡

解决哈希冲突的方法:开放地址法、再哈希法,拉链法

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

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

因为我们要找到元素要存放的位置,就需要对数组长度进行取余,得到的余数就是元素要存储的位置。当数组长度length是2的幂次时,hash%length等价于hash & (length-1),而采用二进制位操作&,相比%能够提高运算效率。所以这也就解释了HashMap的长度为什么时2的幂次方。

HashMap在高并发场景下为什么出现死循环

主要原因在于 并发下的Rehash 会造成元素之间会形成一个循环链表。

6、Hashmap 什么时候进行扩容呢?

当HashMap元素大小超过容量*负载因子时,就会发生扩容

7、List、Map、Set 三个接口,存取元素时,各有什么特点?

List以特定索引来存放元素,元素是顺序的,可重复的

Set存放元素是无序的,不重复的,至多可存储一个null值

Map是通过键值对的形式存储元素,键值是不可重复读 ,value可重复,可以至多存储一个null的key,多个null的value

8、Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用 == 还是 equals()? 它们有何区别?

通过equals方法。==主要用来比较两个对象是否是同一对象,比较基本类型时,比较的是指是否相等,比较引用类型时,比较的是两个对象是否是同一对象。equals方法主要用来比较两个对象的内容是相同,如果没有重写Object方法的equals方法,默认使用的就是==操作符。

9、两个对象值相同 (x.equals(y) == true),但却可有不同的 hash code,这句话对不对?

对且不对

如果对象重写了equals方法,那么可能出现equals相同,但是可能会有不同的hashCode

如果没有重写equals方法,那么equals方法默认就是使用的==,因此如果equals相同,那么hashCode值一定相同

10、heap 和 stack 有什么区别。

java的内存分为两类,一类是栈内存,一类是堆内存。栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。

堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。

1.heap是堆,stack是栈。

2.stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用new关键字来分配。

3.stack空间有限,heap的空间是很大的自由区。

11、什么是迭代器 (Iterator)?

迭代器是一种设计模式,是一种通用的遍历集合元素的接口。

用来遍历Collection 集合的元素,且不需要了解集合的底层的数据结构是什么,直接遍历。

该集合有3个方法。分别是hasNext()、next()、remove()。

遍历集合元素过程中不能 调用集合对象的remove方法来删除集合元素,那样会报异常。可以使用迭代器对象的remove()方法来删除集合元素。

还有一个ListIterator接口,但只能用于List集合。它是Iterator接口的升级版,里面除了Iterator含有的功能外,还具有一些其他的功能。

Iterator遍历集合元素时,只能单向遍历,而ListIterator可以双向进行遍历。

12、Iterator 和 ListIterator 的区别是什么?

ListIterator继承于Iterator,除了包含Iterator功能外,还包含了一些其他功能,Iterator遍历集合元素时,只能单向遍历,而ListIterator可以双向进行遍历

13、数组 (Array) 和列表 (ArrayList) 有什么区别?什么时候应该使用 Array 而不是 ArrayList?

Array可以存储基本类型和引用类型,而ArrayList只能存储引用类型;Array大小是固定的,而ArrayList是可动态改变大小的数组。

ArrayList提供了更多的方法特性,比如addAll(),removeAll(),iterator()等。

适用场景

如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。

如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。

14、Comparable 和 Comparator 接口是干什么的?列出它们的区别

Comparable和Comparator接口都是用来对Array或Collection中的对象进行排序的

Comparable接口就是在我们使用Array或Collection进行排序时,自定义类需要实现Comparable接口提供的compareTo方法,它被排序方法所使用,compareTo方法只能基于一个字段进行排序

Comparator接口可以实现两个对象指定字段的比较,可以提供不同的排序算法。

15、HashMap和HashTable区别

HashMap和HashTable底层都是基于数组+链表(可能还有红黑树)的数据结构,区别于HashMap,HashTable是线程安全的,而HashMap是非线程安全的。

HashTable对元素的操作的方法都被synchronized所修饰,因此HashTable效率上不如HashMap

HashTable不允许存放空的key和空的值,而HashMap则允许存放空的key或者空的值

16、有没有有序的Map

TreeMap和LinkedHashMap

TreeMap实现了SortedMap接口,底层采用红黑树的数据结构,能够把保存的数据按照键值排序,默认使用键值升序排序,也可以指定排序比较器comparator

LinkedHashMap继承于HashMap,可以认为是HashMap + LinkedList,区别HashMap,LinkedHashMap保证了元素的迭代顺序,通过指定accessOrder来设置排序模式,true为访问顺序,false为插入顺序

17、HashSet、LinkedHashSet、TreeSet区别

HashSet底层是基于HashMap实现,可以存储至多一个Null值,但是元素是无序的

LinkedHashSet,底层使用了LinkedHashMap,区别于HashSet,LinkedHashSet保证了元素的插入顺序

TreeSet,底层使用了TreeMap,元素以自然顺序排序或提供的指定的排序比较器Comparator

18、ConcurrentHashMap实现原理(基于JDK1.7)及说出1.7和1.8区别?

在JDK1.7中,ConcurrentHashMap使用的是数组+链表的数据结构,采用分段锁的机制来保证线程安全。ConcurrentHash Map由一个Segment数组组成,Segment继承了ReentrantLock,每次加锁时锁住的是一个Segment。默认的并发级别是16,也就是Segment数组的长度,理论上最多同时支持16个线程进行并发写操作。

JDK1.8相比JDK1.7有了很大的变化,使用了数组+链表+红黑树的实现方式,内部大量采用了CAS操作放弃了原有的Segment锁,采用CAS+synchronized的方式来保证并发安全性。锁的粒度降低了。

ConcurrentHashMap的读是否要加锁

get操作之所以没有加锁是由于Node的元素val和指针next都是用volatile修饰的,在多线程环境下线程A修改节点的val或者新增节点对于线程B都是可见的

19、CopyOnWriteArrayList实现原理

CopyOnWriteArrayList基于“写时复制”的思想,内部持有一个transient volatile修饰的Object类型的数组array,并且这个array只能通过getArray和setArray方法进行访问,添加元素的时候内部持有一个ReentrantLock锁,复制出一个新数组并追加元素后,将新数组的引用赋值给array。

优点:支持读多写少的并发场景

缺点:

内存占用问题:写操作时,内存会同时驻扎两个对象的内存

数据一致性问题:CopyOnWriteArrayList只能保证数据的最终一致性,不能保证数据的实时一致性。

你可能感兴趣的:(Java集合相关)