Java容器
Arraylist 与 LinkedList 区别?
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- null存储
- 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构
- 插入和删除是否受元素位置的影响:
-
- ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
- LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响(add(E e)、addFirst(E e)、addLast(E e)、removeFirst() 、 removeLast()),近似 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element),remove(Object o)) 时间复杂度近似为 O(n) ,因为需要先移动到指定位置再插入。
- 是否支持快速随机访问: LinkedList 不支持,而 ArrayList 支持。内存空间占用: ArrayList 的空 间浪费主 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费每一个元素都要存放直接后继和直接前驱以及数据
以我自己的经验,当然我也测试过,如果只是要实现一个列表的功能,也就是加强版数组吧,使用ArrayList是效率比较高的,因为占用空间小、支持随机访问,当然我们想象中链表在增删元素方面有优势,因为只需要增删指针而不用像Array一样挨个往后挪,但是链表不支持随机访问,增删前得先挨个挨个往后找到位置,尤其是在表一半往后的地方,LinkedList效率是很差的。当然如果要实现队列或者栈,双向链表还是非常有优势的。
Arrays.asList既不是ArrayList也不是LinkedList,而仅仅是把数组进行了包装,不能添加不能删除,本质还是一个数组。可以使用ArrayList(Arrays.asList(T[] a)
ArrayDeque 与 LinkedList 的区别
ArrayDeque 和 LinkedList 都实现了 Deque 接口,两者都具有队列的功能
ArrayDeque 是基于数组和双指针来实现,而 LinkedList 则通过链表来实现。
- ArrayDeque 不支持存储 NULL,但 LinkedList 支持。
- ArrayDeque 插入时可能存在扩容过程, 不过均摊后的插入操作依然为 O(1)。虽然 LinkedList 不需要扩容,但是每次插入数据时均需要申请新的堆空间,均摊性能相比更慢。
从性能的角度上,选用 ArrayDeque 来实现队列要比 LinkedList 更好。此外,ArrayDeque 也可以用于实现栈。
ArrayDeque的增删都是通过移动头尾指针实现的,若数组容量不够,则进行double扩容。容量总是为2的幂次。在头尾指针移动的时候,使用取余(数组长度-1再相与)的方式在数组上循环移动。
Map和Set:Set基于Map实现。
- HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)且数组长度大于64(如果小于 64,那么先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。线程不安全,ConcurrentHashMap
- LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
- HashTable:和 HashMap 类似,效率更低,但它是线程安全的。已淘汰,使用 ConcurrentHashMap 来支持线程安全,效率会更高,因为引入了分段锁。无红黑树
- TreeMap:基于红黑树,元素有序,可自定义排序,访问时间为o(logn)
- TreeSet: 基于TreeMap实现,可自定义排序。
- LinkedHashSet:内部是通过 LinkedHashMap 来实现的,唯一性,元素的增删满足FIFO
HashMap 和 Hashtable 的区别
- 线程是否安全: HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!
- 效率: 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。
- 对 Null key 和 Null value 的支持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
- 初始容量大小和每次扩充容量大小的不同 : ① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用给定值,而 HashMap 会将其扩充为 2 的幂次方大小。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小
- 底层数据结构: 红黑树,table没有
HashMap 的长度为什么是 2 的幂次方
实际上在哈希表中我们使用的并不是直接的哈希值,因为自带的哈希值是分布于-2^31, 2^31-1的区间的,因此使用哈希值对数组长度进行取余,这样哈希值就能分布在数组长度内,而如果数组的长度是2的幂次方的话,取余运算就等价于长度减1在相与的运算,大大提升计算哈希值的速度。
HashMap 和 TreeMap 区别
TreeMap 和HashMap 都是Map的实现方式 ,但TreeMap还实现了NavigableMap接口和SortedMap 接口,可以对元素进行搜索和排序。
底层实现方式不同
HashSet 如何检查重复
首先计算hashcode 若hashcode存在相同 则调用equal,因为hashcode相同仅仅是equal的必要不充分条件。
HashSet在底层为什么要把value设为一个Object?而不存null?
因为HashSet的add和remove方法底层是HashMap的方法,HashMap的put方法若已有这个key会返回这个value,若没有该key会返回null,而HashSet的add方法在包装put方法时,会返回put方法返回值是否为null,如果HashSet存得value是null,那么无论底层map中是否已经有了这个key,put方法都会返回null,add方法也就都会返回true,也就无法判断是否添加成功了。
HashMap的底层实现(HashMap并不是直接用元素的hashcode)
是通过链表数组的方式去实现的,元素首先通过Key值进行哈希值的计算,然后将哈希值通过哈希map的hash函数也就是扰动函数处理之后能够减少哈希冲突,如果hash相同,就比较2个元素的Key值,如果相同那么就直接覆盖,如果不同就将该元素加入到下挂链表中,当然现在hashmap现在的链表如果长度大于默认阈值8时且当前数组长度小于64,否则会进行数组扩容,将会被转化为红黑树进行效率更高的存取。
ConcurrentHashMap:
不同于直接用sync将HashMap上锁的HashTable,Concurrent采用了分段锁。也就是将数据分段,在写数据时,一个线程只会锁定具体要写的一段数据,而其他线程想要获取这个对象则不会被阻塞,可以同时操作对该对象的其他段。读取数据不加锁。
Concurrent在内部采用了Segment链表数组的结构,一个Segment中存放若干HashEntry,类似于一个HashMap,是一个链表数组,所以在访问元素时,首先要经过一次Hash,定位到Segment,第二次Hash才定位到Value。
JDK1.8后 因为synchronized优化,Concurrent底层使用node数组类似hashmap,使用Synchronized+CAS来实现线程安全,只锁头结点,相当于上锁粒度更细。而且1.8后,Concurrent也在链表过长时转为红黑树。
List.toArray(T[] a): size表示将List复制到a中,如果大小足够,就会把list内容装进该数组,并在末尾添加一个null,说明list元素已经添加完了;若不够,将另外创建一个足够大小的数组,并且将其返回给接收的数组。而且不支持基本数据类型,只能使用引用数据类型。