小编发现网上很多Java面试题都没有答案,所以小编利用周末时间为大家整理了一份史上最全Java大厂高频面试题及答案。
本套Java面试题大全,全的不能再全 由于篇幅太长,以下只摘录了一部分。
Java基础
抽象类和接口的区别
- 一个类只能继承一个抽象类,但是可以实现多个接口;
- 抽象类可以包含具体方法,接口中的所有方法都是抽象的;
- 抽象类可以声明和使用字段;接口则不能,接口中的变量都是final类型的常量
- 抽象类中的方法的修饰符可以是public、protected、private或者default;接口中的方法只能是public
- 抽象类可以定义构造函数;接口不能
- 抽象类主要用来抽象类别,接口主要用来抽象方法功能,关注事物的本质,用抽象类;关注一种操作,用接口。
什么时候使用抽象类,什么时候使用接口
- 如果想让一部分方法拥有默认实现,使用抽象类。
- 如果想实现多重继承,必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
- 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
List、Set、Map的区别
1. List
- 可存储重复对象;
- 是一个有序集合,保存了每一个元素的插入顺序,输出顺序就是插入顺序;
- 常见的实现类有ArrayList、LinkedList和Vector
2. Set
- 不允许重复对象,只允许一个null元素
- 无序容器,无法保证每个元素的存储顺序,TreeSet通过Comparator或者Comparable维护了一个排序顺序;
- 常见的实现类有HashSet、LinkedHashSet和TreeSet,最流行的是基于HashMap实现的HashSet
3.Map
- Map不是Collection的子接口或者实现类,Map和Collection同级,是一个接口
- 以键值对的形式进行数据存储
- 键不允许重复,允许null作为Map的键和值
- TreeMap也通过Comparator或者Comparable维护了一个排序顺序
- 常见的实现类有HashMap、TreeMap、LinckedHashMap和HashTable
什么情况下使用List、Map、Set?
- 经常使用索引对元素进行访问,使用ArrayList,经常对元素进行增加删除操作,使用LinckedList
- 想让元素按照插入的次序进行有序存储,使用List
- 想要保证元素的唯一性,使用Set
- 想要使用键值对的形式进行数据存储,使用Map
ArrayList和LinckedList
- ArrayList的底层是可变长数组,LinkedList的底层是链表(链表结构特征)
- 经常随机访问数据,ArrayList更快,因为LinkedList要移动指针
- 经常进行新增和删除操作,LinkedList更快,因为ArrayList要移动数据
八大基本数据类型,所占字节数
| 基本类型 | 字节数 |
|---|---|
| byte | 1 |
| short | 2 |
| int | 4 |
| long | 8 |
| float | 4 |
| double | 8 |
| char | 2 |
| boolean | true/false |
LinckedList底层
LinkedList底层是一个双向链表,LinkedList的基本操作就是对双向链表的操作。
push()和pop()方法实现 改变元素的前驱节点和后驱节点的指向
ArrayList底层
可变长数组,初始容量为10,扩容后容量大概为原容量的1.5倍
如何实现自动扩容和缩容: elementData = Arrays.copyOf(elementData, newCapacity);
总结:
- copyOf()的实现是用的是arrayCopy();
- arrayCopy()需要目标数组,对两个数组的内容进行可能不完全的合并操作;
- copyOf()在内部新建一个数组,调用arrayCopy()将original内容复制到copy中去,并且长度为newLength,返回copy。 .................
JVM
Java 类加载过程
- 加载 「加载是类加载的第一个过程,在这个阶段,将完成一下三件事情:」
- 通过一个类的全限定名获取该类的二进制流。
- 将该二进制流中的静态存储结构转化为方法去运行时数据结 构。
- 在内存中生成该类的 Class 对象,作为该类的数据访问入口。
- 验证 「验证的目的是为了确保 Class 文件的字节流中的信息不回危害到 虚拟机在该阶段主要完成以下四钟验证」:
- 文件格式验证:验证字节流是否符合 Class 文件的规范,如 主次版本号是否在当前虚拟机范围内,常量池中的常量是否 有不被支持的类型.
- 元数据验证:对字节码描述的信息进行语义分析,如这个类是 否有父类,是否集成了不被继承的类等。
- 字节码验证:是整个验证过程中最复杂的一个阶段,通过验 证数据流和控制流的分析,确定程序语义是否正确,主要针 对方法体的验证。如:方法中的类型转换是否正确,跳转指 令是否正确等。
- 符号引用验证:这个动作在后面的解析过程中发生,主要是 为了确保解析动作能正确执行。
- 准备 「准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些 内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的 内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆 中。 public static int value=123;//在准备阶段 value 初始值为 0 。在初 始化阶段才会变为 123 。」
- 解析 「该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一 定在初始化动作完成之前,也有可能在初始化之后。」
- 初始化 「初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段 用户应用程序可以通过自定义类加载器参与之外,其余动作完全由 虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。」
- 使用
- 卸载
描述一下 JVM 加载 Class 文件的原理机制?
Java 语言是一种具有动态性的解释型语言,类(Class)只有被加载到 JVM 后才能运行。
当运行指定程序时,JVM 会将编译生成的 .class 文件按照需求和一定的规则加载到内存中,并组织成为一个完整的 Java 应用程序。这个加载过程是由类加载器完成,具体来说,就是由 ClassLoader 和它的子类来实现的。类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。
类的加载方式分为隐式加载和显示加载。隐式加载指的是程序在使用 new 等方式创建对象时,会隐式地调用类的加载器把对应的类加载到 JVM 中。显示加载指的是通过直接调用 class.forName() 方法来把所需的类加载到 JVM 中。
任何一个工程项目都是由许多类组成的,当程序启动时,只把需要的类加载到 JVM 中,其他类只有被使用到的时候才会被加载,采用这种方法一方面可以加快加载速度,另一方面可以节约程序运行时对内存的开销。
此外,在 Java 语言中,每个类或接口都对应一个 .class 文件,这些文件可以被看成是一个个可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。
在 Java 语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到 JVM 中,至于其他类,则在需要的时候才加载。 类加载的主要步骤:
- 装载。根据查找路径找到相应的 class 文件,然后导入。
- 链接。链接又可分为 3 个小步:
- 检查,检查待加载的 class 文件的正确性。
- 准备,给类中的静态变量分配存储空间。
- 解析,将符号引用转换为直接引用(这一步可选)
- 初始化。对静态变量和静态代码块执行初始化工作。
GC 是什么? 为什么要有 GC?
GC 是垃圾收集的意思(GabageCollection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法
Redis
redis 和 memcached 什么区别?为什么高并发下有时单线程的 redis 比多线程的memcached 效率要高?
区别:
- mc 可缓存图片和视频。rd 支持除 k/v 更多的数据结构;
- rd 可以使用虚拟内存,rd 可持久化和 aof 灾难恢复,rd 通过主从支持数据备份;
- rd 可以做消息队列。
原因:mc 多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。
使用 redis 如何设计分布式锁?说一下实现思路?使用 zk 可以吗?如何实现?这两种有什么区别?
redis:
- 线程 A setnx(上锁的对象,超时时的时间戳 t1),如果返回 true,获得锁。
- 线程 B 用 get 获取 t1,与当前时间戳比较,判断是是否超时,没超时 false,若超时执行第 3 步;
- 计算新的超时时间 t2,使用 getset 命令返回 t3(该值可能其他线程已经修改过),如果 t1==t3,获得锁,如果 t1!=t3 说明锁被其他线程获取了。
- 获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时, 不用处理(防止删除其他线程的锁)。
zk:
- 客户端对某个方法加锁时,在 zk 上的与该方法对应的指定节点的目录下,生成一个唯一 的瞬时有序节点 node1;
- 客户端获取该路径下所有已经创建的子节点,如果发现自己创建的 node1 的序号是最小 的,就认为这个客户端获得了锁。
- 如果发现 node1 不是最小的,则监听比自己创建节点序号小的最大的节点,进入等待。
- 获取锁后,处理完逻辑,删除自己创建的 node1 即可。 区别:zk 性能差一些,开销大,实现简单
知道 redis 的持久化吗?底层如何实现的?有什么优点缺点?
RDB(Redis DataBase:在不同的时间点将 redis 的数据生成的快照同步到磁盘等介质上):内存到硬盘的快照,定期更新。缺点:耗时,耗性能(fork+io 操作),易丢失数据。AOF(Append Only File:将 redis 所执行过的所有指令都记录下来,在下次 redis 重启时,只需要执行指令就可以了):写日志。缺点:体积大,恢复速度慢。
bgsave 做镜像全量持久化,aof 做增量持久化。因为 bgsave 会消耗比较长的时间,不够实时,在停机的时候会导致大量的数据丢失,需要 aof 来配合,在 redis 实例重启时,优先使用 aof 来恢复内存的状态,如果没有 aof 日志,就会使用 rdb 文件来恢复。Redis 会定期做aof 重写,压缩 aof 文件日志大小。
Redis4.0 之后有了混合持久化的功能,将 bgsave 的全量和 aof 的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。bgsave 的原理,fork 和 cow, fork 是指 redis 通过创建子进程来进行 bgsave 操作,cow 指的是 copy onwrite,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来
redis 过期策略都有哪些?LRU 算法知道吗?写一下 java 代码实现?
过期策略:
- 定时过期(一 key 一定时器),惰性过期:只有使用 key 时才判断 key 是否已过期,过期则清 除。定期过期:前两者折中。
- LRU:new LinkedHashMap<K, V>(capacity, DEFAULT_LOAD_FACTORY, true);//第三个参数置为 true,代表 linkedlist 按访问顺序排序,可作为 LRU 缓存;设为 false 代表按插入顺序排序,可作为 FIFO 缓存
LRU 算法实现:
- 通过双向链表来实现,新数据插入到链表头部;
- 每当缓存命中(即缓存 数据被访问),则将数据移到链表头部;
- 当链表满的时候,将链表尾部的数据丢弃。
LinkedHashMap:HashMap 和双向链表合二为一即是 LinkedHashMap。HashMap 是无序的,LinkedHashMap 通过维护一个额外的双向链表保证了迭代顺序。该迭代顺序可以是插入顺序(默认),也可以是访问顺序。
Mysql
有哪些数据库优化方面的经验?
- 用 PreparedStatement, 一般来说比 Statement 性能高:一个 sql 发给服务器去执行,涉及步骤:语法检查、语义分析, 编译,缓存。
- 有外键约束会影响插入和删除性能,如果程序能够保证数据的完整性, 那在设计数据库时就去掉外键。
- 表中允许适当冗余,譬如,主题帖的回复数量和最后回复时间等
- UNION ALL 要比 UNION 快很多,所以,如果可以确认合并的两个结 果集中不包含重复数据且不需要排序时的话,那么就使用 UNION ALL。 >>UNION 和 UNION ALL 关键字都是将两个结果集合并为一 个,但这两者从使用和效率上来说都有所不同。 >1. 对重复结果的处 理:UNION 在进行表链接后会筛选掉重复的记录,Union All 不会去除 重复记录。 >2. 对排序的处理:Union 将会按照字段的顺序进行排 序;UNION ALL 只是简单的将两个结果合并后就返回。
请简述常用的索引有哪些种类?
- 普通索引: 即针对数据库表创建索引
- 唯一索引: 与普通索引类似,不同的就是:MySQL 数据库索引列的值 必须唯一,但允许有空值
- 主键索引: 它是一种特殊的唯一索引,不允许有空值。一般是在建表的 时候同时创建主键索引
- 组合索引: 为了进一步榨取 MySQL 的效率,就要考虑建立组合索引。 即将数据库表中的多个字段联合起来作为一个组合索引。
以及在 mysql 数据库中索引的工作机制是什么?
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更 新数据库表中数据。索引的实现通常使用 B 树及其变种 B+树
MySQL 的基础操作命令
- MySQL 是否处于运行状态:Debian 上运行命令 service mysql status,在 RedHat 上运行命令 service mysqld status
- 开启或停止 MySQL 服务 :运行命令 service mysqld start 开启服 务;运行命令 service mysqld stop 停止服务
- Shell 登入 MySQL: 运行命令 mysql -u root -p
- 列出所有数据库:运行命令 show databases;
- 切换到某个数据库并在上面工作:运行命令 use databasename; 进入 名为 databasename 的数据库
- 列出某个数据库内所有表: show tables;
- 获取表内所有 Field 对象的名称和类型 :describe table_name;
mysql 的复制原理以及流程
Mysql 内建的复制功能是构建大型,高性能应用程序的基础。将 Mysql 的数据分布到多个系统上去,这种分布的机制,是通过将 Mysql 的某一台主机的数据复制到其它主机(slaves)上,并重新执行一遍来实现的。
复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器。主服务器将更新写入二进制日志文件,并维护文件的一个索引以跟踪日志循环。这些日志可以记录发送到从服务器的更新。 当一个从服务器连接主服务器时,它通知主服务器在日志中读取的最后一次成功更新的位置。
从服务器接收从那时起发生的任何更新,然后封锁并等待主服务器通知新的更新。 过程如下
- 主服务器 把更新记录到二进制日志文件中。
- 从服务器把主服务器的二进制日志拷贝 到自己的中继日志(replay log)中。
- 从服务器重做中继日志中的时间,把更新应用到自己的数据库上。
mysql 支持的复制类型?
- 基于语句的复制: 在主服务器上执行的 SQL 语句,在从服务器上执行 同样的语句。MySQL 默认采用基于语句的复制,效率比较高。 一旦发 现没法精确复制时,会自动选着基于行的复制。
- 基于行的复制:把改变的内容复制过去,而不是把命令在从服务器上执 行一遍. 从 mysql5.0 开始支持
- 混合类型的复制: 默认采用基于语句的复制,一旦发现基于语句的无法 精确的复制时,就会采用基于行的复制。
mysql 中 myisam 与 innodb 的区别?
- 事务支持 > MyISAM:强调的是性能,每次查询具有原子性,其执行数 度比 InnoDB 类型更快,但是不提供事务支持。 > InnoDB:提供事 务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚 (rollback)和崩溃修复能力(crash recovery capabilities)的事务安全 (transaction-safe (ACID compliant))型表。
- InnoDB 支持行级锁,而 MyISAM 支持表级锁. >> 用户在操作 myisam 表时,select,update,delete,insert 语句都会给表自动 加锁,如果加锁以后的表满足 insert 并发的情况下,可以在表的尾部插 入新的数据。
- InnoDB 支持 MVCC, 而 MyISAM 不支持
- InnoDB 支持外键,而 MyISAM 不支持
- 表主键 > MyISAM:允许没有任何索引和主键的表存在,索引都是保 存行的地址。 > InnoDB:如果没有设定主键或者非空唯一索引,就会 自动生成一个 6 字节的主键(用户不可见),数据是主索引的一部分,附 加索引保存的是主索引的值。
- InnoDB 不支持全文索引,而 MyISAM 支持。
- 可移植性、备份及恢复 > MyISAM:数据是以文件的形式存储,所以 在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进 行操作。 > InnoDB:免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十 G 的时候就相对痛 苦了
- 存储结构 > MyISAM:每个 MyISAM 在磁盘上存储成三个文件。第一 个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表 定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名 是.MYI (MYIndex)。 > InnoDB:所有的表都保存在同一个数据文件 中(也可能是多个文件,或者是独立的表空间文件),InnoDB 表的大 小只受限于操作系统文件的大小,一般为 2GB。