前言
第一次写文章,慢慢走在程序员的道路上,也阅读过好多前辈的博客,其中借用了大佬们的文章技术点,请多多包涵,有问题的家人们可以直接私信我,本人知错就改,未来请多多指教
一. jwt
1. 如何理解jwt?
JWT(JSON Web Token)是一种在网络应用中安全地传输信息的标准方法。它可以通过数字签名保证数据在传输过程中不被篡改,同时也可以验证发送方的身份。
JWT 由三部分组成,它们分别是头部(Header)、载荷(Payload)和签名(Signature)。具体来说,JWT 的格式如下所示:
<Header>.<Payload>.<Signature>
其中,头部是一个 JSON 对象,用于描述 JWT 的元数据信息,比如算法类型等。载荷部分也是一个 JSON 对象,用于存储实际的信息,比如用户 ID、权限等。签名部分则是对头部和载荷进行数字签名之后得到的字符串,用于验证数据的完整性和发送方的身份。
JWT 的工作流程大致如下:客户端在登录时将用户的身份信息(比如用户名和密码)发送给服务器,服务器验证身份信息是否正确,如果正确,就生成一个 JWT 并返回给客户端。客户端在以后的请求中会携带这个 JWT,服务器接收到请求后会验证 JWT 的合法性,如果合法就会响应请求。
JWT 的优点在于,它不需要在服务器端保存任何状态,因此可以轻松地扩展应用的规模。另外,由于 JWT 包含了足够的信息,因此可以在多个应用之间进行传递,实现单点登录等功能。
二.redis缓存
1. redis缓存的工作机制是什么
Redis 是一款基于内存的高性能缓存数据库,它的工作机制可以简单概括为以下几个步骤:
数据存储: Redis 将数据存储在内存中,因此数据的读写速度非常快。Redis 中的数据可以是字符串、哈希、列表、集合、有序集合等数据类型,每种类型都有对应的操作命令。
数据淘汰: Redis 为了防止内存溢出,设置了一定的内存阈值,当内存使用达到阈值时,Redis 会触发数据淘汰机制,将一些不常用的数据删除或移除出内存,以释放内存空间。Redis 提供了多种数据淘汰策略,如 LRU(最近最少使用)、TTL(过期时间)等。
数据持久化: Redis 可以将数据持久化到磁盘中,以便在服务器重启后恢复数据。Redis 提供了两种持久化方式,分别是 RDB 和 AOF。RDB 是将内存中的数据定期保存到磁盘中,而 AOF 则是将每个写操作记录到一个追加的文件中,这样可以保证即使 Redis 异常退出,也不会丢失数据。
**数据访问:**客户端可以通过 Redis 提供的命令来读写数据,包括读取单个或多个数据项、设置、更新、删除等。Redis 支持事务操作和 Lua 脚本,可以用于实现复杂的操作逻辑。
总之,Redis 的工作机制是将数据存储在内存中,并通过一系列算法和策略,实现快速的数据访问和管理。这使得 Redis 在缓存场景中表现出色,可以极大地提升应用程序的性能。
2. redis数据淘汰机制
Redis 中的数据淘汰机制是为了防止内存溢出,将一些不常用的数据删除或移除出内存,以释放内存空间。Redis 提供了多种数据淘汰策略,主要包括以下几种:
LRU(最近最少使用): LRU 算法将最近最少使用的数据移除,以保留最常用的数据。当内存达到阈值时,Redis 会从当前数据库中选取最近最少使用的键值对进行删除。
LFU(最少使用): LFU 算法将使用频率最低的数据移除,以保留使用频率高的数据。Redis 每个键值对都会记录访问次数,当内存达到阈值时,Redis 会从当前数据库中选取访问次数最少的键值对进行删除。
TTL(过期时间): Redis 中的键值对可以设置过期时间,过期时间到期后,键值对会自动被删除。当内存达到阈值时,Redis 会从当前数据库中选取过期时间最近的键值对进行删除。
Random(随机): Redis 还提供了一种随机删除策略,它会随机选择一些键值对进行删除。这种策略虽然效率不高,但对于一些不需要保留的数据可以采用。
需要注意的是,Redis 的数据淘汰机制不是实时的,当内存达到阈值时才会触发。此外,Redis 还提供了手动删除键值对的命令,可以通过客户端或者程序代码删除指定的键值对。
3. 什么是Redis集群
Redis集群是一个由多个Redis实例组成的分布式系统,旨在提高Redis的可用性和性能。Redis集群可以在多个节点之间分配数据,并在节点之间自动进行故障转移,从而避免单点故障,并且可以扩展以处理更大的负载。
在Redis集群中,数据被分割成多个分片,并分布在集群的多个节点上。每个节点可以存储部分数据,并处理一部分的客户端请求。客户端可以与任何节点进行通信,而节点之间会协调以提供正确的响应。当某个节点出现故障时,其他节点会自动接管该节点的工作,并确保数据不会丢失。
Redis集群提供了一些内置的高可用性和故障转移机制,例如Redis Sentinel和Redis Cluster Manager,它们可以监视节点状态并在节点故障时执行自动故障转移。此外,Redis集群还提供了数据备份和复制功能,以确保数据的安全性和可靠性。
4. 什么是Redis集群
Redis集群是Redis的分布式解决方案,旨在提供更高的性能、可扩展性和可用性。Redis集群的工作流程如下:
节点分配:Redis集群将数据分散在多个节点上,每个节点都有一个唯一的ID,也称为槽。默认情况下,Redis集群有16384个槽,每个节点负责其中一部分。
槽迁移:当节点加入或离开集群时,槽会在节点之间移动以重新分配数据。集群使用一种称为“槽迁移”的过程来实现这一点,这涉及将一个或多个槽从一个节点转移到另一个节点。
哨兵机制:Redis集群还有一个名为Sentinel的监视器,用于检测节点是否正常工作,如果有节点出现故障,Sentinel会通知其他节点并采取必要的措施。
负载均衡:当客户端连接到Redis集群时,集群将自动将请求路由到正确的节点,客户端不需要了解节点的存在。
数据复制:Redis集群使用主从复制来保证数据的可靠性和可用性。每个节点都有一个主节点和多个从节点,主节点负责接收写操作并将数据复制到从节点。
故障转移:如果一个主节点发生故障,Redis集群会自动将其从节点提升为新的主节点,确保数据在不间断的情况下可用。
总之,Redis集群通过将数据分散在多个节点上,使用槽迁移和主从复制来保证可用性和可靠性,使用Sentinel进行监视和故障转移来保证高可用性。
三.Dubbo
1. Dubbo生产者与消费者
Dubbo是一种分布式服务框架,用于构建高性能、可伸缩、可扩展的分布式应用程序。在Dubbo中,服务提供者和服务消费者是框架的两个主要角色。下面简要介绍一下Dubbo的生产者和消费者:
Dubbo生产者:Dubbo生产者指的是提供服务的应用程序,它们将自己的服务注册到Dubbo注册中心,并且暴露服务的接口和实现类。Dubbo生产者通过网络协议将服务暴露出去,等待Dubbo消费者的请求。
Dubbo消费者:Dubbo消费者指的是调用服务的应用程序,它们从Dubbo注册中心中查找需要的服务,获取服务提供者的地址,然后通过网络协议向Dubbo生产者发送请求。Dubbo消费者通过Dubbo的负载均衡算法选择一个服务提供者进行调用,如果调用失败,则自动切换到其他可用的服务提供者。
总的来说,Dubbo生产者和消费者都是Dubbo框架中的重要组成部分,它们通过Dubbo注册中心和网络协议实现服务的发布和调用,从而构建起高性能、可伸缩、可扩展的分布式应用程序。
2. 什么是分布式应用程序
分布式应用程序是指由多个独立的组件或服务在不同的计算机节点上运行,通过网络协议相互协作完成某项功能的应用程序。与单机应用程序不同,分布式应用程序将任务分解成多个独立的模块,这些模块可以独立地部署、管理和扩展,从而提高了应用程序的可靠性、可扩展性和可维护性。
在分布式应用程序中,不同的组件可以是不同的进程、服务或者系统,它们通过网络通信进行交互,实现任务的分配、数据的共享、结果的汇总等功能。分布式应用程序可以运行在不同的计算机、操作系统、硬件平台上,这种灵活性使得分布式应用程序更具有可扩展性和可伸缩性,可以根据实际需求进行横向或纵向的扩展。
常见的分布式应用程序包括:Web应用程序、云计算平台、大数据处理系统、分布式数据库、物联网系统等。随着云计算、大数据、人工智能等技术的快速发展,分布式应用程序的重要性越来越凸显,未来也将成为应用程序开发的主要趋势之一。
3. 网络协议如何将服务暴露出去
网络协议是将服务暴露出去的关键。在Dubbo框架中,服务提供者通过Dubbo协议将自己的服务暴露出去,等待Dubbo消费者的请求。Dubbo协议是一种基于TCP的高性能RPC协议,它支持长连接和异步传输,具有高效、稳定、安全的特点。
具体来说,Dubbo协议的服务暴露过程如下:
服务提供者启动Dubbo框架,并将自己的服务注册到Dubbo注册中心。
当服务消费者需要调用服务时,它通过Dubbo注册中心查找需要的服务,并获取服务提供者的地址。
服务消费者向服务提供者发送Dubbo协议请求,请求中包含了服务的接口名、方法名、参数列表等信息。
服务提供者接收到Dubbo协议请求后,根据请求中的服务名、方法名等信息,调用相应的服务方法,并将方法的执行结果返回给服务消费者。
服务消费者接收到服务提供者返回的结果,完成一次Dubbo协议调用。
总的来说,Dubbo协议通过网络协议将服务暴露出去,使得服务消费者可以通过简单的接口调用方式访问服务提供者提供的功能,从而实现分布式应用程序中的服务调用和交互。
四.数据结构
1.Java冒泡排序演示图
以下是一个简单的Java冒泡排序的演示图示例:
假设我们要对以下数组进行排序:
int[] arr = {5, 3, 8, 6, 4};
首先,我们将数组中的元素按顺序两两比较,如果左边的元素比右边的元素大,则交换它们的位置,直到所有元素都被排序。
第一轮排序:
比较 5 和 3,发现 5 > 3,交换它们的位置,数组变为 {3, 5, 8, 6, 4}
比较 5 和 8,发现 5 < 8,不需要交换位置,数组不变
比较 8 和 6,发现 8 > 6,交换它们的位置,数组变为 {3, 5, 6, 8, 4}
比较 8 和 4,发现 8 > 4,交换它们的位置,数组变为 {3, 5, 6, 4, 8}
第一轮排序结束后,数组的最后一个元素 8 已经被排在了正确的位置。
第二轮排序:
比较 3 和 5,发现 3 < 5,不需要交换位置,数组不变
比较 5 和 6,发现 5 < 6,不需要交换位置,数组不变
比较 6 和 4,发现 6 > 4,交换它们的位置,数组变为 {3, 5, 4, 6, 8}
第二轮排序结束后,数组的倒数第二个元素 6 已经被排在了正确的位置。
第三轮排序:
比较 3 和 5,发现 3 < 5,不需要交换位置,数组不变
比较 5 和 4,发现 5 > 4,交换它们的位置,数组变为 {3, 4, 5, 6, 8}
第三轮排序结束后,数组的第三个元素 5 已经被排在了正确的位置。
第四轮排序:
比较 3 和 4,发现 3 < 4,不需要交换位置,数组不变
第四轮排序结束后,数组的第二个元素 4 已经被排在了正确的位置。
最后,数组已经被排序为 {3, 4, 5, 6, 8},冒泡排序结束。
以下是Java语言的冒泡排序算法实现:
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]) {
// 交换相邻两个元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
该算法使用两层循环,外层循环控制排序次数,内层循环控制每次排序中相邻元素的比较和交换。在每次内层循环结束后,最大的元素会被移动到数组的末尾,因此外层循环可以少循环一次,从而提高效率。
示例用法
int[] arr = {5, 2, 8, 7, 1};
bubbleSort(arr);
System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 5, 7, 8]
五.Java基础
1.java创建对象有哪几种方式
1.1 使用new关键字创建对象:使用new关键字可以创建一个类的实例,例如:
ClassName obj = new ClassName();
1.2 使用Class类的newInstance()方法创建对象:使用Class类的newInstance()方法可以动态地创建一个类的实例,例如:
ClassName obj = (ClassName) Class.forName("ClassName").newInstance();
1.3 使用Constructor类的newInstance()方法创建对象:使用Constructor类的newInstance()方法可以通过指定构造函数的参数来创建一个类的实例,例如:
使用Constructor类的newInstance()方法创建对象:使用Constructor类的newInstance()方法可以通过指定构造函数的参数来创建一个类的实例,例如:
1.4
使用clone()方法创建对象:如果一个类实现了Cloneable接口,那么就可以使用clone()方法创建对象,例如:
ClassName obj1 = new ClassName();
ClassName obj2 = (ClassName) obj1.clone();
1.5
使用反序列化创建对象:如果一个类实现了Serializable接口,那么就可以使用反序列化来创建对象,例如:
ObjectInputStream in = new ObjectInputStream(new FileInputStream("file.txt"));
ClassName obj = (ClassName) in.readObject();
2.java浅层拷贝和深层拷贝
浅拷贝(Shallow Copy)会创建一个新对象,并复制原始对象的字段值。如果字段是基本类型,则拷贝其值;如果字段是对象引用,则拷贝其引用,因此原始对象和副本对象将引用相同的对象。这意味着,如果副本对象更改了引用的对象,原始对象也将受到影响。Java中默认的clone()方法就是实现了浅拷贝。
深拷贝(Deep Copy)会创建一个新对象,并复制原始对象的所有字段值,包括所有嵌套的对象。这意味着,即使副本对象更改了引用的对象,原始对象也不会受到影响。通常,实现深拷贝需要手动递归复制对象的所有引用字段,或使用Java中的序列化和反序列化操作来实现。
以下是一个示例,说明浅拷贝和深拷贝的区别:
class Person implements Cloneable {
public String name;
public Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address {
public String city;
public String country;
public Address(String city, String country) {
this.city = city;
this.country = country;
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("Shanghai", "China");
Person person1 = new Person("Alice", address);
// 浅拷贝
Person person2 = (Person) person1.clone();
System.out.println(person1.address == person2.address); // true,引用相同
// 深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(person1);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Person person3 = (Person) ois.readObject();
System.out.println(person1.address == person3.address); // false,引用不同
}
}
3.java的可用性和可靠性
Java 是一种高级编程语言,它具有很强的可用性和可靠性。下面是 Java 的可用性和可靠性的一些优点:
可移植性:Java 程序可以在多个操作系统和硬件平台上运行,这是由于 Java 语言本身的可移植性以及 Java 虚拟机的存在,它能够在不同的操作系统和硬件平台上运行 Java 程序。
内存管理:Java 通过垃圾回收机制进行自动的内存管理,可以自动释放不再使用的对象占用的内存,这样可以避免内存泄漏等问题。
异常处理:Java 异常处理机制可以使程序更加健壮和可靠,当程序出现异常时,它可以处理异常并恢复程序运行,而不是让程序直接崩溃。
多线程支持:Java 提供了多线程的支持,可以在一个程序中同时执行多个任务,这可以提高程序的效率和可用性。
安全性:Java 通过提供安全性机制来防止恶意程序对系统造成破坏,包括运行时数据类型检查、代码签名和安全管理等。
良好的开发环境:Java 提供了许多开发工具和框架,如Eclipse、IntelliJ IDEA等,使开发人员能够更轻松地编写、测试和调试 Java 应用程序。
总之,Java 的可用性和可靠性是由其语言特性和相关的开发工具和框架支持等多种因素共同决定的。它们都有助于 Java 程序员编写出健壮、高效、安全、可移植、易于维护和扩展的应用程序。
4. synchronized对同一个数组进行两次扩容
在Java中,synchronized关键字用于解决多线程并发访问共享数据时可能出现的同步问题,其作用是保证同一时刻只有一个线程能够访问共享数据,从而避免冲突和竞争。
在对同一个数组进行两次扩容的情况下,如果使用synchronized关键字进行同步控制,可以避免出现多线程占用数组的情况,从而保证程序能够正确地扩容。
具体实现可以使用synchronized关键字来锁住数组对象,使得只有一个线程能够访问数组并进行扩容操作,其他线程需要等待该线程完成扩容后才能继续访问数组。如下所示:
// 定义需要扩容的数组
int[] arr = new int[10];
// 第一次扩容,使用synchronized关键字实现同步控制
synchronized (arr) {
arr = Arrays.copyOf(arr, 20);
}
// 第二次扩容,同样使用synchronized关键字实现同步控制
synchronized (arr) {
arr = Arrays.copyOf(arr, 30);
}
需要注意的是,对于同一个数组进行多次扩容可能会影响程序的性能,因为每次扩容都要重新分配内存空间,并且拷贝原有数据,这会导致程序运行速度变慢。因此,在实际应用中需要对扩容操作进行合理的设计和优化。
在Java中,使用synchronized关键字可以实现同步访问共享资源的操作,从而避免并发访问导致的数据不一致性。
对于同一个数组进行两次扩容,如果在扩容时使用了synchronized关键字进行同步,那么第二次扩容会等待第一次扩容完成后再进行。这会导致第二次扩容的时间会受到第一次扩容的影响,从而可能会导致程序的性能下降。
一种可能的解决方案是在进行第二次扩容之前,检查数组当前的容量是否已经足够,如果足够,则不进行扩容。这种方式可以避免重复扩容导致的性能问题,同时还可以节省内存空间。
以下是在Java中使用synchronized关键字对同一个数组进行两次扩容的示例代码:
public class ArrayExpander {
private Object[] array;
private int size;
public ArrayExpander(int initialCapacity) {
array = new Object[initialCapacity];
size = 0;
}
public synchronized void add(Object element) {
if (size == array.length) {
// 如果当前数组已经满了,则进行扩容
int newCapacity = array.length * 2;
Object[] newArray = new Object[newCapacity];
System.arraycopy(array, 0, newArray, 0, size);
array = newArray;
}
// 添加元素
array[size++] = element;
}
public synchronized void addAll(Collection<?> elements) {
for (Object element : elements) {
// 添加元素
add(element);
}
}
}
在上面的代码中,add方法使用synchronized关键字对数组进行同步访问,如果数组已经满了,则进行扩容。addAll方法调用了add方法,因此也会对数组进行同步访问。
注意,由于synchronized关键字会对整个方法进行同步访问,因此在对大量元素进行添加操作时,可能会导致性能问题。如果需要更高效的并发访问方式,可以使用其他线程安全的数据结构,如ConcurrentLinkedQueue等。
六.Mysql
1.索引是什么
在 MySQL 数据库中,索引是用来提高数据库查询效率的一种数据结构,它可以帮助数据库快速定位到需要查询的数据所在的位置,从而避免全表扫描,提高查询效率。
索引在 MySQL 中是基于 B-Tree 数据结构实现的,它通过将数据存储在树形结构中来提高查询速度。B-Tree 可以在 O(log n) 的时间复杂度下完成查找操作,因此它非常适合作为索引的数据结构。
MySQL 支持多种类型的索引,包括普通索引、唯一索引、主键索引、全文索引等。在创建索引时,需要指定要创建索引的列名或列组合,MySQL 会在这些列上创建索引,以加快查询速度。
需要注意的是,虽然索引可以加速查询,但同时也会增加数据的插入、更新和删除操作的时间成本。因此,在设计数据库时,需要根据实际情况选择合适的索引类型和创建索引的列,以平衡查询速度和数据操作的效率。
2.如何创建索引
在 MySQL 中创建索引需要使用 CREATE INDEX 语句。以下是创建索引的基本语法:
CREATE [UNIQUE] INDEX index_name
ON table_name (column1, column2, ...);
其中,index_name 是要创建的索引的名称,table_name 是要在其上创建索引的表的名称,column1, column2, … 是要在其上创建索引的列的名称。如果要创建唯一索引,需要在 CREATE INDEX 语句中添加 UNIQUE 关键字。
例如,要在 users 表的 name 列上创建一个普通索引,可以使用以下语句:
CREATE INDEX idx_users_name ON users (name);
如果要创建唯一索引,可以使用以下语句:
CREATE UNIQUE INDEX idx_users_email ON users (
- 创建单列索引
可以使用以下语法来创建单列索引:
CREATE INDEX index_name ON table_name (column_name);
例如,以下 SQL 语句将在 customers 表的 email 列上创建一个名为 idx_email 的索引:
CREATE INDEX idx_email ON customers (email);
- 创建多列索引
可以使用以下语法来创建多列索引:
CREATE INDEX index_name ON table_name (column_name1, column_name2, ...);
例如,以下 SQL 语句将在 orders 表的 customer_id 和 order_date 列上创建一个名为 idx_customer_order_date 的索引:
CREATE INDEX idx_customer_order_date ON orders (customer_id, order_date);
- 创建全文索引
可以使用以下语法来创建全文索引:
CREATE FULLTEXT INDEX index_name ON table_name (column_name1, column_name2, ...);
例如,以下 SQL 语句将在 articles 表的 title 和 body 列上创建一个名为 idx_fts 的全文索引:
CREATE FULLTEXT INDEX idx_fts ON articles (title, body);
请注意,要使用全文索引,必须将表的引擎设置为 MyISAM 或 InnoDB。