【Java】面试题目(自用的笔记)

文章目录

  • 前言
  • 一、基础篇
    • 1.介绍一下Java
    • 2.Java怎么实现跨平台的
    • 3.类加载的过程
    • 4.什么是线程
    • 5.线程的创建
    • 6.线程的状态和生命周期
    • 7.Java继承、多态、封装
    • 8.进程和线程的区别
    • 9. HTTP和HTTPS
    • 10.HTTP状态码
      • 1开头:
      • 2开头
      • 3开头
      • 4开头
      • 5开头
    • 11. JDK 中单例的体现
    • 12. Integer和int
    • 13. 抽象类和接口的差别
    • 14. final、finally、finalize 的区别
    • 15. this 和 super的区别
    • 16.说说你对MVC的理解
  • 二、MySQL
    • 1.优化数据库
    • 2. MySQL的删除操作
    • 3.简述一下MySQL的框架
    • 4.MySQL是怎样实现插拔式的存储引擎的
    • 5.char(10)和varchar(10)的区别
    • 6.MySQL如何在页中快速查到行数据
    • 7.为什么要有页数据这个东西?通常大小是多少?
      • 8.事务的四大特性
      • MySQL怎么保证一致性
      • MySQL怎么保证原子性
      • MySQL怎么保证持久性
    • 9.脏读、幻读、不可重复读
    • 10. MySQL获取自增主键Id
    • 11.数据库三大范式
    • 12. 请你说说MySQL索引,以及它们的好处和坏处
  • 三、算法
    • 1.二分查找
    • 2. 冒泡排序
    • 3.选择排序
    • 4.选择排序与冒泡排序的比较
    • 5.插入排序
    • 6.插入排序与选择排序的比较
    • 7.快速排序
      • 单边循环快排
  • 数据结构
    • 1.ArrayList
    • 2. LinkedList
    • 3. HashMap
    • 4. HashSet
    • 5. Hashtable 和 ConcurrentHashMap
    • 6. HashMap 和 Hashtable
  • 线程and并发
    • 1. 线程池的核心参数
    • 2. 线程中sleep和wait
    • 3. 线程中 lock 和 synchronized
    • 4. volatile 能否保证线程安全
    • 5. 悲观锁与乐观锁
    • 6. 线程中run()方法和start()的区别
    • 7. 如何保证线程安全
    • 8. 说说多线程
    • 9.死锁定义及发生的条件
    • 10. 乐观锁与悲观锁
  • JVM
    • 1.JVM的内存结构
  • Spring 和 SpringBoot
    • SpringBoot自动装配原理
    • Spring中Bean对象的作用域
    • 控制反转IOC和依赖注入DI
    • @Autowired 和 @Resource 注解的区别
    • 对AOP的理解


前言

希望能找到个好实习吧


一、基础篇

1.介绍一下Java

Java是一种通用的,基于类的,面向对象的编程语言。是世界上使用最广泛的编程语言之一。具有面向对象、平台无关性、简单性、解释执行、多线程、安全性等很多特点,用于应用程序开发的计算平台。

2.Java怎么实现跨平台的

Java代码运行过程中,文件会被 编译成字节码文件 。字节码文件交由 Java虚拟机JVM 来运行,不同的系统平台有着不同的JVMjava虚拟机,从而借助JVM虚拟机来实现跨平台。

3.类加载的过程

类加载过程就是将.class文件中类的元信息加载进内存,创建Class对象并进行解析、初始化类变量等的过程。
类加载分为三个部分:加载、连接、初始化。

  • 加载:是把class字节码文件从各个来源通过类加载器装 载入内存 中。
    字节码来源:一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
    类加载器:一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
  • 连接
    类的连接分为三个阶段:验证、准备、解析。
    验证:主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
    准备:准备阶段的主要任务是为类的类变量 开辟空间赋默认值
    解析:将常量池内的 符号引用 替换为 直接引用 的过程。

符号引用即字符串;直接引用即偏移量,类的元信息位于内存的地址串。

  • 初始化:主要是对类变量初始化,是执行类构造器的过程。
    【Java】面试题目(自用的笔记)_第1张图片

4.什么是线程

线程是操作系统能够 独立调度 的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。也就是说一个进程可以包含多个线程, 因此线程也被称为轻量级进程。

5.线程的创建

创建线程的方式有三种:

  • 继承Thread类
    继承Thread类并重写run方法
  • 实现Runnable接口
    实现Runable接口的run方法
  • 实现Callable接口
    提供了 Callable 接口和 Future 接口,通过它们,可以在线程执行结束后,返回执行结果。并且线程执行过程中可以抛出异常。

继承Thread类和实现Runnable接口的区别
最主要的区别就是一个是接口一个是实现类,常用接口可以避免单继承的局限性外。
还有继承Thread类的方式可能会导致类的局部变量不能正确的被共享。因为每个线程都是一个独立的对象,它们之间不能共享实例变量,如果需要共享变量,就必须使用静态变量或共享对象锁。
而使用Runnable接口的方式,多个线程可以共享同一个Runnable实例,从而共享实例变量。

Runnable接口和Callable接口的区别
Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息

6.线程的状态和生命周期

  • NEW:新建状态,尚未启动的线程处于此状态;
  • RUNNABLE:可运行状态,Java 虚拟机中执行的线程处于此状态;
  • BLOCK:阻塞状态,等待监视器锁定而被阻塞的线程处于此状态;
  • WAITING:等待状态,无限期等待另一线程执行特定操作的线程处于此状态;
  • TIME_WAITING:定时等待状态,在指定等待时间内等待另一线程执行操作的线程处于此状态;
  • TERMINATED:结束状态,已退出的线程处于此状态。
  • 【Java】面试题目(自用的笔记)_第2张图片

7.Java继承、多态、封装

  • 封装:将属性和方法写在一个类中,并将属性私有化,生成 get 和 set 方法,内部类可以直接访问其属性,外部类若要访问该封装类的属性,只能通过 get 和 set 方法。
  • 继承:子类继承父类,子类的实例可以调用父类除 private 修饰以外的所有属性和方法,在Java中,子类只能有一个父类,但是父类可以有多个子类。
  • 多态:父类的引用指向子类的实例,一个实例的相同方法在不同的情形下有不同的表现形式

8.进程和线程的区别

进程是:一个进程就是CPU执行的单个任务的过程,是程序在执行过程当中CPU 资源分配 的最小单位,并且进程都有自己的地址空间,包含了(操作系统层面)创建态、就绪态、运行态、阻塞态、终止态五个状态。
线程是:CPU 独立调度 的最小单位,它可以和属于同一个进程的其他线程共享这个进程的全部资源。
两者之间的关系:一个进程包含多个线程,一个线程只能在一个进程之中。每一个进程最少包含一个线程。
两者之间的区别
最根本的区别就是 进程是CPU资源分配的最小单位,线程是CPU独立调度的最小单位;
进程之间的切换开销比较大,但是线程之间的切换开销比较小;
CPU会把资源分配给进程,但是线程几乎不拥有任何的系统资源。因为线程之间是共享同一个进程的,所以线程之间的通信几乎不需要系统的干扰。

9. HTTP和HTTPS

HTTP:是 超文本运输协议 ,是实现网络通信的一种规范。在实际应用中,HTTP常被用于在Web浏览器和网站服务器之间 传递信息 ,以 明文方式 发送内容,不提供任何方式的数据加密。
HTTPS:HTTP传递信息是以明文的形式发送内容,这并不安全。而HTTPS出现正是为了解决HTTP不安全的特性
为了保证这些隐私数据能加密传输,让HTTP运行 安全的SSL/TLS协议 上,即 HTTPS = HTTP + SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密
SSL 协议位于TCP/IP 协议与各种应用层协议之间,浏览器和服务器在使用 SSL 建立连接时需要选择一组恰当的加密算法来实现安全通信,为数据通讯提供安全支持。
【Java】面试题目(自用的笔记)_第3张图片
http和https的区别:

  • HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理,相对更安全
  • HTTP 和 HTTPS 使用连接方式不同默认端口也不一样,HTTP是80,HTTPS是443
  • HTTPS 由于需要设计加密以及多次握手,性能方面不如 HTTP
  • HTTPS需要SSL,SSL 证书需要钱,功能越强大的证书费用越高

10.HTTP状态码

状态码第一位数字决定了不同的响应状态,有如下:

1 表示消息
2 表示成功
3 表示重定向
4 表示请求错误
5 表示服务器错误

1开头:

  • 100(客户端继续发送请求,这是临时响应):这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应
  • 101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级

2开头

  • 200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回
  • 201(已创建):请求成功并且服务器创建了新的资源
  • 202(已创建):服务器已经接收请求,但尚未处理
  • 203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源
  • 204无内容):服务器成功处理请求,但没有返回任何内容
  • 205重置内容):服务器成功处理请求,但没有返回任何内容
  • 206(部分内容):服务器成功处理了部分请求

3开头

  • 300多种选择):针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent)选择一项操作,或提供操作列表供请求者选择
  • 301永久移动):请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置
  • 302临时移动): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
  • 303(查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码
  • 305使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理
  • 307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求

4开头

  • 400(错误请求): 服务器不理解请求的语法
  • 401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
  • 403禁止): 服务器拒绝请求
  • 404未找到): 服务器找不到请求的网页

5开头

  • 500(服务器内部错误):服务器遇到错误,无法完成请求
  • 503(服务不可用): 服务器目前无法使用(由于超载或停机维护)

11. JDK 中单例的体现

  • Runtime 体现了饿汉式单例
  • Console 体现了双检锁懒汉式单例
  • Collections 中的 EmptyNavigableSet 内部类懒汉式单例
  • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
  • Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

12. Integer和int

区别:
Integer是int类型的包装类,int是基本数据类型
Integer变量需要进行实例化才能进行实用,int不需要
Integer实际上是对象的引用,new一个Integer的时候,是生成一个指针指向这个对象;int则是直接存储数据。
Integer的默认值是null,int是0;

Integer和int的比较

①以下代码输出结果是true
因为当Integer包装类对象与int类型进行比较的时候,Java会将Integer进行拆包比较,变成两个int基本类型的变量比较。

Integer newInteger1 = new Integer(100);
int int1 = 100;
 System.out.println(newInteger1 == int1); //true

②以下代码输出结果是flase
因为new出来的是两个对象,内存地址不同

Integer newInteger1 = new Integer(100);
Integer newInteger2 = new Integer(100);
System.out.println(newInteger1 == newInteger2); //false

③以下代码输出结果是flase
因为当变量值再 -128 ~ 127 之间时,非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者的地址也不同

Integer newInteger1 = new Integer(100);
Integer integer3 = 100;
System.out.println(newInteger1 == integer3); //false

④以下代码输出结果是true
非new生成的Integer变量值再 -128 ~ 127 之间时,指向的是同一个java常量池中的对象,内存地址相同

Integer integer3 = 100;
Integer integer4 = 100;
System.out.println(integer3 == integer4); //true

⑤ 与④相反,以下代码输出结果是flase
非new生成的Integer变量值再 -128 ~ 127 之外时,相当于new了两个Integer对象,内存地址不相同

Integer integer5 = 128;
Integer integer6 = 128;
System.out.println(integer5 == integer6); //false

13. 抽象类和接口的差别

从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
具体的不同点:
①接口可以多实现,而抽象类只能单继承
②抽象类中的方法,可以用protect和abstract修饰,而接口中,都是默认的public abstract修饰
③抽象类可以像普通类一样有自己的普通方法,但是接口不可以。
④属性上,接口中的属性只能是public static final修饰的,抽象类中任意
⑤抽象类中可以拥有静态代码块,接口中不可以
⑥抽象类可以拥有自己的构造,接口没有构造,不能拥有
⑦接口在1.8之后可以拥有default修饰的方法,抽象类中没有

14. final、finally、finalize 的区别

final ,是修饰符关键字。
修饰类,表示该类不能在被继承。
修饰方法,表示该方法不能被子类重写。
修饰变量,表示该变量是常量,不能被修改。

finally
通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,可以将释放外部资源的代码写在finally块中。

finalize ,是方法名。
这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作

15. this 和 super的区别

this
在Java语言中,this用来指向当前实例对象,它的一个非常重要的作用就是用来区分对象的成员变量与方法的形参。

super()
super 可以用来访问父类的方法或成员变量。当子类成员变量与父类有相同名字时也会覆盖父类的方法或成员变量,要想访问父类的方法或成员变量只能通过super关键字来访问。
当子类构造函数需要显示调用父类构造函数时,super() 必须为构造函数中的第一条语句。

16.说说你对MVC的理解

MVC是一种设计模式,在这种模式下软件被分为三层,Model(模型)、View(视图)、Controller(控制器)。

Model代表的是数据,View代表的是用户界面,Controller代表的是数据处理的逻辑,它是Model和View这两层之间的桥梁。将软件分层,可以将对象之间的耦合度降低,便于代码维护。

Model:指从现实世界中抽象出来的对象模型,是应用逻辑的反应。它封装了数据和对数据的操作,与数据库进行交互。View:是用户界面。Controller:控制器负责视图和模型之间的交互。主要负责把用户的请求分发到相应的模型、把模型的改变及时反映到视图上。


二、MySQL

1.优化数据库

  • 进行SQL语句优化,索引优化,最大程度的利用好索引。
  • 数据库的表结构,可进行分表、分库。
  • 最大化利用机器配置,比如设置使用机器内存的大小。
  • 升级硬件

SQL语句的优化:

  • 避免全表扫描,充分利用索引,避免索引失效。
  • 优化子查询,嵌套查询时会建立一张临时表,临时表的建立和删除都会有较大的系统开销,尽量使用INNER JOIN来代替子查询,以此来提高SQL性能。
  • 减少无效数据的查询

推荐使用自增id作为主键
1.速度更快,int类型比较速度比字符串快
2.避免过多的磁盘碎片
3.节省磁盘空间

2. MySQL的删除操作

删除操作有三种,DELETEDROPTRUNCATE
执行速度不同:DROP > TRUNCATE > DELETE
语言类型不同:DELETE是对表数据进行操作,是DML语言;DROP、TRUNCATE是对表进
行操作,是DDL语言。
原理不同:
DELETE 是对表数据进行一行行的删除,它在执行过程中会触发事务。它不是真正意义的删除,是对这一行的数据进行标记,设为不可读状态,也不会释放存储空间,当有新数据添加的时候,再对这一行的存储空间进行覆盖。
TRUNCATE 是快速的清空一张表,执行过程中不会触发事务,与DROP类似,但是它是先删除一张表再新建一张表,所以执行速度比DROP慢。
DROP 连带着数据一起删除表,不会触发事务,真正意义上的删除,会释放存储空间。

3.简述一下MySQL的框架

(cr b站:@大头不摆)
在这里插入图片描述

4.MySQL是怎样实现插拔式的存储引擎的

(cr b站:@大头不摆)
在这里插入图片描述

5.char(10)和varchar(10)的区别

(cr b站:@大头不摆)
【Java】面试题目(自用的笔记)_第4张图片

6.MySQL如何在页中快速查到行数据

① 因为页中每一行的数据长度不固定,所以不能形成数组结构,使用了链表结构,而链表无法做到随机访问,只能进行 遍历查询,时间复杂度为O(n)。
②所以MySQL运用了 数据冗余建立索引 的思想,在每一页中存储了行数据的索引字段目录,然后把索引字段做成了定长,形成 数组 结构,具备了数组结构就可以对其进行 二分查找算法,时间复杂度为 O ( l o g n ) O(logn) O(logn)
③但其实并没有冗余所有行的索引字段,而是把 行数据分成一组一组,每组冗余最后一条数据的索引字段,然后通过 二分查找法找到目标组 后,再对目标组里的数据进行 遍历查询,在时间和空间上做权衡。

7.为什么要有页数据这个东西?通常大小是多少?

(cr b站:@大头不摆)

【Java】面试题目(自用的笔记)_第5张图片

8.事务的四大特性

  • 原子性(Atomicity):指一个事务是一个不可分割的工作状态,事务的操作要么全部成功,要么全部失败。
  • 一致性(Consistency):指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。事务执行前后,数据处于一种合法的状态。
  • 隔离性 (Isolation):是当多个用户并发访问数据库时,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
  • 持久性 (Durability):是指一个事务一旦被提交了,对数据库中的数据的改变就是永久的。

MySQL怎么保证一致性

从数据库层面:数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID这四大特性之中,一致性是目的,原子性、隔离性、持久性是手段,是为了保持一致性,数据库提供的手段。
从应用层面:通过代码判断数据库是否有效,然后决定回滚还是提交数据。

MySQL怎么保证原子性

回滚日志,是实现原子性的关键,保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。

MySQL怎么保证持久性

利用物理日志,记录的是数据页的物理修改,而不是某一行或者某几行修改成什么样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。

9.脏读、幻读、不可重复读

脏读:是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
不可重复读:是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
幻读
在这里插入图片描述

不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

10. MySQL获取自增主键Id

  • 方法一:
    使用 last_insert_id()
SELECT LAST_INSERT_ID();

如果你一次插入了多条记录,这个 last_insert_id 函数返回的是第一个记录的ID值。
LAST_INSERT_ID 是与table无关的,如果向表a插入数据后,再向表b插入数据,LAST_INSERT_ID会改变。

  • 方法二:
    使用MAX(id)
select max(id) from students;

不适合高并发。如果同时插入的时候返回的值可能不准确。

  • 方法三
    在jdbc中使用 getGeneratedKeys()

11.数据库三大范式

第一范式:要求表中的数据,每一列都是不可分割的原子项数据。
第二范式:消除部分依赖,要求一张表中的每一列都完全依赖于主键(针对组合主键),不会出现某一列只和部分主键相关。
第三范式:消除传递依赖,要求一张表中的每一列都和主键是直接依赖的,不是间接依赖。
以下内容 参照于https://www.cnblogs.com/wsg25/p/9615100.html
【Java】面试题目(自用的笔记)_第6张图片【Java】面试题目(自用的笔记)_第7张图片【Java】面试题目(自用的笔记)_第8张图片

12. 请你说说MySQL索引,以及它们的好处和坏处

MySQL的索引就像是字典的目录,是一种允许查询操作快速确定哪些符合WHERE子句中的条件,并检索到这些行的其他列值的数据结构
在大数据量的查询中,合理使用索引的优点非常明显:
1.大大加快了数据检索的速度
2.通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能
但是它也有坏处:
1.创建索引和维护索引要耗费时间,因为索引需要动态的维护
2.索引会增加存储资源的消耗

扩展:
创建了索引不一定会走索引
比如使用组合索引的时候,如果没用遵从“最左前缀”的原则进行搜索,则索引是不起作用的。
举例,假设在id、name、age字段上已经成功建立了一个名为MultiIdx的组合索引。索引行中按id、name、age的顺序存放,索引可以搜索id、(id,name)、(id, name, age)字段组合。如果列不构成索引最左面的前缀,那么MySQL不能使用局部索引,如(age)或者(name,age)组合则不能使用该索引查询。


三、算法

1.二分查找

算法描述
前提:有序数组A
①定义左边界 L、右边界 R,确定搜索范围,循环执行二分查找(2、3两步)
②获取中间索引 M = Floor((L+R) /2)
③中间索引的值 A[M] 与待搜索的值 T 进行比较
1.如果A[M] == T,表示查找成功,返回M;
2.如果A[M] > T ,表示中间值右侧的值都大于T,无需比较,则右边界设为M - 1;
3.如果A[M] < T ,表示中间值左侧的值都小于T,无需比较,则左边界设为M + 1;
④如果L > R ,则查找失败,退出循环。
时间复杂度度为 O ( l o g n ) O(logn) Ologn

代码示例

/**
     * 非递归的二分查找算法
     *
     * @param array 有序数组
     * @param t     待搜索的值
     * @return 若返回-1表示查找失败
反之则表示待搜索的值在该数组的下标 */
public static int binarySearch1(int[] array, int t) { int l = 0; //当前比较数组最小的下标 int r = array.length - 1; //当前比较数组最大的下标 int m; //中间索引 while (l <= r) { m = (l + r) / 2; if (array[m] == t) { return m; } else if (array[m] > t) { r = m - 1; } else { l = m + 1; } } return -1; }
/**
     * 递归二分查找算法
     *
     * @param array 有序数组
     * @param l     当前比较数组最小的下标
     * @param r     当前比较数组最大的下标
     * @param t     待搜索的值
     * @return 若返回-1表示查找失败
反之则表示待搜索的值在该数组的下标 */
public static int binarySearch2(int[] array, int l, int r, int t) { int m = (l + r) / 2; //中间索引 if (l > r) { return -1; } if (array[m] == t) { return m; } else if (array[m] > t) { return binarySearch2(array, l, m - 1, t); } else { return binarySearch2(array, m + 1, r, t); } } }

解决整数溢出问题
当 l 和 r 都较大时,l + r 有可能超过整数范围,造成运算错误,解决方法有两种:

int m = l + (r - l) / 2;

还有一种是:

int m = (l + r) >>> 1;

2. 冒泡排序

算法描述
①依次比较数组中相邻两个元素大小,若 a[ i ] > a[ j ] ,则交换两个元素,两两都比较一遍称为一轮冒泡;
②重复第一步,直到整个数组有序。
时间复杂度为 O ( n 2 ) O(n^2) O(n2)

代码示例

  • 优化点1:每经过一轮冒泡,内层循环就可以减少一次
  • 优化点2:如果某一轮冒泡没有发生交换,则表示所有数据有序,可以结束外层循环
/**
     * 冒泡排序
     *
     * @param array 所需排序的数组
     */
    public static void bubble(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            // 一轮冒泡
            boolean swapped = false; // 是否发生了交换
            for (int j = 0; j < array.length - 1 - j; j++) {
                if (array[j] > array[j + 1]) {
                    swap(array, j, j + 1);
                    swapped = true;
                }
            }
            System.out.println("第" + i + 1 + "轮冒泡结果:" + Arrays.toString(array));
            if (!swapped) {
                break;
            }
        }
    }
  • 优化点3:每轮冒泡时,最后一次交换索引可以作为下一轮冒泡的比较次数,如果这个值为零,表示整个数组有序,直接退出外层循环即可
/**
     * 优化后的冒泡排序
     *
     * @param array 所需排序的数组
     */
    public static void bubble2(int[] array) {
        int n = array.length - 1;
        while (true) {
            int last = 0; // 表示最后一次交换索引位置
            for (int i = 0; i < n; i++) {
                System.out.println("比较次数" + i);
                if (array[i] > array[i + 1]) {
                    swap(array, i, i + 1);
                    last = i;
                }
            }
            n = last;
            System.out.println("第轮冒泡"
                    + Arrays.toString(array));
            if (n == 0) {
                break;
            }
        }
    }

3.选择排序

算法描述
①将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集;
②重复以上步骤,直到整个数组有序。
时间复杂度为 O ( n 2 ) O(n^2) O(n2)

可视化示例
i 表示已排序子集, s 表示最小元素下标, j 表示循环查找下标

代码示例
优化点:为减少交换次数,每一轮可以先找最小的索引,在每轮最后再交换元素

    /**
     * 选择排序
     * @param array 需要排序的数组
     */
    public static void selection(int[] array) {
        // i 代表每轮选择最小元素要交换到的目标索引
        for (int i = 0; i < array.length - 1; i++) {
            int s = i; //s表示最小元素的索引
            for (int j = s + 1; j < array.length - 1; j++) {
                if (array[s] > array[j]) {
                    s = j;
                }
            }
            if (s != i) {
                swap(array, i, s);
            }
            System.out.println("第" + (i + 1) + "轮排序结果为:" + Arrays.toString(array));
        }

    }

4.选择排序与冒泡排序的比较

  • 二者平均时间复杂度都是 O ( n 2 ) O(n^2) O(n2)
  • 选择排序一般要快于冒泡,因为其交换次数少
  • 但如果集合有序度高,冒泡优于选择
  • 冒泡属于稳定排序算法,而选择属于不稳定排序
    稳定排序指,按对象中不同字段进行多次排序,不会打乱同值元素的顺序
    不稳定排序则反之

5.插入排序

算法描述

①将数组分为两个区域,排序区域和未排序区域,每一轮从未排序区域中取出第一个元素,插入到排序区域(需保证顺序)
②重复以上步骤,直到整个数组有序

代码实现

    /**
     * 插入排序
     *
     * @param array 所需排序的数组
     */
    public static void insert(int[] array) {
        // i 代表待插入元素的索引
        for (int i = 1; i < array.length - 1; i++) {
            int t = array[i];  // 代表待插入的元素值
            int j = i; //循环查找的下标

            while (j >= 1) {
                if (t < array[j - 1]) {
                    array[j] = array[j - 1];
                    j--;
                } else break;
            }
            array[j] = t;

            System.out.println("第" + i + "轮排序结果:" + Arrays.toString(array));
        }
    }

6.插入排序与选择排序的比较

  • 二者平均时间复杂度都是 O ( n 2 ) O(n^2) O(n2)
  • 大部分情况下,插入都略优于选择
  • 有序集合插入的时间复杂度为 O ( n ) O(n) O(n)
  • 插入属于稳定排序算法,而选择属于不稳定排序

提示

插入排序通常被同学们所轻视,其实它的地位非常重要。小数据量排序,都会优先选择插入排序

7.快速排序

算法描述
①每一轮排序选择一个基准点(pivot)进行分区
②让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区
③当分区完成时,基准点元素的位置就是其最终位置
④在子分区内重复以上过程,直至子分区元素个数少于等于 1,这体现的是分而治之的思想 ([divide-and-conquer])

单边循环快排

算法描述
① 选择最右元素作为基准点元素
② j 指针负责找到比基准点小的元素,一旦找到则与 i 进行交换
③ i 指针维护小于基准点元素的边界,也是每次交换的目标索引
④最后基准点与 i 交换,i 即为分区位置


数据结构

1.ArrayList

ArrayList 扩容规则
①ArrayList() 会使用长度为零的数组
②ArrayList(int initialCapacity) 会使用指定容量的数组
③public ArrayList(Collection c) 会使用 c 的大小作为数组容量
④add(Object o) 首次扩容为 10,再次扩容为上次容量的 1.5 倍
⑤addAll(Collection c) 没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量 1.5 倍, 实际元素个数)

2. LinkedList

与ArrayList的区别

LinkedList

  1. 基于双向链表,无需连续内存
  2. 随机访问慢(要沿着链表遍历)
  3. 头尾插入删除性能高
  4. 占用内存多

ArrayList

  1. 基于数组,需要连续内存
  2. 随机访问快(指根据下标访问)
  3. 尾部插入、删除性能可以,其它部分插入、删除都会移动数据,因此性能会低
  4. 可以利用 cpu 缓存,局部性原理

3. HashMap

HashMap的底层数据结构,Java1.7与1.8中有什么不同?

  • 1.7 数组 + 链表
  • 1.8 数组 + (链表 | 红黑树)

树化意义

  • 红黑树用来避免 DoS 攻击,防止链表超长时性能下降,树化应当是偶然情况,是保底策略
  • hash 表的查找,更新的时间复杂度是 O ( 1 ) O(1) O(1),而红黑树的查找,更新的时间复杂度是 O ( l o g 2 ⁡ n ) O(log_2⁡n ) O(log2n),TreeNode 占用空间也比普通 Node 的大,如非必要,尽量还是使用链表
  • hash 值如果足够随机,则在 hash 表内按泊松分布,在负载因子 0.75 的情况下,长度超过 8 的链表出现概率是 0.00000006,树化阈值选择 8 就是为了让树化几率足够小

树化规则

  • 当链表长度超过树化阈值 8 时,先尝试扩容来减少链表长度,如果数组容量已经 >=64,才会进行树化

退化规则

  • 情况1:在扩容时如果拆分树时,树元素个数 <= 6 则会退化链表
  • 情况2:remove 树节点时,若 root、root.left、root.right、root.left.left 有一个为 null ,也会退化为链表

索引计算方法

  • 首先,计算对象的 hashCode()
  • 再进行调用 HashMap 的 hash() 方法进行二次哈希
    • 二次 hash() 是为了综合高位数据,让哈希分布更为均匀
  • 最后 & (capacity – 1) 得到索引(&意思是 按位与 运算)

数组容量为何是 2 的 n 次幂

  1. 计算索引时效率更高:如果是 2 的 n 次幂可以使用 位与运算代替取模
  2. 扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap

注意

  • 二次 hash 是为了配合 容量是 2 的 n 次幂 这一设计前提,如果 hash 表的容量不是 2 的 n 次幂,则不必二次 hash
  • 容量是 2 的 n 次幂 这一设计计算索引效率更好,但 hash 的分散性就不好,需要二次 hash 来作为补偿,没有采用这一设计的典型例子是 Hashtable

put 流程

  1. HashMap 是懒惰创建数组的,首次使用才创建数组
  2. 计算索引(桶下标)
  3. 如果桶下标还没人占用,创建 Node 占位返回
  4. 如果桶下标已经有人占用
    1. 已经是 TreeNode 走红黑树的添加或更新逻辑
    2. 是普通 Node,走链表的添加或更新逻辑,如果链表长度超过树化阈值,走树化逻辑
  5. 返回前检查容量是否超过阈值,一旦超过进行扩容

1.7 与 1.8 的区别

  1. 链表插入节点时,1.7 是头插法,1.8 是尾插法

  2. 1.7 是大于等于阈值且没有空位时才扩容,而 1.8 是大于阈值就扩容

  3. 1.8 在扩容计算 Node 索引时,会优化

扩容(加载)因子为何默认是 0.75f

  1. 在空间占用与查询时间之间取得较好的权衡
  2. 大于这个值,空间节省了,但链表就会比较长影响性能
  3. 小于这个值,冲突减少了,但扩容就会更频繁,空间占用也更多

4. HashSet

如何保证元素不重复
HashSet 在进行添加对象的过程中,首先计算对象的 hashcode,通过 hashcode 来确定对象要添加的位置,同时会与已经加入 HashSet 的对象的 hashcode 进行比较,如果没有相同的hashcode 则表示没有重复,该对象添加进 HashSet ;如果有相同的 hashcode,则会调用对象的 equals() 方法来判断对象是否相同,如果相同,则 HashSet 就不会让重复的对象加入到 HashSet 中,这样就保证了元素的不重复。​

对于自定义对象,需要重写equals方法

5. Hashtable 和 ConcurrentHashMap

1.Hashtable 与 ConcurrentHashMap 都是线程安全的Map集合;键和值都不能为空。
2.Hashtable 并发度低,整个Hashtable 对应一把锁,同一时刻,只能有一个线程区操作它
3.1.8之前ConcurrentHashMap使用 Segment + 数组+ 链表的结构,每个Segment对应一把锁,如果多个线程访问不同的Segment,则不会冲突
4.1.8开始ConcurrentHashMap将每个数组的每个头结点作为锁,如果多个线程访问的头结点不同,则不会冲突

6. HashMap 和 Hashtable

内容进行参考,详细请查看:https://blog.csdn.net/mrs_chens/article/details/93986400
一、线程安全
Hashtable 是线程安全的,HashMap不是线程安全的,
Hashtable中所有元素的操作,比如是get、put操作都是用synchronized修饰的。
二、性能优劣
因为Hashtable是线程安全的,如果有多个线程进行同一操作,就会造成线程的阻塞,所有Hashtable性能比较差,HashMap性能比较好,使用更广。
如果又要保证线程安全又要保证性能,可以使用ConcurrentHashMap
三、关于NULL
Hashtable是不允许键或值为NULL的,而HashMap键值都能为NULL
Hashtable中key为NULL会直接抛出空指针异常,value为NULL手动抛出空指针异常;而HashMap会对NULL进行特殊处理。
HashMap hash 方法逻辑:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

四、关于扩容
HashMap的初始容量为16,Hashtable初始容量为11,两者的负载因子默认都是0.75.
当现有容量大于总容量 * 负载因子的时候,HashMap扩容规则为当前容量翻倍,Hashtable扩容规则则是当前容量翻倍 + 1。


线程and并发

1. 线程池的核心参数

1.核心线程数目 corePoolSize :最多保留的线程数
2.最大线程数目 maximumPoolSize :核心线程 + 救急线程
3.生存时间 keepAliveTime :针对救急线程的
4.时间单位 unit :针对救急线程
5.阻塞队列 workQueue
6.线程工厂 threadFactory :可以为线程创建时起个名字
7.拒绝策略 handler

【Java】面试题目(自用的笔记)_第9张图片

2. 线程中sleep和wait

共同点:wait(),wait(long),sleep(long) 的效果都是让当前线程放弃CPU的使用权,进入阻塞状态
不同点

  • 方法归属不同
    ① sleep(long) 是Thread 的静态方法
    ② wait(),wait(long) 都是Object 的成员方法,每个对象都有
  • 醒来时机不同
    ① 执行wait(long),sleep(long 的线程都会等待相应毫秒后醒来
    ② wait(),wait(long) 还可以被 notify 唤醒,wait() 如果不唤醒就一直等待下去
    ③ 它们都可以被打断唤醒
  • 锁特性不同
    ① wait 方法的调用必须先获取 wait 对象的锁,而sleep则无此限制
    ② wait 方法执行之后会释放对象锁,允许其他线程获得该对象锁
    ③ 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁

3. 线程中 lock 和 synchronized

  • 语法层面
    synchronized 是关键字,源码在 JVM 中,是用C++语言实现的
    Lock 是接口,源码由JDK提供,用Java语言实现的
    使用 synchronized 时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法来释放锁
  • 功能层面
    二者都属于悲观锁、都具备基本的互斥、同步、锁重入功能
    Lock提供了synchronized 不具备的功能,比如获取等待状态、公平锁、可打断等功能
    Lock有适合不同场景的实现,如ReentrantLock、ReentrantReadWriteLock
  • 性能层面
    在没有竞争时,synchronized 做了很多优化,比如偏向锁、轻量级锁,性能比较好
    在竞争激烈时,Lock 的实现通常会有更好的性能

4. volatile 能否保证线程安全

线程安全主要考虑三个方面:可见性、有序性、原子性
可见性:一个线程对共享变量修改,另一个线程能看到最新的结果
有序性:一个线程内代码按编写顺序执行
原子性:一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队

volatile 能够保证共享变量的可见性有序性,但不能保证原子性

5. 悲观锁与乐观锁

【Java】面试题目(自用的笔记)_第10张图片

6. 线程中run()方法和start()的区别

线程直接调用run()方法就相当于一个普通对象调用的他的方法,而只有调用start()方法,线程才会启动,此时才具有抢占CPU资源的资格。当某个线程抢占到 CPU 资源后,会⾃动调⽤ run ⽅法。

7. 如何保证线程安全

Java中比较常用的有三种
原子类:JDK从1.5开始提供了一个atomic包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。在atomic包里,按功能分类可以归纳为4种类型的原子更新方式:原子更新基本类型、原子更新引用类型、原子更新属性、原子更新数组。无论原子更新哪种类型,都遵循“比较和替换”规则,即比较要更新的值是否等于期望值,如果是则更新,如果不是则失败。
volatile:volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”,从而可以保证单个变量读写时的线程安全。当写一个volatile变量时,该线程本地内存中的共享变量的值会被立刻刷新到主内存中;当读一个volatile变量时,该线程本地内存会被置为无效,迫使线程直接从主内存中读取共享变量。
原子类和volatile只能保证单个共享变量的线程安全,锁则可以保证临界区内的多个共享变量的线程安全。Java中加锁的方式有两种,一种是synchronized关键字Lock接口

8. 说说多线程

线程是CPU独立调度的最小单位,进程是CPU资源分配的最小单位。一个进程里至少拥有一个线程,可以让一个进程并发地处理多个任务,所以线程也被称为轻量级进程。在同一个进程的多个线程可以共享进程内的资源,从使用者的角度看就像多个线程在同时运行。使用多线程的优点有:1.提高CPU的利用率;2.更快的程序响应时间;3.创建和切换开销小。

9.死锁定义及发生的条件

死锁是多个线程互相争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态。

死锁产生有四个必要条件,1.互斥条件:一个资源在同一个时刻只能由一个线程执行 2.请求与保持:一个线程在请求被占用资源时,对已经获得的资源保持不放。 3.循环等待:死循环 4.不可剥夺:线程对所获得的资源在未完成时不能被其他线程所剥夺。

10. 乐观锁与悲观锁

乐观锁:乐观锁总是假设最好的情况,每次去拿数据的时候默认别人不会修改,所以不会上锁,只有当更新的时候会判断一下在此期间有没有人更新了这个数据。适用于多读,可以使用版本号机制进行控制

悲观锁:悲观锁总是假设最坏的情况,每次去拿数据是都认为别人会修改,所以每次在拿数据时都会上锁,这样别人想拿这个数据时会阻塞直到拿到锁。mysql数据库的共享锁排他锁都是悲观锁的实现。


JVM

1.JVM的内存结构

【Java】面试题目(自用的笔记)_第11张图片
【Java】面试题目(自用的笔记)_第12张图片
【Java】面试题目(自用的笔记)_第13张图片

线程私有的:①程序计数器 ②虚拟机栈
线程共享的:①堆 ②方法区

线程局部变量方法参数 使用的内存在 虚拟机栈
使用的内存在 方法区
类对象 使用的内存在

Spring 和 SpringBoot

SpringBoot自动装配原理

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。

Spring中Bean对象的作用域

singleton 默认值 当Ioc容器一创建就会创建bean实例,而且是单例的,每次得到的都是同一个

prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。

request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。

session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。

application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。

控制反转IOC和依赖注入DI

IoC Inverse of Control 反转控制的概念:就是将原本在程序中手动创建对象的控制权,交由Spring框架管理

DI:Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件

两者的区别:
IoC 控制反转,指将对象的创建权,反转到Spring容器
DI 依赖注入,指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

@Autowired 和 @Resource 注解的区别

@Autowired 是先根据属性的类型去Spring容器中去找Bean对象,如果找到多个,就会根据属性名去确定其中一个,如果根据属性名字没有找到则报错;
@Resource 是先根据属性名字取去Spring容器中去找Bean对象,如果没有找到,会根据属性类型去找,如果找到多个则会报错;也可以使用 @Resource (name = …)指定name,如果配置了name,则只会根据这个name来找Bean对象,如果没有找到就会直接报错。

@Autowired 是Spring层面提供的,跟Spring强绑定
@Resource 是JDK层面提供的,没有跟Spring强绑定

对AOP的理解

AOP是面向切面编程,是Spring两大核心之一,是一种编程思想,它可以对业务逻辑的各个部分进行隔离,降低耦合,提高代码的可重用性。
它的底层是通过动态代理实现的。它的应用场景有事务、日志管理等。

你可能感兴趣的:(笔记,java,面试,jvm,mysql)