springboot第59集:面试官万字挑战,一文让你走出微服务迷雾架构周刊

科技   其他   2024-02-23 19:44   广东  
加群联系作者vx:xiaoda0423(个人简介:男,深圳,一名全栈架构师,25+)
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
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 可能会引发线程不安全的问题,主要有以下几个原因:

  1. 非线程安全的操作: ArrayList 不是线程安全的数据结构,它的内部结构不是线程安全的。在多线程环境下,多个线程同时对 ArrayList 进行添加、删除、修改等操作可能会导致内部状态混乱,从而产生不可预知的结果。
  2. 并发修改异常: 当一个线程正在对 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());
    }
}

栈溢出通常是由于以下原因之一导致的:

  1. 递归调用:递归调用的层数过多,导致函数调用栈空间不足,从而引发栈溢出错误。
  2. 大量循环或死循环:如果程序中存在大量循环或者死循环,并且循环次数过多,会导致栈空间不断增长,最终导致栈溢出。
  3. 全局变量过多:如果程序中定义了大量的全局变量,会增加栈空间的压力,可能导致栈溢出。
  4. 数据结构过大:如果程序中使用的数据结构(如数组、列表、映射等)过大,会占用大量内存空间,增加栈空间的压力,可能导致栈溢出。

下面是一些示例代码,演示了可能导致栈溢出的情况:

  1. 递归调用:
  2. 大量循环或死循环:
  3. 全局变量过多:
  4. 数据结构过大:
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.gz

tar -zxvf 压缩包 => tar -zxvf 1.tar.gz

解压zip

unzip 压缩包 => 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

启动docker

systemctl start docker

查看docker状态

systemctl status docker

重启docker

systemctl 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
        securetrue
        # true 临时 false 持久
        ephemeraltrue
      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 的基本特点:

  1. 双向链表结构:每个节点包含两个引用,分别指向前一个节点和后一个节点。
  2. 无需连续内存空间:与数组不同,LinkedList 中的节点在内存中可以不必连续存储。
  3. 插入和删除操作高效:由于双向链表的结构,插入和删除操作的时间复杂度为 O(1)。
  4. 随机访问效率低:由于 LinkedList 没有像数组那样可以通过索引进行快速随机访问,因此访问特定位置的元素需要遍历链表,时间复杂度为 O(n)。
  5. 不适合大量数据:由于每个节点都需要额外的空间存储指向前后节点的引用,因此在存储大量数据时,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

算法猫叔
程序员:进一寸有一寸的欢喜
 最新文章