Java-Collections

Java-集合

ArrayList 、Vector 、LinkedList 的区别。

ArrayList:基于动态数组实现,扩容增加为原来的1.5倍,默认容量为10。内部值可以为null。
线程不同步(不支持并发),但是可以使用Collections.synchronziedList() 支持(装饰模式-再不改变原有的基础上进行增强,继承的一种替代关系)。
插入数据时会判断容量,并size 加1。最大数组大小为Integer的最大值减8(有一个额外的大小表示数组的大小)。
Vector:与ArrayLis类似,默认数组大小为10,扩容时扩容为原来的2倍。添加了synchronized具备线程同步(支持并发)。
LinkedList:基于双向链表实现(指向上一个和下一个节点),值可以为null,但是调用时会报出空指针异常。

其中ArrayList的remove() 方法实现中,因为删除机制(删除某个元素时采用的是覆盖前一个元素来达到删除的目的)会导致较多问题,可以采用Iterator 迭代器来删除。

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

ArrayList、Vector:插入和删除都需要重新对数组进行整理。但是查询性能较好,直接通过坐标访问。
LinkedList:插入和删除都比较简单。查询性能较差。

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

hashmap、HashTable、ConcurrentHashMap的数据结构。

Hashmap:数组+链表/数组+链表/红黑树。通过计算key的hashcode值来确定存储在数组的位置。默认大小16,扩容阈值0.75,扩容为原来的2倍。当链表长度大于8时,会转化为红黑树。允许key和value 为null。非线程安全。
Hashmap的容量为何必须是2的幂,因为hashmap通过key的hashcode值定位位置时,需要将hashcode值与容量做一次与操作。(不用除于是因为效率比与操作慢了10倍以上)
多线程扩容引发问题:由于缺乏同步机制,当多个线程同时进行resize操作时,某个线程t所持有的引用next,可能已经被转移到了新桶数组中,那么最后该线程t实际上在对新的桶数组进行transfer操作。
如何实现线程安全:sychronized和volatile修饰的静态标记变量。或者使用Collections.synchronizedMap()进行封装。
hashTable:与HashMap实现原理一致,但是不允许key和value为null,同时是线程安全的,所有方法通过synchronized 加锁。
ConcurrentHashMap:采用分段锁概念,利用CAS(Compare And Swap)和synchronized保证并发安全,基于Segment数组实现(Segment继承了ReentrantLock,是一种可重入锁)。
Segment 内维护了hashEntry数组,默认容量16.负载因子0.75,扩容机制与hashmap类似。
使用volatile 修饰HashEntry 数组保证线程间的一致性。其中HashEntry内部的value也是volatile 修饰的(根据Java 内存的happen before原则,volatile字段的写入操作优先于读取操作,所以get操作可以拿到最新值)

其中在jdk1.8 中ConcurrentHashMap存在若干bug,比如:构造器,其中new ConcurrentHashMap(22,0.75f,1); new ConcurrentHashMap(22).前一个sizeCtl 长度为32,后一个为64。问题出在于新建容器时的计算cap步骤。

还有一个bug,就是computeIfAbsent() 方法,类似于map的getOrDefault() 方法,在未获取到值时,填充一个默认值。问题出在于:AaAa 与 BBBB 的hashcode() 值相同。解决办法,先获取元素,再判断是否为null,为null则放入默认元素,然后再从map中获取默认元素。

hashmap、HashTable、ConcurrentHashMap 的工作原理?

HashMap:基于key的hashcode值与长度做一次与操作,确定下存储的位置,如果entry数组为空就放到数组上,如果不为空就放到当前数组节点的链表的末尾。
在取值时,根据key的hashcode值找到对应的数组坐标,遍历节点上的链表,使用equals() 方法来找到对应的键值对。
HashTable:与HashMap类似。
ConcurrentHashMap:由于采用了分段锁,首先在插入时先定位到segment,之后计算key的hashcode值找到对应的Node节点,遍历节点即可。

hashmap、HashTable、ConcurrentHashMap 扩容原理呢?

Hasmap:resize:创建一个新的Entry空数组,长度为原来的两倍。rehash:遍历旧的entry数组,将所有的Entry重新计算hashcode查询到新数组中。
ConcurrentHashMap:新增节点后元素个数达到8个,由链表转换为红黑树结构。达到容量阈值,进行扩容2倍大小然后重新调整节点位置。

LinkedHashMap 的实现原理?

LinkedHashMap:基于hash表和双向链表保证迭代顺序是插入的顺序。继承于HashMap,允许key和value为null,非同步(线程安全)。

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

x.equals(y)相同,则hashcode必然相同。因为两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
反之,如果hashcode相同,但两个对象不一定equals()。(例子就是程序中存在多于2^32个对象,那么必然有两个一样)

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

List:具有先后顺序,add()按照先后顺序依次加入,也可以根据要插入的坐标值进行插入。Iterator接口遍历,或者get(index) 获取对应下标节点的值。
Set:不允许有重复元素,add()加入元素成功返回true,加入失败返回false。Iterator接口遍历获取元素。
Map:put()放入key和value,不能存储重复的key,根据equals()函数比较而得。get(key)获取对应的value。

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

Set使用equals() 区分不同的元素。==:引用比较,equals()值比较。

HashSet、TreeSet 的底层实现是什么?

HashSet:不包含重复元素的无序集合,允许包含null,但只允许一个null出现。非线程安全,可使用Collections。synchronizedSet()包装后使用。
TreeSet:基于TreeMap的NavigableSet 实现有序集合,使用元素的自然排序对元素进行排序或者根据实例化对象时传入的Compator对象进行排序。
不支持随机遍历,仅支持Iterator遍历。非同步(非线程安全)。不允许出现null元素。

HashSet 和 TreeSet 有什么区别?

HashSet是一个无序的集合,基于HashMap实现;TreeSet是一个有序的集合,基于TreeMap实现。
HashSet集合中允许有null元素,TreeSet集合中不允许有null元素。
HashSet和TreeSet都是非同步!在使用Iterator进行迭代的时候要注意fail-fast。

heap 和 stack 有什么区别?

heap:先进先出。
stack:先进后出。

Java 集合类框架的基本接口有哪些?

两大接口:Collection和Map接口。一个是元素的集合,另一个是键值对集合。Collection:分别有List和Set接口继承,分别是有序元素集合和无序元素集合。Map:由hashMap和HashTable实现。

为什么集合类没有实现 Cloneable 和 Serializable 接口?

Cloneable 和Serializable 应该由具体的实现类来决定是否实现,这些都跟具体的语义和含义有关。

什么是迭代器 (Iterator)?

Iterator 是一种访问集合的方法,用于迭代List和Set集合。迭代器因为创建代价小,被常称为轻量级对象。常用的方法有next()返回下一个元素、hasNext()判断是否存在下一个元素、remove()删除元素。

Iterator 和 ListIterator 的区别是什么?

Iterator可以遍历List和Set,单向遍历。
ListIterator只能遍历List,实现了Iterator接口,可以双向遍历,可以增加、删除元素,获取前一个、后一个元素索引。

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

Array:可以包含基本类型和对象类型。空间大小固定,不能再次申请空间。
ArrayList:只能包含对象类型。空间大小不固定,可以动态增长空间大小。相比Array支持更多的方法和特性。
使用场景:长度固定,优先Array。保存基本数据类型,使用Array。占用内存小,只需随机读写选用Array。其他场景可以选用ArrayList。

Java 集合类框架的最佳实践有哪些?

根据是否存储键值对、是否key排序、是否数据唯一、是否线程安全这些条件进行集合类的选择。比如HashMap、hashSet、ArrayList、LinkedList及其它版本。

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

Comparable:Comparable排序接口,实现了该接口的类可以通过Collections.sort()或者Arrays.sort()进行自动排序。相当于内部比较器。java.util包内。
Compator:Compator比较接口,通过实现该类新建一个比较器控制次序,然后对类进行排序。相当于外部排序器。java.lang包内。

Collection 和 Collections 的区别。

Collection:集合接口,提供了对集合的基本操作的通用接口方法。
Collections:集合类的静态工具方法类。不可实例化。