知识来源沉默王二、小林coding、javaguide

1、ArrayList
list.add("66") list.get(2) list.remove(1) list.set(1,"55")
List<String> list=new ArrayList<>(); 底层是动态数组
添加元素流程:判断是否扩容,无需扩容则直接加元素。否则计算新容量与10比较,取较大值,然后执行grow()方法,扩容至原来的1.5倍,如果还不够则扩到指定容量,调用Arrays.copyOf方法最后回到add函数里面。返回boolean
设置元素流程:检查索引越界,然后替换新值并返回旧值。
删除元素流程:(下标删除)检查索引越界,移动元素使用Syetem.arraycopy(),最后size--,末尾为null让GC回收,返回删除的元素。(元素删除)根据是否为null来分别判断,null用==,其它用equals方法,返回boolean
查找元素流程:根据是否为null来分别判断,null用==,其它用equals方法,返回元素索引,未找到为-1
list.add(1,"66")
list.remove("66")
list.indexOf("66")
list.lastIndexOf("66")
2、LinkedList
LinkedList<String> list = new LinkedList(); 底层是链表实现的
list.add("33") list.addFirst("11") list.addLast("33")
remove(int):删除指定位置的节点remove(Object):删除指定元素的节点- set(0,"33")
- indexOf(Object):查找某个元素所在的位置
- get(int):查找某个位置上的元素
poll()方法用于删除并返回第一个元素peekFirst()方法用于返回但不删除第一个元素。
3、HashMap
put(1,"11")//增加或修改
remove("1")//删除
get(1) //查询
HashMap 是通过拉链法来解决哈希冲突的,也就是当哈希冲突时,会将相同哈希值的键值对通过链表的形式存放起来,采用的是头插法。
hash方法:hash 方法是用来做哈希值优化的,把哈希值右移 16 位之后与原哈希值做异或运算,增大了随机性。其中调用了键的hashcode方法
扩容机制:
当我们往 HashMap 中不断添加元素时,HashMap 会自动进行扩容操作(条件是元素数量达到负载因子(load factor)乘以数组长度时),以保证其存储的元素数量不会超出其容量限制。在进行扩容操作时,HashMap 会先将数组的长度扩大一倍,然后将原来的元素重新散列到新的数组中。由于元素的位置是通过 key 的 hash 和数组长度进行与运算得到的,因此在数组长度扩大后,元素的位置也会发生一些改变。一部分索引不变,另一部分索引为“原索引+旧容量”。
加载因子:
HashMap 的加载因子是指哈希表中填充元素的个数与桶的数量的比值,当元素个数达到负载因子与桶的数量的乘积时,就需要进行扩容。这个值一般选择 0.75,是因为这个值可以在时间和空间成本之间做到一个折中,使得哈希表的性能达到较好的表现。因为容量是2的n次幂,所以与加载因子乘积后最好是整数,而0.75最合适。
4、ArrayDeque、PriorityQueue
ArrayDeque<String> stack = new ArrayDeque<>();
offer("6")
String top = stack.peek(); //获取
String pop = stack.poll(); //出队
PriorityQeque:堆
PriorityQueue<String> queue = new PriorityQueue<>();
peek()
offer("s")
poll()
5、HashSet
HashSet<String> set = new HashSet<>();
set.add("陈清扬");
boolean containsWanger = set.contains("王二");
boolean removeWanger = set.remove("王二");
修改=增加+删除
6、Java面试算法常用api
算法题常用语法(Java篇) - 知乎
Java常用函数总结_java函数-CSDN博客
List<String> rets1 = new ArrayList<>(Arrays.asList(intro));
Arrays.sort()
Arrays.equals()
String[] revised = Arrays.copyOf(intro, 3);
Collections.reverse(list);
Collections.sort(list);
Collections.swap(list, 2,4);
max(Collection coll)
min(Collection coll)
frequency(Collection c, Object o) //返回指定对象出现的频次
集合通用:
toArray()
size()
isEmpty()
contains()
clear()
数组用nums.length 集合用list.size(),String用length()
面试题
1、 Java集合类有哪些
集合主要有两条大的支线:
一条是Collection,由List\Set\Queue组成。
List代表有序可重复的集合,有封装了动态数组的ArrayList,封装了链表的LinkedList
Set代表无序不可重复的集合,主要有HashSet、TreeSet
Queue代表队列,典型的有双端队列ArrayDeque,优先级队列PriorityDeque
第二条线就是Map,表示键值对的集合,主要代表就是HashMap
2、 用过哪些集合类?它们的优劣?
我常用的集合类有 ArrayList、LinkedList、HashMap、LinkedHashMap。
ArrayList 可以看作是一个动态数组,可以在需要时动态扩容数组的容量,只不过需要复制元素到新的数组。访问速度快但是插入和删除元素可能需要移动或者复制元素。
LinkedList 是一个双向链表,适合频繁的插入和删除操作。缺点是访问元素时需要遍历链表。
HashMap 是一个基于哈希表的键值对集合。可以根据键的哈希值快速查找到值,但有可能会发生哈希冲突,并且不保留键值对的插入顺序。
LinkedHashMap 在 HashMap 的基础上增加了一个双向链表来保持键值对的插入顺序
3、队列和栈说说?有什么区别?
队列是先进先出,栈是先进后出的
4、哪些是线程安全的容器,哪些不安全?(后续补充
Vector:线程安全的动态数组,其内部方法基本都经过synchronized修饰
Hashtable(不推荐):线程安全的哈希表,HashTable 的加锁方法是给每个方法加上 synchronized 关键字,不过现在推荐使用ConcurrentHashMap
还有JUC里有很多线程安全的容器:
ConcurrentHashMap:1.7使用了分段锁,而1.8 中取消了 Segment 分段锁,采用 CAS + synchronized 来保证并发安全性,使用了拉链法存放冲突节点,当冲突节点超过8时转为红黑树
CopyOnWriteArraySet:是线程安全的Set实现,它是线程安全的无序的集合
CopyOnWriteArrayList:它是 ArrayList 的线程安全的变体,其中所有写操作(add,set等)都通过对底层数组进行全新复制来实现,允许存储 null 元素。
ArrayList、LinkedList、HashSet、HashMap: 这些集合类是非线程安全的。在多线程环境中,如果没有适当的同步措施,对这些集合的并发操作可能导致不确定的结果。
5、ArrayList和Array有什么区别
1、数组创建时必须指定大小且不能更改,而ArrayList是动态数组实现的,会自动扩容
2、数组不支持泛型,ArrayList支持泛型
3、数组元素可以为基本类型也可以为对象,但ArrayList只能为对象
4、ArrayList有丰富的增删改查的方法,而数组没有
6、ArrayList和LinkedList区别
1、ArrayList是基于数组实现的,LinkedList是基于链表实现的
2、ArrayList实现了RamdomAccess接口,支持随机访问,查找复杂度为O(1),适用于频繁访问读取的场景
而LinkedList不支持随机访问,因为他是双向链表,插入删除效率为O(1),使用于频繁的增删场景
3、ArrayList是空间占用少,使用的是连续的内存空间,而LinkedList包含了节点的引用,占用会更多。,一般而言ArrayList性能会更加高一些
7、ArrayList扩容机制
因为它底层是基于数组实现的,所以没添加元素时它还是个空数组,当添加第一个元素时,默认初始化容量为10.
当往ArrayList中添加元素时,如果超过当前容量的限制则会进行扩容(如果已经达到了Integer,MAX_VALUE则抛出异常)。扩容是通过一个grow方法,扩容后新数组的长度是原来的1.5倍,如果1.5倍不够,则直接扩容到当前所需的大小。最后再把原数组的值拷贝到新数组中。
8、有哪几种实现ArrayList线程安全的方法?
常用的有两种
1、使用Collections的synchronizedList,会返回一个线程安全的集合,其内部是通过synchronized加锁来实现的
2、使用JUC的CopyOnWriteArrayList,使用写时复制技术,每当对列表进行修改时,都会创建一个新副本,这个新副本会替换旧的列表,而对旧列表的所有读取操作仍然在原有的列表上进行,这样并发读时无需加锁就实现了线程安全。
9、ArrayList和Vector的区别
Vector是线程安全的,是1.0时期的遗留类,现在基本已经不使用了。所有方法都使用synchronized进行同步,单线程环境效率很低。
ArrayList是1.2时期引入的,不支持多线程安全,但在单线程下效率很高
10、Map接口有哪些实现类
比较常用的有 HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap。
如果无需排序则使用HashMap,因为它的性能最好。
如果考虑到多线程安全的问题则使用ConcurrentHashMap,使用了分段锁和CAS机制,性能好于Hashtable
如果考虑到顺序则可以用LinkedhashMap,因为它额外维护了一个双向链表记录插入和访问顺序。
如果需要范围查询按自定义顺序排列则可以用TreeMap,因为它是基于红黑树实现的。
11、详细说说HashMap及其底层原理?
HashMap是将数据以键值对的形式存储的,是线程不安全的。
jdk7是使用数组+链表来实现的,Hash冲突时会使用拉链法将冲突元素放进一个链表中。
jdk8引入了红黑树,链表长度超过8会将链表转换为红黑树,具有更好的性能。
HashMap 的初始容量是 16,如果传入的容量参数不为2的幂次方,则会增大到2的幂次方
随着元素的不断添加,HashMap 就需要进行扩容,阈值是capacity * loadFactor,capacity 为容量,loadFactor 为负载因子,默认为 0.75
12、了解红黑树吗?简单说说
红黑树是一种自平衡的二叉查找树,每个节点只能是红色或黑色其中一种。其中根和叶子节点必须是黑色。从任一节点到叶子节点的简单路径都包含相同数目的黑色节点。红色节点的子节点一定是黑色。
13、为什么超过8会变为红黑树小于6变为链表?
因为它链表节点数量遵从泊松分布,当超过8时概率小于百万分之一,然后才转换为红黑树。如果数量变少的话使用链表更加方便,而如果7就转换的话会产生不小开销,甚至容易产生链表与红黑树的不断转换你。选择6的话是兼顾时间和空间比较合适的数字。
14、为什么使用红黑树,不使用二叉搜索树和AVL树?
二叉树容易出现极端情况,比如插入的数据是有序的,那么二叉树就会退化成链表,查询效率就会变成 O(n)
而AVL树每个节点的左右子树的高度最多相差 1,这种高度的平衡保证了极佳的查找效率,但在进行插入和删除操作时,可能需要频繁地进行旋转来维持树的平衡,维护成本更高
使用红黑树更像是一种折中的方案,查找插入删除的效率都是O(logN)
15、HashMap的put流程
先判断数组是否为空,为空则进行初始化。
然后计算哈希值,(n-1)&hash计算下标位置,构造Node节点放入
如果发生哈希冲突则判断是否为同一个key,如果key不同就要根据数据结构放入节点
如果是红黑树就构造树形节点插进去,链表的化就是Node节点插进去,这里看看是否需要转为红黑树。
最后判断节点数是否大于阈值,大于则扩容为原数组的两倍。
16、为什么hashMap的容量是2的幂次方?
因为hashmap计算下标使用hash&(n-1),n为数组大小,n-1之后恰好产生低位全是1的掩码,保证能很好利用容量空间,并尽量的均匀分布。
(计算hash时利用高16位与低16位异或运算)
实际这里hashmap是将取模运算优化成了位运算,而容量只有为2的幂次方时,两者结果才一致,由于位运算比取模运算快,所以采用位运算+2的幂次方来完美替代取模运算
17、 Hash冲突有哪些解决方式?
有线性探测法:如果发生冲突则顺序查看该下标的下一个位置,直到该下标未被使用
二次探测法:发生冲突则交替变化正负x的平方移动,x从1开始递增。
伪随机探测法:预先生成一个伪随机序列,根据序列的值来进行移动
最后还有链地址法,HashMap就是基于这种方法实现的,冲突的话会放在对应下标的链表上。
这里冲突的判断方式是先判断hashcode,再判断equals,如果都一样则认为key一样,更新value
18、HashMap的扩容机制说说?
jdk1.8中扩容会先生成新数组,其容量是原来的两倍,然后遍历旧哈希表元素
如果是链表的话,则重新计算下标放入新数组中,放置的结果等效于hash&(n-1),n为新的容量大小。
如果是红黑树的话,会遍历红黑树计算出新的下标位置。
如果该位置下元素超过8则生成新的红黑树放进去。
如果没超过8则生成一个链表将元素放进去
最后将新数组赋值给HashMap的table属性
19、负载因子是多少?为什么用这个数?
负载因子是0.75,当HashMap里元素的数量超过容量*负载因子时会发生扩容至原来的2倍。
负载因子如果太低,比如0.5则会浪费很多空间,如果是0.9则会发生太多冲突导致性能下降。
0.75 是 JDK 作者经过大量验证后得出的最优解,能够最大限度减少 rehash 的次数。
而且由于容量是2的幂,所以算出来的数恰好都为整数。虽然0.625,0.875也能整除,但折中考虑0.75更加恰当
20、jdk8对HashMap做了哪些优化
1、底层数据结构由数组+链表转为数组+链表+红黑树
2、链表的插入方式由头插法改为尾插法,能不改变链表的顺序
3、扩容的时机由插入时判断改为插入后判断,避免了覆盖旧值时不必要的扩容问题
4、hash算法进行了优化,原来是多次移位和异或实现,jdk8则是高16位与低16位异或实现
21、HashMap是线程安全的吗?
不是线程安全的,多个线程同时读写时可能会出现并发修改问题。而且它的一些操作不是原子性的,在多线程下可能会出现竟态条件。
比如Jdk7里会出现死锁问题,因为多线程操作HashMap并触发扩容时,可能会形成环形链表,后续遍历链表则会发生死循环。
jdk虽然使用头插法解决了死锁问题,但并发修改导致的数据异常依然没有解决
22、HashMap如何实现线程安全?
1、使用Collections下的synchronizedMap来创建,返回一个同步的Map包装器,所有的Map操作都是同步的。内部是通过 synchronized 对象锁来保证线程安全的
2、使用ConcurrentHashMap,使用了分段锁机制,允许多个线程同时读,提高并发性能。
使用了CAS和synchronized来保证线程安全
3、自己使用显式的锁,比如ReentrantLock来保证线程安全
23、 讲讲HashMap和TreeMap
①、HashMap 是基于数组+链表+红黑树实现的,put 元素的时候会先计算 key 的哈希值,然后通过哈希值计算出元素在数组中的存放下标,然后将元素插入到指定的位置,如果发生哈希冲突,会使用链表来解决,如果链表长度大于 8,会转换为红黑树。
②、TreeMap 是基于红黑树实现的,put 元素的时候会先判断根节点是否为空,如果为空,直接插入到根节点,如果不为空,会通过 key 的比较器来判断元素应该插入到左子树还是右子树。
在没有发生哈希冲突的情况下,HashMap 的查找效率是
O(1)。适用于查找操作比较频繁的场景。TreeMap 的查找效率是
O(logn)。并且保证了元素的顺序,因此适用于需要大量范围查找或者有序遍历的场景。
24、讲讲HashMap和Hashtable
1、 Hashtable 是同步的,即它的方法是线程安全的。这是通过在每个方法上添加同步关键字来实现的,而HashMap 不是同步的,因此它不保证在多线程环境中的线程安全性。
2、Hashtable 不允许键或值为 null。 HashMap 允许键和值都为 null
3、现在HashTable已经不常用了,一般考虑线程安全都会使用ConcurrentHashMap
25、讲讲HashSet的底层实现
HashSet 是由 HashMap 实现的,只不过值由一个固定的 Object 对象填充,而键用于操作。
实际上HashSet并不常用,如果需要去重会考虑使用它,否则会用HashMap或ArrayList来替代它
26、HashSet和ArrayList区别
- ArrayList 是基于动态数组实现的,HashSet 是基于 HashMap 实现的。
- ArrayList 允许重复元素和 null 值。HashSet 保证每个元素唯一,不允许重复元素,基于元素的 hashCode 和 equals 方法来确定元素的唯一性。
- ArrayList 保持元素的插入顺序,可以通过索引访问元素;HashSet 不保证元素的顺序,元素的存储顺序依赖于哈希算法,并且可能随着元素的添加或删除而改变。
27、HashMap和HashSet的区别?
1、HashMap 使用键值对的方式存储数据,通过哈希表实现。 HashSet 实际上是基于 HashMap 实现的,它只使用了 HashMap 的键部分,将值部分设置为一个固定的常量。
2、HashMap 用于存储键值对,其中每个键都唯一,每个键关联一个值。 HashSet 用于存储唯一的元素,不允许重复。
28、HashMap和ConcurrentHashMap区别
1、HashMap 不是线程安全的。在多线程环境中,如果同时进行读写操作可能会导致数据不一致。 ConcurrentHashMap 是线程安全的,它使用了分段锁的机制,将整个数据结构分成多个段,每个段都有自己的锁。这样不同的线程可以同时访问不同的段,提高并发性能。
2、HashMap 在实现上没有明确的同步机制,需要在外部进行同步,例如通过使用 Collections.synchronizedMap() 方法。
3、在单线程或低并发环境下,HashMap 的性能会比 ConcurrentHashMap 稍好,因为 ConcurrentHashMap 需要维护额外的并发控制。 在高并发情况下,ConcurrentHashMap 的性能通常更好,因为它能够更有效地支持并发访问。
高频自测:
- Java的集合类有哪些
- 哪些是线程安全的?哪些是线程不安全的?
- ArrayList 和 Array 有什么区别?
- ArrayList 和 LinkedList 的区别是什么?底层实现是怎么样的?
- ArrayList 扩容机制
- Map 接口有哪些实现类
- Java中的HashMap了解吗?HashMap 的底层实现 【重要】
- Hash 冲突有什么解决方式? HashMap 是如何解决 hash 冲突的
- HashMap 的 put 方法流程
- HashMap 的扩容机制
- HashMap 为什么是线程不安全的? 如何实现线程安全
- concurrentHashMap 如何保证线程安全
- HashSet 和 HashMap 和 HashTable 的区别
- HashMap和ConcurrentHashMap