Java面试知识点
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
工作也有好些年了从刚毕业到前几年看过无数的面试题总想着自己写一个面试总结随着自我认识的变化一些知识点的理解也越来越不一样了。写下来温故而知新。很多问题可能别人也总结过但是答案不尽相同如有问题欢迎指正以备完善。
现在的问题还不是很全面会持续更新大家有遇到一些面试题的话也可以评论出来一起完善。
1. JDK与JRE的区别
- JDKJava development kitJava开发工具包JREJava runtime environmentJava运行环境。JDK中包含JREJDK中有一个名为jre的目录里面包含两个文件夹bin和libbin就是JVMlib就是JVM工作所需要的类库。
- JRE包含了java虚拟机、java基础类库。是使用java语言编写的程序运行所需要的软件环境是提供给想运行java程序的用户使用的。JDK是程序员使用java语言编写java程序所需的开发工具包是提供给程序员使用的。运行java程序只需安装JRE。如果需要编写java程序需要安装JDK。
2. final修饰符的作用
1修饰一个引用
如果引用为基本数据类型则该引用为常量该值无法修改
如果引用为引用数据类型比如对象、数组则该对象、数组本身可以修改但指向该对象或数组的地址的引用不能修改。
如果引用是类的成员变量则必须当场赋值否则编译会报错。
2用来修饰一个方法
当使用final修饰方法时这个方法将成为最终方法无法被子类重写。但是该方法仍然可以被继承。
3用来修饰类
当用final修改类时该类成为最终类无法被继承。比如常用的String类就是最终类。
3. Math相关方法
Math.ceil向上取整Math.floor向上取整Math.round数值加0.5后向下取整。
Math.ceil(11.3) = 12;
Math.ceil(-11.3) = 11;
Math.floor(11.3) = 11;
Math.floor(-11.3) = -12;
Math.round(11.3) = 11;
Math.round(11.8) = 12;
Math.round(-11.3) = -11;
Math.round(-11.8) = -12;
4. String str="i"与 String str=new String(“i”
不一样String str="i"会将其分配到常量池中常量池中没有重复的元素如果常量池中有i就将i的地址赋给变量如果没有就创建一个再赋给变量。String str=new String(“i”)会将对象分配到堆中即使内存一样还是会重新创建一个新的对象。
5. 如何将字符串翻转
这儿提供两种方式stringbuilder.reverse及char[]。
public static void main(String[] args) {
String abc = "hello world";
StringBuilder stringBuilder = new StringBuilder(abc);
String reverse = stringBuilder.reverse().toString();
System.out.println(reverse);
}
public static void main(String[] args) {
String abc = "hello world";
char[] chars = abc.toCharArray();
char[] newd = new char[abc.length()];
for (int i = 0; i < chars.length; i++) {
char aChar = chars[chars.length - i - 1];
newd[i]=aChar;
}
String s = String.valueOf(newd);
System.out.println(s);
}
6. 普通类和抽象类的区别
- 抽象类的存在是为了被继承不能实例化而普通类存在是为了实例化一个对象。
- 抽象类的子类必须重写抽象类中的抽象方法而普通类可以选择重写父类的方法也可以直接调用父类的方法。
- 抽象类必须用abstract来修饰普通类则不用。
- 普通类和抽象类都可以含有普通成员属性和普通方法。
- 普通类和抽象类都可以继承别的类或者被别的类继承。
- 普通类和抽象类的属性和方法都可以通过子类对象来调用。
7. 接口与抽象类的区别
- 抽象类和接口都不能直接实例化。如果要实例化抽象类变量必须指向实现所有抽象方法的子类对象接口变量必须指向实现所有接口方法的类对象。
- 抽象类要被子类继承接口要被类实现。
- 接口只能做方法申明抽象类中可以做方法申明也可以做方法实现。
- 接口里定义的变量只能是公共的静态的常量抽象类中的变量是普通变量。
- 抽象类里的抽象方法必须全部被子类所实现如果子类不能全部实现父类抽象方法那么该子类只能是抽象类。同样实现接口的时候如不能全部实现接口方法那么该类也只能为抽象类。
- 抽象方法只能申明不能实现。
- 抽象类里可以没有抽象方法
- 如果—个类里有抽象方法那么这个类只能是抽象类
- 抽象方法要被实现所以不能是静态的也不能是私有的。
8. JAVA中IO流分类及区别
按照数据的流向输入流读数据。输出流写数据
按照数据类型来分
字节流字节输入流Inputstream字节输出流Outputstream
字符流字符输入流Reader字符输出流Writer
- 字符流和字节流是根据处理数据的类型的不同来区分的。
- 字节流按照8位传输字节流是最基本的所有文件的储存是都是字节byte的储存在磁盘上保留的并不是文件的字符而是先把字符编码成字节再储存这些字节到磁盘。
- 字节流可用于任何类型的对象包括二进制对象而字符流只能处理字符或者字符串
- 字节流提供了处理任何类型的IO操作的功能但它不能直接处理Unicode字符而字符流就可以。
- 理论上任何文件都能够用字节流读取但当读取的是文本数据时为了能还原成文本你必须再经过一个转换的工序相对来说字符流就省了这个麻烦可以有方法直接读取。所以如果是处理纯文本数据就要优先考虑字符流除此之外都是用字节流。
9. 什么是反射
JAVA反射机制是在运行状态中对于任意一个类都能够知道这个类的所有属性和方法对于任意一个对象都能够调用它的任意一个方法和属性这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法所以先要获取到每一个字节码文件对应的Class类型的对象。
反射就是把java类中的各种成分映射成一个个的Java对象。
反射这儿比较重要再次不详细展开可查看相关链接
Java基础-反射
10. 什么是为什么要怎么实现序列化
如果我们需要持久化Java对象比如将Java对象保存在文件中或者在网络传输Java对象这些场景都需要用到序列化。简单来说
序列化 将数据结构或对象转换成二进制字节流的过程。
反序列化将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程。
常见的序列化实现方式为JDK 自带的序列化只需实现 java.io.Serializable接口即可。
序列化号 serialVersionUID 属于版本控制的作用。序列化的时候serialVersionUID也会被写入二级制序列当反序列化时会检查serialVersionUID是否和当前类的serialVersionUID一致。如果serialVersionUID不一致则会抛出 InvalidClassException 异常。强烈推荐每个序列化类都手动指定其 serialVersionUID如果不手动指定那么编译器会动态生成默认的序列化号。日常使用指定为1L即可。
11. throw与throws的异同
较为基础的问题日常开发过程中会较多用到的异常处理方式。
- 不同点
位置不同。throws用在函数上后边跟的是异常类可以跟多个异常类。throw用在函数内后面跟的是异常对象。
功能不同。①throws用来声明异常让调用者只知道该功能可能出现的问题可以给出预先得处理方式。throw抛出具体的问题对象执行到throw。功能就已经结束了跳转到调用者并将具体的问题对象抛给调用者也就是说throw语句独立存在时下面不要定义其他语句因为执行不到。②throws表示出现异常的一种可能性并不一定会发生这些异常throw则是抛出了异常执行throw则一定抛出了某种异常对象。 - 相同点
两者都是消极处理异常的方式只是抛出或者可能抛出异常但是不会由函数去处理异常真正的处理异常由函数的上层调用处理。
12. try-catch-finally-return的执行顺序
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
int abc = 1/0;
System.out.println("try");
return 0;
} catch (Exception e) {
System.out.println(e.getMessage());
return 1;
} finally {
System.out.println("finally");
}
}
/ by zero
finally
1
13. 什么是为什么要有hashcodehashcode与equals关系
- hashCode() 的作用是获取哈希码也称为散列码它实际上是返回一个 int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义 在 JDK 的 Object.java 中这就意味着Java 中的任何类都包含有 hashCode() 函数。
散列表存储的是键值对(key-value)它的特点是能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码可以快速找到所需要的对象 - 为什么要有hashcode
当你把对象加入 HashSet 时HashSet 会先计算对象的 hashcode 值来判断对象加入的位置同时也会与其他已经加入的对象的 hashcode 值作比较如果没有相符的 hashcodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象这时会调用 equals方法来检查 hashcode 相等的对象是否真的相同。如果两者相同HashSet 就不会让其加入操作成功。
如果不同的话就会重新散列到其他位置。这样我们就大大减少了 equals 的次数相应就大大提高了执行速度。 - Object 类中的 equals 方法用于检测一个对象是否等于另外一个对象。在 Object类中这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用它们一定是相等的。当我们对比两个对象是否相等时我们就可以先使用 hashCode 进行比较如果比较的结果是 true那么就可以使用 equals再次确认两个对象是否相等如果比较的结果是true那么这两个对象就是相等的否则其他情况就认为两个对象不相等。这样就大大的提升了对象比较的效率这也是为什么 Java 设计使用hashCode 和 equals 协同的方式来确认两个对象是否相等的原因。
- 如果equals为truehashcode一定相等没有重写equals的情况下
如果equals为falsehashcode不一定不相等
如果hashcode值相等equals不一定相等
如果hashcode值不等equals一定不等没有重写equals的情况下
14. 在 Java 中为什么不允许从静态方法中访问非静态变量
- 静态变量属于类本身在类加载的时候就会分配内存可以通过类名直接访问
- 非静态变量属于类的对象只有在类的对象产生时才会分配内存通过类的实例去访问
- 静态方法也属于类本身但是此时没有类的实例内存中没有非静态变量所以无法调用非静态变量。
15. 实例化对象的方式
Class<?> cls = Class.forName("com.dao.User");
User u = (User)cls.newInstance();
- 序列化反序列化
将一个对象实例化后进行序列化再反序列化也可以获得一个对象远程通信的场景下使用
ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt"));
//序列化对象
out.writeObject(user1);
out.close();
//反序列化对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt"));
User user2 = (User) in.readObject();
System.out.println("反序列化user" + user2);
in.close();
16. Bean的生命周期
- Spring 对bean 进行实例化。
- Spring 将值和bean的引用注入到bean对应的属性中。
- 如果bean实现了BeanNameAware接口Spring将bean的ID传递给setBean-Name() 方法。
- 如果bean 实现了BeanFactoryAware接口Spring将调用setBeanFactory() 方法将BeanFactory容器实例传入。
- 如果bean实现了ApplicationContextAware接口Spring将调用setApplicationContext() 方法将bean所在的应用上下文的引用传入进来。
- 如果bean实现了BeanPostProcessor接口Spring将调用它们的post-ProcessBeforeInitialization() 方法
- 如果bean实现了InitializingBean接口Spring将调用它们的after-PropertiesSet()方法。类似的如果bean使用init-method声明了初始化方法该方法也会被调用。
- 如果bean实现了BeanPostProcessor接口Spring将调用它们的post-ProcessAfterInitialization() 方法。
- 此时, bean 已经准备就绪可以被应用程序使用了它们将一直驻留在应用上下文中直到该应用上下文被销毁。
- 如果bean实现了DisposableBean接口Spring将调用它的destory()接口方法。同样,如果bean使用destroy-method声明了销毁方法该方法也会被调用。
17. bean初始化执行是顺序
可以参考我之前写的文章
初始化执行顺序
18. HashMap与HashTable的区别
这个知识点比较老旧HashTable在工作中基本没用到过。但是还是写出来了仅限于了解知道这个事儿即可现在面试基本不会问。
- Hashtable是基于陈旧的Dictionary类的HashMap是Java 1.2引进的Map接口的一个实现。
- Hashtable是线程安全的也就是说是同步的而HashMap是线程序不安全的不是同步的。
- HashMap可以让你将空值作为一个表的条目的key或value。
- HashMap的默认容器是16为2倍扩容HashTable默认是11为2倍+1扩容。
19. HashMap put及get的实现原理
- HashMap是基于哈希表的Map接口的非同步实现。元素以键值对的形式存放并且允许null键和null值因为key值唯一不能重复因此null键只有一个。另外hashmap不保证元素存储的顺序是一种无序的和放入的顺序并不相同此类不保证映射的顺序特别是它不保证该顺序恒久不变。HashMap是线程不安全的。
- map.put(k,v)实现原理
首先将k,v封装到Node对象当中节点。
然后它的底层会调用K的hashCode()方法得出hash值。
通过哈希表函数/哈希算法将hash值转换成数组的下标下标位置上如果没有任何元素就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true那么这个节点的value将会被覆盖。 - map.get(k)实现原理
先调用k的hashCode()方法得出哈希值并通过哈希算法转换成数组的下标。
通过上一步哈希算法转换成数组的下标之后在通过数组下标快速定位到某个位置上。如果这个位置上什么都没有则返回null。如果这个位置上有单向链表那么它就会拿着K和单向链表上的每一个节点的K进行equals如果所有equals方法都返回false则get方法返回null。如果其中一个节点的K和参数K进行equals返回true那么此时该节点的value就是我们要找的value了get方法最终返回这个要找的value。
20. HashMap相关面试题
- HashMap内部的bucket数组长度为什么一直都是2的整数次幂答这样做有两个好处第一可以通过(table.length - 1) & key.hash()这样的位运算快速寻址第二在HashMap扩容的时候可以保证同一个桶中的元素均匀地散列到新的桶中具体一点就是同一个桶中的元素在扩容后一般留在原先的桶中一般放到了新的桶中。
- HashMap默认的bucket数组是多大答默认是16即时指定的大小不是2的整数次幂HashMap也会找到一个最近的2的整数次幂来初始化桶数组。
- HashMap什么时候开辟bucket数组占用内存答在第一次put的时候调用resize方法。
- HashMap何时扩容答当HashMap中的元素熟练超过阈值时阈值计算方式是capacity * loadFactor在HashMap中loadFactor负载因子是0.75。
- 桶中的元素链表何时转换为红黑树什么时候转回链表为什么要这么设计答当同一个桶中的元素数量大于等于8的时候元素中的链表转换为红黑树反之当桶中的元素数量小于等于6的时候又会转为链表这样做的原因是避免红黑树和链表之间频繁转换引起性能损耗。
- Java 8中为什么要引进红黑树是为了解决什么场景的问题答引入红黑树是为了避免hash性能急剧下降引起HashMap的读写性能急剧下降的场景正常情况下一般是不会用到红黑树的在一些极端场景下假如客户端实现了一个性能拙劣的hashCode方法可以保证HashMap的读写复杂度不会低于O(lgN)public int hashCode() {
return 1;
} - HashMap如何处理key为null的键值对答放置在桶数组中下标为0的桶中。
21. HashSet实现原理
HashSet是Set的实现HashSet底层其实是一个HashMap实例都是一个存放链表的数组。
HashSet是基于HashMap实现的HashSet中所有的元素都存放在HashMap的key上而value中的值都是统一的一个固定的对象private static final Object PRESENT = new Object(); HashSet的add方法
HashSet中add方法调用的是底层HashMap的put方法。如果是在HashMap中调用put方法首先会去判断key是否已经存在如果存在则修改value的值如果不存在则插入这个k-v对。而在Set中value是没有用的所以也就不存在修改value的情况故而向HashSet中添加新的元素首先判断元素是否存在不存在则插入存在则pass这样HashSet中就不存在重复值了。
所以判断key是否存在就需要去重写元素类的equals()和hashCode()方法。当向Set中添加元素的时候先调用元素所在类的hashCode()方法计算元素对象的哈希值这个哈希值决定了这个元素在Set中存放的位置如果这个位置是空的没有存放其他元素那么就直接把这个元素存放在这里如果这个位置已经被别人占了那么就调用元素所在类的equals()方法比较两个对象是否相同相同就直接pass掉保证了元素的不可重复性。
所以在使用HashMap和HashSet的时候如果Map的key或者Set中要存入自定义类的对象必须重写hashCode和equals方法。
22. Java中如何确保一个集合不会被修改
我们很容易想到用final关键字进行修饰我们都知道
final关键字可以修饰类方法成员变量final修饰的类不能被继承final修饰的方法不能被重写final修饰的成员变量必须初始化值如果这个成员变量是基本数据类型表示这个变量的值是不可改变的如果说这个成员变量是引用类型则表示这个引用的地址值是不能改变的但是这个引用所指向的对象里面的内容还是可以改变的。
那么我们怎么确保一个集合不能被修改首先我们要清楚集合map,set,list…都是引用类型所以我们如果用final修饰的话集合里面的内容还是可以修改的。
那我们应该怎么做才能确保集合不被修改呢
我们可以采用Collections包下的unmodifiableMap方法通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。
同理Collections包也提供了对list和set集合的方法。Collections.unmodifiableList(List) Collections.unmodifiableSet(Set)
23. HashMap 在 JDK7 和 JDK8 有哪些区别?
- 数据结构在 JDK7 及之前的版本HashMap 的数据结构可以看成“数组+链表”在 JDK8 及之后的版本数据结构可以看成"数组+链表+红黑树"当链表的长度超过8时链表就会转换成红黑树从而降低时间复杂度由O(n) 变成了 O(logN)提高了效率
- 对数据重哈希JDK8 及之后的版本对 hash() 方法进行了优化重新计算 hash 值时让 hashCode 的高16位参与异或运算目的是在 table 的 length较小的时候在进行计算元素存储位置时也让高位也参与运算。
- 在 JDK7 及之前的版本在添加元素的时候采用头插法所以在扩容的时候会导致之前元素相对位置倒置了在多线程环境下扩容可能造成环形链表而导致死循环的问题。DK1.8之后使用的是尾插法扩容是不会改变元素的相对位置
- 扩容时重新计算元素的存储位置的方式JDK7 及之前的版本重新计算存储位置是直接使用 hash & (table.length-1)JDK8 使用节点的hash值与旧数组长度进行位与运算如果运算结果为0表示元素在新数组中的位置不变否则则在新数组中的位置下标=原位置+原数组长度。
- JDK7 是先扩容后插入这就导致无论这次插入是否发生hash冲突都需要进行扩容但如果这次插入并没有发生Hash冲突的话那么就会造成一次无效扩容JDK8是先插入再扩容的优点是减少这一次无效的扩容原因就是如果这次插入没有发生Hash冲突的话那么其实就不会造成扩容
24. HashMap的线程不安全体现在哪儿如何变成线程安全
无论在JDK7还是JDK8的版本中HashMap 都是线程不安全的主要体现在以下两个方面
- 在JDK7及以前的版本表现为在多线程环境下进行扩容由于采用头插法位于同一索引位置的节点顺序会反掉导致可能出现死循环的情况。
- 在JDK8及以后的版本表现为在多线程环境下添加元素可能会出现数据丢失的情况。
如果想使用线程安全的 Map 容器可以使用以下几种方式
- 使用线程安全的 Hashtable它底层的每个方法都使用了 synchronized 保证线程同步所以每次都锁住整张表在性能方面会相对比较低。
- 使用Collections.synchronizedMap()方法来获取一个线程安全的集合底层原理是使用synchronized来保证线程同步。
- 使用 ConcurrentHashMap 集合。
25. ConcurrentHashMap 在 JDK7 和 JDK8的区别
- 数据结构JDK7 的数据结构是 Segment数组 + HashEntry数组 + 链表JDK8 的数据结构是 HashEntry数组 + 链表 + 红黑树当链表的长度超过8时链表就会转换成红黑树从而降低时间复杂度由O(n) 变成了 O(logN)提高了效率
- 锁的实现JDK7的锁是segment是基于ReentronLock实现的包含多个HashEntry而JDK8 降低了锁的粒度采用 table 数组元素作为锁从而实现对每行数据进行加锁进一步减少并发冲突的概率并使用 synchronized 来代替 ReentrantLock因为在低粒度的加锁方式中synchronized 并不比 ReentrantLock 差在粗粒度加锁中ReentrantLock 可以通过 Condition 来控制各个低粒度的边界更加的灵活而在低粒度中Condition的优势就没有了。
- 统计集合中元素个数 size 的方式JDK7 是先尝试 2次通过不锁住 segment 的方式来统计各个 segment 大小如果统计的过程中容器的 count 发生了变化则再采用加锁的方式来统计所有Segment的大小在 JDK8 中对于size的计算在扩容和 addCount() 方法中就已经有处理了等到调用 size() 时直接返回元素的个数
26. concurrentHashMap和HashTable有什么区别
concurrentHashMap融合了hashmap和hashtable的优势hashmap是不同步的但是单线程情况下效率高hashtable是同步的同步情况下保证程序执行的正确性。
但hashtable每次同步执行的时候都要锁住整个结构如下图
concurrentHashMap锁的方式是细粒度的。concurrentHashMap将hash分为16个桶默认值诸如get、put、remove等常用操作只锁住当前需要用到的桶。
concurrentHashMap的读取并发因为读取的大多数时候都没有锁定所以读取操作几乎是完全的并发操作只是在求size时才需要锁定整个hash。
而且在迭代时concurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式弱一致迭代器。在这种方式中当iterator被创建后集合再发生改变就不会抛出ConcurrentModificationException取而代之的是在改变时new新的数据而不是影响原来的数据iterator完成后再讲头指针替代为新的数据这样iterator时使用的是原来的数据。
27. ArrayList与LinkedList的区别
-
ArrayList 和 LinkedList 是 List 接口的两种不同实现并且两者都不是线程安全的。
-
ArrayList 内部使用的动态数组来存储元素LinkedList 内部使用的双向链表来存储元素这也是 ArrayList 和 LinkedList 最本质的区别。由于内部使用的存储方式不同导致它们的各种方法具有不同的时间复杂度。
-
ArrayList 和 LinkedList 在内存的使用上也有所不同。LinkedList 的每个元素都有更多开销因为要存储上一个和下一个元素的地址。ArrayList 没有这样的开销。
-
ArrayList 占用的内存在声明的时候就已经确定了默认大小为 10不管实际上是否添加了元素因为复杂对象的数组会通过 null 来填充。LinkedList 在声明的时候不需要指定大小元素增加或者删除时大小随之改变双向链表决定的。LinkedList 允许内存进行动态分配这就意味着内存分配是由编译器在运行时完成的我们无需在 LinkedList 声明的时候指定大小。。
-
ArrayList 只能用作列表LinkedList 可以用作列表或者队列因为它还实现了 Deque 接口。
-
查询的时候ArrayList 比 LinkedList 快。插入删除的时候LinkedList会更快些。
因为数组的元素需要连续的内存位置来存储其值。这就是 ArrayList 进行删除或者插入元素的时候成本很高的真正原因因为我们必须移动某些元素为新的元素留出空间比如说现在有一个数组10、12、15、20、4、5、100如果需要在 12 的位置上插入一个值为 99 的元素就必须得把 12 以后的元素往后移动为 99 这个元素腾出位置。LinkedList 不需要在连续的位置上存储元素因为节点可以通过引用指定下一个节点或者前一个节点。也就是说LinkedList 在插入和删除元素的时候代价很低因为不需要移动其他元素只需要更新前一个节点和后一个节点的引用地址即可。。
个人看法如果不知道该用 ArrayList 还是 LinkedList就选择 ArrayList 。
28. HashMap与HashSet的区别
先贴一个众所周知的区别
但其实从根本上来说它俩本来就是同一个东西。再说的清楚明白一点 HashSet 就是个套了壳儿的 HashMap。虽然hashset是调用add()方法添加元素但是其实HashSet的 add方法其实就是调用了HashMap的put方法
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |