Java基础知识面试题(2024年最新版,持续更新(1)

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

类和对象体现了抽象和封装

抽象就是解释类与对象之间关系的词。类与对象之间的关系就是抽象的关系。一句话来说明:类是对象的抽象,而对象则是类得特例,即类的具体表现形式。

封装两个方面的含义:一是将有关数据和操作代码封装在对象当中,形成一个基本单位,各个对象之间相对独立互不干扰。二是将对象中某些属性和操作私有化,已达到数据和操作信息隐蔽,有利于数据安全,防止无关人员修改。把一部分或全部属性和部分功能(函数)对外界屏蔽,就是从外界(类的大括号之外)看不到,不可知,这就是封装的意义。

二、继承

面向对象的继承是为了软件重用,简单理解就是代码复用,把重复使用的代码精简掉的一种手段。如何精简,当一个类中已经有了相应的属性和操作的代码,而另一个类当中也需要写重复的代码,那么就用继承方法,把前面的类当成父类,后面的类当成子类,子类继承父类,理所当然。就用一个关键字extends就完成了代码的复用。

三、多态

没有继承就没有多态,继承是多态的前提。虽然继承自同一父类,但是相应的操作却各不相同,这叫多态。由继承而产生的不同的派生类,其对象对同一消息会做出不同的响应。

题2:JDK、JRE、JVM 之间有什么关系?

1、JDK

JDK(Java development Toolkit),JDK是整个Java的核心,包括了Java的运行环境(Java Runtime Environment),一堆的Java工具(Javac,java,jdb等)和Java基础的类库(即Java API 包括rt.jar).

Java API 是Java的应用程序的接口,里面有很多写好的Java class,包括一些重要的结构语言以及基本图形,网络和文件I/O等等。

2、JRE

JRE(Java Runtime Environment),Java运行环境。在Java平台下,所有的Java程序都需要在JRE下才能运行。只有JVM还不能进行class的执行,因为解释class的时候,JVM需调用解释所需要的类库lib。JRE里面有两个文件夹bin和lib,这里可以认为bin就是JVM,lib就是JVM所需要的类库,而JVM和lib合起来就称JRE。

JRE包括JVM和JAVA核心类库与支持文件。与JDK不同,它不包含开发工具-----编译器,调试器,和其他工具。

3、JVM

JVM:Java Virtual Machine(Java 虚拟机)JVM是JRE的一部分,它是虚拟出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件构架,入处理器,堆栈,寄存器等,还有相应的指令系统。

JVM是Java实现跨平台最核心的部分,所有的Java程序会首先被编译为class的类文件,JVM的主要工作是解释自己的指令集(即字节码)并映射到本地的CPU的指令集或OS的系统调用。Java面对不同操作系统使用不同的虚拟机,一次实现了跨平台。JVM对上层的Java源文件是不关心的,它关心的只是由源文件生成的类文件

编译和运行Java文件,需了解两个命令:

1)javac命令:编译java文件;使用方法: javac Hello.java ,如果不出错的话,在与Hello.java 同一目录下会生成一个Hello.class文件,这个class文件是操作系统能够使用和运行的文件。

2)java命令: 作用:运行.class文件;使用方法:java Hello,如果不出错的话,会执行Hello.class文件。注意:这里的Hello后面不需要扩展名。

题3:如何使用命令行编译和运行 Java 文件?

新建文件,编写代码如下:

public class Hello{

public static void main(String[] args){

System.out.println(“Hello world,欢迎关注微信公众号“Java精选”!”);

}

}

文件命名为Hello.java,注意后缀为“java”。

打开cmd,切换至当前文件所在位置,执行javac Hello.java,该文件夹下面生成了一个Hello.class文件

输入java Hello命令,cmd控制台打印出代码的内容Hello world,欢迎关注微信公众号“Java精选”!

题4:Java 中的关键字都有哪些?

1)48个关键字:abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while。

2)2个保留字(目前未使用,以后可能用作为关键字):goto、const。

3)3个特殊直接量(直接量是指在程序中通过源代码直接给出的值):true、false、null。

题5:Java 中基本类型都有哪些?

Java的类型分成两种,一种是基本类型,一种是引用类型。其中Java基本类型共有八种。

基本类型可以分为三大类:字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。

数值类型可以分为整数类型byte、short、int、long和浮点数类型float、double。

JAVA中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或操作系统的改变而改变。实际上《Thinking in Java》一书作者,提到Java中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,因为Void是不能new,也就是不能在堆里面分配空间存对应的值,所以将Void归成基本类型,也有一定的道理。

8种基本类型表示范围如下:

byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。

int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。

double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。

boolean:只有true和false两个取值。

char:16位,存储Unicode码,用单引号赋值。

题6:main 方法中 args 参数是什么含义?

java中args即为arguments的缩写,是指字符串变量名,属于引用变量,属于命名,可以自定义名称也可以采用默认值,一般习惯性照写。

String[] args是main函数的形式参数,可以用来获取命令行用户输入进去的参数。

1)字符串变量名(args)属于引用变量,属于命名,可以自定义名称。

2)可以理解成用于存放字符串数组,若去掉无法知晓"args"声明的变量是什么类型。

3)假设public static void main方法,代表当启动程序时会启动这部分;

4)String[] args是main函数的形式参数,可以用来获取命令行用户输入进去的参数。

5)java本身不存在不带String args[]的main函数,java程序中去掉String args[]会出现错误。

​题7:final 关键字的基本用法?

在Java中final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面从这三个方面来了解一下final关键字的基本用法。

1、修饰类

当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

2、修饰方法

下面这段话摘自《Java编程思想》第四版第143页:

“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“

因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。即父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。

final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。)

3、修饰变量

final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。

final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。

题8:如何理解 final 关键字?

1)类的final变量和普通变量有什么区别?

当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。

2)被final修饰的引用变量指向的对象内容可变吗?

引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的

3)final参数的问题

在实际应用中,我们除了可以用final修饰成员变量、成员方法、类,还可以修饰参数、若某个参数被final修饰了,则代表了该参数是不可改变的。如果在方法中我们修改了该参数,则编译器会提示你:

The final local variable i cannot be assigned. It must be blank and not using a compound assignment。

java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。

题9:为什么 String 类型是被 final 修饰的?

1、为了实现字符串池

final修饰符的作用:final可以修饰类,方法和变量,并且被修饰的类或方法,被final修饰的类不能被继承,即它不能拥有自己的子类,被final修饰的方法不能被重写, final修饰的变量,无论是类属性、对象属性、形参还是局部变量,都需要进行初始化操作。

String为什么要被final修饰主要是为了”安全性“和”效率“的原因。

final修饰的String类型,代表了String不可被继承,final修饰的char[]代表了被存储的数据不可更改性。虽然final修饰的不可变,但仅仅是引用地址不可变,并不代表了数组本身不会改变。

为什么保证String不可变呢?

因为只有字符串是不可变,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现,反之变量改变它的值,那么其它指向这个值的变量值也会随之改变。

如果字符串是可变,会引起很严重的安全问题。如数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接或在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则改变字符串指向的对象值,将造成安全漏洞。

2、为了线程安全

因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

3、为了实现String可创建HashCode不可变性

因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。使得字符串很适合作为Map键值对中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

题10:接口(interface)和抽象类(abstract class)有什么区别?

默认方法

抽象类可以有默认的方法实现;而接口类在JDK1.8之前版本,不存在方法的实现。

实现方式

抽象类子类使用extends关键字来继承抽象类,如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现;而接口类子类使用implements来实现接口,需要提供接口中所有声明的实现。

构造器

抽象类中可以有构造器;而接口中不能有构造器。

和正常类区别

抽象类不能被实例化;而接口是完全不同的类型。

访问修饰符

抽象类中抽象方法可以有public、protected、default等修饰;而接口类默认是public,不能使用其他修饰符。

多继承

抽象类一个子类只能存在一个父类;而接口类一个子类可以存在多个接口。

添加新方法

抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码;而接口类中添加新方法,则子类中需要实现该方法。

题11:面向过程与面向对象有什么区别?

面向过程

性能相比面向对象高,因其类调用时需要实例化,开销比较大,消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

面向对象

易维护、复用以及扩展,由于面向对象有封装、继承、多态性等特征,可以设计出低耦合、高内聚的系统,使得更加灵活,易于维护。

题12:Java 编程语言有哪些特点?

1)简单易学;

2)面向对象(封装,继承,多态);

3)平台无关性(Java虚拟机实现平台无关性);

4)可靠性;

5)安全性;

6)支持多线程;

7)支持网络编程并方便易用;

8)编译与解释并存。

题13:重载和重写有什么区别?

重载(Overload) 是指让类以统一的方式处理不同类型数据的一种手段,实质表现就是多个具有不同的参数个数或者不同类型的同名函数,存在于同一个类中,返回值类型不同,是一个类中多态性的一种表现。

调用方法时通过传递不同参数个数和参数类型来决定具体使用哪个方法的多态性。

重写(Override) 是指父类与子类之间的多态性,实质就是对父类的函数进行重新定义。

如果子类中定义某方法与其父类有相同的名称和参数则该方法被重写,需注意的是子类函数的访问修饰权限不能低于父类的。

如果子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用super关键字。

题14:静态方法和实例方法有什么不同?

静态方法和实例方法的区别主要体现在两个方面:

其一在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式而实例方法只能试用后面这种方式。也就是说,调用静态方法可以无需创建对象进行实例化。

其二静态方法在访问本类的成员时,只允许访问静态成员也就是静态成员变量和静态方法,而不允许访问实例成员变量和实例方法,实例方法是没有这个限制的。

题15:== 和 equals 两者有什么区别?

使用==比较

用于对比基本数据类型的变量,是直接比较存储的 “值”是否相等;

用于对比引用类型的变量,是比较的所指向的对象地址。

使用equals比较

equals方法不能用于对比基本数据类型的变量;

如果没对Object中equals方法进行重写,则是比较的引用类型变量所指向的对象地址,反之则比较的是内容。

题16:Integer 和 int 两者有什么区别?

Integer是int的包装类,默认值是null;int是基本数据类型,默认值是0;

Integer变量必须实例化后才能使用;int变量不需要;

Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值。

分析总结

1)Integer与new Integer不相等。new出来的对象被存放在堆,而非new的Integer常量则在常量池,两者内存地址不同,因此判断是false。

2)两个值都是非new Integer,如果值在-128,127区间,则是true,反之为false。

这是因为java在编译Integer i2 = 128时,被翻译成:

Integer i2 = Integer.valueOf(128);

而valueOf()函数会对-128到127之间的数进行缓存。

3)两个都是new Integer,两者判断为false,内存地址不同。

4)int和Integer对比不管是否new对象,两者判断都是true,因为会把Integer自动拆箱为int再去比。

题17:什么是 Java 内部类?

内部类是指把A类定义在另一个B类的内部。

例如:把类User定义在类Role中,类User就被称为内部类。

class Role {

class User {

}

}

1、内部类的访问规则

1)可以直接访问外部类的成员,包括私有

​2)外部类要想访问内部类成员,必须创建对象

2、内部类的分类

​1)成员内部类

​2)局部内部类

​3)静态内部类

​4)匿名内部类

题18:什么是自动装箱?什么是自动拆箱?

自动装箱是指将基本数据类型重新转化为对象。

public class Test {

public static void main(String[] args) {

Integer num = 9;

}

}

num = 9的值是属于基本数据类型,原则上不能直接赋值给对象Integer。但是在JDK1.5版本后就可以进行这样的声明自动将基本数据类型转化为对应的封装类型,成为对象后可以调用对象所声明的方法。

自动拆箱是指将对象重新转化为基本数据类型。

public class Test {

public static void main(String[] args) {

// 声明Integer对象

Integer num = 9;

// 隐含自动拆箱

System.out.print(num–);

}

}

由于对象不能直接进行运算,而是需要转化为基本数据类型后才能进行加减乘除。

// 装箱

Integer num = 10;

// 拆箱

int num1 = num;

题19:JDK1.8 中 ConcurrentHashMap 不支持空键值吗?

首先明确一点HashMap是支持空键值对的,也就是null键和null值,而ConcurrentHashMap是不支持空键值对的。

查看一下JDK1.8源码,HashMap类部分源码,代码如下:

public V get(Object key) {

Node<K,V> e;

return (e = getNode(hash(key), key)) == null ? null : e.value;

}

static final int hash(Object key) {

int h;

return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}

HashMap在调用put()方法存储数据时会调用hash()方法来计算key的hashcode值,可以从hash()方法上得出当key==null时返回值是0,这意思就是key值是null时,hash()方法返回值是0,不会再调用key.hashcode()方法。

ConcurrentHashMap类部分源码,代码如下:

public V put(K key, V value) {

return putVal(key, value, false);

}

/** Implementation for put and putIfAbsent */

final V putVal(K key, V value, boolean onlyIfAbsent) {

if (key == null || value == null) throw new NullPointerException();

int hash = spread(key.hashCode());

int binCount = 0;

for (Node<K,V>[] tab = table;😉 {

Node<K,V> f; int n, i, fh;

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

tab = initTable();

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

if (casTabAt(tab, i, null,

new Node<K,V>(hash, key, value, null)))

break; // no lock when adding to empty bin

}

else if ((fh = f.hash) == MOVED)

tab = helpTransfer(tab, f);

else {

V oldVal = null;

synchronized (f) {

if (tabAt(tab, i) == f) {

if (fh >= 0) {

binCount = 1;

for (Node<K,V> e = f;; ++binCount) {

K ek;

if (e.hash == hash &&

((ek = e.key) == key ||

(ek != null && key.equals(ek)))) {

oldVal = e.val;

if (!onlyIfAbsent)

e.val = value;

break;

}

Node<K,V> pred = e;

if ((e = e.next) == null) {

pred.next = new Node<K,V>(hash, key,

value, null);

break;

}

}

}

else if (f instanceof TreeBin) {

Node<K,V> p;

binCount = 2;

if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,

value)) != null) {

oldVal = p.val;

if (!onlyIfAbsent)

p.val = value;

}

}

}

}

if (binCount != 0) {

if (binCount >= TREEIFY_THRESHOLD)

treeifyBin(tab, i);

if (oldVal != null)

return oldVal;

break;

}

}

}

addCount(1L, binCount);

return null;

}

ConcurrentHashmap在调用put()方法时调用了putVal()方法,而在该方法中判断key为null或value为null时抛出空指针异常NullPointerException。

ConcurrentHashmap是支持并发的,当通过get()方法获取对应的value值时,如果指定的键为null,则为NullPointerException,这主要是因为获取到的是null值,无法分辨是key没找到null还是有key值为null。

题20:父类中静态方法能否被子类重写?

父类中静态方法不能被子类重写。

重写只适用于实例方法,不能用于静态方法,而且子类当中含有和父类相同签名的静态方法,一般称之为隐藏。

public class A {

public static String a = “这是父类静态属性”;

public static String getA() {

return “这是父类静态方法”;

}

}

public class B extends A{

public static String a = “这是子类静态属性”;

public static String getA() {

return “这是子类静态方法”;

}

public static void main(String[] args) {

A a = new B();

System.out.println(a.getA());

}

}

如上述代码所示,如果能够被重写,则输出的应该是“这是子类静态方法”。与此类似的是,静态变量也不能被重写。如果想要调用父类的静态方法,应该使用类来直接调用。

集合


题1:Java 中常用的集合有哪些?

Map接口和Collection接口是所有集合框架的父接口

Collection接口的子接口包括:Set接口和List接口。

Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

Map接口的实现类主要有:HashMap、Hashtable、ConcurrentHashMap以及TreeMap等。Map不能包含重复的key,但是可以包含相同的value。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。

Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等

List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

Iterator所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:

hasNext()是否还有下一个元素

next()返回下一个元素

remove()删除当前元素

题2:为什么 Map 接口不继承 Collection 接口?

1)Map提供的是键值对映射(即Key和value的映射),而Collection提供的是一组数据并不是键值对映射。

2)若果Map继承了Collection接口,那么所实现的Map接口的类到底是用Map键值对映射数据还是用Collection的一组数据呢?比如平常所用的hashMap、hashTable、treeMap等都是键值对,所以它继承Collection是完全没意义,而且Map如果继承Collection接口的话,违反了面向对象的接口分离原则。

接口分离原则:客户端不应该依赖它不需要的接口。

另一种定义是类间的依赖关系应该建立在最小的接口上。

接口隔离原则将非常庞大、臃肿的接口拆分成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。

接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署,让客户端依赖的接口尽可能地小。

3)Map和List、Set不同,Map放的是键值对,List、Set存放的是一个个的对象。说到底是因为数据结构不同,数据结构不同,操作就不一样,所以接口是分开的,还是接口分离原则。

题3:Collection 和 Collections 有什么区别?

java.util.Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java类库中有很多具体的实现。

Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。其直接继承接口有List与Set。

Collection

├List

│├LinkedList

│├ArrayList

│└Vector

│ └Stack

└Set

java.util.Collections是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

题4:ArrayList 和 LinkedList 有什么区别?

1)ArrayList是Array动态数组的数据结构,LinkedList是Link链表的数据结构,此外,它们两个都是对List接口的实现。前者是数组队列,相当于动态数组;后者为双向链表结构,也可当作堆栈、队列、双端队列。

2)当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。

3)当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

4)从利用效率来看,ArrayList自由性较低,因为它需要手动设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。

5)ArrayList主要控件开销在于需要在List列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。

题5:HashMap 和 HashTable 有什么区别?

Hashtable是线程安全,而HashMap则非线程安全。

Hashtable所有实现方法添加了synchronized关键字来确保线程同步,因此相对而言HashMap性能会高一些,平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合。

HashMap允许使用null作为key,不过建议还是尽量避免使用null作为key。HashMap以null作为key时,总是存储在table数组的第一个节点上。而Hashtable则不允许null作为key。

HashMap继承了AbstractMap,HashTable继承Dictionary抽象类,两者均实现Map接口。

HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。

HashMap扩容时是当前容量翻倍即:capacity*2,Hashtable扩容时是容量翻倍+1即:capacity*2+1。

HashMap和Hashtable的底层实现都是数组+链表结构实现。

题6:HashMap 是怎么扩容的?

当HashMap中元素个数超过数组大小*loadFactor时,需进行数组扩容。

loadFactor默认值为0.75,默认情况下,数组大小为16,HashMap中元素个数超过16 * 0.75=12的时,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以能够预选知道HashMap中元素的个数,应该预设数组的大小,可以有效的提高HashMap的性能。

假设有1000个元素new HashMap(1000),理论上来讲new HashMap(1024)更合适,不过上面已经提过即使是1000个元素,HashMap也会自动设置为1024。但是new HashMap(1024),而0.75*1024 <1000, 为了可以0.75 * size >1000,必须new HashMap(2048),避免了resize的问题。

总结:

添加元素时会检查容器当前元素个数。当HashMap的容量值超过临界值(默认16 * 0.75=12)时扩容。HashMap将会重新扩容到下一个2的指数幂(16->32->64)。调用resize方法,定义长度为新长度(32)的数组,然后对原数组数据进行再Hash。注意的是这个过程比较损耗性能。

题7:JDK1.8 和 JDK1.7 中 ArrayList 的初始容量多少?

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料整理不易,读者朋友可以转发分享下!

Java核心知识体系笔记.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

中高级Java开发面试高频考点题笔记300道.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

架构进阶面试专题及架构学习笔记脑图

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

Java架构进阶学习视频分享
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

题6:HashMap 是怎么扩容的?

当HashMap中元素个数超过数组大小*loadFactor时,需进行数组扩容。

loadFactor默认值为0.75,默认情况下,数组大小为16,HashMap中元素个数超过16 * 0.75=12的时,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以能够预选知道HashMap中元素的个数,应该预设数组的大小,可以有效的提高HashMap的性能。

假设有1000个元素new HashMap(1000),理论上来讲new HashMap(1024)更合适,不过上面已经提过即使是1000个元素,HashMap也会自动设置为1024。但是new HashMap(1024),而0.75*1024 <1000, 为了可以0.75 * size >1000,必须new HashMap(2048),避免了resize的问题。

总结:

添加元素时会检查容器当前元素个数。当HashMap的容量值超过临界值(默认16 * 0.75=12)时扩容。HashMap将会重新扩容到下一个2的指数幂(16->32->64)。调用resize方法,定义长度为新长度(32)的数组,然后对原数组数据进行再Hash。注意的是这个过程比较损耗性能。

题7:JDK1.8 和 JDK1.7 中 ArrayList 的初始容量多少?

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料整理不易,读者朋友可以转发分享下!

Java核心知识体系笔记.pdf

[外链图片转存中…(img-96Cob7M6-1714681731860)]

中高级Java开发面试高频考点题笔记300道.pdf

[外链图片转存中…(img-0sK4FmlA-1714681731861)]

架构进阶面试专题及架构学习笔记脑图

[外链图片转存中…(img-Kh4l2JX6-1714681731861)]

Java架构进阶学习视频分享
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值