面试题总结(一)java基础,集合框架

Java 基础

1.JDK 和 JRE 有什么区别?

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。

2.== 和 equals 的区别是什么?

== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

不对,两个对象的 hashCode()相同,equals()不一定 true。

4.final 在 java 中有什么作用?

  1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的
    变量,则在对其初始化之后便不能再让其指向另一个对象。
  2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
  3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。
    在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的
    任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地
    指定为fianl。

5.java 中的 Math.round(-1.5) 等于多少?

加0.5向下取整
等于 -1。

6.String 属于基础的数据类型吗?

String 不属于基础类型

基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。

7.java 中操作字符串都有哪些类?它们之间有什么区别?String 为什么是不可变的?
String 和 StringBuffer、StringBuilder
可变性
简单的来说:String 类中使用 final 关键字字符数组保存字符串, private final char value[] ,所以 String
对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder
中也是使用字符数组保存字符串 char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自
行查阅源码。
AbstractStringBuilder.java

    abstract class AbstractStringBuilder implements Appendable, CharSequence {
    char[] value;
    int count;
    AbstractStringBuilder() {
    } A
    bstractStringBuilder(int capacity) {
    value = new char[capacity];
    }

线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与
StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共
方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对
方法进行加同步锁,所以是非线程安全的。
性能
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用
StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
1. 操作少量的数据 = String
2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer

8.String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

9.如何将字符串反转?

使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。

// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba

10.String 类的常用方法都有那些?

  1. indexOf():返回指定字符的索引。
  2. charAt():返回指定索引处的字符。
  3. replace():字符串替换。
  4. trim():去除字符串两端空白。
  5. split():分割字符串,返回一个分割后的字符串数组。
  6. getBytes():返回字符串的 byte 类型数组。
  7. length():返回字符串长度。
  8. toLowerCase():将字符串转成小写字母。
  9. toUpperCase():将字符串转成大写字符。
  10. substring():截取字符串。
  11. equals():字符串比较。

11.抽象类必须要有抽象方法吗?

不需要,抽象类不一定非要有抽象方法。

12.普通类和抽象类有哪些区别?

普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。
13.抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

14.接口和抽象类有什么区别?

  1. 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
  2. 构造函数:抽象类可以有构造函数;接口不能有。接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象
    从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
  3. 成员变量:抽象类中可以有普通成员变量 接口中只能有public static final 修饰的静态常量必须赋初始值
  4. 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
  5. 访问修饰符:接口的方法默认是 public,所有方法在接口中不能有实现(Java 8
    开始接口方法可以有默认实现)。抽象类中的方法可以是任意访问修饰符,抽象类可以有非抽象的方法。

15.java 中 IO 流分为几种?

按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。

16.BIO、NIO、AIO 有什么区别?

BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
参考nio

17.Files的常用方法都有哪些?

  • Files.exists():检测文件路径是否存在。
  • Files.createFile():创建文件。
  • Files.createDirectory():创建文件夹。
  • Files.delete():删除一个文件或目录。
  • Files.copy():复制文件。
  • Files.move():移动文件。
  • Files.size():查看文件个数。
  • Files.read():读取文件。
  • Files.write():写入文件。

18.重载和重写的区别

  • **重载:**发生在同一个类中,方法名相同,方法形参类型,个数或顺序不同方法的返回值,修饰符可以不同,发生在编译时
  • **重写:**发生在父子类,或者接口和实现类中,方法名,参数必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,
    访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。

19.自动装箱与拆箱
**装箱:**将基本类型用它们对应的引用类型包装起来;
**拆箱:**将包装类型转换为基本数据类型;

20.Object类的常见方法总结
Object类是一个特殊的类,是所有类的父类。它主要提供了以下11个方法:

public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。

public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。

public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。

protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,
x.clone().getClass() == x.getClass()true。Object本身没有实现Cloneable接口,所以不重写clone
方法并且进行调用的话会发生CloneNotSupportedException异常。

public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个
方法。

public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监
视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。

public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤
醒在此对象监视器上等待的所有线程,而不是一个线程。

public final native void wait(long timeout) throws InterruptedException//native方法,并且不
能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。

public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参
数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。2.1.7 Java 中的异常处理

public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直
等待,没有超时时间这个概念

protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作

集合容器

1.java 容器都有哪些?

## set list map

2.Collection 和 Collections 有什么区别?

java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作

3.List、Set、Map 之间的区别是什么?

1) 元素重复性:

List允许有重复的元素。任何数量的重复元素都可以在不影响现有重复元素的值及其索引的情况下插入到List集合中;

Set集合不允许元素重复。Set以及所有实现了Set接口的类都不允许重复值的插入,若多次插入同一个元素时,在该集合中只显示一个;

Map以键值对的形式对元素进行存储。Map不允许有重复键,但允许有不同键对应的重复的值;

2) 元素的有序性:

List及其所有实现类保持了每个元素的插入顺序;

Set中的元素都是无序的;但是某些Set的实现类以某种殊形式对其中的元素进行排序,如:LinkedHashSet按照元素的插入顺序进行排序;

Map跟Set一样对元素进行无序存储,但其某些实现类对元素进行了排序。如:TreeMap根据键对其中的元素进行升序排序;

3) 元素是否为空值:

List允许任意数量的空值;

Set最多允许一个空值的出现;[ 当向Set集合中添加多个null值时,在该Set集合中只会显示一个null元素]

Map只允许出现一个空键,但允许出现任意数量的空值;

总结

List中的元素,有序、可重复、可为空;
Set中的元素,无序、不重复、只有一个空元素;
Map中的元素,无序、键不重,值可重、可一个空键、多可空值;

4.Arraylist 与 LinkedList 异同

  1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  2. 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构
  3. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素
    位置的影响。 比如:执行 add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种
    情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话( add(int index, E element) )时
    间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向
    前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是
    近似 O(1)而数组为近似 O(n)。
  4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通
    过元素的序号快速获取元素对象(对应于 get(int index) 方法)
  5. ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空
    间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数
    据)

5. ArrayList 与 Vector 区别
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要
在同步操作上耗费大量的时间。
Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist

6.HashMap的底层实现

JDK1.8之前
JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashCode 经
过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的
长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的
话,直接覆盖,不相同就通过拉链法解决冲突

所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的
hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。
JDK 1.8 HashMap 的 hash 方法源码:

static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
对比一下 JDK1.7的 HashMap 的 hash 方法源码.

static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}

相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希
冲突,则将冲突的值加到链表中即可。
在这里插入图片描述
相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转
化为红黑树,以减少搜索时间。
推荐阅读:
《Java 8系列之重新认识HashMap》 :https://zhuanlan.zhihu.com/p/21673805

7.HashMap 和 Hashtable 有什么区别?

  • 线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过
    synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
  • 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在
    代码中使用它;
  • 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键
    所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
  • 初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为
    11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来
    的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充
    为2的幂次方大小(HashMap 中的 tableSizeFor() 方法保证,下面给出了源代码)。也就是说 HashMap 总
    是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
  • 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为
    8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

8.HashMap 的长度为什么是2的幂次方
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的
范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应
用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之
前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算
方法是“ (n - 1) & hash ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。
这个算法应该如何设计呢?
我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其
除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。”
并且 采
用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

9.HashMap 多线程操作导致死循环问题
在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一
个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。复
制链表过程如下:
以下模拟2个线程同时扩容。假设,当前 HashMap 的空间为2(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 经过 hash 运算,得到散列地址为 1,这时候由于超过了临界值,空
间不够,需要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争,模拟过程如下:
线程一:读取到当前的 HashMap 情况,在准备扩容时,线程二介入
在这里插入图片描述
线程二:读取 HashMap,进行扩容
在这里插入图片描述
线程一:继续执行
在这里插入图片描述
这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头(A 的前边:B.next=A),本来 B.next=null,
到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将 B.next=A,所以,这里继续复制A,让
A.next=B,由此,环形链表出现:B.next=A; A.next=B
注意:jdk1.8已经解决了死循环的问题。

10HashSet 和 HashMap 区别
如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)
在这里插入图片描述

11.ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。

  • 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap
    的底层数据结构类 似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;

  • 实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了 分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁 竞争,提高并发访问率。 到了
    JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑 树的数据结构来实现,并发控制使用
    synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很 多优化)
    整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,
    但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,
    效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
    HashTable:
    在这里插入图片描述
    JDK1.7的ConcurrentHashMap:
    在这里插入图片描述
    JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点)
    在这里插入图片描述

12ConcurrentHashMap线程安全的具体实现方式/底层具体实现
JDK1.7(上面有示意图)
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的
数据也能被其他线程访问。
ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数
据。

static class Segment<K,V> extends ReentrantLock implements Serializable {
}

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结
构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个
HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。
JDK1.8 (上面有示意图)
ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8
的结构类似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

13集合框架底层数据结构总结
Collection

  1. List
    Arraylist: Object数组
    Vector: Object数组
    LinkedList: 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) 详细可阅读JDK1.7-LinkedList循环链表优

  2. Set
    HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
    LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类
    似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
    TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

  3. Map

    HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希
    冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默
    认为8)时,将链表转化为红黑树,以减少搜索时间 LinkedHashMap: LinkedHashMap 继承自
    HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和 链表或红黑树组成。另外,LinkedHashMap
    在上面结构的基础上,增加了一条双向链表,使得上面的结构可以
    保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看: 《LinkedHashMap
    源码详细分析(JDK1.8)》 HashTable: 数组+链表组成的,数组是 HashMap
    的主体,链表则是主要为了解决哈希冲突而存在的 TreeMap: 红黑树(自平衡的排序二叉树)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值