Java面试必读之基础篇
Java面试必读之基础篇
无论个人基础如何,时间长了总会有些内容是会淡忘,但是基于一个面试的节点,总不能从头来一遍,这里从几个方面整理了一些java方面的常见面试题。大概会耗费二十分钟,在临门一脚的时候给提供一些助力。
基础篇
1、重载和重写的区别(了解)
重载: 发生在同一个类中,方法名必须相同,参数类型不同,个数不同, 顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名,参数列表必须相同,返回值范围小于等 于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父 类方法访问修饰符为 private 则子类就不能重写该方法。
2、String和StringBuffer,StringBuilder 的区别是什么?String 为 什么是不可变的?(必会)
简述:
操作少量的数据 => 使用 String(线程安全)
单线程操作字符串缓冲区下操作大量数据 => 使用 StringBuilder (非线程安全)
多线程操作字符串缓冲区下操作大量数据 => 使用 StringBuffer(线程安全)
可变性 简单的来说:String 类中使用 final 关键字字符数组保存字符串, private final char value[] ,所以 String 对象是不可变的。StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[]value 但是没有用 final 关键字修饰, 所以这两种对象都是可变的。
线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义 了一些字符串的基本操作方法,StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以 是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
3、自动装箱与拆箱(了解)
类int、Integer之间的关联关系
装箱:将基本类型用它们对应的引用类型包装起来; 拆箱:将包装类型转换为基本数据类型;
4、== 与 equals (必会)
重点掌握,面试新手超高频
== : 它的作用是判断两个对象的地址是不是相等。即: 判断两个对象是不 是同一个对象。(基本数据类型 == 比较的是值,引用数据类型==比较的是内存地址)。 equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况, 如下:
情况 1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两 个对象时,等价于通过"=="比较这两个对象。 情况 2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法 来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象 相等)。
5、关于 final 关键字的一些总结(必会)
final 关键字主要用在三个地方:变量、方法、类。
-
对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始 化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其 指向另一个对象。
-
当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成 员方法都会被隐式地指定为 final 方法。
-
使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承 类修改它的含义;第二个原因是效率。 在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法 过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不 需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定 为 final。
6 、Java 中的异常处理(了解)
实际工作中一般封装使用,面试属于冷门,了解内容
在 Java 中,所有的异常都有一个共同的祖先java.lang包中的Throwable 类。 Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误),二者都是 Java 异常处理的重要 子类,各自都包含大量子类。
Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空 间不足,方法调用栈溢出等。对于这类 错误的导致的应用程序中断,仅靠程序 本身无法恢复和和预防,遇到这样的错误,建议让程序终止
Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异 常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Exception 类又分为运行时异常(Runtime Exception)和受检查的异常 (CheckedException ),运行时异常。
7、什么是单例模式?有几种?(必会)
博主个人面试过程中,被问到这个问题多次,重点掌握
单例模式:某个类的实例在 多线程环境下只会被创建一次出来。 单例模式有饿汉式单例模式、懒汉式单例模式和双检锁单例模式三种。 饿汉式:线程安全,一开始就初始化。
懒汉式:非线程安全,延迟初始化。
双检锁:线程安全,延迟初始化。
有一点规模的公司就喜欢考这种手写代码,代码一定得记牢,真的遇到这种问题一定不会给你用IDE的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class Sort {
public static void sort() {
Scanner input = new Scanner(System.in);
int sort[] = new int[10];
int temp;
System.out.println("请输入 10 个排序的数据:");
for (int i = 0; i < sort.length; i++) {
sort[i] = input.nextInt();
}
for (int i = 0; i < sort.length - 1; i++) {
for (int j = 0; j < sort.length - i - 1; j++) {
if (sort[j] < sort[j + 1]) {
temp = sort[j];
sort[j] = sort[j + 1]; sort[j + 1] = temp;
}
}
}
System.out.println("排列后的顺序为:");
for(int i=0;i<sort.length;i++){
System.out.print(sort[i]+"======");
}
}
public static void main(String[] args) { sort(); }
}
|
9、常见的数据结构有哪些?(了解)
10、Java 集合体系有什么?(必会)
集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue) 和 map(映射)。
-
Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
-
Iterator:迭代器,可以通过迭代器遍历集合中的数据。
-
Map:是映射表的基础接口。
11、List 的三个子类的特点(必会)
工作中应用场景比较多,重点记忆
ArrayList 底层结构是数组,底层查询快,增删慢
LinkedList 底层结构是链表型的,增删快,查询慢
Voctor 底层结构是数组 线程安全的,增删慢,查询慢
12、 List 和 Map、Set 的区别(必会)
结构特点:
List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合
List 中存储的数据是有顺序,并且允许重复;
Map 中存储的数据是没有顺序的,其键是不能重复的;
List 接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快, 查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不 便于插入删除;Vector:基于数组实现,线程安全的,效率低)。
Map 接口有三个实现类(HashMap:基于 hash 表的 Map 接口实 现,非线程安全,高效,支持 null 值和 null 键;HashTable:线程安全,低效, 不支持 null 值和 null 键;LinkedHashMap:是 HashMap 的一个子类,保 存了记录的插入顺序;SortMap 接口:TreeMap,能够把它保存的记录根据键 排序,默认是键值的升序排序)。
Set 接口有两个实现类(HashSet:底层是由 HashMap 实现,不允 许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法; LinkedHashSet:继承与 HashSet,同时又基于 LinkedHashMap 来进行实 现,底层使用的是 LinkedHashMp)。
13、ConcurrentHashMap 特点(高薪常问)
Segment 段
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并 发操作,所以要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组 成,Segment 代表"部分"或"一段"的意思,所以很多地方都会将其描述为 分段锁。注意,行文中,我很多地方用了"槽"来代表一个 segment。 线程安全(Segment 继承 ReentrantLock 加锁) 简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线 程安全。
并行度(默认 16)
concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要, 理解它。默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments, 所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操 作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值, 但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其 实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以 处理起来要麻烦些。
Java8开始 对 ConcurrentHashMap 进行了比较大的改动,引入了红黑树
14、HashMap 和 HashTable 有什么区别?(必会)
HashMap 是线程不安全的,HashMap 是一个接口,是 Map 的一个子接 口,是将键映射到值得对象,不允许键值重复,允许空键和空值;由于非线程安 全,HashMap 的效率要较 HashTable 的效率高一些.
HashTable 是线程安全的一个集合,不允许 null 值作为一个 key 值或者 Value 值;
HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同 步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步;
15、HashMap,HashTable,ConcurrentHashMap 之间的区别, 及性能对比(必会)
性能:ConcurrentHashMap(线程安全) > HashMap > HashTable(线程安全) 区别对比一(HashMap 和 HashTable 区别):
1、HashMap 是非线程安全的,HashTable 是线程安全的。
2、HashMap 的键和值都允许有 null 值存在,而 HashTable 则不行。
3、因为线程安全的问题,HashMap 效率比 HashTable 的要高。
4、Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线 程环境,而 Hashtable 适合于多线程环境。一般现在不建议用 HashTable,
1 是 HashTable 是遗留类,内部实现很多没优化和冗余。
2 即使在多线程环境下, 现在也有同步的 ConcurrentHashMap 替代,没有必要因为是多线程而用 HashTable。
区别对比二(HashTable 和 ConcurrentHashMap 区别):
HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是使 用了锁分段技术来保证线程安全的。
Hashtable 中采用的锁机制是一次锁住整个 hash 表,从而在同一时刻只能由一 个线程对其进行操作;而 ConcurrentHashMap 中则是一次锁住一个桶。
ConcurrentHashMap 默认将 hash 表分为 16 个桶,诸如 get、put、remove 等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能 同时有 16 个写线程执行,并发性能的提升是显而易见的。
16、什么是线程?线程和进程的区别?(必会)
1.继承 Thread 类
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。
2.实现 Runnable 接口
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个 Runnable 接口。
3.实现 Callable 接口
Callabled 接口有点儿像是 Runnable 接口的增强版,它以 call()方法作为线程执行体, call()方法比 run()方法功能更强大。 call()方法可以有返回值,可以声明抛出异常类。获取 call()方法里的返回值: 通过 FutureTask 类(实现 Future 接口)的实例对象的 get()方法得到,得到结果类型与创建 TutureTask 类给的泛型一致。
-
通过线程池方法
什么是线程池,如何使用?
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线 程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。 在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态 方法. 然后调用他们的 execute 方法即可。
合理利用线程池能够带来三个好处。 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消 耗。 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执 行。 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗 系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
常用的线程池有哪些?
newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任 务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个 线程,直到线程达到线程池的最大大小。
newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池 大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大 线程大小。
newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时 以及周期性执行任务的需求。
17、线程的基本方法有什么?(必会)
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield
sleep 不会释放当前占有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方*** 导致当前线程进入 WATING 状态.
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。
18、在 java 中 wait 和 sleep 方法的不同?(必会)
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常 被用于线程间交互,sleep 通常被用于暂停执行。
19、线程池原理(高薪常问)
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队 列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的 线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。他的主要特 点为:线程复用;控制最大并发数;管理线程。
线程复用: 每一个 Thread 的类都有一个 start 方法。 当调用 start 启动线程时 Java 虚拟机会调用该类的 run 方法。 那么该类的 run() 方法中就是调用了 Runnable 对象的 run() 方法。 我们可以继承重写 Thread 类,在其 start 方 法中添加不断循环调用传递过来的 Runnable 对象。 这就是线程池的实现原 理。循环方法中不断获取 Runnable 是用 Queue 实现的,在获取下一个 Runnable 之前可以是阻塞的。
线程池的组成: 一般的线程池主要分为以下 4 个组成部分:
-
线程池管理器:用于创建并管理线程池
-
工作线程:线程池中的线程
-
任务接口:每个任务必须实现的接口,用于工作线程调度其运行 4. 任务队列:用于存放待处理的任务,提供一种缓冲机制 拒绝策略: 线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已 经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这 个问题。 JDK 内置的拒绝策略如下:
-
AbortPolicy : 直接抛出异常,阻止系统正常运行。
-
CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中, 运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的 性能极有可能会急剧下降。
-
DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一 个任务,并尝试再次提交当前任务。
-
DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。 如果允许任务丢失,这是最好的一种方案
20、线程执行的顺序(高薪常问)
-
当线程数小于核心线程数时,会一直创建线程直到线程数等于核心线程数;
-
当线程数等于核心线程数时,新加入的任务会被放到任务队列等待执行;
-
当任务队列已满,又有新的任务时,会创建线程直到线程数量等于最大线程 数;
-
当线程数等于最大线程数,且任务队列已满时,新加入任务会被拒绝。
21、死锁产生的条件以及如何避免?(高薪常问)
死锁产生的四个必要条件: 互斥:一个资源每次只能被一个进程使用(资源独立)。 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放(不 释放锁)。 不剥夺:进程已获得的资源,在未使用之前,不能强行剥夺(抢夺资源)。 循环等待:若干进程之间形成一种头尾相接的循环等待的资源关闭(死循 环)。 避免死锁:
-
破坏"互斥"条件:系统里取消互斥、若资源一般不被一个进程独占使 用,那么死锁是肯定不会发生的,但一般"互斥"条件是无法破坏的,因此,在 死锁预防里主要是破坏其他三个必要条件,而不去涉及破坏"互斥"条件。
-
破坏"请求和保持"条件: 方法 1:所有的进程在开始运行之前,必须一次性的申请其在整个运行过程 各种所需要的全部资源。优点:简单易实施且安全。 缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费。 方法 2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资 源,便开始运行,在运行过程中逐步释放掉分配到,已经使用完毕的资源,然后 再去请求新的资源。这样的话资源的利用率会得到提高,也会减少进程的饥饿问题。
-
破坏"不剥夺"条件:当一个已经持有了一些资源的进程在提出新的资 源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时 候再重新申请。这就意味着进程已占有的资源会被短暂的释放或者说被抢占了。
-
破坏"循环等待"条件:可以通过定义资源类型的线性顺序来预防,可 以将每个资源编号,当一个进程占有编号为 i 的资源时,那么它下一次申请资源 只能申请编号大于 i 的资源。
22、JVM 是什么?JVM 的基本结构 (高薪常问)
虚拟机,一种能够运行 java 字节码的虚拟机。 类加载子系统
-
加载 .class 文件到内存。
-
内存结构
-
运行时的数据区。
-
执行引擎
-
执行内存中的.class,输出执行结果(包含 GC:垃圾收集器)。
-
本地方法的接口。
-
本地方法库。
23、类的加载, 类加载器的种类, 类加载机制(高薪常问)
-
类加载
1.加载将.class 文件从磁盘读到内存。
2.连接
2.1 验证: 验证字节码文件的正确性。
2.2 准备: 给类的静态变量分配内存,并赋予默认值。
2.3 解析: 类装载器装入类所引用的其它所有类。
3.初始化
为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚 拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值,执 行静态代码块。
4.使用
5.卸载
-
类加载器的种类
-
启动类加载器(Bootstrap ClassLoader)负责加载 JRE 的核心类库,如 JRE 目标下的 rt.jar,charsets.jar 等。
-
扩展类加载器(Extension ClassLoader) 负责加载 JRE 扩展目录 ext 中 jar 类包。
-
系统类加载器(Application ClassLoader) 负责加载 ClassPath 路径下的类包。
-
用户自定义加载器(User ClassLoader) 负责加载用户自定义路径下的类包。
类加载机制
全盘负责委托机制
-
当 A 类中引用 B 类,那么除非特别指定 B 类的类加载器,否则就直接使用加载 A 类的类加载器加载 B 类。
双亲委派机制
指先委托父类加载器寻找目标类,在找不到的情况下再在自己的路径中查找 并载入目标类。
24、JVM 调优的工具有哪些?(高薪常问)
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用 的是 jconsole 和 jvisualvm 这两款视图监控工具。
-
jconsole:用于对 JVM 中的内存、线程和类等进行监控;
-
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程 快照、程序死锁、监控内存的变化、gc 变化等。
25、常用的 JVM 调优的参数
XX 比 X 的稳定性更差,并且版本更新不会进行通知和说明。
-Xms:s 为 strating,表示堆内存起始大小。
-Xmx:x 为 max,表示最大的堆内存(一般来说-Xms 和-Xmx 的设置为相同大小,因为当 heap 自动扩容时,会发生内存抖动,影响程序的稳定性)。 -Xmn:n 为 new,表示新生代大小(-Xss:规定了每个线程虚拟机栈 (堆栈)的大小)。
-XX:SurvivorRator=8 表示堆内存中新生代、老年代和永久代的比为8:1:1。
-XX:PretenureSizeThreshold=3145728 表示当创建(new)的对象大于 3M 的时候直接进入。
-XX:MaxTenuringThreshold=15 表示当对象的存活的年龄(minor gc一次加 1)大于多少时,进入老年代。
-XX:-DisableExplicirGC 表示是否(+表示是,-表示否)打开 GC 日志。