Java基础篇
Java基础面试题
- Java基础篇
- 1 什么是面向对象?
- Java的基本数据类型
- 2 JRE、JDK、JVM的关系
- 3 为什么局部内部类和匿名内部类只能访问局部final变量?
- 4 String和StringBuffer、StringBuilder(*)
- 5 重载和重写的区别(*)
- 6 接口和抽象类的区别以及使用场景 *
- 7 List和Set的区别
- 8 hashCode与equals
- 9 ArrayList和LinkedList的区别
- 10 冒泡排序
- 11 HashMap和HashTable的区别?底层的实现(*)
- 12 ConcurrentHashMap原理,JDK7和JDK8版本的区别
- 13 如何实现一个IOC容器
- 14 什么是字节码?采用字节码的好处是什么?
- 15 Java类加载器有哪些
- 16 双亲委派机制(模型)
- 17 Java中的异常体系
- 18 什么是线程安全?
- 19 并发编程的三要素?
- 20 深拷贝和浅拷贝
- 21 HashMap和TreeMap的区别 *
- MYSQL篇
- SPring篇
- SpringMVC篇
- Redis篇
- 网络基础篇
1 什么是面向对象?
面向对象注重事情的参与者(对象)、以及各自需要做什么。
举例:
洗衣机洗衣服
面向对象会拆分出人和洗衣机两个对象
人: 打开洗衣机、放衣服、放洗衣液
洗衣机:清洗、烘干
面向过程比较直接高效,但面向对象易于维护、易于扩展易于复用。
面向对象三大特性
封装、继承、多态
封装
明确标识允许外部使用的所有成员函数和数据项
内部对外调用透明,外部无需关心或者修改内部的实现
继承
继承父类的方法,并自己做出扩展或者改变
子类共有的方法、属性可以直接使用父类,而不需要自己定义、只需要扩展自己个性化的方法或属性
多态
对象所属的类不同,外部对同一个方法的调用,实际的执行逻辑不同
继承,方法重写,父类引用指向子类对象
父类类型 变量名 = new 子类对象;
变量名.方法名();
无法调用子类特有的功能、方法
Java的基本数据类型
整数类型
- byte 【1B】
- short 【2B】
- int 【4B】
- long 【8B】
浮点数:
- float 【4B】
- double 【8B】
字符类型:
- char 【2B】
布尔型
- bolean 【true/flase】
2 JRE、JDK、JVM的关系
JDK包含 JRE和JVM
JDK :开发工具
JRE:运行环境
Java文件——> .class文件 —— > JVM ——> 映射到Windows系统
3 为什么局部内部类和匿名内部类只能访问局部final变量?
内部类和外部类是一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
这里产生一个问题:外部类方法结束时,局部变量就销毁了,但是内部类可能还存在,这里就有一个矛盾的地方:内部类对象访问了一个不存在的变量,为了解决这个问题,就将局部变量COPY一份,作为内部类的成员变量,这样当局部变量销毁之后,内部类还可以访问他,实际上访问的是局部变量的COPY,这样就变相延长了局部变量的生命周期。
那在copy时就必须要保证两个变量一直,所以使用final。
4 String和StringBuffer、StringBuilder(*)
- 可变性:
- String不可变,
使用final修饰
- StringBuffer和StringBuilder可变
没有使用final修饰
,继承来自AbstractStringBuilder,在原对象上进行操作。
-
线程安全:
- String是线程安全的,因为他不可变,也可理解为常量
- StringBuffer是线程安全的,
Buffer对线程增加了同步锁或者对调用方法增加了同步锁(synchronized),所以线程是安全的
-
性能:
StringBuilder > String Buffer > String
- String每次改变的时候都会new 一个新的对象所以性能差
- StringBuffer因为有同步机制,所以在单线程下执行效率低
- StingBuilder没有同步机制,所以单线程环境下性能更好
-
使用场景
- 经常需要改变字符串使用
StringBuffer
和StringBuilder
- 优先使用
StringBuilder
,多线程使用共享变量时使用StringBuffer
- 经常需要改变字符串使用
5 重载和重写的区别(*)
重载:发生在同一个类中,方法名和参数不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同
重写:发生在子父类中,方法名、参数列表必须相同,返回值范围必须小于等于父类,抛出异常必须小于等于父类,访问修饰符范围必须大于等于父类;如果父类方法的修饰符是private私有的,那么子类不能重写。
6 接口和抽象类的区别以及使用场景 *
- 抽象类可以存在普通成员函数,而接口只能存在Public abstract方法。
- 抽象类中的成员变量可以是各种类型,而接口的成员变量只能是public static final类型的
- 抽象类只能继承一个,而接口可以实现多个
使用场景:
7 List和Set的区别
- List:==有序,按照对象进入的顺序保存对象,可重复,允许多个Null元素对象,==可以使用iterator取出所有元素。再逐一遍历,还可使用get方法获取指定下标的元素
- Set:==无序,不可重复,最多允许有一个Null元素对象,==取元素只能用iterator接口取得所有元素,再逐一遍历各个元素。
8 hashCode与equals
hashCodehashCode的作用是获取哈希码,也称散列码。他实际上返回的是一个int整数。这个哈希码作用是确定该对象在哈希表中的索引位置。
散列表存储的是键值对(Key-Value)。
特点是能根据key快速检索对应的Value
为什么会有hashCode
以“HashSet“如何检查重复为例说明为什么要有hashCode
对象加入HashSet时,会先调用HashCode去计算值来判断加入的位置,看看该位置是否有值,如果没有,HashSet会假设没有重复,如果有重复则会调用equals方法区判断两个对象是否一致,如果一致则不会让其加入操作成功。如果不同,就会重新散列(重新计算)到其他位置。,这样就减少了equals的次数,提高了执行速度。
区别or联系
- 如果两个对象相等,那么hashCode一定相同
- 两个对象相等,分别调用equals方法都返回true
- 两个对象有相同的hashCode,他们也不一定相同
- 所以,覆盖equals方法,就必须覆盖hashCode方法
- hashCode的默认行为是对堆上的对象产生独特值。如果没有重写hashCode方法,那么这个类的两个对象无论如何不会相等(即使这两个对象指向相同的数据)
9 ArrayList和LinkedList的区别
ArrayList
基于动态数组,连续内存存储,适合下标访问(随机访问),
扩容机制:数组长度固定、超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组里面。如果不是尾部插入的数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能,甚至超过LinkList(需要创建大量的node对象)
LinkList
基于链表,可以储存在分散的内存中,适合做数据的插入和删除操作,不适合查询需要逐一遍历
遍历LinkedList必须使用iterator
不能使用for循环,因为每次for循环体内通过get取得某个元素时都需要对list重新遍历,性能消耗大。
10 冒泡排序
冒泡排序:通过对排序序列从前向后(从下标较小的元素开始)依次比较相邻元素的值,若发现逆序则交换,使得值比较大的元素逐渐从前向后移动,就像水底下的气泡一样逐渐向上冒。
int[] arr = {5,9,7,28,9,18,38}
for( i = 0 , i < arr.length - 1 , i++ ){
for( j = 0 , j < arr.length - 1 - i , j++ ){
if( arr[j] > arr[j+1] ){
temp = arr[j];
arr[ j ] = arr [ j + 1 ];
arr [j + 1] = temp;
}
}
}
11 HashMap和HashTable的区别?底层的实现(*)
区别:
- HashTable的每个方法都增加了
synchronized
修饰,所以是线程安全的 - HashMap没有,所以不安全
- HashMap允许key和value为空,但是HashTable不允许
HashMap底层实现:
数组 + 链表实现
JDK8开始当==链表达到8,数组长度超过64时,链表会转为红黑树,==元素以内部类Node节点存在
- 计算key的Hash值,然后二次Hash后对数组取模,对应放到数组下标的位置。
- 如果没有产生hash冲突(下标位置没有元素),则直接创建Node节点存入数组中
- 如果产生hash冲突,则调用equals方法判断是否相等,相等直接取代原有元素。不相等则判断链表高度插入链表,当链表达到8,数组长度大于64则转为红黑树,长度低于6转回链表
- key为null,存在下标0的位置
数组扩容:
- 和ArrayList方式一样。
12 ConcurrentHashMap原理,JDK7和JDK8版本的区别
JDK7:
- 数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
- 元素查询:二次Hash,第一次Hash定位到Segment,第二次Hash定位到元素所在链表的头部
- 锁:Segment分段锁 Segment继承了ReentranLock,锁定操作Segment,其他Segment并不受影响,并发度为Segment的个数,可以通过构造函数指定,数组扩容不会影响到其他Segment
- get方法无需加锁,Volatile保证
JDK8:
数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找、替换、赋值操作都用CAS
锁 :锁链表的head节点,不影响其他元素读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
13 如何实现一个IOC容器
- 配置文件配置包扫描路径
- 递归包扫描获取.class文件
- 反射、确定需要交给IOC管理的类
- 对需要注入的类进行依赖注入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zg6ImnX6-1692959870051)(C:\Users\23234\Desktop\笔记\面经\Java基础面试题.assets\image-20230821102353209.png)]
14 什么是字节码?采用字节码的好处是什么?
Java中的编译器和解释器:
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口。
编译程序只需要面向虚拟机。生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码==(扩展名为.class的文件)==,他不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译成字节码,字节码由虚拟机解释运行。虚拟机将每一条要执行的字节码送给解释器。解释器将其翻译为特定机器的机器码,然后在特定的机器上运行。这也解释了Java的编译和解释并存的特点,
Java源代码——> 编译器 ——>JVM可执行的Java指令——> JVM ——> JVM中解释器 ——> 机器可执行二进制机器码 ——> 程序运行
采用字节码的好处:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题。同时有保留了解释语言可一直的特点。所以Java程序运行时比较高效。而且,由于字节码并不专对一种特定的机器。因此,Java程序无序重新编译便可在多种不同的计算机上运行。
15 Java类加载器有哪些
JKD自由三个类加载器:Bootstrap ClassLoader、ExtClassLoader、AppClassLoader
BootstrapClassLoader是ExtClassLoader的父类加载器,默认加载%JAVA_HOME%/lib下的jar包和class文件
ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/ lib / ext文件夹下的jar包和class类
AppClassLoader是自定义类加载器的父类,负责加载classpash下的类文件(系统类加载器,线程上下文加载器)
继承ClassLoader实现自定义类加载器
16 双亲委派机制(模型)
双亲委派机制是java类加载机制的一种实现方式,用于避免类的重复加载。
双亲指的是:类加载先向上寻找,再向下寻找的流程就叫双亲委派机制。
原理
- 类加载器收到类加载的请求
- 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
- 启动类加载器检查是否能够加载这个类,能加载就结束,使用当前加载器,否则就抛出异常,通知子加载器进行加载
- 重复步骤 3
都找不到就会报错:Class Not Found
作用
-
避免类的重复加载
类加载器加载某个类时,先委托父类加载器加载,如果父类找不到,才会自己尝试去加载,这种机制可以避免重复加载一个类,节省内存空间,同时也保证类的唯一性,避免出现类不一致的情况。
-
确保类的安全性
可以避免应用程序自己写的类替换掉核心类库中的类
-
提高类加载的效率
因为每个加载器在加载前都会先委托父类加载器去加载,所以如果父类已经加载了就不需要在加载一次。这种机制可以提高加载效率,减少资源的消耗。
17 Java中的异常体系
Java中所有的异常都来自顶级父类Throwable
Throwstable有两个子类:Exception 和 Error
Error是程序无法处理的错误,一旦出现错误,则程序被迫停止
Exception不会导致程序停止,分为两部分:RunTimeException和CheckedException检查异常
RunTimeException常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常发生在程序编译过程中,会导致程序编译不通过。
18 什么是线程安全?
代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码,如果每次运行结果和单线程的结果是一样的,而且其他的变量的值也和预期是一样的,就是线程安全的。
19 并发编程的三要素?
- 原子性
- 原子性指的是一个或多个操作,要么全部执行并且在执行过程中不被打断,要么就全部不执行。
- 可见性
- 可见性指多个线程操作一个共享变量时,其中一个线程先对变量进行修改后,其他线程可以立刻看到修改结果。
- 有序性
- 程序的执行顺序按照代码的先后顺序执行。
20 深拷贝和浅拷贝
深拷贝:复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
- 既拷贝克隆基本类型变量,又克隆引用类型变量
浅拷贝:会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
- 只拷贝基本类型变量,不拷贝引用类型变量
21 HashMap和TreeMap的区别 *
- HashMap是无序的,TreeMap是有序的(会按照键的大小排序)
- HashMap是数组+链表+红黑树,TreeMap是红黑树
- HashMap允许键值都为null,但是TreeMap不允许键为null,允许值为null
- HashMap插入删除等操作时间复杂度为O(1),TreeMap为O(log n),
MYSQL篇
1 mysql的事务特性
- 原子性
- 一个事务内操作要么失败要么成功。
- 隔离性
- 事务与事务之间互相不影响
- 一致性
- 事务前后的数据总量不变
- 持久性
- 事务的改变一旦提交发生的改变不可逆
2 mysql如何做慢SQL优化
- 尽量减少select的数据列
- orderby查找时使用索引排序
- groupby查询时,同样使用索引,尽量避免使用到临时表
- 使用复杂查询时,适用关联查询来替代子查询,并且最好使用内连接
- 使用count函数的话,count(*)的效率是最高的
- 写update语句时,where条件使用索引
- 表中的数据是否太大,太大时使考虑分库分表
3 为什么使用内连接不使用外连接?
外链接的话连接顺序是固定死的,比如left join 必须先对左表进行全表扫描然后一条条到右表去匹配,内连接的话mysql会自己根据查询优化器去判断用哪个表做驱动
4 mysql的整个查询过程
- 客户端向SQL服务器发送一条查询请求
- 服务器首先检查查询缓存。如果命中查缓存,返回存储在缓存中的结果,否则进入下一阶段
- 服务器进行SQL解析,预处理、再由优化器生成对应执行的计划
- mysql根据执行计划,调用存储引擎的API执行查询
- 将结果返回给客户端,同时缓存查询结果(8.0之间才有查询缓存,之后没有)
5 如何设计数据库?
-
抽取实体,如商品信息,用户信息
-
分析其中属性,如商品名称,商品价格,用户姓名
-
分析表与表之间的关联关系
参考三大范式进行设计,设计主键时,主键要尽量小并且定义为自增和不可修改
6 三大范式
- 第一范式:每个列都不可再拆分
- 第二范式:在第一范式的基础上,非主键列完全依赖主键,而不能是依赖主键的一部分
- 第三范式:在第二范式的基础上,非主键列只能依赖主键,不能依赖其他非主键
7 where和Having的区别?
where是执行约束,having是过滤条件
where早于having执行
where不可使用聚合函数,having可以使用
8 char 和 vachar 的区别
char是不可变的,最大长度为255,vachar是可变的字符串最大长度2^16
SPring篇
1 Spring是什么?
轻量级的开源的J2EE框架,他是一个容器框架,用来装JavaBean(Java对象),中间层框架可以起一个连接作用
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架
2 谈谈你对AOP的理解?
AOP即面向切面编程,简单来说就是把程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改原有代码的基础上,对已有的方法进行扩展、增强。
3 谈谈你对IOC的理解
容器概念、控制反转、依赖注入(DI)
IOC是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
IOC思想基于IOC容器来完成的。
之前需要我们自己手动new对象的,但是我们现在不需要反复去new对象了,而是把new对象的主动权交给IOC容器,我们什么时候用什么时候取就可以了。
4 Spirng Bean的生命周期?*
- 实例化Bean
- 属性注入
- 初始化方法调用
- 使用和销毁
SpringMVC篇
1 Sping MVC 的执行流程?*
- 客户端发送请求:客户端向服务器发送请求,请求可以是通过URL、表单提交、AJAX等方式发起。
- DisPatcherServlet接受请求:视图解析器将控制器返回的视图名解析为具体的视图对象。
- 处理器映射器(HandlerMapping)定位处理器:DispatcherServlet通过处理器映射器(HandlerMapping)将请求的URL映射到对应的处理器(Controller)。
- 处理器适配器(HandlerAdapter)执行处理器:处理器适配器根据处理器的类型调用相应的方法来处理请求,并返回一个ModelAndView对象,其中包含视图名和模型数据。
- 控制器(Controller)处理请求:控制器是实际处理请求的组件,根据业务逻辑处理请求并生成模型数据
- 视图解析器(ViewResolver)解析视图:视图解析器将控制器返回的视图名解析为具体的视图对象。
- 视图渲染器(ViewRenderer)渲染视图:视图渲染器将模型数据填充到视图中,并生成最终的HTML响应
- 返回响应给客户端:DispatchServlet将悬案后的视图响应给客户端。
Redis篇
1 Redis中Value常用的五种数据类型 *
- 字符串 string
- 哈希 hash
- 列表 list
- 集合 set
- 有序集合 sorted set / zset
网络基础篇
1 TCP和UDP的区别?*
- TCP是可靠的,UPD是不可靠的
- TCP提供可靠的数据传输,确保数据的完整性和顺序性。他使用序号、确认和重传机制来保证数据的正确性。
- UDP不提供可靠保证,数据包可能会丢失,重复或者乱序
- 连接性
- TCP是面向连接的协议,通过建立虚拟的连接来进行数据传输
- UDP是无连接协议,数据包之间相互独立,每个数据包都是独立发送
- 数据量
- TCP没有数据大小限制,可以处理任意大小的数据
- UDP对长度有限制,单个数据包长度通常在64KB以内
- 延迟
- TCP比UDP延迟更高,因为他有可靠性保证和流量控制机制
- 使用场景:
- TCP可靠,常用于文件传输、电子邮件等
- UDP则适用实时性较强高的应用,如音视频流传输