2024年全套Java面试题,整理好了!

String的字符串为什么不可被变

 因为String底层char类型的value数组是private final修饰的:

  • final:导致value不能指向新数组(但无法保证value这个引用变量指向的真实数组不可变)
  • private:导致value指向的原数组不可变。私有成员变量,且没对外暴露任何修改value的方法,所以value这个引用变量指向的真实数组不可变。

JDK9开始,为了节省内存,进而减少垃圾回收次数,String底层由char数组改成了byte[]。 

不可变的优点:因为压根不会被改,所以线程安全、节省空间、效率高。

 String源码有用到什么设计模式?

享元设计模式:当一个系统中存在大量重复对象,若这些重复的对象是不可变对象,就能利用享元模式将对象设计成享元,在内存中只保留一份实例,供引用。这就减少内存中对象的数量,最终节省内存。享元模式是结构型设计模式,用于对象的创建。

享元模式和单例模式区别
  • 单例模式是类级别的,一个类只能有一个对象实例;
  • 享元模式是对象级别的,一个类可以有多个不同的对象实例,主要为了解决重复对象问题,池技术一般是用享元模式实现的,例如字符串常量池、数据库连接池、缓冲池。
  • 单例模式可以看作是享元模式的一种特立,享元模式只有一种对象实例时就是单例,有多种不重复的对象实例时就是享元。享元模式主要是为了节约内存空间,提高系统性能,而单例模式主要为了可以共享数据;

直接量是指在程序中通过源代码直接给出的值

String类是Java最常用的API,它包含了大量处理字符串的方法,比较常用的有:

  • - char charAt(int index):返回指定索引处的字符;
  • - String substring(int beginIndex, int endIndex):从此字符串中截取出一部分子字符串;
  • - String[] split(String regex):以指定的规则将此字符串分割成数组;
  • - String trim():删除字符串前导和后置的空格;
  • - int indexOf(String str):返回子串在此字符串首次出现的索引;
  • - int lastIndexOf(String str):返回子串在此字符串最后出现的索引;
  • - boolean startsWith(String prefix):判断此字符串是否以指定的前缀开头;
  • - boolean endsWith(String suffix):判断此字符串是否以指定的后缀结尾;
  •  - String toUpperCase():将此字符串中所有的字符大写;
  •  - String toLowerCase():将此字符串中所有的字符小写;
  •  - String replaceFirst(String regex, String replacement):用指定字符串替换第一个匹配的子串;
  •  - String replaceAll(String regex, String replacement):用指定字符串替换所有的匹配的子串。

 验证常量池:

 
 
  1. //比较地址

  2. //只要new,就在堆内存开辟空间。直接赋值字符串在常量池里。

  3. //常量池里无“hello”对象,创建“hello”对象,str1指向常量池“hello”对象。

  4. String str1 = "hello";

  5. //先检查字符串常量池中有没有"hello",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"hello";

  6. //常量池里有“hello”对象,str2直接指向常量池“hello”对象。

  7. String str2 = "hello";

  8. //堆中new创建了一个对象。假如“hello”在常量池中不存在,Jvm还会常量池中创建这个对象“hello”。

  9. String str3 = new String("hello");

  10. String str4 = new String("hello");

  11. //下面输出true,因为str1和str2指向的是常量池中的同一个内存地址

  12. System.out.println(str1 == str2);

  13. //下面输出false,str1常量池旧地址,str3是new出的新对象,指向一个全新的地址

  14. System.out.println(str1 == str3);

  15. //下面输出false,因为它们引用不同

  16. System.out.println(str4 == str3);

  17. //比较内容

  18. //下面输出true,因为String类的equals方法重写过,比较的是字符串值

  19. System.out.println(str4.equals(str3));

结果:

3.3.new String("abc")创建了几个字符串对象?

答案、原理 

答案:一个或两个。

首先,new string 这边由于 new 关键字,所以这边肯定会在堆中直接创建一个字符串对象。

其次,如果字符串常量池中不存在 "abc"(通过equals比较)这个字符串的引用,则会在字符串常量池中创建一个字符串对象。如果已存在则不创建。注意这边说的在字符串常量池创建对象,最终对象还是在堆中创建,字符串常量池只放引用。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

3.4.String、StringBuffer、Stringbuilder有什么区别

得分点

是否可变、复用率、效率、线程安全问题

标准回答

String:不可变字符序列,效率低,但是复用率高、线程安全。

不可变是指String对象创建之后,直到这个对象销毁为止,对象中的字符序列都不能被改变。

复用率高是指String类型对象创建出来后归常量池管,可以随时从常量池调用同一个String对象。StringBuffer和StringBuider在创建对象后一般要转化成String对象才调用。

StringBuffer和StringBuilderStringBuffer和Stringbuilder都是字符序列可变的字符串,方法也一样,有共同的父类AbstractStringBuilder。 

  • StringBuffer:可变字符序列、效率较高(增删)、线程安全
  • StringBuilder:可变字符序列、效率最高、线程不安全

Java中提供了String,StringBuffer两个类来封装字符串,并且提供了一系列方法来操作字符串对象。

String是一个不可变类,也就是说,一个String对象创建之后,直到这个对象销毁为止,对象中的字符序列都不能被改变。

StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer对象被创建之后,我们可以通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()、等方法来改变这个字符串对象的字符序列。当通过StringBuffer得到期待中字符序列的字符串时,就可以通过toString()方法将其转换为String对象。

StringBuilder类是JDK1.5中新增的类,他也代表了字符串对象。和StringBuffer类相比,它们有共同的父类`AbstractStringBuilder`,二者无论是构造器还是方法都基本相同,不同的一点是,StringBuilder没有考虑线程安全问题,也正因如此,StringBuilder比StringBuffer性能略高。因此,如果是在单线程下操作大量数据,应优先使用StringBuilder类;如果是在多线程下操作大量数据,应优先使用StringBuilder类。

四、集合 

4.1.请说说你对Java集合的了解

得分点

Set、List、Quque、Map、Collection接口、线程安全的集合

标准回答

Java中的集合类分为4大类,分别由4个接口来代表,它们是Set、List、Queue、Map。其中,Set、List、Queue接口都继承自Collection接口,Map接口不继承自其他接口。

Set代表无序的、元素不可重复的集合。

List代表有序的、元素可以重复的集合。有序说的是元素顺序直接由插入顺序决定。

Queue代表先进先出(FIFO)的队列。

Map代表具有映射关系(key-value)的集合。

Java提供了众多集合的实现类,它们都是这些接口的直接或间接的实现类,其中比较常用的有:HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap、TreeMap等。这些集合都是线程不安全的。

线程安全的集合:

  1. Collections工具类:Collections工具类的synchronizedXxx()方法,将ArrayList等集合类包装成线程安全的集合类。例如List<String> synchronizedList = Collections.synchronizedList(list);
  2. 古老api:java.util包下性能差的古老api,如Vector、Hashtable,它们在JDK1就出现了,不推荐使用,因为线程安全的方案不成熟,性能差。
  3. 降低锁粒度的并发容器:JUC包下Concurrent开头的、以降低锁粒度来提高并发性能的容器,如ConcurrentHashMap。适用于读写操作都很频繁的场景。
  4. 复制技术实现的并发容器:JUC包下以CopyOnWrite开头的、采用写时写入时复制技术实现的并发容器,如CopyOnWriteArrayList。写操作时,先将当前数组进行一次复制,对复制后的数组进行操作,操作完成后再将原来的数组引用指向复制后的数组。避免了并发修改同一数组的线程安全问题。适用于读操作比写操作频繁且数据量不大的场景。适用于读操作远多于写操作的场景。
List list = Collections.synchronizedList(new ArrayList());

上面所说的集合类的接口或实现,都位于java.util包下,这些实现大多数都是非线程安全的。虽然非线程安全,但是这些类的性能较好。如果需要使用线程安全的集合类,则可以利用Collections工具类,该工具类提供的synchronizedXxx()方法,可以将这些集合类包装成线程安全的集合类。

java.util包下的集合类中,也有少数的线程安全的集合类,例如Vector、Hashtable,它们都是非常古老的API。虽然它们是线程安全的,但是性能很差,已经不推荐使用了。

从JDK1.5开始,并发包下新增了大量高效的并发的容器,这些容器按照实现机制可以分为三类。

第一类是以降低锁粒度来提高并发性能的容器,它们的类名以Concurrent开头,如ConcurrentHashMap。

第二类是采用写时复制技术实现的并发容器,它们的类名以CopyOnWrite开头,如CopyOnWriteArrayList。

第三类是采用Lock实现的阻塞队列,内部创建两个Condition分别用于生产者和消费者的等待,这些类都实现了BlockingQueue接口,如ArrayBlockingQueue。

4.2.请你说说List与Set的区别

得分点

Collection接口、有序性和重复性、TreeSet有序

标准回答 

List和Set都是Collection接口的子接口,它们的主要区别在于元素的有序性和重复性

List代表有序可以重复的集合,集合中每个元素都有对应的顺序索引,它默认按元素的添加顺序设置元素的索引,并且可以通过索引来访问指定位置的集合元素。另外,List允许使用重复元素。

Set代表无序不可重复的集合,无序是指存取顺不是按照添加顺序。Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set,则会引发失败,添加方法将会返回false。通过hashCode值来判断重复元素。

加分回答-TreeSet支持自然有序和定制排序

虽然Set代表无序的集合,但是它有支持排序的实现类,即TreeSet。TreeSet可以确保集合元素处于排序状态,并支持自然排序和定制排序两种排序方式,它的底层是由TreeMap实现的。TreeSet也是非线程安全的,但是它内部元素的值不能为null。

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

4.3.说说你对ArrayList的理解

得分点

数组实现、默认容量10、每次扩容1.5倍、缩容、迭代器ListIterator

标准回答

数组实现:

ArrayList是基于数组实现的,它的内部封装了一个Object[]数组。 通过默认构造器创建容器时,该数组先被初始化为空数组,之后在首次添加数据时再将其初始化成长度为10的数组。我们也可以使用有参构造器来创建容器,并通过参数来显式指定数组的容量,届时该数组被初始化为指定容量的数组。

每次扩容1.5倍:

如果向ArrayList中添加数据会造成超出数组长度限制,则会触发自动扩容,然后再添加数据。扩容就是数组拷贝,将旧数组中的数据拷贝到新数组里,而新数组的长度为原来长度的1.5倍

手动缩容:

ArrayList支持缩容,但不会自动缩容,即便是ArrayList中只剩下少量数据时也不会主动缩容。如果我们希望缩减ArrayList的容量,则需要自己调用它的trimToSize()方法,届时数组将按照元素的实际个数进行缩减,底层也是通过创建新数组拷贝实现的。

迭代器ListIterator:

Set、List、Queue都是Collection的子接口,它们都继承了父接口的iterator()方法,从而具备了迭代的能力。Map使用迭代器必须通过先entrySet()转为Set,然后再使用迭代器或for遍历。

但是,相比于另外两个接口,List还单独提供了listIterator()方法,增强了迭代能力。iterator()方法返回Iterator迭代器,listIterator()方法返回ListIterator迭代器,并且ListIterator是Iterator的子接口。ListIterator在Iterator的基础上,增加了listIterator.previous()向前遍历的支持,增加了listIterator.set()在迭代过程中修改数据的支持。

排序方法:

  • Collections工具类的sort()方法:Collections.sort(list);
  • stream流:list.stream().sort();
  • 比较器:list.sort(new Comparator<Integer>() {})
  • 手写排序:冒泡排序、选择排序、插入排序、二分法排序、快速排序、堆排序。

entrySet() 

 
 
  1. HashMap<String,String> map=new HashMap<String,String>();

  2. map.put("aaa","bbb");map.put("cc","dd");map.put("e","f");

  3. Set<Map.Entry<String,String>> set=map.entrySet();

  4. for(Map.Entry<String,String> i:set){

  5. System.out.println(i.getKey()+i.getValue());

  6. }

listIterator()

 
 
  1. List<String> list = new ArrayList<String>();

  2. list.add(1);

  3. //迭代器

  4. Iterator<String> it=list.iterator();

  5. while(it.hasNext()) System.out.println(it.next());

  6. // 使用ListIterator向前遍历并修改元素

  7. ListIterator<Integer> listIterator = list.listIterator(list.size());

  8. while (listIterator.hasPrevious()) {

  9. int num = listIterator.previous();

  10. listIterator.set(num + 1);

  11. }

4.4.请你说说ArrayList和LinkedList的区别

得分点

数据结构(数组和链表)、访问增删效率、时间复杂度、内存占用率

标准回答

直接对比数组和链表的空间复杂度、对比插删查的时间复杂度、即可。

1. ArrayList的实现是基于数组,LinkedList的实现是基于双向链表

2. 对于随机访问ArrayList要优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问,而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,查找某个元素的时间复杂度是O(N)。

3. 对于插入和删除操作,LinkedList要优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引。list首部、中间插删时ArrayList时间复杂度O(n),因为要移动后面的元素,LinkedList时间复杂度O(1),不需要移动。list尾部插删时两者时间复杂度都是O(1),因为LinkedList是双向链表,有尾结点的指针。 

4. LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。 

自然有序时间复杂度:如果两者自然有序,ArrayList可以使用二分查找,时间复杂度O(logn),LinkedList可以使用跳跃表,时间复杂度O(logn)。Redis的zset底层是采用压缩列表或跳跃表。

跳跃表:链表基础上增加多级索引,跳跃查找:

4.5.请你说说HashMap底层原理

得分点

底层数据结构、哈希表处理冲突、扩容机制、put()流程、为什么2的指数扩容、死循环问题

关键字:数组初始16、负载因子0.75、2的指数扩容、链表头结点地址、链表长度8、红黑树、hash&(2^n-1)

标准回答 

HashMap是线程不安全的,多线程环境下建议使用Collections工具类和JUC包的ConcurrentHashMap。

底层数据结构
  • JDK7:HashMap底层是采用“数组+单向链表”来实现的。数组用作哈希查找,链表用作链地址法头插法处理冲突。
  • JDK8:HashMap底层是采用“数组+单向链表+红黑树”来实现的。数组用作哈希查找,链表用作链地址法尾插法处理冲突,链表长度为8时转为红黑树。
HashMap是否允许重复?

key本质上是不能重复,如果两个value的key相同,到时候就无法准确读取value值了

但本质上相同不代表“表面上”不可以相同,当HashMap元素是类时,HashMap判断两个元素是否重复不是直接通过他们的地址,而是通过该类的重写hashCode()、equals()方法判断两个对象是否是一个对象。

示例:下面Dog类没有重写hashCode()、equals()方法,所以HashMap计算哈希值是通过两个对象的地址,因为地址不同,所以HashMap将两个名字、体重Dog对象认为是两只狗

 
 
  1. /**

  2. * @Author: vince

  3. * @CreateTime: 2024/07/02

  4. * @Description: 狗类

  5. * @Version: 1.0

  6. */

  7. public class Dog{

  8. /**

  9. * 体重

  10. */

  11. public int weight;

  12. /**

  13. * 名字

  14. */

  15. public String name;

  16. public Dog(int weight, String name) {

  17. this.weight = weight;

  18. this.name = name;

  19. }

  20. // @Override

  21. // public boolean equals(Object o) {

  22. // Dog dog = (Dog) o;

  23. // return weight == dog.weight && Objects.equals(name, dog.name);

  24. // }

  25. //

  26. // @Override

  27. // public int hashCode() {

  28. // return Objects.hash(weight, name);

  29. // }

  30. @Override

  31. public String toString() {

  32. return "Dog{" +

  33. "weight=" + weight +

  34. ", name='" + name + '\'' +

  35. '}';

  36. }

  37. }

 
 
  1. /**

  2. * @Author: wangming

  3. * @CreateTime: 2024/09/10

  4. * @Description: 测试类

  5. * @Version: 1.0

  6. */

  7. public class Test {

  8. public static void main(String[] args) {

  9. HashMap<Dog, Integer> dogCountMap = new HashMap<>();

  10. dogCountMap.put(new Dog(1, "dog1"), 2);

  11. dogCountMap.put(new Dog(1, "dog1"), 2);

  12. System.out.println(dogCountMap);

  13. }

  14. }

扩容机制 

HashMap中,数组的默认初始容量为16,这个容量会以2的指数进行扩容。具体来说,当数组中的元素达到一定比例的时候HashMap就会扩容,这个比例叫做负载因子,默认为0.75。

自动扩容机制,是为了保证HashMap初始时不必占据太大的内存,而在使用期间又可以实时保证有足够大的空间。采用2的指数进行扩容,是为了利用位运算,提高扩容运算的效率。

数组每个元素存的是链表头结点地址,链地址法处理冲突,若链表的长度达到了8,红黑树代替链表。

  篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

put()流程

put()方法的执行过程中,主要包含四个步骤:

  1. 计算key存取位置,与运算hash&(2^n-1),实际就是哈希值取余,位运算效率更高。
  2. 判断数组,若发现数组为空,则进行首次扩容为初始容量16。
  3. 判断数组存取位置的头节点,若发现头节点为空,则新建链表节点,存入数组。
  4. 判断数组存取位置的头节点,若发现头节点非空,则看情况将元素覆盖或插入链表(JDK7头插法,JDK8尾插法)、红黑树。
  5. 插入元素后,判断元素的个数,若发现超过阈值则以2的指数再次扩容。

其中,第3步又可以细分为如下三个小步骤:

1. 若元素的key与头节点的key一致,则直接覆盖头节点。

2. 若元素为树型节点,则将元素追加到树中。

3. 若元素为链表节点,则将元素追加到链表中。追加后,需要判断链表长度以决定是否转为红黑树。若链表长度达到8、数组容量未达到64,则扩容。若链表长度达到8、数组容量达到64,则转为红黑树。

哈希表处理冲突:开放地址法(线性探测、二次探测、再哈希法)、链地址法

HashMap容量为什么是2的n次方?

主要有以下两个原因: 

  • 提高性能:2^n-1和2^(n+1)-1的二进制除了第一位,后几位都是相同的。扩容后只需要左移一次。例如15的二进制为1111,31的二进制为11111,63的二进制为111111,127的二进制为1111111。
  • 使元素均匀分布在底层数组:以2的指数扩容,可以保证每次扩容后,索引值改变的元素都移动到新增加的位置,而不会再移动到旧数组上的位置,可以减少哈希碰撞。

例如:从2^4扩容成2^5,取余公式key&(2^n-1)=index,index是要存储在数组的下标。

0&(2^4-1)=0;0&(2^5-1)=0

16&(2^4-1)=0;16&(2^5-1)=16。所以扩容后,key为0的一部分value位置没变,一部分value迁移到扩容后的新位置。

1&(2^4-1)=1;1&(2^5-1)=1

17&(2^4-1)=1;17&(2^5-1)=17。所以扩容后,key为1的一部分value位置没变,一部分value迁移到扩容后的新位置。

JDK7扩容时死循环问题

单线程扩容流程:JDK7中,HashMap链地址法处理冲突时采用头插法,在扩容时依然头插法,所以链表里结点顺序会反过来。

假如有T1、T2两个线程同时对某链表扩容,他们都标记头结点和第二个结点,此时T2阻塞,T1执行完扩容后链表结点顺序反过来,此时T2恢复运行再进行翻转就会产生环形链表,即B.next=A; A.next=B,从而死循环。

JDK8 尾插法

JDK8中,HashMap采用尾插法,扩容时链表节点位置不会翻转,解决了扩容死循环问题,但是性能差了一点,因为要遍历链表再查到尾部。 

JDK8 put时数据覆盖问题

HashMap非线程安全,如果两个并发线程插入的数据hash取余后相等,就可能出现数据覆盖。

线程A刚找到链表null位置准备插入时就阻塞了,然后线程B找到这个null位置插入成功。借着线程A恢复,因为已经判过null,所以直接覆盖插入这个位置,把线程B插入的数据覆盖了。

 
  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

  2. boolean evict) {

  3. Node<K,V>[] tab; Node<K,V> p; int n, i;

  4. if ((tab = table) == null || (n = tab.length) == 0)

  5. n = (tab = resize()).length;

  6. if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有 hash 碰撞,则直接插入

  7. tab[i] = newNode(hash, key, value, null);

  8. }

modCount非原子性自增问题

put会执行modCount++操作(modCount是HashMap的成员变量,用于记录HashMap被修改次数),这步操作分为读取、增加、保存,不是一个原子性操作,也会出现线程安全问题。 

线程安全解决方案:

  • 使用Hashtable(古老api不推荐)
  • 使用Collections工具类,将HashMap包装成线程安全的HashMap。
    Collections.synchronizedMap(map);
  • 使用更安全的ConcurrentHashMap(推荐),ConcurrentHashMap通过对槽(链表头结点)加锁,以较小的性能来保证线程安全。
  • 使用synchronized或Lock加锁HashMap之后,再进行操作,相当于多线程排队执行(比较麻烦,也不建议使用)。

HashMap是基于哈希算法来确定元素的位置(槽)的,当我们向集合中存入数据时,它会计算传入的Key的哈希值,并利用哈希值取余来确定槽的位置。如果元素发生碰撞,也就是这个槽已经存在其他的元素了,则HashMap会通过链表将这些元素组织起来(链地址法处理冲突)。如果碰撞进一步加剧,某个链表的长度达到了8,则HashMap会创建红黑树来代替这个链表,从而提高对这个槽中数据的查找的速度。

向HashMap中添加数据时,有三个条件会触发它的扩容行为:

1. 如果数组为空,则进行首次扩容。

2. 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。

3. 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。 并且,每次扩容时都是将容量翻倍,即创建一个2倍大的新数组,然后再将旧数组中的数组迁移到新数组里。由于HashMap中数组的容量为2^N,所以可以用位移运算计算新容量,效率很高。

加分回答-HashMap是非线程安全

HashMap是非线程安全的,在多线程环境下,多个线程同时触发HashMap的改变时,有可能会发生冲突。所以,在多线程环境下不建议使用HashMap,可以考虑使用Collections将HashMap转为线程安全的HashMap,更为推荐的方式则是使用ConcurrentHashMap。

红黑树: 近似平衡二叉树,左右子树高差有可能大于 1,查找效率略低于平衡二叉树,但增删效率高于平衡二叉树,适合频繁插入删除。

  • 结点非黑即红;
  • 根结点是黑色,叶节点是黑色空节点(常省略);
  • 任何相邻节点不能同时为红色;
  • 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点;
  • 查询性能稳定O(logN),高度最高2log(n+1);
     
源码
  • 元素:HashMap的链表元素对应的是一个静态内部类Entry,Entry主要包含key,value,next三个元素

  • put():通过hash%Entry.length计算index,此时记作Entry[index]=该元素。如果index相同就是新入的元素放置到Entry[index],原先的元素记作Entry[index].next

  • get():先遍历数组,再遍历链表元素。

  • 空键:null key总是放在Entry数组的第一个元素

  • 再散列rehash的过程:确定容量超过目前哈希表的容量,重新调整table 的容量大小,当超过容量的最大值时,取Integer.MAX_VALUE,即2^31-1

      篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

    需要全套面试笔记【点击此处即可】免费获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值