一、Java基础系列面试题
JDK 和 JRE 有什么区别?
jre是java程序运行的环境,jdk是java开发所需要的环境,jdk包含jre
== 和 equals 的区别是什么
==和Object的equals都是比较的地址,但有一些类会重写equals方法,string中会比较两个字符串的值
两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
不对,hashcode和equals是两个方法,没有重写的equals比较的是对象的地址值,hashcode比较的是对象地址的哈希值。如果目标对象重写了equals保证相同内容的两个对象返回true,而这个对象有可能需要被放入到散列表中(hashmap等),那么需要重写hashcode方法保证两个对象返回的哈希值也是相同的,不然可能会出现一个map中出现两个key 相同的对象。
hashcode相等两个类一定相等吗?
- hashcode方法和equals方法没有重写时
- hashcode相等两个类不一定相等
- equals返回true的两个类一定相等(为同一个对象)
- 两个类相等hashcode不一定相等
- 两个类相等equals不一定返回true
- hashcode方法和equals方法均已按规范重写时
- hashcode相等两个类不一定相等(存在哈希冲突)
- equals返回true的两个类一定相等
- 两个类相等hashcode一定相等
- 两个类相等equals一定返回true
java对重写equals方法的要求
1. 对称性:如果x.equals(y)返回是"true",那么y.equals(x)也应该返回是"true"。 2. 反射性:x.equals(x)必须返回是"true"。 3. 类推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也应该返回是"true"。 4. 一致性:如果x.equals(y)返回是"true",只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"。 5. 非空性,x.equals(null),永远返回是"false";x.equals(和x不同类型的对象)永远返回是"false"。
final 在 Java 中有什么作用?
final修饰的类不能被继承,final修饰的方法不能被重写,final修饰的变量不能被修改
final修饰引用类型时,其引用的地址中存储的值是允许改变的。
Java 中的 Math. round(-1. 5) 等于多少?
等于1,四舍五入原理是+0.5,然后向下取整
String 属于基础的数据类型吗?
基础类型就八个,其他都是引用类型
Java 中操作字符串都有哪些类?它们之间有什么区别?
String,StringBuilder,StringBuffer
String声明的字符串是final类型的,也就是个常量,底层是final类型的char数组
StringBuilder和StringBuffer都是用来字符串拼接操作,value不是final类型的,builder是非线程安全的,buffer是线程安全的,因为用synchronized修饰了方法
在涉及较少的修改操作时,尽可能用string类不通过new关键字去创建字符串,这样不会在堆空间产生对象,而是在String的常量池中生成,可以复用,效率更高
String str="i"与 String str=new String(“i”)一样吗?
不一样,前者在常量池中生成,后者在堆空间生成;字符串常量在编译器就已经确定,所以String str = "a" + "b";也会放在常量池中
如何将字符串反转?
StringBuilder的reverse()方法:new StringBuffer(s).reverse().toString();源码大概就是从中间往两边循环,两两交换。
用一个char数组去逆序遍历,通过stringbuilder去拼接append;
递归二分法,终止条件是s,len == 0
public static String reverse1(String s) {
int length = s.length();
if(length <= 1){
return s;
}
String left = s.substring(0, length / 2);
String right = s.substring(length / 2, length);
return reverse1(right) + reverse1(left);
}
String 类的常用方法都有那些?
(1).indexOf():返回指定字符的索引。
(2).charAt():返回指定索引处的字符。
(3).replace():字符串替换。
(4).trim():去除字符串两端空白。
(5).split():分割字符串,返回一个分割后的字符串数组。
(6).getBytes():返回字符串的 byte 类型数组。
(7).length():返回字符串长度。
(8).toLowerCase():将字符串转成小写字母。
(9).toUpperCase():将字符串转成大写字符。
(10).substring():截取字符串。
(11).equals():字符串比较。
抽象类必须要有抽象方法吗?
不是必须的,但有抽象方法一定要声明类是个抽象类,且抽象类中可以有具体方法;接口现在也可以通过default关键字声明一个具体实现的方法。abstract和stataic不能同时修饰方法,因为abstract是抽象方法,需要被子类实现的,但static是静态方法,类独有的,不能被子类重写,所以冲突。
普通类和抽象类有哪些区别?
普通类中所有方法都是具体实现了的,抽象类可以有具体方法,也可以有抽象方法,抽象类和抽象方法都需要abstract关键字修饰。普通类可以创建对象,抽象类不能。
抽象方法需要暴露出来给子类重写,所以不能用static,private,final修饰
抽象类能使用 final 修饰吗?
不能
接口和抽象类有什么区别?
接口和抽象类分别用interface和abstract修饰,抽象类相关的参照11,12,13.
接口需要用interface替代class修饰,接口中的普通方法都是默认public和abstract修饰的(可以不写),同抽象类一样,接口中方法都需要暴露出去给实现类实现,所以也不能用static,private,final修饰。
实现类通过implement实现接口,实现方法具体实现。
接口中定义的变量都是public和static修饰的常量(可以不写修饰符),抽象类则和普通类一样。
关于接口中的default和static:
static和普通类中的static一样,都是静态方法,通过类名引用;
default方法默认public,可以被子类继承;如果子类同时实现两个接口中有同名default方法,则子类必须重写;如果子类继承的类和实现的接口也有同名方法,则优先继承实现类的方法
Java 中 IO 流分为几种?
BIO、NIO、AIO 有什么区别?
- 同步,就是我调用一个功能,该功能没有结束前,我死等结果。
- 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)。
- 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
- 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
BIO表示同步阻塞式IO,一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO表示同步非阻塞IO,一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO表示异步非阻塞IO,一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成IO操作后再通知服务器应用来启动线程进行处理。
应用场景:
- BIO适用于连接数目比较小且固定的架构,该方式对服务器资源要求比较高,JDK 1.4以前的唯一选择。
- NIO适用于连接数目多且连接比较短(轻操作)的架构,如聊天服务器,编程复杂,JDK 1.4开始支持,如在Netty框架中使用。
- AIO适用于连接数目多且连接比较长(重操作)的架构,如相册服务器,充分调用操作系统参与并发操作,编程复杂,JDK 1.7开始支持。
备注:在大多数场景下,不建议直接使用JDK的NIO类库(门槛很高),除非精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,可以使用NIO框架Netty来进行NIO编程,其既可以作为客户端也可以作为服务端,且支持UDP和异步文件传输,功能非常强大。
Files的常用方法都有哪些?
Files. exists():检测文件路径是否存在。
Files. createFile():创建文件。
Files. createDirectory():创建文件夹。
Files. delete():删除一个文件或目录。
Files. copy():复制文件。
Files. move():移动文件。
Files. size():查看文件个数。
Files. read():读取文件。
Files. write():写入文件。
二、容器系列面试题
Java 容器都有哪些?
Collection 和 Collections 有什么区别?
Collection是接口,Collections是工具类。
Collection提供集合的统一规范,Collections提供了一些静态方法,提供排序,搜索等。
List、Set、Map 之间的区别是什么?
有序:一组元素添加进集合的顺序和在集合中的顺序是一样的。
无序:一组元素添加进集合后,会在集合中按照某种规则进行排序。
List和Set都实现了Collection接口,List提供有序的集合,Set提供无序的集合(LinkedHashSet按照插入排序,SortedSet可排序)。
List允许可重复数据,Set和Map不允许重复数据。
Map提供哈希表的顶层接口,key-value形式存储数据
HashMap 和 Hashtable 有什么区别?
Hashtable
- 初始size为11,扩容:newsize = olesize*2+1
- 方法用synchronized修饰,是线程安全
- 不允许键或者值是null
HashMap
- 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
- 线程不安全
- 允许键和值是null
- 如果链表大小超过8,链表就会被改造为树形结构。
如何决定使用 HashMap 还是 TreeMap?
什么时候使用特定的map取决于他们各自的特点。TreeMap要求作为key的对象必须实现Comparable接口,重写compareTo方法,所以内部是按照key升序排列的,底层实现是红黑树;而HashMap存储数据是无序的,通过hash值和容器长度计算元素位置hash & (size - 1)。
所以需要排序的时候要用TreeMap,否则用HashMap。
说一下 HashMap 的实现原理?
首先是数据结构,HashMap用数组+链表/红黑树的方式进行存储数据。
当一个key-value进入map时,首先计算出key的hash值,然后通过hash值和容器长度-1进行与&运算,得到元素应该放的位置,如果对应的index位置上是空的,就直接放上去。如果已经有元素了,先看看key是不是相同,相同的话用新value替换old-value;如果key不相同往后追加链表节点,当链表节点超过8个,链表转换为红黑树。
HashMap中存储数据的主干是Entry数组,且长度默认是2的n次方,因为这样可以减少hash冲突(具体计算方式为hash & (size - 1))。扩容方法:先拿oldTable看下是否为空,为空说明map还没初始化,就初始化一个16大小的数组;否则扩容为原来2倍大小。具体看问题31.
说一下 HashSet 的实现原理?
HashSet用HashMap作为实际存储数据的容器,对于add进来的值作为key,然后用一个final修饰的Object对象存入HashMap中,set中几乎所有操作都是基于map的函数调用的。
说一下 ConcurrentHashMap 的实现原理?
首先底层实现:和HashMap一样,也是Node数组作为散列表,后面跟链表/红黑树。线程安全的原因是因为用CAS+synchronized关键字的方式处理。put的时候进行初始化,如果对应的散列表位置上个没有值就通过CAS方式放入,如果有就对该节点加synchronized锁,插入新节点;当长度为8时就变为红黑树,节点类型变为TreeBin。
参考关于Java中的ConcurrentHashMap的实现原理有大神可以详细介绍下吗? - 知乎
ArrayList 和 LinkedList 的区别是什么?
都是线程不安全的。ArrayList底层是动态数组,LinkedList是链表;因为数组支持通过下标直接访问数据,所以查询性能上ArrayList更佳;对于插入和删除操作,如果是对于尾部的插入,数组比链表更快,因为ArrayList不需要额外的移动元素,如果是对于随机数据的插入,因为链表的插入删除只需要改变指针指向,而数组需要重新移动数据的位置,所以LinkedList效率更高。
ArrayList 和 Vector 的区别是什么?
底层实现:都是数组。
线程安全:Vector 的方法都是同步的,线程安全,方法上都有synchronized;ArrayList 非线程安全,但性能比Vector好
扩容机制:默认初始化容量都是10。Vector 扩容默认是原来的2倍,可指定扩容的大小;ArrayList扩容是原来的1.5倍
Array 和 ArrayList 有何区别?
Array指的是数组,数组有的特点ArrayList都有。
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList是动态扩容的(其实就是帮你new了一个新的复制老数据过去)
ArrayList提供了很多方法,也都是在数组的基础上进行的。
在 Queue 中 poll()和 remove()有什么区别?
Queue是一个FIFO的数据结构,在头删除,在尾插入。
也要看实现了Queue的子类是如何处理这些方法的,比如LinkedList由于是链表,所以不存在队列满的情况。
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null
哪些集合类是线程安全的?
- Vector
- Stack
- Hashtable
- java.util.concurrent 包下所有的集合类 ArrayBlockingQueue、ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque...
HashMap扩容如何实现的?
参考:Hashmap实现原理及扩容机制详解_lkforce的博客-CSDN博客_hashmap扩容
大概就是扩容发生在两个时机:
一个是初始化的时候,如果没有指定容量,那么按照默认16的大小初始化,如果指定了,按照最近的2进制数向上转换。
另一个发生在put过程,如果添加完元素的大小超过了阈值,就进行resize()。
先判断旧数组的长度如果大于Integer.MAX_VALUE,就把阈值设置为Integer.MAX_VALUE,然后返回旧数组;如果旧数组长度扩大为原来2倍仍然小于Integer.MAX_VALUE且大于默认初识容量,则把阈值扩大为原来两倍。阈值设置完后,判断如果旧数组的阈值大于0,就把阈值赋值给新数组的容量;初始化的时候阈值为0,所以此处要进行初始化新数组的阈值(容量乘以负载因子)和容量。至此,新容量和新阈值确定完毕,下一步就是要移动数据。
如果旧数组不为空(就是说不是初始化 时候),从0遍历旧数组,取出来每一个位置上的node,判断如果不为空说明有数据。然后分为几种情况:第一种:node的next为空,就是说这个位置只有一个节点,没有链表,那就直接计算出该节点在新数组中的位置并放入。第二种:如果当前节点是红黑树,那就按照红黑树的方式插入(后续再说)。第三种:后面跟了链表就看图吧。这块需要结合HashMap线程不安全的地方一起看。
Iterator 怎么使用?有什么特点?
- java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
- next() 方法获得集合中的下一个元素
- hasNext() 检查集合中是否还有元素
- remove() 方法将迭代器新返回的元素删除
- forEachRemaining(Consumer<? super E> action) 方法,遍历所有元素
说一下红黑树?
红黑树是一种自平衡的二叉查找树AVL,也就是说二叉查找树的特点它都有:左子树的节点值都小于当前节点值,右子树节点值都大于当前节点值,左右子树也都是二叉查找树。由于AVL在删除和插入时要进行复杂的平衡操作,所以红黑树应用更广泛。
红黑树特性:
1.每个节点非红即黑。
2.根节点总是黑色的。
3.如果节点是红色的,那么它的子节点必须是黑色的(反之不一定)。
4.每个叶子节点都是黑色的空节点(NIL节点)。
5.从根节点到叶节点或空节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
新增节点为N,父节点为P,叔父节点为U,祖父节点为G