1. AIO BIO NIO 有什么区别
- 区别在于阻塞与非阻塞、同步与异步。
- BIO是阻塞同步模型,NIO是非阻塞同步模型,而AIO是非阻塞异步模型。
BIO(Blocking I/O,阻塞I/O):
- BIO 是传统的I/O模型,它是同步阻塞的。
- 在BIO中,每个I/O操作都会阻塞线程,直到I/O操作完成。这意味着当一个线程执行I/O操作时,它无法执行其他任务,导致线程资源的浪费。
- BIO适用于连接数较少且每个连接的I/O操作比较耗时的情况。
NIO(Non-blocking I/O,非阻塞I/O):
- NIO是Java的新I/O模型,引入了Channel和Selector的概念,它是同步非阻塞的。
- 在NIO中,一个线程可以同时处理多个I/O操作,因为它可以等待多个通道变得可用,而不需要阻塞等待每个I/O操作完成。
- NIO适用于需要处理大量连接的高并发情况,如网络服务器。
AIO(Asynchronous I/O,异步I/O):
- AIO是Java 7引入的I/O模型,它是异步非阻塞的。
- 在AIO中,I/O操作是异步的,线程不需要等待I/O操作完成,而是可以继续执行其他任务。当I/O操作完成时,会通知应用程序。
- AIO适用于需要处理大量连接且每个连接的I/O操作不是很耗时的情况,例如高性能的网络通信应用。
同步:
- 调用者要一直等待调用结果的通知后才能进行后续的执行,
- 现在就要,我可以等,等出结果为止
异步:
- 指被调用方先返回应答让调用者先回去,
- 然后再计算调用结果,计算完最终结果后再通知并返回给调用方
异步调用要想获得结果一 般通过回调
同步与异步的理解:同步、 异步的讨论对象是被调用者(服务提供者),重点在于获得调用结果的消息通知方式上。
阻塞
- 调用方一直在等待而且别的事情什么都不做,当前进/线程会被挂起,啥都不干
非阻塞
- 调用在发出去后,调用方先去忙别的事情,不会阻塞当前进/线程,而会立即返回
阻塞与非阻塞的理解
阻塞、非阻塞的讨论对象是调用者(服务请求者),重点在于等消息时候的行为,调用者是否能干其它事
2.Java中的锁有哪些
Java 中提供了多种锁机制来实现多线程并发控制,以下是一些常见的锁类型:
1.互斥锁(Mutex Lock):
- 互斥锁是最常见的锁类型,用于确保在同一时间只有一个线程能够访问共享资源。在 Java 中,synchronized 关键字和 ReentrantLock 类都提供了互斥锁的实现。
2.可重入锁(Reentrant Lock):
- 可重入锁允许同一个线程在多个代码块中获取锁,而不会造成死锁。ReentrantLock 是 Java 中的可重入锁的实现。
3.读写锁(Read-Write Lock):
- 读写锁允许多个线程同时读取共享资源,但只有一个线程能够写入资源。这对于读多写少的情况非常有用。在 Java 中,ReentrantReadWriteLock 提供了读写锁的实现。
4.独占锁(Exclusive Lock):
- 独占锁用于限制只有一个线程可以同时访问共享资源。synchronized 和 ReentrantLock 都是独占锁的实现。
5.共享锁(Shared Lock):
- 共享锁允许多个线程同时访问共享资源。读写锁是一种常见的共享锁。
6.条件锁(Condition Lock):
- 条件锁允许线程等待某个条件为真时才能继续执行。在 Java 中,ReentrantLock 的 Condition 接口提供了条件锁的支持。
7.自旋锁(Spin Lock):
- 自旋锁是一种非阻塞锁,它不会让线程休眠,而是一直尝试获取锁。在 Java 中,AtomicInteger 和 AtomicReference 等类提供了自旋锁的实现。
8.信号量(Semaphore):
- 信号量是一种控制并发访问资源的机制,它允许多个线程同时访问资源,但根据信号量的许可数量限制访问的线程数量。在 Java 中,Semaphore 类提供了信号量的实现。
9.倒计时门闩(CountDownLatch):
- 倒计时门闩是一种同步工具,它允许一个或多个线程等待其他线程完成某个操作后再继续执行。在 Java 中,CountDownLatch 类提供了倒计时门闩的实现。
3.redis 持久化策略 对比优缺点
Redis支持两种主要的持久化策略,分别是RDB快照和AOF日志,它们各自有优点和缺点。
RDB(Redis DataBase)持久化策略:
- RDB,Redis DataBase,是指将内存中某一时刻的数据快照全量写入到指定的 rdb 文件的 持久化技术。RDB 持久化默认是开启的。当 Redis 启动时会自动读取 RDB 快照文件,将数据 从硬盘载入到内存,以恢复 Redis 关机前的数据库状态。
优点:
- 高性能:RDB是在一定时间间隔内对数据进行快照的方式,因此生成快照的时候不会对正常的Redis操作造成影响。
- 数据压缩:RDB文件采用二进制格式,因此在磁盘上占用的空间通常比AOF文件更小。
- 恢复速度快:在恢复数据时,由于RDB文件只是一个快照,因此加载速度通常比AOF日志更快。
缺点:
- 数据丢失:由于RDB是在一定时间间隔内生成的快照,因此在两次生成快照之间的数据变化可能会丢失。
- 不适用于追加操作:RDB不适合用于记录追加(append-only)的操作,因为它不会记录每次写操作的详细信息。
AOF(Append-Only File)持久化策略:
- AOF,Append Only File,是指 Redis 将每一次的写操作都以日志的形式记录到一个 AOF文件中的持久化技术。当需要恢复内存数据时,将这些写操作重新执行一次,便会恢复到之 前的内存数据状态。
优点:
- 数据完整性:AOF记录了每次写操作的详细信息,因此可以保证在Redis重启时不会丢失数据。
- 可读性:AOF日志文件是可读的,可以用于检查Redis操作历史。
缺点:
- 性能:AOF日志需要记录每次写操作,因此相比RDB,AOF对性能有一定的影响。但可以通过将AOF文件定期重写来降低写入性能开销。
- 文件体积:AOF日志文件通常比RDB文件占用更多的磁盘空间,尤其在文件重写的情况下。
- 恢复速度:在Redis启动时,恢复数据可能会比RDB加载快照慢一些。
综合比较:
-
对于需要数据完整性和不希望有数据丢失的场景,AOF更适合,尤其是金融、电商等对数据一致性要求很高的领域。
-
对于需要高性能、可以容忍一定数据丢失的场景,RDB可能更适合,尤其是一些缓存应用。
-
有些情况下,可以同时使用两种策略,RDB用于备份和快速恢复,AOF用于数据完整性。
4. 树、b树、b+树的区别
- 树(Tree):
- 树是一种基本的数据结构,它包括一个根节点,每个节点可以有零个或多个子节点,子节点又可以有自己的子节点,以此类推。
- 树的结构是多层的,通常包括根节点、内部节点和叶子节点。
- 二叉树是树的一种特殊形式,其中每个节点最多有两个子节点。
- 树的应用非常广泛,包括文件系统、XML解析、数据库索引等。
- B树(B-Tree):
- B树是一种自平衡树,通常用于数据库和文件系统中的索引结构。
- B树的特点是它的每个节点可以有多个子节点,通常用于处理大量数据的索引。
- B树的平衡性保证了插入、删除和查找的时间复杂度都是O(log n),其中n是数据的数量。
- B+树(B+ Tree):
- B+树也是一种自平衡树,类似于B树,但在某些方面有不同的特点。
- B+树的所有数据都存储在叶子节点,而非叶子节点只包含索引信息。
- B+树通常用于数据库索引,因为它的叶子节点形成一个有序链表,适合范围查询。
- B+树的平衡性和范围查询性能使其在数据库系统中非常流行。
5. Java实现注解需要注意什么(需要哪些步骤)?
实现注解的步骤如下:
定义注解:首先定义一个注解类型,使用@interface关键字。注解类型通常包括注解的名称、元素(也就是注解的属性),以及默认值。
public @interface MyAnnotation {
String value() default "default_value";
int count() default 0;
}
使用注解:在需要使用注解的地方,使用**@注解名**来标记。你可以为注解的元素指定具体的值。
@MyAnnotation(value = "custom_value", count = 1)
public class MyClass {
// ...
}
解析注解:要想在运行时获取注解信息,需要使用反射机制来解析注解。可以使用Class对象的getAnnotation方法或者通过反射获取方法、字段、类等上的注解信息。
Class<?> clazz = MyClass.class;
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
处理注解:一旦获取了注解对象,可以根据注解信息来进行相应的处理。例如,可以根据注解值来调整程序的行为。
注意事项:
-
注解元素不能有不确定的值,也就是不能使用null作为默认值。
-
注解的生命周期(RetentionPolicy)可以是源码级别、类文件级别或运行时级别,具体取决于注解类型上的**@Retention**注解。
- Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为\RUNTIME
- 只有声明为RUNTIME生命周期的注解,才能通过反射获取。
- Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为\RUNTIME
-
注解可以用于类、方法、字段、参数等不同的地方,具体取决于注解类型上的@Target注解。
-
6. Java类加载的过程?
Java类加载是指将Java类从磁盘加载到内存中,并将其转换为字节码的过程。Java类加载分为以下三个阶段:
- 加载(Loading):在这个阶段,类加载器从磁盘上加载类的字节码文件到内存中,并生成一个代表这个类的java.lang.Class对象。类加载器通过类的全限定名来查找类文件。
- 连接(Linking):连接分为三个子阶段:
- 验证(Verification):在这个阶段,类加载器验证字节码文件的格式,确保它符合Java虚拟机规范。这包括检查字节码的格式、语法和语义。
- 准备(Preparation):在这个阶段,为类的静态变量分配内存,并初始化为默认值。这包括数值类型的默认值为0,对象引用的默认值为null。
- 解析(Resolution):在这个阶段,解析符号引用,将它们替换为直接引用。符号引用包括类和接口的全限定名、字段和方法的名称。
- 初始化(Initialization):在这个阶段,执行类的初始化代码,包括静态变量的赋值(准备阶段是默认值)、静态块的执行等。类初始化是在类首次主动使用时进行的,例如创建类的实例、访问静态字段、调用静态方法等。
- 类加载过程是Java虚拟机的基础之一,它确保了类的安全性和正确性。类加载器通过双亲委派机制(Parent-Delegation Model)来管理类的加载,确保类加载的一致性和隔离性。
- 符号引用(Symbolic Reference):在Java类的字节码中,一些符号用来表示引用其他类、方法、字段等。这些符号引用通常是以字符串形式表示的,比如类的全限定名、方法名、字段名等。符号引用是抽象的,它不包含具体的内存地址或偏移量信息。
- 直接引用(Direct Reference):与符号引用相对,直接引用是具体的、可以直接定位到内存中的对象、方法、字段等。直接引用包括内存地址或偏移量,可以用于访问实际的数据。
7. Linux常用命令
-
1.pwd 命令
- 功能: 显示用户当前所在的目录
-
ls 命令
- 功能:对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息
- 格式:ls [选项][目录或文件]
-
cd 命令
- 功能:改变工作目录。将当前工作目录改变到指定的目录下
- 格式:cd 目录名
-
man 命令
- Linux的命令有很多参数,我们不可能全记住,我们可以通过查看联机手册获取帮助。访问Linux手册页的命令是man
-
grep 命令
- 功能:用于查找文件里符合条件的字符串
- 格式:grep [选项] ‘查找字符串’ 文件名
-
find 命令
- 功能:用来在指定目录下查找文件
- 格式:find [路径] [选项] 操作
-
chmod 命令
- 功能:控制用户对文件的权限的命令
- 格式:chmod [选项] 文件名
-
ps 命令
- 功能:用来列出系统中当前正在运行的那些进程,类似于 windows 的任务管理器。
-
kill 命令
- 功能:用于删除执行中的程序或工作
- 格式:kill [选项]/[信号] 进程号
信号:
-
tail 命令
-
功能:查看测试项目的日志
-
说明:一般测试的项目里面,有个logs的目录文件,会存放日志文件,有个xxx.out的文件,可以用tail -f 动态实时查看后端日志
-
格式:tail [选项] 文件名
-
-
netstat 命令
- 功能:查看端口
- 格式:netstat -anp | grep 端口号
-
echo 打印 选项 -e
- 打印常量 直接打印
- 打印变量 变量前加$
- 打印命令 用反引号把命令引起来
- 终端间传递信息 echo 内容>/dev/pts/终端号
echo -e "要打印的东西 \c"
- 以覆盖方式写入文件,写入语句会覆盖目标文件原有内容,保证文件保存的始终是最新内容。
echo "Hello World" > hello.txt
- 以追加方式写入文件,写入语句不会覆盖目标文件原有内容,只会追加在文件末尾。追加方式适用于记录运行log,便于后期问题分析。
echo "Hello World" >> hello.txt
-
ping 地址 检测是否与主机连通
- 格式:ping 地址
-
mkdir 命令
- 功能:创建空目录
- 格式:mkdir [选项] [路径] 文件名
-
touch 命令
- 功能:新建空文件
- 格式:touch [路径] 文件名 (可以多个)
-
rm 命令
- 功能:删除文件或目录
- 格式:rm [选项] 文件名
-
mv 命令
- 功能:mv命令是move的缩写,可以用来移动文件或者将文件改名(move(rename)files),是Linux系统下常用的命令,经常用来备份文件或者目录
- 格式:mv [选项] [路径] 旧文件名 [新路径][新文件名]
-
cp 命令
- 功能: 复制文件或目录
- cp [选项] [路径] 旧文件名 [新路径][新文件名]
-
cat 命令
- 功能: 查看目标文件的内容
- 格式:cat [选项] 文件名
8.java中IO、Socket的常用操作和方法。
I/O 操作:
文件读写:
- 读取文件:可以使用 FileInputStream、BufferedReader 等类进行文件读取。
- 写入文件:可以使用 FileOutputStream、BufferedWriter 等类进行文件写入。
标准输入输出:
- 读取标准输入(键盘输入):使用 System.in 和 Scanner 或 BufferedReader。
- 输出到标准输出(屏幕):使用 System.out 和 System.err 进行输出。
字节流和字符流:
- 使用字节流:InputStream 和 OutputStream 类用于字节数据的读写。
- 使用字符流:Reader 和 Writer 类用于字符数据的读写。可以使用 FileReader、FileWriter 等。
缓冲:
- 可以使用 BufferedReader、BufferedWriter、BufferedInputStream 和 BufferedOutputStream 来提高I/O性能。
Socket 操作:
建立Socket连接:
- 创建客户端Socket:Socket socket = new Socket(host, port);
- 创建服务器Socket:ServerSocket serverSocket = new ServerSocket(port);
数据传输:
- 从Socket读取数据:使用 InputStream 和 Reader。
- 向Socket写入数据:使用 OutputStream 和 Writer。
- 关闭Socket:使用 close() 方法关闭Socket连接。
- 多线程通信:通常,服务器端使用多线程来处理多个客户端的连接。
- 异常处理:捕获和处理 IOException 异常,以确保程序健壮性。
- 网络通信协议:根据需要选择TCP或UDP协议。
服务器程序的工作过程包含以下四个基本的步骤:
调用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
调用 **accept():**监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收。
关闭ServerSocket和Socket对象:客户端访问结束,关闭通信套接字。
客户端Socket的工作过程包含以下四个基本的步骤:
创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输
按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
关闭 Socket:断开客户端到服务器的连接,释放线路