java面试题之java基础(持续更新)

目录

一、Java基础

jvm、jre、jdk之间的关系

访问修饰符 public,private,protected,以及default的 区别

&和&&的区别

String与StringBuffer的区别

int和Integer的区别

== 和 equals 的区别是什么

什么是内存泄漏和内存溢出

下面两个代码块能正常编译和执行吗

指出下题的输出结果

String 是 Java 基本数据类型吗?

String 类可以继承吗?

String s = new String("xyz") 创建了⼏个字符串对象?

深拷⻉和浅拷⻉区别是什么?

并发和并⾏有什么区别?

当⼀个对象被当作参数传递到⼀个⽅法后,此⽅法可改变这个对象

的属性,并可返回变化后的结果,那么这⾥到底是值传递还是引⽤传递?

重载(Overload)和重写(Override)的区别

为什么不能根据返回类型来区分重载?

Java静态变量与成员变量的区别

写一下变量的执行顺序

抽象类(abstract class)和接⼝(interface)有什么区别

二、Java集合

集合框架图

聊一聊Java中容器体系结构

Hashmap底层原理

Hashtable和HashMap的区别

LinkedList底层原理

HashMap 底层有顺序吗

HashMap null排在数组的那个位置

HashMap的红黑树,当他链表小于6就一定会转为链表吗?

List、Set和Map的区别,以及各自的优缺点

ArrayList的底层原理

HashSet的底层原理

HashMap的扩容机制


一、Java基础

jvm、jre、jdk之间的关系

        1、JVM(Java Virtual Machine) Java 虚拟机, Java 程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此 Java语言可以实现跨平台。
        2、JRE(Java Runtime Environment)包括Java虚拟机和 Java 程序所需的核心类库等。核 心类库主要是 java.lang 包:包含了运行Java 程序必不可少的系统类,如基本数 据类型、基本数学函数、字符串处理、线程、异 常处理类等,系统缺省加载这个包 如果想要运行一个开发好的Java 程序,计算机中只需要安装 JRE 即可。
        3、 JDKJava Development Kit是提供给 Java 开发人员使用的,其中包含了 Java 的开发 工具,也包括了 JRE 。所以 安装了JDK ,就无需再单独安装 JRE 了。其中的开发工 具:编译工具 (javac.exe) ,打包工具 (jar.exe) 等。
如图:

访问修饰符 public,private,protected,以及default的 区别

private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
default ( 即缺省,什么也不写,不使用任何关键字) : 在同一包内可见,不使用 任何修饰符。使用
对象:类、接口、变量、方法。
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意: 不能修饰类(外部
类)。
public : 对所有类可见。使用对象:类、接口、变量、方法。

&&&的区别

     & 运算符有两种用法: (1) 按位与; (2) 逻辑与。
     && 运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布 尔值都是true 整个表达式的值才是 true 。&&之所以称 为短路运算,是因为如果&& 左边的表达式的值是 false ,右边的表达式会被直 接短路掉,不会进行运算。

注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

String与StringBuffer的区别

        String不可变,存值使用了常量的字符数组,并且严格执行了封装,操作时无法直接访问字符数组,只能通过,设计String留下的对外接口进行操作,所以每次字符串改变时,都是开启一个新的空间,和已有的空间不同,原值没有变化因此,字符串是不可变的,StringBuffer可变使用字符数组,内存地址是可变的。

int和Integer的区别

int 基础数据类型,Integer是int对应的包装类,是一个引用类型

int 默认0 ,Integer默认null,Integer 有一个缓冲区 -128 127,

Integer提供一系列方便操作的方法,字符串转换。

== 和 equals 的区别是什么

== 比较内存地址 ,equals 底层也是==,只不过引用类型可以重写Object equals方法自定义比较方式,如String的equals,

==在基础数据类型中,比较的是值,引用类型中比较内存地址

什么是内存泄漏和内存溢出

内存泄漏:指的是 new 一些资源出来,没有及时释放,这部份的资源一直被占用,如connection ,io流没有调用关闭方法,如果大量内存泄漏没有处理,最终会导致内存溢出。

内存溢出:指程序申请内存时,没有足够的内存供申请者使用,导致数据无法正常存储到内存中。也就是说给你个int类型的存储数据大小的空间,但是却存储一个long类型的数据,这样就会导致内存溢出。

下面两个代码块能正常编译和执行吗

// 代码块1
short s1 = 1; s1 = s1 + 1;
// 代码块2
short s1 = 1; s1 += 1;
代码块1编译报错,错误原因是:不兼容的类型: 从int转换到short可能会有损失”。
代码块2正常编译和执⾏。
我们将代码块2进⾏编译,字节码如下:
public class com.joonwhee.open.demo.Convert {
public com.joonwhee.open.demo.Convert();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1 // 将int类型值1⼊(操作数)栈
1: istore_1 // 将栈顶int类型值保存到局部变量1中
2: iload_1 // 从局部变量1中装载int类型值⼊栈
3: iconst_1 // 将int类型值1⼊栈
4: iadd // 将栈顶两int类型数相加,结果⼊栈
5: i2s // 将栈顶int类型值截断成short类型值,后带符号扩展成int类型值⼊栈。
6: istore_1 // 将栈顶int类型值保存到局部变量1中
7: return
}
可以看到字节码中包含了 i2s 指令,该指令⽤于将 int 转成 short。i2s 是 int to short 的缩写。
其实,s1 += 1 相当于 s1 = (short)(s1 + 1),有兴趣的可以⾃⼰编译下这两⾏代码的字节码,你会发现是⼀摸⼀样的。

指出下题的输出结果

public static void main(String[] args) {
Integer a = 128, b = 128, c = 127, d = 127;
System.out.println(a == b);
System.out.println(c == d);
}
答案是:false,true。
执⾏ Integer a = 128,相当于执⾏:Integer a = Integer.valueOf(128),基本类型⾃动转换为包装类 的过程称为⾃动装箱(autoboxing)。
在 Integer 中引⼊了 IntegerCache 来缓存⼀定范围的值,IntegerCache 默认情况下范围
为:-128~127。
本题中的 127 命中了 IntegerCache,所以 c 和 d 是相同对象,⽽ 128 则没有命中,所以 a 和 b 是不 同对象。 但是这个缓存范围时可以修改的,可能有些⼈不知道。可以通过JVM启动参数:
-XX:AutoBoxCacheMax=<size> 来修改上限值。

String 是 Java 基本数据类型吗?

答:不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、
boolean;除了基本类型(primitive type),剩下的都是引⽤类型(reference type)。
基本数据类型:数据直接存储在栈上
引⽤数据类型区别:数据存储在堆上,栈上只存储引⽤地址。

String 类可以继承吗?

不⾏。String 类使⽤ final 修饰,⽆法被继承。

String s = new String("xyz") 创建了⼏个字符串对象?

⼀个或两个。如果字符串常量池已经有“xyz”,则是⼀个;否则,两个。 当字符串常量池没有 “xyz”,此时会创建如下两个对象:
⼀个是字符串字⾯量 "xyz" 所对应的、驻留(intern)在⼀个全局共享的字符串常量池中的实例,此 时该实例也是在堆中,字符串常量池只放引⽤。
另⼀个是通过 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。

深拷⻉和浅拷⻉区别是什么?

数据分为基本数据类型和引⽤数据类型。
基本数据类型:数据直接存储在栈中;引⽤数据类型:存储 在栈中的是对象的引⽤地址,真实的对象数据存放在堆内存⾥。
浅拷⻉:对于基础数据类型:直接复制数据值;对于引⽤数据类型:只是复制了对象的引⽤地址,新 旧对象指向同⼀个内存地址,修改其中⼀个对象的值,另⼀个对象的值随之改变。
深拷⻉:对于基础数据类型:直接复制数据值;对于引⽤数据类型:开辟新的内存空间,在新的内存 空间⾥复制⼀个⼀模⼀样的对象,新⽼对象不共享内存,修改其中⼀个对象的值,不会影响⼀个对 象。
深拷⻉相⽐于浅拷⻉速度较慢并且花销较⼤。

并发和并⾏有什么区别?

并发:两个或多个事件在同⼀时间间隔发⽣。
并⾏:两个或者多个事件在同⼀时刻发⽣。
并⾏是真正意义上,同⼀时刻做多件事情,⽽并发在同⼀时刻只会做⼀件事件,只是可以将时间切
碎,交替做多件事情。
并⾏在多处理器系统中存在,⽽并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器 系统中存在是因为并发是并⾏的假象,并⾏要求程序能够同时执⾏多个操作,⽽并发只是要求程序假 装同时执⾏多个操作(每个⼩时间⽚执⾏⼀个操作,多个操作快速切换执⾏)。
当系统有⼀个以上 CPU 时,则线程的操作有可能⾮并发。当⼀个 CPU 执⾏⼀个线程时,另⼀个 CPU 可以执⾏另⼀个线程,两个线程互不抢占 CPU 资源,可以同时进⾏,这种⽅式我们称之为并⾏ (Parallel)。
并发编程的⽬标是充分的利⽤处理器的每⼀个核,以达到最⾼的处理性能。

当⼀个对象被当作参数传递到⼀个⽅法后,此⽅法可改变这个对象

的属性,并可返回变化后的结果,那么这⾥到底是值传递还是引⽤传递?

值传递。Java 中只有值传递,对于对象参数,值的内容是对象的引⽤。

重载(Overload)和重写(Override)的区别

方法的重写和重载都是实现多态的方式,区别在于前者是编译时的多态性,后者是运行时的多态性。

重载:一个类中有多个同名的方法,但是具有不同的参数列表(参数类型不同、参数个数不同或者两者都不同)。

重写:发生在子类与父类之间,子类对父类的方法进行重写,参数都不能改变,返回值类型可以不相同,但必须是父类返回值的派生类,也就是外壳不变,核心重写,重写的好处在于子类可以根据需要定义特定于自己的行为。

为什么不能根据返回类型来区分重载?

如果我们有两个⽅法如下,当我们调⽤:test(1) 时,编译器⽆法确认要调⽤的是哪个。
// ⽅法1
int test(int a);
// ⽅法2
long test(int a);
⽅法的返回值只是作为⽅法运⾏之后的⼀个“状态”,但是并不是所有调⽤都关注返回值,所以不能
将返回值作为重载的唯⼀区分条件。

Java静态变量与成员变量的区别

public class Demo {
/**
* 静态变量:⼜称类变量,static修饰
*/
public static String STATIC_VARIABLE = "静态变量";
/**
* 实例变量:⼜称成员变量,没有static修饰
*/
public String INSTANCE_VARIABLE = "实例变量";
}
成员变量存储在堆内存中,而静态变量存在于方法区中。
成员变量与对象共存亡,随着对象创建而存在,随着对象被回收而释放。
静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。
成员变量所属于对象,也成为实例变量;静态变量所属于类,也叫类变量。
成员变量只能为对象所调用,静态变量可以被对象调用,也可以被类名调用。

写一下变量的执行顺序

首先,静态变量只会初始化(执行)一次;

当有父类时的完整的初始化顺序为:父类静态变量(静态代码块)-->子类静态变量(静态代码块)-->父类非静态变量(非静态代码块)-->父类构造器-->子类非静态变量(非静态代码块)-->子类构造器。

抽象类(abstract class)和接⼝(interface)有什么区别

1.抽象类只能单继承,接口可以多实现;

2.抽象类可以有构造方法,接口不能有构造方法;3.抽象类中可以有成员变量,接⼝中没有成员变量,只能有常量(默认就是 public static final)

4.抽象类中可以包含⾮抽象的⽅法,在 Java 7 之前接⼝中的所有⽅法都是抽象的,在 Java 8 之
后,接⼝⽀持⾮抽象⽅法:default ⽅法、静态⽅法等。Java 9 ⽀持私有⽅法、私有静态⽅法。
5.抽象类中的抽象⽅法类型可以是任意修饰符,Java 8 之前接⼝中的⽅法只能是 public 类型,Java 9 ⽀持 private 类型。
设计思想的区别:
        接⼝是⾃上⽽下的抽象过程,接⼝规范了某些⾏为,是对某⼀⾏为的抽象。我需要这个⾏为,我就去 实现某个接⼝,但是具体这个⾏为怎么实现,完全由⾃⼰决定。
       抽象类是⾃下⽽上的抽象过程,抽象类提供了通⽤实现,是对某⼀类事物的抽象。我们在写实现的 时候,发现某些实现类具有⼏乎相同的实现,因此我们将这些相同的实现抽取出来成为抽象类,然后 如果有⼀些差异点,则可以提供抽象⽅法来⽀持⾃定义实现。

Java中的final关键字有哪些用法

修饰类:该类不能再派生出新的子类,不能作为父类被继承,所以一个类不能被同时声明为abstract和final。

修饰方法:该方法不能被子类重写。

修饰变量:该变量必须在声明时给定初值,而在以后不能修改,只能读取,如果变量是对象则指的是引用不可修改,但是对象的属性还是可以修改的。

public class FinalDemo {
// 不可再修改该变量的值
public static final int FINAL_VARIABLE = 0;
// 不可再修改该变量的引⽤,但是可以直接修改属性值
public static final User USER = new User();
public static void main(String[] args) {
// 输出:User(id=0, name=null, age=0)
System.out.println(USER);
// 直接修改属性值
USER.setName("test");
// 输出:User(id=0, name=test, age=0)
System.out.println(USER);
}
}

阐述 final、finally、finalize 的区别。

其实是三个完全不相关的东西,只是⻓的有点像。
final 如上所⽰。
finally:finally 是对 Java 异常处理机制的最佳补充,通常配合 try、catch 使⽤,⽤于存放那些⽆论
是否出现异常都⼀定会执⾏的代码。在实际使⽤中,通常⽤于释放锁、数据库连接等资源,把资源释 放⽅法放到 finally 中,可以⼤⼤降低程序出错的⼏率。
finalize:Object 中的⽅法,在垃圾收集器将对象从内存中清除出去之前做必要的清理⼯作。
finalize()⽅法仅作为了解即可,在 Java 9 中该⽅法已经被标记为废弃,并添加新的
java.lang.ref.Cleaner,提供了更灵活和有效的⽅法来释放资源。这也侧⾯说明了,这个⽅法的设计是失败的,因此更加不能去使⽤它。

二、Java集合

集合框架图

聊一聊Java中容器体系结构

Collection:集合接口
       List :是 Collection 的子接口,用来存储有序的数据集合
       ArrayList: List 集合的实现类,底层采用动态数组进行存储数据,默认是空数组,如果存值则
扩容至 10 ,如果不够则以 1.5 倍进行扩容。
       LinkedList: List 集合的实现类,底层采用双向链表结构进行存储数据,增删数据比较方便,
速度快。
        Vector :Vector 类实现了可扩展的对象数组 , 像数组一样,它包含可以使用整数索引访问的组
件。但是, Vector 的大小可以根据需要增长或缩小,以适应在创建 Vector 之后添加和删除。
【注】 Vector 是同步的(线程安全)。 如果不需要线程安全的实现,建议使用 ArrayList 代替Vector.
       
       Set: Collection 的子接口,用来存储无序的数据集合
       HashSet: 底层采用哈希表 (HashMap) 的方式存储数据,数据无序且唯一
       TreeSet :采用有序二叉树进行存储数据,遵循了自然排序。
Map:集合接口
     HashMap: 哈希表结构存储数据, key 值可以为 null
     TreeMap: 红黑树算法的实现
     HashTable: 哈希表实现, key 值不可以为 null
     Properties:HashTable 的子类,键值对存储数据均为 String 类型,主要用来操作以 .properties
    结尾的配置文件。
  【特点】存储数据是以 key value 对进行存储,其中 key 值是以 Set 集合形式存储,唯一且不可重复。
    value 是以 Collection 形式存储,可以重复。

Hashmap底层原理

Hashmap 数组+(单向)链表 1.7,JDK 1.8 数组_链表+红黑树,

hashMap存值,时候先把key hash计算找到数组的位置,数组new出来的时候0,第一次存值时,会设置上默认大小长度16的数组hash计算的就是这个key应该存在你这个数组的那个下标位置,如果该数组位置是的空的就直接存入,如果该数组位置不为空,调用equals方法判断2个key是否为一样,如果不一样就会形成链表,在链表上存,如果是一样的就不存进去,如果数组长度大于64,并且链表大于8就会转为红黑树,当链表小于6的时候重新转为链表结构

只有数组长度大于64,并且链表长度大于8才会转红黑,否则只是链表大于8优先数组扩容

Hashtable和HashMap的区别

Hashtable 线程安全,HashMap线程不安全

hashtable 可以不能为null,hashMap都可以为null

hashtable现在已经不使用,如果需要使用线程安全map,应该使用ConcurrentHashMap

LinkedList底层原理

       LinkedList的底层是通过链表来实现的,是一个双向链表结构,对于新增节点和删除节点效率高,Node 里面分别有当前值 上一个值 下一个值因此它的随机访问速度是比较差的,但是它的删除,插入操作会很快。

  • LinkedList是基于双向循环链表实现的,除了可以当做链表来操作外,它还可以当做栈、队列和双端队列来使用。
  • LinkedList同样是非线程安全的,只在单线程下适合使用。
  • LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。

HashMap 底层有顺序吗

HashMap 底层存储,没有顺序,存储时,根据key值的hash结果找对应数组下标,所以存储时没有顺序。

HashMap null排在数组的那个位置

当存储null值key的时候,他是无法hash,所以固定存储在数组第一个位置下标0的位置。

HashMap的红黑树,当他链表小于6就一定会转为链表吗?

HashMap的链表并不是 小于6就会转为链表,必须是触发resize方法,并且链表长度小于6才会转为链表。

ListSetMap的区别,以及各自的优缺点

List :
        可以允许重复的对象。可以插入多个 null 元素。是一个有序容器,保持了每个元素的插入顺
序,输出的顺序就是插入的顺序。
常用的实现类有 ArrayList LinkedList Vector ArrayList 最为流行,它提供了使用索引的
随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
Set :
        不允许重复对象。无序容器,你无法保证每个元素的存储顺序, TreeSet 通过 Comparator
Comparable 维护了一个排序顺序。只允许一个 null 元素。
Set 接口最流行的几个实现类是 HashSet LinkedHashSet 以及 TreeSet 。最流行的是基于
HashMap 实现的 HashSet TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其
compare() compareTo() 的定义进行排序的有序容器。而且可以重复。
Map :
         Map 不是 collection 的子接口或者实现类。 Map 是一个接口。
Map 的 每个 Entry 都持有两个对象,也就是一个键一个值, Map 可能会持有相同的值对象但
键对象必须是唯一的。
TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
Map 接口最流行的几个实现类是 HashMap LinkedHashMap Hashtable TreeMap
HashMap TreeMap 最常用)

ArrayList的底层原理

        ArrayList作为List的典型实现,完全实现了List的全部接口功能,它是基于数组实现的List类,它封装了一个Object[]类型的数组,长度可以动态的增长。如果在创建ArrayList时没有指定Object[]数组的长度,它默认创建一个长度为10的数组,当新添加的元素已经没有位置存放的时候,ArrayList就会自动进行扩容,扩容的长度为原来长度的1.5倍。它的线程是不安全的。

HashSet的底层原理

  1. HashSet底层是HashMap,第一次添加时,table的数组扩容到16,临界值(threshold)是16 * 加载因子(loadFactor是0.75)=12
  2. 如果table数组使用到了临界值12,就会扩容到16 * 2 = 32,新的临界值就是32 * 0.75 = 24,依次类推
  3. Java8以后,如果一条链表中的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行数化(红黑树),否则仍然采用数组扩容机制

HashMap的扩容机制

为什么会需要扩容:
       当它在初始化时会创建一个容量为 16 的数组,当元素数量超过阈值(当前容量 X 加载因子 (
常为 0.75)= 扩容阈值)时,会将数组大小扩展为当前容量的两倍。
       但是 HashMap 的容量是有上限的。如果 hashmap 的数组当前容量达到了 1073741824 ,则该
数组不会再增长。且阈值将被设置为 Integer.MAX_VALUE 2^31-1 )即永远不会超出阈值。
Hashmap 的扩容机制在 JDK8 中,容量变化通常有以下集中情况:
       无参构造:实例化的 hashmap 默认内部数组是 null ,也就是无实例化。第一次调用 put 方法
时,会开始第一次初始化扩容,长度为 16.
       有参构造:用于指定容量。会根据指定的正整数找到不小于指定容量的 2 的幂数,将这个数设
置赋值给阈值。第一次调用 put 方法时,会将阈值赋值给容量,然后让 阈值 = 容量 X 负载因子。
因此并不是手动指定了容量就一定不会触发扩容,超过阈值后一样扩容。
       如果不是第一次扩容,则容量变为原来的 2 倍,阈值也变为原来的 2 倍。但负载因子不变。
       补充: 首次 put 时会以扩容的方式初始化,然后存入数据,之后判断是否需要扩容
       如果不是首次 put ,则不咋需要初始化,会直接存入数据,然后判断是否需要扩容。
元素迁移 :
JDK8 中,由于数组的容量是以 2 的幂次方扩容的,那么一个数组在扩容时,一个元素要么在
原位置要么在原长度 + 原位置的位置。
数组长度变为原来的 2 倍表现在二进制上多了一个高位参与数组下标确定。此时,一个元素通
hash 转换坐标的方法计算后会出现一个现象:
最高位是 0 ,则坐标不变,最高位是 1 则坐标变为 10000+ 原坐标,即原长度 + 原坐标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值