目录
2. TCP(传输控制协议)和UDP(用户数据报协议)是两种常见的传输层协议。它们的区别主要体现在以下几个方面:
5. 死锁是指两个或多个进程因争夺系统资源而无限等待的状态。死锁的产生通常涉及以下四个必要条件:
1.TCP粘包拆包
。TCP 是一种面向流的协议,数据在发送端和接收端之间通过字节流进行传输。由于网络传输的不确定性,发送端的数据可能会被拆分成多个 TCP 分段发送,而接收端可能会一次性接收到多个 TCP 分段,导致粘包和拆包的问题。
实现 TCP 粘包拆包的原理可以通过以下方式:
1. 固定长度:在发送端将数据按照固定的长度进行切割并发送,在接收端根据固定长度进行拼接。
2. 分隔符:在发送端将数据以特定的分隔符(如换行符)进行分割并发送,在接收端根据分隔符进行拼接。
3. 消息长度字段:在发送端在消息头部添加一个固定长度的字段来表示消息的长度,在接收端根据消息长度字段进行拼接。
如果让我来设计 TCP 粘包拆包的实现,我可能会采用消息长度字段的方式。在发送端,将消息的长度作为一个固定长度的字段添加到消息的头部;在接收端,首先读取消息长度字段,然后根据消息长度读取相应长度的数据,并进行拼接。
使用数据库连接池的主要目的是为了提高数据库访问的性能和可靠性。
2. TCP(传输控制协议)和UDP(用户数据报协议)是两种常见的传输层协议。它们的区别主要体现在以下几个方面:
- 连接性:TCP是面向连接的协议,它在通信双方建立连接后,保证数据传输的可靠性和有序性。UDP是无连接的协议,数据包之间没有建立持久的连接,也不保证数据的可靠性和有序性。
- 可靠性:TCP通过使用序号、确认和重传等机制保证数据传输的可靠性。UDP不提供可靠性保证,数据包可能会丢失或乱序。
- 延迟:由于TCP需要建立连接和保证数据的可靠性,其延迟较高。UDP没有建立连接的过程,因此延迟较低。
- 适用场景:TCP适用于对数据传输可靠性要求较高的场景,比如文件传输、网页浏览等。UDP适用于实时性要求较高、数据丢失可接受的场景,比如音视频传输、实时游戏等。
3. OSI七层网络结构是一种网络架构模型,
将计算机网络通信划分为七个层次,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每一层都有特定的功能和协议,从物理层到应用层逐层封装和解析数据。而TCP/IP四层模型是一种简化的网络架构模型,包括物理层、网络层、传输层和应用层,与OSI模型对应的层次有所重叠。
- 物理层:负责物理媒介之间的原始数据传输。
- 数据链路层:负责在直连的网络节点之间传输数据帧。
- 网络层:负责在不同网络间进行数据包的路由选择和转发。
- 传输层:负责提供端到端的可靠数据传输,并处理数据分割和重组。
- 会话层:负责建立、管理和终止网络中的会话连接。
- 表示层:负责数据的格式化、加密、压缩和解压缩等操作。
- 应用层:提供特定应用程序之间的通信服务,如HTTP、FTP等。
4. 线程和进程是操作系统中的两个重要概念:
- 进程:是计算机中正在运行的一个程序的实例。它拥有自己的内存空间、文件句柄和系统资源,是程序执行时的一个独立单位。
- 线程:是进程中的一个执行单元,可以看作是轻量级的进程。同一个进程中的多个线程共享同一份内存空间和系统资源,但每个线程都有自己的栈空间和寄存器上下文。
主要区别:
- 资源占用:进程拥有独立的系统资源,包括内存、文件句柄等;而线程共享进程的资源。
- 调度和切换:进程间的切换开销较大,需要保存和恢复整个进程的上下文;线程间的切换开销较小,只需要保存和恢复线程的上下文。
- 通信和同步:进程间通信需要使用特定的通信机制,如管道、消息队列等;线程间通信更加方便,可以通过共享内存等直接进行数据交换。
- 稳定性:一个进程崩溃不会影响其他进程的正常运行;一个线程崩溃可能导致整个进程的崩溃。
5. 死锁是指两个或多个进程因争夺系统资源而无限等待的状态。死锁的产生通常涉及以下四个必要条件:
- 互斥条件:至少有一个资源只能被一个进程占用。
- 请求与保持条件:一个进程在等待资源时,继续占用已获得的资源。
- 不可剥夺条件:已分配的资源不能被其他进程强行剥夺。
- 循环等待条件:存在一个进程链,每个进程都在等待下一个进程所占有的资源
解决死锁的方法包括:
- 预防死锁:通过破坏死锁产生的四个必要条件之一来预防死锁。
- 避免死锁:在资源分配过程中,使用安全序列算法来避免产生死锁。
- 检测死锁:通过资源分配图等方法检测是否存在死锁,若存在则采取措施解除死锁。
- 解除死锁:通过剥夺资源、撤销进程等方式解除已经产生的死锁。
6. Object类常用的方法
- equals():判断两个对象是否相等。
- hashCode():返回对象的哈希码值。
- toString():返回对象的字符串表示。
- getClass():返回对象的运行时类。
- clone():创建并返回此对象的副本。
- wait():导致当前线程等待,直到另一个线程调用notify()或notifyAll()方法唤醒它。
- notify():唤醒在此对象监视器上等待的单个线程。
- notifyAll():唤醒在此对象监视器上等待的所有线程。
7. equal和==在Java中有不同的作用:
- ==用于比较两个基本类型的数据或两个对象的引用是否相等。
- equals()用于比较两个对象的内容是否相等,默认情况下与==的作用相同,但可以被子类重写以实现自定义的相等逻辑。
对于基本类型,使用==进行比较是比较它们的值是否相等。对于对象,==比较的是它们的引用是否指向同一块内存空间,即是否是同一个对象。而equals()比较的是对象的内容是否相等,具体的逻辑由对象的类来定义。
8. wait()和notify()为什么在Object中
是Object类中用于实现线程间协作的方法。它们为什么在Object类中而不是其他类中,是因为线程间协作需要依赖对象的监视器(即锁)。
wait()方法使当前线程进入等待状态,并让出对象的锁,直到其他线程调用了notify()或notifyAll()方法唤醒它。notify()方法用于唤醒在此对象监视器上等待的单个线程,而notifyAll()方法则唤醒所有等待的线程。这些方法必须在同步代码块或同步方法中调用,因为它们要求当前线程持有对象的锁。
9. HashMap插入过程
,它的插入过程大致如下:
- 根据键的哈希值计算存储位置。
- 若该位置还没有存储其他键值对,则直接插入。
- 若该位置已经存储了其他键值对,则使用键的equals()方法比较是否存在相同的键。
- 若存在相同的键,则更新对应的值。
- 若不存在相同的键,则以链表或红黑树的形式将新键值对插入该位置。
10. 多线程并发编程中常用的关键字和机制包括:
- volatile:用于保证变量的可见性和禁止指令重排。
- synchronized:用于实现线程的互斥同步,保证同一时间只有一个线程可以访问被synchronized修饰的代码块或方法。
- ReentrantLock:与synchronized类似,也用于实现线程的互斥同步,但提供了更灵活的锁定机制。
- wait()和notify():用于线程间的协作,wait()使线程进入等待状态并释放对象锁,notify()唤醒等待的线程。
- CountDownLatch:用于线程间的等待,允许一个或多个线程等待其他线程的完成。
- CyclicBarrier:用于线程间的同步,允许多个线程相互等待,直到所有线程都到达某个公共屏障点。
- Semaphore:用于控制同时访问某个资源的线程数量。
- ThreadLocal:用于在每个线程中维护变量的副本,实现线程间的数据隔离。
这些关键字和机制可以帮助解决多线程并发编程中的线程安全、同步、协作和资源管理等问题。
11. 线程池核心参数以及执行过程
- corePoolSize:线程池中保留的核心线程数;
- maximumPoolSize:线程池中允许的最大线程数;
- keepAliveTime:当线程池中线程数量超过corePoolSize时,多余的空闲线程在被终止之前等待新任务的最长时间;
- workQueue:用于保存等待执行的任务的阻塞队列;
- threadFactory:用于创建新线程的工厂类;
- handler:用于执行无法处理的任务的策略。
线程池的执行过程大致如下:
- 当有新任务提交时,线程池会首先判断核心线程数是否已满。
- 若未满,则创建新线程执行任务;
- 若已满,则将任务添加到等待队列中。
- 当等待队列已满时,线程池会判断当前线程数是否已达到最大值。
- 若未达到最大值,则创建新线程执行任务;
- 若已达到最大值,则根据指定的策略处理无法执行的任务。
12. 聚簇索引和非聚簇索区别
是数据库中常见的两种索引类型。它们的区别在于索引结构和存储方式:
- 聚簇索引:将表中的数据按照索引的顺序存储,即数据行的物理顺序与索引顺序一致。因此,一个表只能有一个聚簇索引。因为数据行的存储方式与索引一致,所以聚簇索引适合于需要频繁查询某一范围的数据,或者需要通过主键快速查询数据的场景。
- 非聚簇索引:将索引和数据行分开存储。索引结构中保存着指向对应数据行的指针,而数据行则根据需要存储在表的任意位置上。因为索引和数据行的存储方式不一致,所以一个表可以有多个非聚簇索引。非聚簇索引适合于需要快速定位某一行数据的场景。
13. 为什么用Redis
redis是一种高性能的键值对数据库,常用于缓存、会话管理、排行榜等场景。使用Redis作为缓存的主要原因包括以下几点:
- 高性能:Redis基于内存存储,读写速度非常快。
- 持久化:Redis支持数据持久化,可以将数据存储到磁盘中,保证数据不易丢失。
- 分布式:Redis支持主从复制和分片机制,可以实现数据的高可用性和负载均衡。
- 丰富的数据类型:Redis支持多种复杂的数据类型,如列表、哈希表、集合等,方便开发人员存储和操作数据。
- 支持事务:Redis支持事务,可以保证一组命令的原子性。
因此,使用Redis作为缓存可以提高应用程序的性能和扩展性,并且减轻了数据库的压力。
14.TCP三次握手和四次挥手。为什么不握两次而是三次
三次握手过程如下:
1. 客户端发送SYN报文,请求与服务器建立连接。
2. 服务器接收到SYN报文后,向客户端发送SYN ACK报文,表示可以建立连接。
3. 客户端接收到SYN ACK报文后,再次向服务器发送ACK报文,确认建立连接。
四次挥手过程如下:
1. 客户端发送FIN报文,请求关闭连接。
2. 服务器接收到FIN报文后,回复ACK报文,表示已经接收到关闭请求。
3. 服务器发送FIN报文,请求关闭连接。
4. 客户端接收到FIN报文后,回复ACK报文,表示已经接收到关闭请求。
通过三次握手,客户端和服务器确认了彼此的身份,并且同意建立连接。在四次挥手中,客户端和服务器都可以主动发送关闭请求,同时也需要对方回复确认。这样可以保证双方都能够正常地关闭连接,避免数据丢失和网络拥塞等问题。
为什么TCP进行三次握手而不是两次呢?
这涉及到确保双方都能正常进行数据传输的可靠性。
在建立TCP连接时,客户端首先发送一个SYN(同步)报文给服务器,表示客户端请求建立连接。服务器收到后,会回复一个带有SYN/ACK(同步/确认)标志的报文,表示接受连接请求,并指定一个初始序列号。客户端再回复一个带有ACK(确认)标志的报文,表示收到服务器的确认,并指定下一个序列号。这样,双方都知道对方已准备好,并且可以按照顺序传输数据。
为什么不是两次握手呢?如果只进行两次握手,客户端发送了连接请求后,服务器收到并回复了确认,但客户端没有收到服务器的确认,此时客户端无法确定是否连接成功。如果此时客户端重新发送连接请求,之前已经建立的连接可能还存在于网络中,会导致资源浪费或其他问题。通过进行三次握手,可以确保双方都能正常收发数据,避免了潜在的问题。
15. BIO和NIO模型
BIO模型(Blocking I/O)基于阻塞式I/O,每个连接都需要创建一个线程来处理读写操作。当有大量连接同时到达时,就会创建大量线程,导致资源消耗较大。这种模型在低并发情况下可以工作良好,但在高并发场景下效率较低。
NIO模型(Non-blocking I/O)使用非阻塞式I/O和事件驱动模型。它通过使用一个线程处理多个连接的读写操作,提供了更高的并发性能。在NIO模型中,当一个连接有数据可读或可写时,会触发相应的事件,而不需要线程一直阻塞等待。这使得服务器可以同时处理多个连接,提高了系统的吞吐量和响应速度。
因此,在高并发情况下,NIO模型通常比BIO模型更适合,可以更有效地利用系统资源。
16 快排和冒泡
快速排序采用分治的思想,通过选择一个基准元素,将数组划分为左右两部分,并递归地对左右子数组进行排序。冒泡排序则是通过相邻元素的比较和交换来实现的,每一轮循环都将最大的元素移动到末尾。这两种排序方法在实践中都被广泛使用,具有不同的时间复杂度和性能特点。
public class QuickSort {
public static void main(String[] args) {
int[] arr = {9, 4, 2, 7, 6, 8, 3, 5, 1};
quickSort(arr, 0, arr.length - 1);
System.out.println("排序结果:" + Arrays.toString(arr));
}
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivot = partition(arr, low, high);
quickSort(arr, low, pivot - 1);
quickSort(arr, pivot + 1, high);
}
}
public static int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, high);
return i + 1;
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {9, 4, 2, 7, 6, 8, 3, 5, 1};
bubbleSort(arr);
System.out.println("排序结果:" + Arrays.toString(arr));
}
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
17.红黑树?
红黑树是一种自平衡二叉查找树,它能够保证在最坏情况下基本动态集合操作的时间复杂度为O(log n)。红黑树中每个节点都有一个颜色属性,可以是红色或黑色。红黑树满足以下性质:
1. 节点是红色或黑色。
2. 根节点是黑色。
3. 每个叶节点(NIL节点,空节点)是黑色的。
4. 如果一个节点是红色的,则它的两个子节点都是黑色的。
5. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
在插入和删除节点时,为了保持红黑树的性质,需要对节点进行旋转和重新着色等操作。具体来说,插入节点时需要考虑节点颜色、父节点颜色、叔节点颜色等因素;删除节点时需要考虑被删除节点的子节点、兄弟节点、父节点等因素。
红黑树适合用于大量动态插入、删除、查找数据的场景,比如C++标准库中的set和map容器就是基于红黑树实现的。如果面试中被问到红黑树,可以从上述定义、性质、操作等方面进行回答,同时也可以结合自己的实际项目经验进行举例说明。
18.RDB和AOF存储的区别?
RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)
AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
19. 解释下回表查询 怎么避免?
在MySQL中,回表(Referring to table)是指在使用非聚族索引进行查询时,需要从索引中获取主键值,然后再根据主键值去访问对应的数据行。这个过程中需要进行两次访问:先通过索引找到主键值,再通过主键值查找数据行
避免回表的方法主要有以下几种:
1. 覆盖索引查询
覆盖索引查询是指查询语句中只需要用到索引列即可返回所有需要的结果,不需要再通过回表操作来获取行数据。这种方式可以减少SQL语句执行的IO和网络开销,从而提高查询性能。例如,对于以下查询语句:
SELECT id, name FROM user WHERE age > 30;
如果为user表中的(age, id, name)建立了联合索引,则该查询可以直接使用该索引来获取查询结果,从而避免回表操作。
2. 聚簇索引查询
聚簇索引是指将数据按照主键进行排序存储的一种索引,它可以使得查询某个范围内的数据时,能够将数据行连续地读取到内存中,从而减少IO和网络开销。如果表中数据按照主键分散存储,那么在查询时就很容易涉及到大量的回表操作。
3. 列存储技术
MySQL中的InnoDB存储引擎使用的是行存储技术,即将一行数据保存在一起。而列存储则是将一列数据保存在一起,这种方式可以提高查询的效率和压缩率,从而减少IO和网络开销。
4. 查询缓存
查询缓存是指在MySQL中缓存查询结果,当相同的查询语句被多次执行时,直接从缓存中获取结果,从而避免了重复的查询和回表操作。但是,启用查询缓存可能会带来缓存失效、缓存占用过多内存等问题,因此需要谨慎使用。
综上所述,避免回表的方法主要包括覆盖索引查询、聚簇索引查询、列存储技术和查询缓存等。在实际应用中,需要根据具体的情况选择合适的方法来提高查询性能。
20 synchronized 锁原理
synchronized 是 Java 中用于实现线程同步的关键字,它可以修饰方法或代码块。synchronized 关键字的作用是确保在同一时间只有一个线程可以执行被 synchronized 修饰的代码段,从而实现线程安全的操作。
synchronized 锁原理如下:
1. 对象锁(也称为监视器锁或内置锁):synchronized 关键字可以应用于普通方法、静态方法和代码块。当 synchronized 修饰在方法上时,它是获取对象锁,只有一个线程可以进入该方法执行;当 synchronized 修饰在静态方法上时,它是获取类锁,只有一个线程可以进入该静态方法执行;当 synchronized 修饰在代码块上时,它是获取括号内指定的对象锁,只有一个线程可以进入该代码块执行。
2. 悲观锁和乐观锁:synchronized 是一种悲观锁,它认为并发环境下会出现资源竞争,因此每次都会获取锁来保证数据的一致性。而乐观锁则认为并发环境下很少出现资源竞争,因此不采取加锁的方式,而是采用版本号等机制来判断数据是否被修改。
3. 锁的获取和释放:当一个线程尝试获取 synchronized 锁时,会首先尝试获取对象的锁,如果锁没有被其他线程占用,则该线程获得锁,继续执行代码。如果锁已经被其他线程占用,则该线程会进入阻塞状态,直到锁被释放或等待超时。当线程执行完 synchronized 代码块或方法后,会释放锁,让其他线程有机会获取锁并执行。
4. 锁的粒度:synchronized 锁的粒度可以是对象级别、类级别或代码块级别。锁的粒度越小,可以提高并发性能,但可能会增加锁竞争的概率;锁的粒度越大,可以减少锁竞争,但可能会降低并发性能。
21.锁膨胀
锁膨胀是指在某些情况下,JVM 会将对象锁从偏向锁或轻量级锁转换为重量级锁。锁膨胀的原因一般是为了解决竞争激烈或锁占用时间较长的情况。锁膨胀的过程如下:
1. 偏向锁:当一个线程访问同步块时,JVM 将这个对象头的 Mark Word 记录下当前线程的 ID,表示该对象进入了偏向模式。此时,锁是无竞争的,可以快速获得锁。如果其他线程尝试获取该锁,则会撤销偏向模式。
2. 轻量级锁:当一个线程尝试获取偏向锁失败时,JVM 会将锁升级为轻量级锁。轻量级锁使用 CAS 操作来尝试获取锁,如果成功,则执行同步操作;如果失败,则表示有竞争,锁膨胀为重量级锁。
3. 重量级锁:当轻量级锁获取失败时,JVM 会将锁膨胀为重量级锁(也称为互斥锁)。重量级锁使用 OS 的互斥量来实现同步,即需要通过内核态和用户态之间的切换来进行锁操作,开销较大。
锁膨胀过程的目的是为了解决并发竞争的问题,但同时也会引入额外的开销,因此在设计代码时需要考虑合适的锁粒度,避免不必要的锁膨胀。
22 B+树原理 与和B树的区别
B+树是一种平衡查找树,常用于数据库和文件系统中的索引结构。它是基于B树的一种变体,相比于B树具有更高的查询效率和更好的顺序访问特性。
B+树的原理如下:
1. 节点结构:B+树的节点除了存储关键字和指向子节点的指针外,还会存储所有关键字的副本。叶子节点之间通过指针连接形成一个有序链表,用于支持范围查询和顺序遍历。
2. 节点分裂与合并:当插入或删除操作导致节点的关键字个数超过了节点容量上限时,B+树会进行节点分裂;当节点的关键字个数低于下限时,B+树会进行节点合并。这样可以保持树的平衡性。
3. 查找操作:从根节点开始,按照关键字大小进行比较,根据指针找到对应的子节点,直到找到目标节点或者叶子节点。在叶子节点中按照关键字大小进行二分查找,找到目标关键字或者离目标关键字最近的关键字。
4. 插入操作:从根节点开始,按照关键字大小进行比较,找到待插入的叶子节点。如果叶子节点未满,则直接插入关键字;如果叶子节点已满,则进行节点分裂,并将中间关键字插入到父节点。
5. 删除操作:从根节点开始,按照关键字大小进行比较,找到待删除的叶子节点。如果关键字存在于叶子节点中,则直接删除;如果关键字不存在,则无需操作。如果删除操作导致叶子节点关键字个数低于下限,则进行节点合并。
B+树相对于B树的区别如下:
1. 节点结构:B+树的非叶子节点只存储关键字和指向子节点的指针,不存储关键字的副本。而B树的非叶子节点同时存储关键字和指向子节点的指针。
2. 叶子节点结构:B+树的叶子节点之间通过指针连接形成一个有序链表,而B树的叶子节点之间没有连接。
3. 查询性能:由于B+树的叶子节点形成有序链表,并且每个节点都存储了所有关键字的副本,所以在B+树上进行范围查询和顺序遍历的效率更高。
4. 内部节点数量:B+树相对于B树来说,由于没有存储关键字的副本,所以能够容纳更多的内部节点,降低树的高度,减少磁盘I/O操作。
总的来说,B+树相比于B树在查询性能和范围查询方面更优秀,适合用于大规模数据的索引结构。而B树则更适用于内存较小的场景,或者需要随机访问的情况。
23.Redis隔离级别:
Redis没有像传统关系型数据库那样明确的隔离级别概念。Redis是单线程的,通过使用事件驱动模型来处理多个客户端请求,因此不存在并发问题。由于Redis是在内存中操作数据,并且通常是作为缓存使用,所以不需要像关系型数据库那样的复杂的隔离级别。
24. Redis为什么快:
Redis之所以快速,主要有以下几个原因:
- 内存存储:Redis将数据存储在内存中,读写速度非常快。相比于磁盘操作,内存存储可以大大提高数据的访问速度。
- 单线程模型:Redis采用单线程模型,避免了多线程之间的资源竞争和锁的开销。单线程模型简化了并发控制,减少了上下文切换的开销。
- 非阻塞I/O:Redis使用了事件驱动的非阻塞I/O模型,通过异步处理客户端请求,提高了系统的并发性能。
- 简单的数据结构:Redis的数据结构相对简单,对于常见的操作如读取、写入、删除等,具有很高的效率。此外,Redis还对一些复杂的数据结构进行了优化,如Bitmap和HyperLogLog等。
- 持久化方式:Redis支持多种持久化方式,包括快照(snapshot)和日志(AOF)。持久化可以将数据写入磁盘,保证数据的持久性,同时通过异步操作可以减少对性能的影响。
综合上述因素,Redis在高并发读写场景下表现出色,成为了一个快速、高效的键值存储系统。
25. Redis数据类型:
Redis支持多种数据类型,包括:
- 字符串(String):最基本的数据类型,可以存储字符串、整数或浮点数。
- 列表(List):按照插入顺序存储一组字符串。
- 集合(Set):无序、唯一的字符串集合。
- 有序集合(Sorted Set):与集合类似,但每个成员都关联一个分数,用于排序。
- 哈希表(Hash):类似于关联数组,存储字段和值的映射。
- 位图(Bitmap):可以进行位级操作的数据结构。
- 超时队列(HyperLogLog):用于进行基数估计的数据结构。
- 地理空间索引(Geospatial Index):存储地理位置信息的数据结构。
26 什么是自旋锁
自旋锁(Spin Lock)是一种基本的同步机制,通过循环等待的方式来实现线程的同步和互斥访问共享资源的目的。当一个线程尝试获取自旋锁时,如果该锁已经被其他线程占用,那么该线程会一直在原地循环等待,直到该锁被释放。自旋锁适用于短期占用的场景,因为长时间的自旋可能会消耗大量的处理器时间。
自旋锁的特点是在获取锁时,如果锁已被其他线程占用,则当前线程会处于忙等(自旋)状态,不断地检查锁是否被释放,而不是让线程进入睡眠状态。这种忙等的方式在短时间内可以提高效率,因为线程不需要进行上下文切换和阻塞等开销,但是也会消耗CPU资源。
自旋锁的实现通常使用原子操作,如原子比较和交换(CAS),来实现对锁的获取和释放。当一个线程成功获得自旋锁时,其他线程会继续自旋,直到获得锁或达到最大自旋次数,此时可以选择进入睡眠状态等待锁的释放。
自旋锁适用于以下情况:
- 线程持有锁的时间较短,且期望等待时间很短。
- 系统中线程竞争锁的概率较低。
自旋锁的使用要注意两点:
1. 避免死锁:如果在一个线程中获取了自旋锁后,又尝试获取其他自旋锁,而其他线程也在等待这个线程释放锁,就会导致死锁。因此,在使用自旋锁时需要谨慎避免死锁情况的发生。
2. 自旋时间:自旋锁需要占用CPU资源,因此自旋时间不宜过长,否则会浪费大量的CPU时间。可以根据具体情况和性能测试结果来调整自旋时间。
总之,自旋锁是一种轻量级的同步机制,适用于对共享资源访问时间较短且竞争概率较低的情况,通过自旋等待来避免线程阻塞和上下文切换的开销。
27.InnoDB和MyISAM的区别
MySQL数据库中的两种常见的存储引擎,它们在功能、性能和适用场景等方面存在一些区别。
1. 事务支持:
- InnoDB:InnoDB是一个支持事务的存储引擎,它遵循ACID(原子性、一致性、隔离性和持久性)特性。它提供了提交、回滚和并发控制等功能,适用于需要保证数据完整性和一致性的应用。
- MyISAM:MyISAM不支持事务,它是一个非事务型存储引擎。它更适合于读密集的应用,如Web应用或只读数据库。
2. 并发性能:
- InnoDB:InnoDB采用了多版本并发控制(MVCC)来实现高并发性能,它支持行级锁定。这意味着在多个事务同时读写数据库时,可以同时进行,提高并发性能。
- MyISAM:MyISAM使用表级锁定,这意味着当一个事务对表进行写操作时,其他事务无法读取或写入该表,导致并发性能较差。
3. 数据一致性:
- InnoDB:InnoDB通过使用redo log和undo log来保证数据的一致性和持久性。如果在事务提交前发生系统崩溃,InnoDB可以通过日志来恢复未完成的事务,并回滚已提交的事务。
- MyISAM:MyISAM不提供类似的机制来保证数据一致性,如果在写操作期间发生系统崩溃,可能会导致数据损坏或不一致。
4. 外键支持:
- InnoDB:InnoDB支持外键约束,可以在表之间建立引用关系,保证数据的完整性和一致性。它可以执行级联删除和更新等操作。
- MyISAM:MyISAM不支持外键约束,无法自动处理相关的级联操作。
5. 全文搜索功能:
- InnoDB:InnoDB存储引擎在MySQL 5.6版本之后开始支持全文索引,可以进行全文搜索。
- MyISAM:MyISAM存储引擎早在MySQL 3.23版本就开始支持全文索引,可以进行高效的全文搜索。
总体来说,InnoDB适用于需要事务支持和高并发性能的应用,如电子商务系统、银行系统等;而MyISAM适用于读密集型应用,如新闻网站、博客等。在选择存储引擎时,需要根据应用的特点和需求综合考虑。此外,MySQL还提供了其他存储引擎,如MEMORY、NDB Cluster等,每个存储引擎都有自己的特点和适用场景。
28.数据库锁的粒度,以MySQL为例
在MySQL数据库中,锁按照数据操作的颗粒度可以分为以下几类:
1. 表级锁(Table-level Lock):表级锁是最粗粒度的锁,它对整个表进行锁定。当一个事务获取了表级锁后,其他事务无法对该表进行任何读写操作,直到锁被释放。
2. 行级锁(Row-level Lock):行级锁是最细粒度的锁,它在数据行级别进行锁定。当一个事务获取了某一行数据的锁后,其他事务可以读取其他行的数据,但无法读取或修改被锁定的行。
3. 页级锁(Page-level Lock):页级锁介于表级锁和行级锁之间,它将数据按页(通常是固定大小的数据块)进行分组,一个页级锁会锁定一个或多个页面。当一个事务获取了某个页面的锁后,其他事务可以同时访问不同的页面,但无法访问被锁定的页面。
需要注意的是,MySQL的默认存储引擎InnoDB支持行级锁,并发性能较好,而MyISAM存储引擎则主要支持表级锁和页级锁。在使用MySQL时,可以根据具体的需求和并发情况选择适当的锁粒度,以提高性能和并发控制效果。
29.HTTP和HTTPS的区别?
HTTP(超文本传输协议)和HTTPS(安全超文本传输协议)是用于在客户端和服务器之间传输数据的协议,它们的主要区别在于安全性:
1. 安全性:HTTP是明文协议,数据在传输过程中不加密,容易被窃听和篡改。而HTTPS使用SSL/TLS协议对传输的数据进行加密,保证数据的机密性和完整性,防止信息被窃听和篡改。
2. 默认端口:HTTP使用的默认端口是80,而HTTPS使用的默认端口是443。这样可以方便区分使用哪种协议进行通信。
3. 证书验证:HTTPS使用数字证书来验证服务器的身份,确保用户连接的是正确的服务器。这样可以防止中间人攻击和伪造网站的风险。
4. 运行层:HTTP运行在应用层,而HTTPS在HTTP上加入了SSL/TLS加密层,相当于在传输层和应用层之间增加了一个安全层。
需要注意的是,由于HTTPS在数据传输上增加了加密和解密的过程,相对于HTTP会增加一些计算和处理的开销,因此对于一些对安全性要求不高的场景,使用HTTP可能更加合适。但是对于涉及敏感信息的网站、电子商务等需要保护用户隐私和数据完整性的场景,使用HTTPS是必要的。
30.String常用方法
1. length():返回字符串的长度。
String str = "Hello";
int len = str.length(); // len的值为5
2. charAt(index):返回指定索引位置的字符。
String str = "Hello";
char ch = str.charAt(0); // ch的值为'H'
3. substring(startIndex, endIndex):返回从startIndex到endIndex-1之间的子字符串。
String str = "Hello";
String subStr = str.substring(1, 4); // subStr的值为"ell"
4. equals(str):比较两个字符串是否相等。
String str1 = "Hello";
String str2 = "hello";
boolean isEqual = str1.equals(str2); // isEqual的值为false
5. toUpperCase():将字符串转换为大写形式。
String str = "hello";
String upperCaseStr = str.toUpperCase(); // upperCaseStr的值为"HELLO"
6. toLowerCase():将字符串转换为小写形式。
String str = "HELLO";
String lowerCaseStr = str.toLowerCase(); // lowerCaseStr的值为"hello"
7. indexOf(str):返回指定字符串在源字符串中第一次出现的索引位置。
String str = "Hello, world!";
int index = str.indexOf("world"); // index的值为7
8. startsWith(prefix):判断字符串是否以指定的前缀开始。
String str = "Hello, world!";
boolean startsWithHello = str.startsWith("Hello"); // startsWithHello的值为true
9. endsWith(suffix):判断字符串是否以指定的后缀结尾。
String str = "Hello, world!";
boolean endsWithWorld = str.endsWith("world!"); // endsWithWorld的值为true
10. split(delimiter):将字符串按指定的分隔符拆分成字符串数组。
String str = "apple,banana,orange";
String[] fruits = str.split(","); // fruits的值为["apple", "banana", "orange"]