网上看到的一些面试题,自己整理一下答案,查缺补漏。
Java 基础
1.0 JAVA中的几种基本数据类型是什么,各自占用多少字节。
byte, short, int, long, float, double, char, boolean
数据类型 | 字节 |
---|---|
boolean | 1 bit,不到一个字节 |
byte | 8 bit,1字节 |
short | 16 bit,2字节 |
char | 16 bit,2字节 |
int | 32 bit,4字节 |
float | 32 bit,4字节 |
long | 64 bit,8字节 |
double | 64 bit,8字节 |
1.1 String类能被继承吗,为什么。
String类不能被继承,因为String类有final修饰符,final修饰的类不能被继承。
1.2 String,Stringbuffer,StringBuilder 的区别。
String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全)
简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的: String S1 = “This is only a” “ simple” “ test”; StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”); 你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个 String S1 = “This is only a” “ simple” “test”; 其实就是: String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:String S2 = “This is only a”;String S3 = “ simple”;String S4 = “ test”;String S1 = S2 S3 S4;这时候 JVM 会规规矩矩的按照原来的方式去做 在大部分情况下 StringBuffer > StringStringBufferJava.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。在大部分情况下 StringBuilder > StringBufferjava.lang.StringBuildejava.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。
1.3 ArrayList 和 LinkedList 有什么区别。
- ArrayList,采用数组数据结构的List,创建一个数组a,加索引即角标就可以访问到数据,数组在内容中是一段连续的数据,可以支持随机访问。a则表示数组的内存地址,索引则是数据所处位置距离第一个元素的偏移量,如a0表示当前第一个元素,和a指的是一个位置,所以无论任何位置,只需要两步,找到a的位置,然后获取偏移量即可访问到数据,时间复杂度是O(1)。数组创建时需要指定长度,ArrayList可以一直增加是因为当超过长度时,会新创建一个新的数组,把原来的数据拷贝进去,然后将老的数组抛弃掉。ArrayList支持随机访问,实现了RandomAccess接口。查询多的情况使用ArrayList,当需要删除数据时,当前数据的后续数据角标都发现改变,所以时间复杂度是O(n-i),所以适合用在查询多,增删少的情况下。
- LinkedList,采用链表数据结构的List,不支持随机,在创建时并没有指定长度,使用时是由系统分配内存,所以在内存中的位置是随机。LinkedList在添加数据时不光会记录当前数据,还会记录上个元素的位置,所以通过上个元素访问这个元素,通过一个个元素互相指向形成一个链条一样的结构。当需要访问一个位置的数据时,只能通过第一个元素,一步一步寻找,时间复杂度是O(n),不能随机访问。所以查询的效率很慢,当删除时,只需要将数据删除后,再下个元素的指向到上个元素即可,删除的时间复杂度是O(1),所以适合用在频繁增删的情况下。
具体可以看以前转的一篇文章:关于一次List的面试 之前写过的ArrayList相关内容:ArrayList的扩容机制
1.4 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序。
顺序为:
- 父类静态变量、
- 父类静态代码块、
- 子类静态变量、
- 子类静态代码块、
- 父类非静态变量(父类实例成员变量)、
- 父类构造函数、
- 子类非静态变量(子类实例成员变量)、
- 子类构造函数。
代码验证可以看这个:Java基础-类的实例化顺序
1.5 用过哪些 Map 类,都有什么区别,HashMap 是线程安全的吗,并发下使用的 Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
用过 HashMap、LinkedHashMap、TreeMap、 具体查看这个 笔记(三) - Java集合 HashMap不是线程安全的,并发下应该使用 ConcurrentHashMap,
1.6 JAVA8的 ConcurrentHashMap 为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
1.7 有没有有顺序的 Map 实现类,如果有,他们是怎么保证有序的。
Hashmap和Hashtable 都不是有序的。
TreeMap和LinkedHashmap都是有序的。(TreeMap默认是key升序,LinkedHashmap默认是数据插入顺序)
TreeMap是基于比较器Comparator来实现有序的。
LinkedHashmap是基于链表来实现数据插入有序的。
1.8 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。
之前有写过,点击 这里 查看
1.9 继承和聚合的区别在哪。
- 继承 指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识,在设计时一般没有争议性;
- 聚合 聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分;
在Family类中包含一个Child.并且包含Child的get,set方法,可以只先创建Family然后通过构造器或者get,set给Child赋值
参考:继承、实现、依赖、关联、聚合、组合的联系与区别
2.0 IO 模型有哪些,讲讲你理解的 nio ,他和 bio,aio 的区别是啥,谈谈 reactor 模型。
具体看 面试题之IO 模型有哪些,讲讲你理解的 nio ,他和 bio,aio 的区别是啥,谈谈 reactor 模型。
2.1 反射的原理,反射创建类实例的三种方式是什么。
关于反射可以看这个反射相关内容
- 方式一 通过对象的getClass方法进行获取。这种方式需要具体的类和该类的对象,以及调用getClass方法。
Class class2 = foo1.getClass(); System.out.println(class1==class2);//true'
- 方式二 任何数据类型(包括基本数据类型)都具备着一个静态的属性class,通过它可直接获取到该类型对应的Class对象。这种方式要使用具体的类,然后调用类中的静态属性class完成,无需调用方法,性能更好。
Class class1 = Foo.class;
- 方式三 通过Class.forName()方法获取。这种方式仅需使用类名,就可以获取该类的Class对象,更有利于扩展。 ` Class class3 = null; try { class3 = Class.forName("com.imooc.reflect.Foo"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
System.out.println(class2==class3);//true
代码语言:txt复制
2.2 反射中,Class.forName 和 ClassLoader 区别。
在java中Class.forName()和ClassLoader都可以对类进行加载。
区别:
- Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
- 而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
看简书文章讲的比较细致,反射中,Class.forName和ClassLoader区别
2.3 描述动态代理的几种实现方式,分别说出相应的优缺点。
2.4 动态代理与 cglib 实现的区别。
2.5 为什么CGlib 方式可以对接口实现代理。
2.6 final 的用途。
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变
另外还有:
- 被final修饰的方法,JVM会尝试为之寻求内联,这对于提升Java的效率是非常重要的。因此,假如能确定方法不会被继承,那么尽量将方法定义为final的
- 被final修饰的常量,在编译阶段会存入调用类的常量池中
2.7 写出三种单例模式实现。
详细的看 设计模式之单例模式
2.8 如何在父类中为子类自动完成所有的 hashcode 和 equals实现?这么做有何优劣。
2.9 请结合OO 设计理念,谈谈访问修饰符 public、private、protected、default 在应用设计中的作用。
3.0 深拷贝和浅拷贝区别。
3.1 数组和链表数据结构描述,各自的时间复杂度。
3.2 error和exception 的区别,CheckedException,RuntimeException 的区别。
3.3 请列出5 个运行时异常。
ClassCastException(类转换异常) IndexOutOfBoundsException(数组越界) NullPointerException(空指针) ArrayStoreException(数据存储异常,操作数组时类型不一致) 还有IO操作的BufferOverflowException异常
3.4 在自己的代码中,如果创建一个 java.lang.String 类,这个类是否可以被类加载器加载?为什么。
在《深入理解java虚拟机》一书中有这样一段话,“即使自定义了自己的类加载器,强行使用defineClass()方法去加载一个以‘java.lang’开头的类也不会成功,如果尝试这样做的话,将会收到一个由虚拟机自己抛出的‘java.lang.SecurityException:Prohibited package name:java.lang’异常”。 所以如果创建一个java.lang.String的类,是不能被类加载器加载的。因为虚拟机会抛出异常。
说到类加载,就必须对类加载机制非常熟悉。
类加载使用的是双亲委派模型,当你想要加载一个类的时候,必须先给你的父加载器,它再去想办法加载,如果它不能加载,再告诉我们,我们自己想办法。
所以,在java中java.lang.String肯定在上层的ClassLoader被加载过了,所以你自己写的完全没有机会加载。
3.5 说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法。
3.6 在 jdk1.5 中,引入了泛型,泛型的存在是用来解决什么问题。
泛型主要针对向下转型时所带来的安全隐患,其核心组成是在声明类或接口时,不设置参数或属性的类型。
3.7 这样的a.hashcode() 有什么用,与 a.equals(b)有什么关系。
3.8 有没有可能 2 个不相等的对象有相同的 hashcode。
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必 须有相同的 hashcode 值,但是没有关于不相等对象的任何规定。
3.9 Java 中的 HashSet 内部是如何工作的。
4.0 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
4.1 java8 的新特性。
Java8 新增了非常多的特性,我们主要讨论以下几个:
- Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
JVM
4.2 什么情况下会发生栈内存溢出。
4.3 JVM 的内存结构,Eden 和 Survivor 比例。
4.4 JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分Eden和Survivor。
4.5 JVM 中一次完整的 GC流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的 JVM 参数。
4.6 你知道哪几种垃圾收集器,各自的优缺点,重点讲下 cms和 G1,包括原理,流程,优缺点。
4.7 垃圾回收算法的实现原理。
以前写过的 垃圾回收算法
4.8 当出现了内存溢出,你怎么排错。
4.9 JVM 内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等。
5.0 简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。
5.1 讲讲JAVA的反射机制。
5.2 你们线上应用的 JVM 参数有哪些。
5.3 g1 和 cms区别,吞吐量优先和响应优先的垃圾收集器选择。
5.4 怎么打出线程栈信息。
开源框架
5.5 简单讲讲 tomcat 结构,以及其类加载器流程,线程模型等。
5.6 tomcat如何调优,涉及哪些参数。
Tomcat 调优及 JVM 参数优化 springboot内置tomcat参数调优
5.7 讲讲 Spring加载流程。
5.8 Spring AOP的实现原理。
5.9 讲讲 Spring事务的传播属性。
6.0 Spring如何管理事务的。
6.1 Spring怎么配置事务(具体说出一些关键的 xml 元 素 )。
6.2 说说你对 Spring 的理解,非单例注入的原理?它的生命周期?循环注入的原理,aop的实现原理,说说 aop 中的几个术语,它们是怎么相互工作的。
6.3 Springmvc 中 DispatcherServlet 初始化过程。
6.4 netty的线程模型,netty如何基于 reactor 模型上实现的。
6.5 为什么选择 netty。
6.6 什么是TCP粘包,拆包。解决方式是什么。
6.7 netty的 fashwheeltimer 的用法,实现原理,是否出现过调用不够准时,怎么解决。
6.8 netty的心跳处理在弱网下怎么办。
6.9 netty的通讯协议是什么样的。
7.0 springmvc 用到的注解,作用是什么,原理。
7.1 springboot 启动机制。
- 1.通过 SpringFactoriesLoader加载 META-INF/spring.factories⽂件,获取并创建 SpringApplicationRunListener对象
- 2.然后由 SpringApplicationRunListener来发出 starting 消息
- 3.创建参数,并配置当前 SpringBoot 应用将要使用的 Environment
- 4.完成之后,依然由 SpringApplicationRunListener来发出 environmentPrepared 消息
- 5.创建 ApplicationContext
- 6.初始化 ApplicationContext,并设置 Environment,加载相关配置等
- 7.由 SpringApplicationRunListener来发出 contextPrepared消息,告知SpringBoot 应用使用的 ApplicationContext已准备OK
- 8.将各种 beans 装载入 ApplicationContext,继续由SpringApplicationRunListener来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext已装填OK
- 9.refresh ApplicationContext,完成IoC容器可用的最后⼀步
- 10.由 SpringApplicationRunListener来发出 started 消息
- 11.完成最终的程序的启动
- 12.由 SpringApplicationRunListener来发出 running 消息,告知程序已运行起来了
操作系统
7.2 Linux 系统下你关注过哪些内核参数,说说你知道的。
这个比较复杂 Linux内核参数配置 Linux内核参数优化
7.3 Linux下 IO 模型有几种,各自的含义是什么。
7.4 epoll 和poll 有什么区别。
面试经典问题---select、poll、epoll之间有什么区别
7.5 平时用到哪些 Linux命令。
head、tail、tar、ssh、sftp、telnet、curl、wget、cp、mv、sed、su cd、ls、grep、find、rm、kill、ps、cat、chown、chmod、
7.6 用一行命令查看文件的最后五行。
tail -5f filename
7.7 用一行命令输出正在运行的 java 进程。
ps -ef|grep java
7.8 介绍下你理解的操作系统中线程切换过程。
7.9 进程和线程的区别。
8.0 top 命令之后有哪些内容,有什么作用。
8.1 线上 CPU爆高,请问你如何找到问题所在。
Copyright: 采用 知识共享署名4.0 国际许可协议进行许可
Links: https://cloud.tencent.com/developer/article/2020458