一、易考代码

1、运算符

| 类别 | 操作符 | 关联性 |
| - | - | - |
| 后缀 | () [] . (点操作符) | 左到右 |
| 一元 | expr++ expr-- | 从左到右 |
| 一元 | ++expr --expr + - ~ ! | 从右到左 |
| 乘性 | * / % | 左到右 |
| 加性 | + - | 左到右 |
| 移位 | >> >>> << | 左到右 |
| 关系 | > >= < <= | 左到右 |
| 相等 | == != | 左到右 |
| 按位与 | & | 左到右 |
| 按位异或 | ^ | 左到右 |
| 按位或 | | | 左到右 |
| 逻辑与 | && | 左到右 |
| 逻辑或 | || | 左到右 |
| 条件 | ?: | 从右到左 |
| 赋值 | = + = - = * = / =%= >> = << =&= ^ =|= | 从右到左 |
| 逗号 | , | 左到右 |

  i = i++ 等于 i = i

  • 赋值最后计算;
  • 等号(=)右边的从左到右加载值,依次压入操作数栈int k = i + ++i * i++;
  • 实际先算哪个,看运算符优先级。
class Demo {

    public static void main(String[] args) {
        int i = 1; // 1
        i = i++; // 1
        int j = i++; // j =1 i =2
        int k = i + ++i * i++; //2 + 3 * 3
        System.out.println(i); // 4
        System.out.println(j); // 1
        System.out.println(k); // 11
    }
}

2、面对对象

第一题:

  给出以下代码段:

class Demo {

    int w, x, y, z;

    public Demo(int a, int b) {
        this.x = a;
        this.y = b;
    }

    public Demo(int a, int b, int c, int d) {
        // assignment x = a,y = b
        this.w = c;
        this.z = d;
    }
}

  在代码说明 // assignment x = a,y = b 处写入如下哪几个代码是正确的?()

A. Demo(a,b);
B. x=a, y=b;
C. x=a; y=b;
D. this(a,b);

  这题我的答案是 ABCD,正确答案是CD(人傻了)。

  • A 选项:构造方法不能直接调用,只能使用 new 关键字;或者在构造方法中使用 this(a,b) 调用;
  • B 选项:在 java 中,逗号只能用来分隔方法的参数;或者分割多个变量的声明;或者用于 for 循环的表达式中:
int n = 0, m = 0;
for (int i = 0, j = 0; i < 5; i++, j++) {
}
第二题:

  给出以下代码段,父类如下:

public class Father {

    private int i = test();
    private static int j = method();
    static {
        System.out.println("(1)");
    }
    Father(){
        System.out.println("(2)");
    }
    {
        System.out.println("(3)");
    }

    public int test(){
        System.out.println("(4)");
        return 1;
    }
    public static int method(){
        System.out.println("(5)");
        return 1;
    }
}

  子类如下:

public class Son extends Father {
    private int i = test();
    private static int j = method();

    static {
        System.out.println("(6)");
    }

    Son() {
        System.out.println("(7)");
    }

    {
        System.out.println("(8)");
    }

    @Override
    public int test() {
        System.out.println("(9)");
        return 1;
    }

    public static int method() {
        System.out.println("(10)");
        return 1;
    }

    public static void main(String[] args) {
        Son s1 = new Son();
        System.out.println();
        Son s2 = new Son();
    }
}

  打印结果:

(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
  • 先初始化父类:5、1;
  • 初始化子类:10、6;
  • 子类实例化:
    • super()(不管写不写都存在,如果写则覆盖),实例化父类:
      • 父类非静态实例变量显示赋值代码和非静态代码块,代码从上到下顺序执行。用子类对象调用,如果该方法被子类重写,则执行子类重写过的代码。9、3;
      • 父类相应的构造方法。2;
    • 子类非静态实例变量显示赋值代码和非静态代码块,代码从上到下顺序执行。9、8;
    • 子类相应的构造方法。7。

考点

类初始化过程:

  • 一个类要创建实例需要先加载并初始化该类;
    • main 方法所在的类需要先加载和初始化。
  • 一个子类要初始化需要先初始化父类;
  • 一个类初始化就是执行<clinit>() 方法;
    • <clinit>() 方法由静态类变量显示赋值代码和静态代码块组成;
    • 类变量显示赋值代码和静态代码块,代码从上到下顺序执行;
    • <clinit>() 方法只执行一次。

实例初始化过程:

  实体初始化就是执行 <init>() 方法:

  • <init>() 方法可能重载有多个,有几个构造器就有几个<init> 方法;
  • <init>() 方法由非静态实例变量、显示赋值代码、非静态代码块、对应构造器代码组成;
  • 非静态实例变量显示赋值代码和非静态代码块,代码从上到下顺序执行,而对应构造器的代码最后执行;
  • 每次创建实例对象,调用对应构造器,执行的就是对应<init> 方法;
  • <init> 方法的首行是super()super(实参列表)(不管写不写都存在),即对应父类的<init> 方法。

方法的重写:

  哪些方法不可以被重写:

  • 父类 final 方法,子类不能有相同方法,子类可以调用;
  • 父类静态方法,子类不能有相同方法,子类可以调用;
  • 父类 private 等子类中不可见方法,子类可以有相同方法,子类无法调用父类的方法。

  对象的多态性:

  • 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码;
  • 非静态方法默认的调用对象 this;
  • this 对象在构造器或者说 方法中就是正在创建的对象。

3、方法传参问题

public class Exam4 {

    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 2;
        int[] arr = {1, 2, 3, 4, 5};
        MyData my = new MyData();
        change(i, str, num, arr, my);
        System.out.println("i = " + i);
        System.out.println("str = " + str);
        System.out.println("num = " + num);
        System.out.println("arr = " + Arrays.toString(arr));
        System.out.println("my.a = " + my.a);
    }

    public static void change(int j, String s, Integer n, int[] a, MyData m) {
        j += 1;
        s += "world";
        n += 1;
        a[0] += 1;
        m.a += 1;
    }
}

class MyData {
    int a = 10;
}

  上面代码运行结果为:(原因详见 Java 中的值传递和地址传递

i = 1
str = hello
num = 2
arr = [2, 2, 3, 4, 5]
my.a = 11

4、成员变量与局部变量

public class Exam5 {

    static int s;
    int i;
    int j;

    {
        int i = 1;
        i++;
        j++;
        s++;
    }

    public void test(int j) {
        j++;
        i++;
        s++;
    }

    public static void main(String[] args) {
        Exam5 obj1 = new Exam5();// i=0 j=1 s=1
        Exam5 obj2 = new Exam5();// i=0 j=1 s=2
        obj1.test(10);// i=1 j=1 s=3
        obj1.test(20);// i=2 j=1 s=4
        obj2.test(30);// i=1 j=1 s=5
        System.out.println(obj1.i + "," + obj1.j + "," + obj1.s);
        System.out.println(obj2.i + "," + obj2.j + "," + obj2.s);
    }
}

  运行结果:

2,1,5
1,1,5
  • 变量的访问采用就近原则;
  • 变量分类:
    • 成员变量:类变量、实例变量;
    • 局部变量。

局部变量与成员变量的区别:

声明的位置

  • 局部变量:方法体 {} 中,形参,代码块 {} 中;
  • 成员变量:类中方法外:
    • 类变量:有 static 修饰;
    • 实例变量:没有 static 修饰。

修饰符

  • 局部变量:final;
  • 成员变量:public、protected、private、final、static、volatile、transient。

值存储的位置

  • 局部变量:栈;
  • 实例变量:堆;
  • 类变量:方法区。

作用域

  • 局部变量:从声明处开始,到所属的 } 结束;
  • 实例变量:在当前类中 “this.”(有时可以省略),在其他类中“对象名.”访问;
  • 类变量:在当前类中“类名.”(有时可以省略),在其他类中“类名.”或“对象名.”(不建议)访问。

生命周期

  • 局部变量:每一个线程,每一次调用执行都是新的生命周期;
  • 实例变量:随着对象的创建而初始化,随着对象的收回而消亡,每一个对象的实例变量是独立的;
  • 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的。

2HGPBG5OXI9CAECATW0.png

5、设计模式

单例模式

  什么是 Singleton?在 Java 中指单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。

  • 某个类只能有一个实例:构造器私有化;
  • 它必须自行创建这个实例:含有一个该类的静态变量来保存这个唯一的实例;
  • 它必须自行向整个系统提供这个实例;

饿汉式

  饿汉式:直接创建对象,不存在线程安全问题,不管你是否需要这个对象都会创建。

  静态成员变量:

  • 优点:没有加锁,执行效率会提高。
  • 缺点:类加载时就初始化,浪费内存。
public class Zyx {

    // 创建 Zyx 的一个对象
    private static final Zyx ZYX = new Zyx();

    // 让构造函数为 private,这样该类就不会被实例化
    private Zyx() {

    }
  
    // 获取唯一可用的对象
    public static Zyx getInstance() {
        return ZYX;
    }
}

  枚举式(推荐):它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象。

public enum Zyx {
    ZYX;

    private void print() {
        System.out.println("zyxwmj.top");
    }
}

  静态代码块(适合复杂实例化):

public class Zyx {

    public static Zyx ZYX = null;

    private String domainName;

    static {
        try {
            Properties properties = new Properties();
            // 读取 src 下的 zyx.properties 文件的内容是 domainName=zyxwmj.top
            properties.load(Zyx.class.getClassLoader().getResourceAsStream("zyx.properties"));
            ZYX = new Zyx(properties.getProperty("domainName"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private Zyx(String domainName) {
        this.domainName = domainName;
    }
}

懒汉式

  线程不安全(适合单线程)

public class Zyx {

    private static Zyx ZYX;

    public static Zyx getInstance() {
        // 不存在则创建
        if (ZYX == null) {
            ZYX = new Zyx();
        }
        return ZYX;
    }

    private Zyx() {
    }
}

  线程安全(适合多线程):采用双重校验锁机制,安全且在多线程情况下能保持高性能。

public class Zyx {

    private static Zyx ZYX;

    public static Zyx getInstance() {
        // 用双重校验锁机制,提升性能
        if (ZYX == null) {
            synchronized (Zyx.class) {
                // 不存在则创建
                if (ZYX == null) {
                    ZYX = new Zyx();
                }
            }
        }
        return ZYX;
    }

    private Zyx() {
    }
}

  静态内部类形式(适合多线程,推荐)

  • 在内部类被加载和初始化时,才创建外部类的实例对象;
  • 静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。
  • 因为是在内部类加载和初始化时创建的,因此是线程安全的。
public class Zyx {

    private Zyx() {
    }

    private static class Inner {
        private static final Zyx ZYX = new Zyx();

        public static Zyx getInstance() {
            return ZYX;
        }
    }
}

6、整型包装类值的比较

  所有整型包装类对象值的比较必须使用 equals 方法。当使用自动装箱方式创建一个 Integer 对象时,当数值在 -128 ~127 时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的 Integer 对象。

    public static void main(String[] args) throws CloneNotSupportedException {
        Integer x = 3;
        Integer y = 3;
        System.out.println(x == y);// true
        Integer a = new Integer(3);
        Integer b = new Integer(3);
        System.out.println(a == b);// false
        System.out.println(a.equals(b));// true
    }

二、SSM 面试题

1、Bean 的作用域

  在 Spring 中,可以在 元素的 scope 属性里设置 bean 的作用域,以决定这个 bean 是单实例的还是多实例的。

  默认情况下,Spring 只为每个在 IOC 容器里声的 bean 创建唯一的实例,整个 IOC 容器范围内都能共享该实例:所有后续的 getBean() 调用和 bean 引用都将返回这个唯一的 bean 实例。该作用域被称为 singleton,它是所有 bean 的默认作用域。

| 类别 | 说明 |
| - | - |
| singleton | 在 SpringIOC 容器中仅存在一个 Bean 实例,Bean 以单实例的方式存在。 |
| prototype | 每次调用 getBean() 时都会返回一个新的实例。 |
| request | 每次 HTTP 请求都会创建给一个新的 Bean,该作用域仅适用于 WebApplicationContext 环境。 |
| session | 同一个 HTTP Session 共享一个 Bean,不同的 HTTP Session 使用不同的 Bean。该作用域仅适用于 WebApplicationContext 环境。 |

  当 bean 的作用域为单例时,Spring 会在 IOC容器对象创建时就创建 bean 的实例。

  使用配置文件时,通过 bean 标签的 scope 属性值设置作用域。

<bean id="book" class="spring.bean.Book" scope="prototype"></bean>

  使用注解方式设置 bean 的作用域:

@Scope(ConfigurableListableBeanFactory.SCOPE_SINGLETON)
@Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
@Scope(WebApplicationContext.SCOPE_REQUEST)
@Scope(WebApplicationContext.SCOPE_SESSION)

  测试:

@SpringBootTest
public class Demo {

    ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");

    @Test
    public void text() {
        Book book1 = (Book) ioc.getBean("book");
        Book book2 = (Book) ioc.getBean("book");
        // singleton 时为 true(默认)
        // prototype 时为 false
        System.out.println(book1 == book2);
    }
}

2、Spring 支持的数据库事务传播属性和事务隔离级别

事务的传播属性

  当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。传播属性的注解设置方法:

@Transactional(propagation = Propagation.REQUIRED)

| 传播属性 | 描述 |
| - | - |
| REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。 |
| REQUIRES_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起。 |
| SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。 |
| NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。 |
| MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。 |
| NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。 |
| NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行;否则,就启动一个新的事务,并在它自己的事务内运行。 |

事务并发问题

  假如现在有两个事务:t1 和 t2 并发执行。

脏读:

  • t1 将某条记录的 AGE 值从 20 修改为 30;
  • t2 读取了 t1 更新后的值 30;
  • t1 回滚,AGE 值恢复到了 20;
  • t2 读取到的 30 就是一个无效的值。

不可重复读:

  • t1 读取了 AGE 值为 20;
  • t2 将 AGE 值修改为 30;
  • t1 再次读取 AGE 值为 30,和第一次读取的不一致。

幻读:

  • t1 读取了 A 表中的数据;
  • t2 向 A 表中插入了新的行;
  • t1 再读 A 表时,多了一行数据;

隔离级别

  数据库系统必须具有隔离并发运行各个事务的能力,使它们不会互相影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

  • Read Uncommitted(读未提交):允许 t1 读取 t2 未提交的修改;
  • Read Committed(读已提交):要求 t1 只能读取 t2 已提交的修改(开发中常用);
  • Repeatable Read(可重复读):确保 t1 可以多次从一个字段中读取到相同的值,即使 t2 已经提交了,t1 也依然还显示以前的数据。
  • Serializable(可串行化):确保 t1 可以多次从一个表中读取到相同的行数据。如果有一个连接的隔离级别设置了可串行化,那么谁先打开事务,谁就有了执行的权利,其他事务只能等前面的事务提交或者回滚才能执行。

  不同隔离级别的事务并发问题:

隔离级别脏读不可重复读幻读
Read Uncommitted
Read Committed×
Repeatable Read××
Serializable×××

  各种数据库对事务隔离级别的支持程度:

隔离级别OracleMySQL
Read Uncommitted×
Read Committed√(默认)
Repeatable Read×√(默认)
Serializable

  通过注解设置当前事务的隔离级别。

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)

3、SpringMVC 中如何解决 POST、GET 请求中文乱码问题

  • POST请求:修改 CharacterEncodingFilter 的 encoding 过滤器的编码。
  • GET 请求:在 Tomcat 的 conf/server.xml 文件的 标签中添加 URIEncoding="UTF-8" 属性。

4、SpringMVC 的工作流程

  处理模型数据方式一:将方法的返回值设置为 ModelAndView

    @RequestMapping("/zyx")
    public ModelAndView test1() {
        // 1、创建 ModelAndView 对象
        ModelAndView modelAndView = new ModelAndView();
        // 2、设置模型数据,最终会放到 request 域中
        modelAndView.addObject("user", "admin");
        // 3、设置视图
        modelAndView.setViewName("success");
        return modelAndView;
    }

  处理模型数据方式二:方法的返回值仍是 String 类型,在方法的入参中传入 Map、Model、ModelMap。不管将处理器方法的返回值设置为 ModelAndView 还是在方法的入参中传入 Map、Model、ModelMap,SpringMVC 都会转换为一个 ModelAndView 对象。

    @RequestMapping("/zyx")
    public String test2(Map<String, Object> map) {
        // 向 Map 中添加模型数据,最终会自动方到 request 域中
        map.put("user", new User("Yi-Xing", 18, "..."));
        return "success";
    }

QNUUPZNL35OTTX.png

5、MyBatis 中当实体类中的属性名和表中的字段名不一样,怎么办?

  • 写 sql 语句时起别名;
  • 在 MyBatis 的全局配置文件中开启驼峰命名规则(last_name 映射为 lastName);
  • 在 Mapper 映射文件中使用 resultMap 来自定义映射规则。

三、Redis

1、Redis 持久化有几种类型以及区别

RDB(Redis DataBese)

  在指定的时间间隔内,将内存中的数据集快照写入磁盘,即 Snapshot 快照。它恢复时是将快照文件直接读到内存里。

  备份是如何执行的:Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那么 RDB 方式要比 AOF 方式更加高效。RDB 的缺点是最后一次持久化后的数据可能丢失。

优点:

  • 节省磁盘空间;
  • 恢复速度快。

缺点:

  • 虽然 Redis 在 fork 时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能;
  • 在备份周期在一定间隔时间做一次备份。所以如果 Redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。

AOF(Append Of File)

  以日志的形式来记录每个操作,将 Redis 执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis 启动之初会读取该文件重新构建数据。换言之,Redis 重启的话就根据日志文件的内容将写指令从前到后执行一次,以完成数据的恢复工作。

优点:

  • 备份机制更稳健,丢失数据概率更低;
  • 可读的日志文件,通过操作 AOF 文件,可以处理误操作。

缺点:

  • 比起 RDB 占用更多的磁盘空间;
  • 恢复备份速度要慢;
  • 每次读写都同步的话,有一定的性能压力;
  • 存在个别 Bug,造成不能恢复。

2、各种数据类型在项目中的使用场景

| 数据类型 | 使用场景 |
| - | - |
| String | 比如:统计指定 IP 地址的访问次数,从而进行封锁,Incrby 命令。 |
| Hash | 存储用户信息(id,name,age)
Hset(key,field,value)
Hset(userkey,id,1)
Hset(userkey,name,admin)
Hset(userkey,age,23)
获取 Hget(userkey,id) |
| List | 实现最新消息的排行,还可以利用 List 的 push 命令,将任务存在 list 集合中,同时使用另一个命令,将任务从集合中取出(pop)。
Redis-list 数据类型来模拟消息队列(电商秒杀) |
| Set | 可以自动排重 |
| Zset | 可以以某一个条件为权重,进行排序
比如:商品有综合排名和价钱排名 |

四、MySQL

1、什么时候适合建索引,什么时候不适合

  索引是指帮助 MySQL 高效获取数据的数据结构。可以说索引是排好序的快速查找的数据结构(以索引文件的形式存储在磁盘上)。

  优点:提高检索效率,降低 IO 成本和 CPU 的消耗。缺点:降低表的更新速度,占用空间。

什么时候需要创建索引

  • 主键自动建立唯一索引。
  • 频繁作为查询条件的字段应该创建索引。
  • 查询中与其他表关联的字段,外键关系建立索引。
  • 单键/组合索引的选择问题(在高并发下倾向创建组合索引)。
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。
  • 查询中统计或分组字段建立索引。

什么时候不需要创建索引

  • 表的记录太少。
  • 经常增删改的字段;频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引。
  • where 条件里用不到的字段
  • 数据重复且分布平均的表字段不建立索引,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。(即过滤性不好)

五、JVM

1、JVM 垃圾回收机制,GC 发生在 JVM 哪部分,有几种GC,有什么算法

  GC 发生在 JVM 的堆内存。GC(分代收集算法):频繁收集 young 区(Minor GC);较少收集 Old 区(Full GC),基本不动 Perm 区(永久区)。

  GC 算法

  • 引用计数法
  • 复制算法
  • 标记清楚法
  • 标记压缩法
  • 标记清楚压缩法

六、Linux

1、CentOS 6

  注册在系统中的标准化程序,有方便统一的管理方式:

  • service 服务名 start
  • service 服务名 stop
  • service 服务名 restart
  • service 服务名 reload
  • service 服务名 status

  查看服务的方法:/etc/init.d/服务名。

  通过 chkconfig 命令设置自启动

  • 查看服务 chkconfig --list | grep xxx
  • chkconfig --level 5 服务名 on(没图形化时 3,有图形化时 5)

QB0W1IPU0MG1JO90L1NF7.png

2、CentOS 7

  注册在系统中的标准化程序,有方便统一的管理方式:

  • systemctl start 服务名
  • systemctl stop 服务名
  • systemctl restart 服务名
  • systemctl reload 服务名
  • systemctl status 服务名

  查看服务的方法:/usr/lib/systemd/system。查看服务的命令:

  • systemctl list-unit-files
  • systemctl --type service

  通过 systemctl 命令设置自启动

  • 开启自启动 systemctl enable 服务名
  • 关闭自启动 systemctl disable 服务名

七、Git

  创建分支:

  • git branch 分支名
  • 查看分支 git branch -v

  切换分支:

  • git checkout 分支名
  • 创建+切换分支:git checkout -b 分支名

  合并分支:

  • 先切换到主干 git checkout master
  • git merge 分支名

  删除分支:

  • 先切换到主干 git checkout master
  • git branch -D 分支名

CP8ZNIPEL41G8RTQL.png

八、消息队列

  在分布式系统中是如何处理高并发的:由于在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求发生阻塞。比如说,大量的 insert,update 之类的请求同时达到数据库 MySQL,直接导致无数的行锁表锁,甚至会导致请求堆积很多。从而触发 too many connections 错误。使用消息队列可以解决异步通信(异步、并行、排队)。

  消息队列的弊端:消息的不确定性,延迟队列,轮询技术来解决该问题即可。


标题:尚硅谷第一季面试题
作者:Yi-Xing
地址:http://47.94.239.232:10014/articles/2021/01/29/1611932606102.html
博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!