springboot第59集:面试官万字挑战,一文让你走出微服务迷雾架构周刊
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedArrayListExample {
public static void main(String[] args) {
// 创建一个线程安全的 ArrayList
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
// 创建并启动多个线程同时向 ArrayList 中添加元素
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
synchronized (synchronizedList) {
synchronizedList.add(j);
}
}
}).start();
}
// 等待所有线程执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出 ArrayList 中的元素个数
System.out.println("Size of synchronized ArrayList: " + synchronizedList.size());
}
}
在高并发的情况下,多个线程同时操作 ArrayList 可能会引发线程不安全的问题,主要有以下几个原因:
- 非线程安全的操作: ArrayList 不是线程安全的数据结构,它的内部结构不是线程安全的。在多线程环境下,多个线程同时对 ArrayList 进行添加、删除、修改等操作可能会导致内部状态混乱,从而产生不可预知的结果。
- 并发修改异常: 当一个线程正在对 ArrayList 进行修改操作(如添加、删除元素)时,另一个线程也同时对 ArrayList 进行修改操作,可能会导致并发修改异常(ConcurrentModificationException)。
为了避免这些问题,通常需要在多线程环境下使用线程安全的数据结构,或者采用同步机制来保护共享数据的访问。下面是一个示例,演示了在多线程环境下操作 ArrayList 可能引发的线程不安全问题:
import java.util.ArrayList;
public class UnsafeArrayListExample {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
// 创建并启动多个线程同时向 ArrayList 中添加元素
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
arrayList.add(j);
}
}).start();
}
// 等待所有线程执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出 ArrayList 中的元素个数
System.out.println("Size of ArrayList: " + arrayList.size());
}
}
栈溢出通常是由于以下原因之一导致的:
- 递归调用:递归调用的层数过多,导致函数调用栈空间不足,从而引发栈溢出错误。
- 大量循环或死循环:如果程序中存在大量循环或者死循环,并且循环次数过多,会导致栈空间不断增长,最终导致栈溢出。
- 全局变量过多:如果程序中定义了大量的全局变量,会增加栈空间的压力,可能导致栈溢出。
- 数据结构过大:如果程序中使用的数据结构(如数组、列表、映射等)过大,会占用大量内存空间,增加栈空间的压力,可能导致栈溢出。
下面是一些示例代码,演示了可能导致栈溢出的情况:
- 递归调用:
- 大量循环或死循环:
- 全局变量过多:
- 数据结构过大:
public class StackOverflowExample {
public static void main(String[] args) {
recursiveCall(0);
}
public static void recursiveCall(int n) {
recursiveCall(n + 1);
}
}
fifinally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
public class Main {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
System.out.println("In try block");
return 1;
} catch (Exception e) {
System.out.println("In catch block");
return 2;
} finally {
System.out.println("In finally block");
}
}
}
在 Java 中,无论在 try
块中是否有 return
语句,finally
块都会执行。finally
块通常用于释放资源或执行清理操作,无论 try
块中是否发生异常,都会执行 finally
块。
使用 BigDecimal
类可以避免浮点数精度问题,确保得到精确的计算结果。
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal result = new BigDecimal("3").multiply(new BigDecimal("0.1"));
System.out.println(result); // 输出:0.3
}
}
在 Java 中,3 * 0.1
应该返回 0.3
,但由于浮点数的精度问题,可能会出现计算结果不准确的情况。这是因为在计算机中,浮点数的表示方式是有限的,而某些十进制小数无法精确地表示为二进制小数。
因此,当我们执行 3 * 0.1
这样的计算时,可能会出现一个非精确的结果。在实际测试中,可能会得到 0.30000000000000004
或者 0.29999999999999999
这样的结果,而不是精确的 0.3
。这是由于浮点数的精度问题导致的。
为了避免由于浮点数精度问题导致的误差,通常建议在需要精确计算的场景中,使用 BigDecimal
类进行计算。
使用序列化机制创建对象(需要实现 Serializable
接口):
import java.io.*;
public class SerializationExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 使用序列化机制创建对象
MyClass obj1 = new MyClass();
// 将对象写入到字节流中
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj1);
// 从字节流中读取对象
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
MyClass obj2 = (MyClass) objectInputStream.readObject();
obj2.printMessage(); // 输出:Hello, world!
}
}
使用 clone()
方法创建对象(需要实现 Cloneable
接口):
public class CloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 使用 clone() 方法创建对象
MyClass obj1 = new MyClass();
MyClass obj2 = (MyClass) obj1.clone();
obj2.printMessage(); // 输出:Hello, world!
}
}
使用 new
关键字创建新对象:
public class NewObjectExample {
public static void main(String[] args) {
// 使用 new 关键字创建新对象
MyClass obj = new MyClass();
obj.printMessage(); // 输出:Hello, world!
}
}
class MyClass {
public void printMessage() {
System.out.println("Hello, world!");
}
}
使用反射机制创建对象:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectionExample {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 使用反射机制创建对象
Class<MyClass> cls = MyClass.class;
Constructor<MyClass> constructor = cls.getConstructor();
MyClass obj = constructor.newInstance();
obj.printMessage(); // 输出:Hello, world!
}
}
import java.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) {
// 创建一个 ConcurrentHashMap
ConcurrentHashMap<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();
// 创建并启动多个线程
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
// 向 ConcurrentHashMap 中添加元素
for (int j = 0; j < 10; j++) {
concurrentHashMap.put(finalI * 10 + j, "Value_" + finalI + "_" + j);
System.out.println(Thread.currentThread().getName() + " added: " + finalI * 10 + j);
}
}).start();
}
// 等待所有线程执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出 ConcurrentHashMap 中的所有元素
System.out.println("ConcurrentHashMap: " + concurrentHashMap);
}
}
当需要在多线程环境中操作时,可以使用线程安全的 ConcurrentHashMap
。
Hashtable
是线程安全的,但在性能上可能不如 HashMap
,因为 Hashtable
中的方法使用了 synchronized
关键字进行同步,这会造成一定的性能开销。因此,在不需要线程安全保证的情况下,推荐使用 HashMap
,在需要线程安全保证的情况下,再考虑使用 Hashtable
或者 ConcurrentHashMap
。
import java.util.Hashtable;
public class Main {
public static void main(String[] args) {
// 创建一个 Hashtable
Hashtable<Integer, String> hashtable = new Hashtable<>();
// 创建并启动多个线程
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
// 向 Hashtable 中添加元素
for (int j = 0; j < 10; j++) {
hashtable.put(finalI * 10 + j, "Value_" + finalI + "_" + j);
System.out.println(Thread.currentThread().getName() + " added: " + finalI * 10 + j);
}
}).start();
}
// 等待所有线程执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出 Hashtable 中的所有元素
System.out.println("Hashtable: " + hashtable);
}
}
Hashtable
是线程安全的,因为它的每个方法都使用了 synchronized
关键字进行同步,这使得它可以直接用于多线程环境中。
目录和文件
创建目录mkdir 名称 => mkdir /data
创建目录及子目录mkdir -p 名称 => mkdir -p /data/node
创建一个或多个(用空格分开即可)touch 文件1 文件2 => touch 1.txt 2.txt
复制文件cp 文件 目录 => cp 1.txt /opt/data
复制文件并改名cp 文件 目录 => cp 1.txt /opt/data/2.txt
移动目录到另一个目录mv 目录 目录 => mv data /opt
移动目录到另一个目录并改名mv 目录 目录 => mv data /opt/data2
强制删除一个目录rm -rf data
文件夹授予权限chmod 777 -R 目录 => chmod 777 -R data
解压tar.gztar -zxvf 压缩包 => tar -zxvf 1.tar.gz
解压zipunzip 压缩包 => unzip 1.zip
查询目录路径pwd
查看文件cat 目录 => cat 1.txt
编辑文件vi 目录 => vi 1.txt
将xxx写入文件echo 内容 >> 文件 => echo '111' >> 1.txt
输出文件尾部内容tail -n 行数 文件 => tail -n 1000 1.txt
查看文件find /-name 文件 => find /-name 1.txt #网络
重启网络service network restart #防火墙
关闭防火墙systemctl stop firewalld.service
重启防火墙systemctl restart firewalld.service
启动防火墙systemctl start firewalld.service
防火墙状态systemctl status firewalld.service
开启开机自启动systemctl enable firewalld.service
关闭开机自启动systemctl disable firewalld.service
查看已开放端口firewalld-cmd --list-ports
开放端口(永久有效)(需要重新加载防火墙)firewalld-cmd --zone=public --add-port=端口/tcp --permanent => firewalld-cmd --zone=public --add-port=8080/tcp --permanent
重新加载防火墙firewalld-cmd --reload #服务
查看服务开机启动状态systemctl list-run-files
关闭指定服务自启动systemctl disable 服务 => systemctl disable mysql
开启指定服务自启动systemctl enable 服务 => systemctl enable mysql #磁盘
查看磁盘df -h #进程
查看端口netstat -ntlp
进程启动情况ps -ef|grep 进程名 => ps -ef|grep java
查看端口占用netstat -tunlp|grep 端口 => netstat -tunlp|grep 8080
查看进程ps -aux
终止进程kill 进程 => kill 884
强制终止进程kill -9 进程 => kill -9 884 #CPU
查看cpu情况top #账号
切换账号su 账号 => su root
新增账号useradd 用户名 => useradd mysql
添加到分组useradd -g 组名 用户名 => useradd -g mysql-group mysql
设置密码passwd 用户名 => passwd mysql
删除用户userdel 用户名 => userdel mysql
登录用户信息whomi
Docker
查看内核uname -r
启动dockersystemctl start docker
查看docker状态systemctl status docker
重启dockersystemctl restart docker
查看版本docker version
查看信息docker info
获取帮助docker --help
查看镜像docker images
启动镜像docker run -d -p 对外端口:容器端口 镜像名称 => docker run -d -p 6379:6379 redis
查看日志docker logs 容器id => docker logs xz2wxdf
搜索镜像docker search 镜像名称 => docker search jdk
打包镜像docker tag 镜像名称:标签 => docker tag redis:7.0.1
删除镜像docker rmi 镜像id => docker rmi dxfdxzsa
进入容器docker exec -it 容器id /bin/bash => docker exec -it xsdfds /bin/bash
重启容器docker restart 容器id => docker restart xsddf
列出容器docker ps --a
停止容器docker stop 容器id => docker stop exfds
删除容器docker rm 容器id => docker rm xsdfds
强制停止容器docker kill 容器id
查看容器内部细节docker inspect 容器id
查看所有卷情况docker volume ls
查看某个卷docker volume inspect 卷名 => docker volume inspect /data
构建镜像dokcer build -t 镜像名称:标签 . => docker build -t jdk:21 . #docker-compose
构建镜像docker-compose build
构建镜像(不带缓存构建)docker-compose build --no-cache
查看docker镜像docker-compose images
启动所有镜像docker-compose up -d
查看所有编排容器(包括已停止容器)docker-compose ps -a
进入指定容器docker-compose exec 容器名 bash => docker-compose exec nginx bash
停止所有启动容器docker-compose stop
停止所有启动容器并删除docker-compose down
停止某一个容器docker-compose stop 容器名称 => docker-compose stop nginx
启动某一个容器docker-compose up -d 容器名称 => docker-compose up -d nginx
重启某一个容器docker-compose restart 容器名称 => docker-compose restart nginx
删除所有容器docker-compose rm
查看容器日志docker-compose logs -f 容器名称 => docker-compose logs -f nginx
查看容器运行进程docker-compose top
yaml配置
nacos使用https和http协议,只要注册临时服务,都是走RPC,因此使用https需要改为https
spring:
cloud:
# nacos
nacos:
discovery:
server-addr: https://127.0.0.1:8848
namespace: public
username: nacos
password: nacos
group: LAOKOU_GROUP
# https
secure: true
# true 临时 false 持久
ephemeral: true
config:
server-addr: https://127.0.0.1:8848
namespace: public
username: nacos
password: nacos
# 指定读取的文件格式
file-extension: yaml
group: LAOKOU_GROUP
refresh-enabled: true
keytool命令详解
1.生成pfx证书(.p12是pfx的新格式)
keytool -genkey
-alias laokou-register # 证书别名,不区分大小写
-storetype PKCS12 # 密钥库类型,常用类型有JKS、PKCS12
-keyalg RSA # 密钥算法,可选密钥算法:RSA\DSA,默认DSA
-keysize 2048 # 密钥长度(RSA=2048,DSA=2048)
-keystore scg-keystore.p12 # 密钥库文件名称
-validity 3650 # 证书有效天数
2.导出证书为cer(cer/crt是证书的公钥格式,cer是crt证书的微软形式)
keytool -exportcert -v
-alias laokou-register # 证书别名,不区分大小写
-keystore scg-keystore.p12 # 密钥库文件名称
-storepass laokou # 密钥库口令,推荐与keypass一致(获取keystore信息所需要密码)
-file register.cer # 导出的文件名
image.png
import java.util.Hashtable;
import java.util.Enumeration;
public class Main {
public static void main(String[] args) {
// 创建一个 Hashtable
Hashtable<Integer, String> hashtable = new Hashtable<>();
// 添加元素到 Hashtable
hashtable.put(1, "Apple");
hashtable.put(2, "Banana");
hashtable.put(3, "Orange");
// 获取 Hashtable 中的值的枚举
Enumeration<String> values = hashtable.elements();
// 遍历枚举并输出值
while (values.hasMoreElements()) {
System.out.println(values.nextElement());
}
}
}
Hashtable
类的 elements()
方法用于返回此 Hashtable
中的值的枚举(Enumeration)。
LinkedList 是 Java 中的双向链表实现。在 LinkedList 中,每个节点都包含对前一个节点和后一个节点的引用,这使得在链表中插入和删除元素的操作更加高效,因为它不需要像数组那样移动其他元素来保持顺序。
以下是 LinkedList 的基本特点:
- 双向链表结构:每个节点包含两个引用,分别指向前一个节点和后一个节点。
- 无需连续内存空间:与数组不同,LinkedList 中的节点在内存中可以不必连续存储。
- 插入和删除操作高效:由于双向链表的结构,插入和删除操作的时间复杂度为 O(1)。
- 随机访问效率低:由于 LinkedList 没有像数组那样可以通过索引进行快速随机访问,因此访问特定位置的元素需要遍历链表,时间复杂度为 O(n)。
- 不适合大量数据:由于每个节点都需要额外的空间存储指向前后节点的引用,因此在存储大量数据时,LinkedList 的空间开销会比较大。
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String[] args) {
// 创建一个 LinkedList
LinkedList<String> linkedList = new LinkedList<>();
// 添加元素到 LinkedList
linkedList.add("Apple");
linkedList.add("Banana");
linkedList.add("Orange");
// 访问元素
System.out.println("LinkedList: " + linkedList);
// 获取第一个和最后一个元素
String firstFruit = linkedList.getFirst();
String lastFruit = linkedList.getLast();
System.out.println("First fruit: " + firstFruit);
System.out.println("Last fruit: " + lastFruit);
// 添加元素到列表的开头和末尾
linkedList.addFirst("Grape");
linkedList.addLast("Watermelon");
System.out.println("LinkedList after adding first and last elements: " + linkedList);
// 删除第一个和最后一个元素
linkedList.removeFirst();
linkedList.removeLast();
System.out.println("LinkedList after removing first and last elements: " + linkedList);
// 获取元素个数
int size = linkedList.size();
System.out.println("Size of LinkedList: " + size);
// 判断是否包含某个元素
boolean containsBanana = linkedList.contains("Banana");
System.out.println("LinkedList contains 'Banana': " + containsBanana);
}
}
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个 ArrayList
ArrayList<String> arrayList = new ArrayList<>();
// 添加元素到 ArrayList
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Orange");
// 访问元素
System.out.println("ArrayList: " + arrayList);
// 获取指定位置的元素
String fruit = arrayList.get(1);
System.out.println("Fruit at index 1: " + fruit);
// 修改元素
arrayList.set(0, "Grape");
System.out.println("Updated ArrayList: " + arrayList);
// 删除元素
arrayList.remove(2);
System.out.println("ArrayList after removing element at index 2: " + arrayList);
// 获取元素个数
int size = arrayList.size();
System.out.println("Size of ArrayList: " + size);
// 判断是否包含某个元素
boolean containsBanana = arrayList.contains("Banana");
System.out.println("ArrayList contains 'Banana': " + containsBanana);
}
}
ArrayList 适用于随机访问和遍历操作,而 LinkedList 适用于频繁的插入和删除操作
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
// 创建一个数组
String[] array = {"apple", "banana", "orange"};
// 使用 asList 方法将数组转换为列表
List<String> list = Arrays.asList(array);
// 修改原数组或列表中的元素
array[0] = "grape";
list.set(1, "watermelon");
// 打印数组和列表
System.out.println("Array: " + Arrays.toString(array));
System.out.println("List: " + list);
}
}
加群联系作者vx:xiaoda0423
仓库地址:https://github.com/webVueBlog/JavaGuideInterview