记录java岗面试问题
对java的了解
Java 是一门面向对象的编程语言,吸收了 C++ 语言中大量的优点,但又抛弃了 C++ 中容易出错的地方,如垃圾回收。Java 又是一门平台无关的编程语言,通过java虚拟机(jvm)可以实现一次编译,处处运行。
对jvm的了解
Java 虚拟机,是 Java 实现跨平台的关键所在,针对不同的操作系统,有不同的 JVM 实现。JVM 负责将 Java 字节码转换为特定平台的机器码,并执行。
作用
提供了Java程序跨平台运行的能力、负责管理系统内存、监控程序执行。 Java虚拟机的作用主要包括:确保Java程序在任何平台上都能运行不受影响、提高程序安全性、提升程序性能、提供内存管理和垃圾收集功能。
内存结构
程序计数器
虚拟机栈
本地方法栈
堆内存
方法区
-
程序计数器也被称为 PC 寄存器,是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
-
虚拟机栈通常指的就是“栈”,它的生命周期与线程相同。当线程执行一个方法时,会创建一个对应的栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,然后栈帧会被压入栈中。当方法执行完毕后,栈帧会从栈中移除。
-
本地方法栈与虚拟机栈相似,区别在于虚拟机栈是为 JVM 执行 Java 编写的方法服务的,而本地方法栈是为 Java 调用本地方法服务的,由 C/C++ 编写。在本地方法栈中,主要存放了 native 方法的局部变量、动态链接和方法出口等信息。当一个 Java 程序调用一个 native 方法时,JVM 会切换到本地方法栈来执行这个方法。
-
堆是 JVM 中最大的一块内存区域,被所有线程共享,在 JVM 启动时创建,主要用来存储对象的。也是垃圾收集器管理的目标区域称作“GC 堆”
-
方法区并不真实存在,属于 Java 虚拟机规范中的一个逻辑概念,用于存储已被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等。
栈和堆的区别
堆属于线程共享的内存区域,几乎所有的对象都在堆上分配,生命周期不由单个方法调用所决定,可以在方法调用结束后继续存在,直到不再被任何变量引用,然后被垃圾收集器回收。
栈属于线程私有的内存区域,主要存储局部变量、方法参数、对象引用等,通常随着方法调用的结束而自动释放,不需要垃圾收集器处理。
jvm垃圾回收机制
垃圾回收就是对内存堆中已经死亡的或者长时间没有使用的对象进行清除或回收。
JVM 在做 GC 之前,通常会通过可达性分析算法来判断对象是否存活。
- 可达性分析算法:通过一组名为 “GC Roots” 的根对象,进行递归扫描。那些无法从根对象到达的对象是不可达的,可以被回收;反之,是可达的,不会被回收。(前提:在进行垃圾回收之前,JVM 会暂停所有正在执行的应用线程)
在确定了哪些垃圾可以被回收后,垃圾收集器(如 CMS、G1、ZGC)要做的事情就是进行垃圾回收,可以采用标记清除算法、复制算法、标记整理算法、分代收集算法等。
Java 的垃圾回收过程
垃圾回收过程主要分为标记存活对象、清除无用对象、以及内存压缩/整理三个阶段。
finalize()方法了解吗?有什么作用?
当对象不再被任何对象引用时,GC会调用该对象的finalize()方法
finalize()是Object的方法,子类可以覆盖这个方法来做一些系统资源的释放或者数据的清理。可以在finalize()让这个对象再次被引用,避免被GC回收
如果对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalize()方法。如果对象在在 finalize()中重新与引用链上的任何一个对象建立关联即可,譬如把自己 (this 关键字)赋值给某个类变量或者对象的成员变量
覆盖了finalize方法的对象需要经过两个GC周期才能被清除。
垃圾回收算法
-
标记-清除算法分为两个阶段:
- 标记:标记所有需要回收的对象
- 清除:回收所有被标记的对象
- 优点是实现简单,缺点是回收过程中会产生内存碎片。
-
标记-复制算法可以解决标记-清除算法的内存碎片问题,因为它将内存空间划分为两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后清理掉这一块。
- 缺点是浪费了一半的内存空间。
-
标记-整理算法是标记-清除复制算法的升级版,它不再划分内存空间,而是将存活的对象向内存的一端移动,然后清理边界以外的内存。
- 缺点是移动对象的成本比较高。
-
分代收集算法是目前主流的垃圾收集算法,它根据对象存活周期的不同将内存划分为几块,一般分为新生代和老年代。新生代用复制算法,因为大部分对象生命周期短。老年代用标记-整理算法,因为对象存活率较高。优化垃圾回收。
多线程线程安全
线程安全:是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染的情况。
中间键
Java 中间件是一种基于 Java 技术的中间件
Web中间件
主要用于构建 Web 应用程序和服务。
提供 HTTP 服务和 Web 应用程序部署支持,常见的Web 中间件有 :Apache Tomcat、Jetty、GlassFish、JBOSS 等。
消息中间件
消息中间件是一种基于 Java 技术的中间件,主要用于实现异步通信和解耦,使得应用程序能够通过发送和接收消息来实现分布式的交互。
常见的消息中间件有:ActiveMQ、RabbitMQ、Kafka、RocketMQ、Pulsar等。
分布式缓存中间件
分布式缓存中间件:提供一种分布式的缓存机制,能够存储大量数据并加快应用程序的访问速度。
分布式缓存中间件通常包括缓存节点、数据分片、数据同步、失效策略等组件。
常见的分布式缓存有Memcached、Redis、Ehcache 等。
分布式事务中间件
主要用于保证分布式环境下的事务一致性和可靠性。
分布式事务中间件通常包括事务管理器、资源管理器、事务协调器等组件。
常见的 Java 分布式事务中间件包括 Atomikos、Bitronix、Narayana 等。
数据库中间件
数据库中间件可以简化对读写分离以及分库分表的操作,并隐藏底层实现细节,可以像操作单库单表那样操作多库多表。
一些常见的数据库中间件如下:
- MyCat:开源数据库中间件,目前更新了MyCat2版本;
- Atlas:Qihoo 360公司Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目;
- tddl:阿里巴巴自主研发的分布式数据库服务;
- Sharding-JDBC:ShardingShpere的一个子产品,一个轻量级Java框架;
RPC 中间件
主要用于实现远程过程调用和服务治理。
RPC 中间件通常包括序列化、反序列化、传输协议、服务注册、服务发现等组件。
常见的 Java RPC 中间件包括: Dubbo、gRPC、Thrift、Spring Cloud 、Spring Cloud Alibaba等。
java微服务
微服务是一种经过良好架构设计的分布式架构方案,它的出现即是为了解决一般的分布式架构产生的问题。
微服务(Microservices)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。
微服务架构特征:
-
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发。
-
面向服务:微服务对外暴露业务接口。
-
自治:团队独立、技术独立、数据独立、部署独立。
-
隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题。
常见微服务
- Spring Boot:这可能是最好的Java微服务框架了,它适用于控制反转、面向切面编程等等。
- Jersey:这个开源框架支持Java的JAX-RS API,使用起来非常容易。
- Play:框架可以让你很方便地使用Scala和Java来构建、创建和部署Web应用程序。对于需要并行处理远程调用的RESTful应用程序来说,Play框架是理想的选择。它是模块化的,支持异步。
python拷贝文件到新目录
使用shutil.copy2函数复制文件
source_file = r"F:\源文件夹\example.txt"
# 目标文件路径(包括文件名)
target_file = r"F:\目标文件夹\example_copy.txt"
# 使用shutil.copy2复制文件
try:
shutil.copy2(source_file, target_file)
print(f"文件已成功复制到 {target_file}")
except Exception as e:
print(f"复制文件时出错: {e}")
数组和链表的区别
数组
将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。
链表
链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储 数据元素 的 数据域,另一个是存储下一个结点地址的 指针。
内存区别
数组从栈中分配空间, 对于程序员方便快速,但自由度小。
链表从堆中分配空间, 自由度大但申请管理比较麻烦。
区别总结
-
存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取;
-
存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定;
-
存储空间上,链表由于带有指针域,存储密度不如数组大;
-
按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n);
-
按值查找时,若数组无序,数组和链表时间复杂度均为O(1),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);
-
插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可;
适用场景
数组适用于需要随机访问的场景,具有较高的访问效率和内存使用效率;
链表适用于需要频繁插入和删除元素的场景,具有较高的插入和删除效率,但访问效率较低。
面向对象编程的特点
面向过程是以过程为核心,通过函数完成任务,程序结构是函数+步骤组成的顺序流程。
面向对象是以对象为核心,通过对象交互完成任务,程序结构是类和对象组成的模块化结构,代码可以通过继承、封装、多态等方式复用。
- 封装是指将数据(属性,或者叫字段)和操作数据的方法(行为)捆绑在一起,形成一个独立的对象(类的实例)。
- 继承允许一个类(子类)继承现有类(父类或者基类)的属性和方法。以提高代码的复用性,建立类之间的层次关系。
- 多态允许不同类的对象对同一消息做出响应,但表现出不同的行为(即方法的多样性)。
排序算法
十大排序算法:1、冒泡排序2、选择排序3、插入排序4、希尔排序5、归并排序6、快速排序7、堆排序8、计数排序9、桶排序10、基数排序
快速排序和归并排序区别
归并排序和快排的相同点:
-
利用分治思想
-
具体实现都用递归
归并排序和快排的不同点: -
先分解再合并:归并排序先递归分解到最小粒度,然后从小粒度开始合并排序,自下而上的合并排序;
-
边分解边排序:快速排序每次分解都实现整体上有序,即参照值左侧的数都小于参照值,右侧的大于参照值;是自上而下的排序;
-
归并排序不是原地排序,因为两个有序数组的合并一定需要额外的空间协助才能合并;
-
快速排序是原地排序,原地排序指的是空间复杂度为O(1);
-
归并排序每次将数组一分为二,快排每次将数组一分为三
数据库
数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。
数据库的范式
- 第一范式:确保表的每一列都是不可分割的基本数据单元
- 第二范式:要求表中的每一列都和主键直接相关,而不能只与主键的某一部分相关。
- 第三范式:非主键列应该只依赖于主键列,不依赖于其他非主键列。
第一范式(1NF):强调数据表的原子性
所谓第一范式(1NF)是指在关系模型中,对于添加的一个规范要求,所有的域都应该是原子性的,即数据库表的每一列都是不可分割的原子数据项,而不能是集合,数组,记录等非原子数据项。即实体中的某个属性有多个值时,必须拆分为不同的属性。在符合第一范式(1NF)表中的每个域值只能是实体的一个属性或一个属性的一部分。简而言之,第一范式就是无重复的域。
第二范式(2NF):在1NF基础上,消除了非主属性对于码的部分函数依赖
在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)
第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是在第一范式的基础上属性完全依赖于主键。
第三范式(3NF):在2NF的基础上,消除了非主属性对于码的传递函数依赖
在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)
访问数据库
python访问数据库
import MySQLdb
# 打开数据库连接
db = MySQLdb.connect("localhost", "testuser", "test123", "TESTDB", charset='utf8' )
# 使用cursor()方法获取操作游标
cursor = db.cursor()
# 数据库语言。
sql=""
# 执行数据库操作。
cursor.execute(sql)
java访问数据库
JDBC是连接java应用程序和数据库之间的桥梁。jdbc api即Java数据库编程接口,是一组标准的Java语言中的接口和类,使用这些接口和类,Java客户端程序可以访问各种不同类型的数据库。比如建立数据库连接、执行SQL语句进行数据的存取操作。
import java.sql.*;
public class JDBC {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//连接成功,数据库对象 Connection
Connection connection = DriverManager.getConnection(url,username,password);
//执行SQL对象Statement,执行SQL的对象
Statement statement = connection.createStatement();
//执行SQL的对象去执行SQL,返回结果集
String sql = "";
ResultSet resultSet = statement.executeQuery(sql);
// 展开结果集数据库
while(resultSet.next()){
```
}
// 6.释放连接
resultSet.close();
statement.close();
connection.close();
}
}
string类
String 类使用 final 修饰,是所谓的不可变类,无法被继承。
常用方法
length() - 返回字符串的长度。
charAt(int index) - 返回指定位置的字符。
substring(int beginIndex, int endIndex) - 返回字符串的一个子串,从 beginIndex 到 endIndex-1。
contains(CharSequence s) - 检查字符串是否包含指定的字符序列。
equals(Object anotherObject) - 比较两个字符串的内容是否相等。
indexOf(int ch) 和 indexOf(String str) - 返回指定字符或字符串首次出现的位置。
replace(char oldChar, char newChar) 和 replace(CharSequence target, CharSequence replacement) - 替换字符串中的字符或字符序列。
trim() - 去除字符串两端的空白字符。
split(String regex) - 根据给定正则表达式的匹配拆分此字符串。