【1】Java基础面试总结

210 阅读18分钟

JDK、JRE、JVM之间的区别

JDK包含了JRE,JRE包含了JVM

  • JVM是Java跨平台的核心,解释字节码文件(.class)
  • JRE是Java运行环境,所有Java程序必须依赖JRE才能运行
  • JDK是Java的核心,包含运行Java运行环境(JRE)和一些Java工具及Java基础类库 。

Java有哪些数据类型?

基本数据类型:

  • 数值型
    • 整数类型(byte,short,int,long)
    • 浮点类型(float,double)
  • 字符型(char)
  • 布尔型(boolean)

引用数据类型:

  • 类(class)
  • 接口(interface)
  • 数组([])

== 和 equals 的区别是什么?

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)

equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

  • 情况 1:类没有覆盖 equals()方法。则通过 equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。

  • 情况 2:类覆盖了 equals()方法。一般,我们都覆盖 equals()方法来两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。

  • String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。

  • 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

int 和 Integer 有什么区别?

  • Integer是int的包装类,int则是java的一种基本数据类型
  • Integer变量必须实例化后才能使用,而int变量不需要
  • Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
  • Integer的默认值是null,int的默认值是0

java 中操作字符串都有哪些类?它们之间有什么区别?

java中操作字符串的类,分别是String,StringBufferStringBuilder.

  • 这三个类都是以char[]的形式保存的字符串,

  • String类型的字符串是不可变的,对String类型的字符床做修改操作都是相当于重新创建对象.

  • 对StringBuffer和StringBuilder进行增删操作都是对同一个对象做操作.

  • StringBuffer中的方法大部分都使用synchronized关键字修饰,所以StringBuffer是线程安全的,

  • StringBuilder中的方法则没有,线程不安全,但是StringBuilder因为没有使用使用synchronized关键字修饰,所以性能更高,

  • 在单线程环境下我会选择使用StringBuilder,多线程环境下使用StringBuffer.

  • 如果声明的这个字符串几乎不做修改操作,那么我就直接使用String,因为不调用new关键字声明String类型的变量的话它不会在堆内存中创建对象,直接指向String的常量池,并且可以复用.效率更高.

如何将字符串反转?

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

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

  • indexOf():返回指定字符的索引。

  • charAt():返回指定索引处的字符。

  • replace():字符串替换。

  • trim():去除字符串两端空白。

  • split():分割字符串,返回一个分割后的字符串数组。

  • getBytes():返回字符串的 byte 类型数组。

  • length():返回字符串长度。

  • toLowerCase():将字符串转成小写字母。

  • toUpperCase():将字符串转成大写字符。

  • substring():截取字符串。

  • equals():字符串比较

为什么重写 equals 时必须重写 hashCode 方法?

hashCode()和 equals()定义在Object类中,所以Java中的任何类都包含有hashCode()方法和equals(),hashCode()作用是获取哈希码,实际上是返回一个 int 整数。用来确定该对象在哈希表中的索引位置。

我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

hashCode()与 equals()的相关规定

  1. 如果两个对象相等,则 hashcode 一定也是相同的
  2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
  3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
  4. 因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
  5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

深拷贝和浅拷贝

  • 浅拷贝🥝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝。
  • 深拷贝🥝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。

IO流的分类?

  • 按照流的流向分,可以分为输入流和输出流;

  • 按照操作单元划分,可以划分为字节流和字符流;

  • 按照流的角色划分为节点流和处理流。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

BIO、NIO、AIO 有什么区别?

BIO(blocking I/O 同步并阻塞)

Java BIO就是传统的 socket编程.

服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器 端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程 池机制改善(实现多个客户连接服务器)。

问题:

  • 1.每个请求都需要创建独立的线程,与对应的客户端进行数据 Read,业务处理,数据 Write
  • 2.并发数较大时,需要创建大量线程来处理连接,系统资源占用较大
  • 3.连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费

NIO(同步非阻塞)

同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到 多路复用器上,多路复用器轮询到连接有 I/O 请求就进行处理

NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)

Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的 数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可 以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某 通道,但不需要等待它完全写入, 这个线程同时可以去做别的事情。通俗理解:NIO 是可以做到 用一个线程来处理多个操作的。假设有 10000 个请求过来,根据实际情况,可以分配50 或者 100 个 线程来处理。不像之前的阻塞 IO 那样,非得分配 10000 个

AIO(异步非阻塞)

客户端以事件驱动的形式通知服务端

NIO和 BIO的比较

  • BIO 以流的方式处理数据,而 NIO 以缓冲区的方式处理数据,缓冲区 I/O 的效率比流 I/O 高很多
  • BIO 是阻塞的,NIO则是非阻塞的
  • BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据 总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的 事件(比如:连接请求, 数据到达等),因此使用单个线程就可以监听多个客户端通道

NIO 三大核心原理

  • 1.每个 channel 都会对应一个 Buffer
  • 2.Selector 对应一个线程, 一个线程对应多个 channel(连接) 3. 每个 channel 都注册到 Selector选择器上
  • 4.Selector不断轮询查看Channel上的事件, 事件是通道Channel非常重要的概念
  • 5.Selector 会根据不同的事件,完成不同的处理操作
  • 6.Buffer 就是一个内存块 , 底层是有一个数组
  • 7.数据的读取写入是通过 Buffer, 这个和 BIO , BIO 中要么是输入流,或者是输出流, 不能双向,但是
  • 8.NIO 的 Buffer 是可以读也可以写 , channel 是双向的

反射的理解?

反射是⼀种能够在程序运⾏时动态访问,修改某个类中任意属性和方法的机制(包括private实例和方法)

  • 在运⾏时判断任意⼀个对象所属的类
  • 在运⾏时构造任意⼀个类的对象
  • 在运⾏时判断任意⼀个类所具有的成员变量和方法
  • 在运⾏时调⽤任意⼀个对象的方法

反射涉及到的四个核心类:

  • java.lang.Class.java:类对象;
  • java.lang.reflect.Constructor.java:类的构造器对象;
  • java.lang.reflect.Method.java:类的⽅法对象;
  • java.lang.reflect.Field.java:类的属性对象;

反射有什么用?

  • 操作因访问权限限制的属性和⽅法;
  • 实现⾃定义注解;
  • 动态加载第三⽅jar包
  • 按需加载类;

java集合框架

list、set、map的区别?

  • list 存储数据是有序的,并且可重复
  • set 存储数据是无序的,并且不可重复
  • map 使用键值对(key-value)存储,key是无序的不可重复,value是无序的,可重复的,一个key只对应一个value。

list 接口有三个实现类

  • LinkedList 基于链表实现的,增删快,查找慢
  • ArrayList 基于数组实现 非线程安全效率高,增删慢查找快
  • Vector 基于数组实现,线程安全,效率低增删慢,查找也慢

Map 接口有四个实现类

  • HashMap 基于hash表的Map接口实现,非线程安全,高效支持null值和null键
  • HashTable 线程安全 低效不支持null 值 和null键
  • LinkedHashMap 是HashMap的一个子类 保存记录了插入的顺序
  • ConcurrentHashMap 线程安全的map
  • TreeMap 能够把它保存的记录根据键排序,默认是键值的升序排序

Set接口有两个实现类

  • HashSet 底层是由hashmap实现的,不予许有重复的值 ,使用该方式的时候需要重写 equals() 和 hashcode()方法
  • LinkedHashSet 继承与 HashSet 同时又根据 LinkedHashMap 来实现底层使用的是LinkedHashMap

除了vector能实现线程安全list?

1.加锁

sync关键字加在函数上或者是使用ReentrentLock这种可重入锁锁定代码块都可以。

synchronized和Lock区别

  • synchronized是关键字,而Lock是接口
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁
  • synchronized自动释放锁(a线程执行完同步代码会自动释放锁,b线程执行过程中发生异常会释放锁)lock需在finally中手动释放锁(unlock()方法释放锁),否则容易造成线程死锁。
  • synchronized关键字的两个线程1和线程2,若当前线程1获得锁,线程2等待,如果线程1阻塞,线程2会一直等待下去。而lock锁不一定会等待下去,如果尝试获得不到锁,线程可以不用一直等待就结束了

2.Collections.synchronizedList()

 ArrayList<String> list = new ArrayList<>();
 List<String> sycList = Collections.synchronizedList(list);

ynchronizedList线程安全的原因是因为它几乎在每个方法中都使用了synchronized同步锁。

3.CopyOnWriteArrayList

底层通过复制数组的方式来实现,加锁由ReentrantLock来完成。

synchronizedList适合对数据要求较高的情况,但是因为读写全都加锁,所有效率较低。 CopyOnWriteArrayList效率较高,适合读多写少的场景,因为在读的时候读的是旧集合,所以它的实时性不高。

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 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
  • 5.内存空间占用: ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

作者:JavaGuide

HashMap的底层实现?

JDK1.8之前

JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(HashMap 采用 “拉链法也就是链地址法” 解决冲突)

JDK1.8之后

JDK1.8之后 数组 + 链表 + 红黑树,在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。

HashMap 基于 Hash 算法实现的

  • 1.当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
  • 2.存储时,如果出现hash值相同的key,此时有两种情况。
    • (1)如果key相同,则覆盖原始值;
    • (2)如果key不同(出现Hash冲突),则将当前的key-value放入链表中
  • 3.获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
  • 4.理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。

作者:小杰要吃蛋

红黑树

juejin.cn/post/684490…

如何解决hash冲突总结:

  • 链表法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位;
  • 开放地址法是通过一个探测算法,当某个槽位已经被占据的情况下继续查找下一个可以使用的槽位。

扩容机制

扩容机制,即调用了resize() 方法进行扩容,会伴随着一次重新 hash 分配,并且会遍历 hash 表中所有的元素,是非常耗时的。在编写程序中,要尽量避免触发扩容机制

与扩容机制相关的几个参数

  • 负载因子loadFactor,其默认值是 0.75
  • 数组大小capacity,刚创建时默认值时16
  • 临界值threshold,超过这个值表示数组可以进行扩容了,这个值由capacity*loadFactor 得到
  • 已存储Node的数量size

扩容的时候大致做了以下几件事

  1. 判断目前的容量是多少,一开始的时候是 null,第一次put() 的时候进行容量初始化,默认值是16,也可以是我们自定义的值,需要注意的是,假设我们给定的值是3,他会找大于三的2的幂来替换,也就是4,所以指定值最好是2的幂数
  2. 判断目前的容量是否大于限制的最大值,如果是的话设置thresholdInteger.MAX_VALUE,并且不触发扩容机制,随你去吧,太大了忍不了了
  3. 如果没有超过最大值,就扩充为原来的两倍,临界值threshold 也同时扩充两倍
  4. 将旧的 HashMap 中的元素迁移至新创建的 HashMap 中

能否使用任何类作为 Map 的 key?

可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点:

  • 重写hashCode()和equals()方法
  • 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。
  • 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。

为什么HashMap中String、Integer这样的包装类适合作为K?

String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率

  • 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
  • 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范不容易出现Hash值计算错误的情况;

线程安全的map?

  • HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap );
  • 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;(如果你要保证线程安全的话就使用 ConcurrentHashMap );
  • Collections.synchronizedMap

HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

juejin.cn/post/698684…

Java8新特性?

函数式接口和Lambda表达式

函数式接口主要指只包含一个抽象方法的接口

@FunctionalInterface注解来定义函数式接口

接口名称方法声明功能介绍
Runnablevoid run()既没有参数又没有返回值的方法
CallableV call()没有参数有返回值的方法
Consumervoid accept(T t)根据指定的参数执行操作
Function<T,R>R apply(T t)根据指定的参数执行操作并返回
SupplierT get()得到一个返回值
Comparatorint compare(T o1, T o2)比较
Predicateboolean test(T t)判断指定的参数是否满足条件

Stream接口

  • 对集合功能的增强,可以对集合元素进行复杂的查找、过滤、筛选等操作。

  • Stream接口借助于Lambda 表达式极大的提高编程效率和程序可读性,同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势。

常见的方法有,filter过滤,map遍历,sorted排序

接口的默认方法和静态方法

在接口中可以使用default和static关键字来修饰接口中定义的普通方法

Optional类

可以避免显式的null值判断,避免空指针异常

改进时间、日期的处理

LocalDate LocalTime LocalDateTime Clock

JVM和调优

Java中有哪些类加载器

  • 启动类加载器: Bootstrap ClassLoader
  • 扩展类加载器: Extension ClassLoader
  • 应用程序类加载器: Application ClassLoader
  • 自定义类加载器

双亲委派机制

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

双亲委派机制过程?

  1. 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

  2. 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

  3. 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

  4. 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。