Java —— 泛型

目录

1. 什么是泛型

2. 泛型背景及其语法规则

3. 泛型类的使用

3.1 语法

3.2 示例

3.3 类型推导(Type Inference)

4. 裸类型(Raw Type)

4.1 说明

5. 泛型如何编译的

5.1 擦除机制

5.2 为什么不能实例化泛型类型数组

6. 泛型的上界

6.1 上界语法产生的背景

6.2 语法

6.3 示例

6.4 复杂示例

7. 泛型方法

泛型方法的语法

8. 通配符

8.1 通配符解决什么问题

8.2 通配符的上界

8.3 通配符下界

9. 包装类

9.1 基本数据类型和对应的包装类

9.2 装箱和拆箱

9.3 自动装箱和自动拆箱


1. 什么是泛型

一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《Java编程思想》对泛型的介绍。
    public static void func(int a) {
    }

对于func()这个代码来说, 去传参只能传int类型的数据.

而泛型就是可以传类型. 也就是说, 如果这里是泛型, 就可以传各种数据类型, 包括自定义的数据类型在内, 都可以作为参数进行传参.

泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。

泛型就是 对类型进行参数化.

2. 泛型背景及其语法规则

实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?

答: 我们可以把数组定义为Object数组, 因为所有类都默认继承于Object类.

// JVM会做一些改变 把1 2 3 4 这些数据放到这个数组当中(怎么放的涉及装箱拆箱, 在后文解析)
Object[] array = {1, 2, 3, 4, "hello", "ok"};

可以看到, 因为是array是Object数组, 所以什么类型的数据都可以放.

但是这样写会带来一个问题, 如果要写以下代码:

String[] arr2 = (Object)array;  // 报错, array中放的不一定都是String类型的数据
String[] array3 = (String[]) new Object[10];    // 报错, 不能确定Object中都是String类型的数据
实际开发中不要尝试把Object类型的数组强制转换成String类型的数组.

再回到原来的问题:

// 实现一个类
class MyArray {
    // 类中包含一个数组成员
    public Object[] obj = new Object[10];

    // 使得数组中可以存放任何类型的数据
    public void setVal(int pos, Object val) {
        obj[pos] = val; // 在 obj 的 pos位置 放 val
    }

    // 也可以根据成员方法返回数组中某个下标的值
    public Object getPos(int pos) {
        return obj[pos];
    }
}

现在我们调用一下上面这两个方法.

public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0, 10);
        myArray.setVal(1, "苹果");
        myArray.setVal(2, 10.5);
    }
}

可以看到, 这个类确实非常通用, 将需要的东西往里面放就可以了, 但是接下来就会有问题了.

我们尝试获取一下1下标的值, 由于它是一个字符串:

String str = myArray.getPos(1);

但是编译器直接报红了:

图中提到, 需要类型是String, 但是返回类型是Object. 我们知道, 父类类型给到子类类型是向下转型, 所以在getPos()返回的是Object, 所以我们尝试加上强转为String, 就不会报错了.

String str = (String) myArray.getPos(1);

但是这对开发人员来说很不友好, 如果此时要取2号元素, 那么就必须再看一下它的数据类型是什么, 这降低了开发的效率.


总结两个问题:以上代码实现后我们发现:

  1. 当前数组是可以存放任何类型数据
  2. 1号下标本身就是字符串,但是却编译报错。必须进行【强制类型转换】

虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望这个数组只能够持有一种数据类型。而不是同时持有这么多类型。所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象, 让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

我们在类名的后面加上<T>:

class MyArray<T> {}
<T>在这里相当于是一个占位符, 表示当前类是一个泛型类.
T只是一个"参数", 类似于形参变量, 也可以是K, 也可以是V, 只是名字而已.
其实泛型在这里不同的字母代表的是不同的意思, 只不过在这里是为了增强可阅读性.
【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型

接下来我们把所有的Object改成T:

class MyArray<T> {
    //public T[] obj = new T[10]; // 把所有的Object改成T, 但是此时这里会报错
    public T[] obj = (T[]) new Object[10]; // 暂且先写成这样, 但是依然不太好, 后续解释

    public void setVal(int pos, T val) {
        obj[pos] = val;
    }

    public T getPos(int pos) {
        return obj[pos];
    }
}

此时在main中修改代码为:

public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<Integer>();  // 改为这样的代码后, 下面代码报错了
        myArray.setVal(0, 10);
        myArray.setVal(1, "苹果");    // err
        myArray.setVal(2, 10.5);    // err

        String str = (String) myArray.getPos(1);    // err
    }
}

可以发现, 在存元素的时候, 第一个没有报错, 而第二, 三个报错了, 也就是说这里指定了当前把类型作为参数传递的时候, 传的是Integer类型. 所以就能放整型. 于是:

public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<Integer>();
        myArray.setVal(0, 10);
        myArray.setVal(1, 2);
        myArray.setVal(2, 6);
        int a = myArray.getPos(1);

        // 同理, 要在MyArray中放字符串, 就指定String类型
        MyArray<String> myArray2 = new MyArray<String>();
        myArray2.setVal(0, "hello");
        myArray2.setVal(1, "hello2");
        String str = myArray2.getPos(0);
    }
}

可以看到, 当传的类型参数不一样的时候, setVal()的第二个参数就能放不同类型. 所以在我们以前传的是变量的值, 而现在则相当于传递的是类型.

那么相比前面来说, 存的时候, 它自动进行了类型的检查, 看存放的类型是否是Integer/String, 如果不是就会报错.而且, 在取数据的时候, 就不需要进行强转了. 这便是泛型所存在的两个最大意义.

泛型存在的两个最大的意义:
1. 存放元素的时候, 会进行类型的检查
2. 取出元素的时候, 会自动进行类型的转换(即 不需要再进行类型的强转).
以上两步都是 在编译的时候完成的(运行的时候, 是没有泛型的概念的).
泛型主要是编译时期的一种机制, 这种机制有一个概念 —— 擦除机制.
class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}

class ClassName<T1, T2, ..., Tn> {
    // 一个泛型类的参数列表可以指定多个类型
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
    // 这里可以使用类型参数
}

class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
    // 可以只使用部分类型参数
}

比如:

// 拿到类型后可以定义变量
class TestDemo<K,V> {
    K k;    // 通过 K类型 定义k
    V v;    // 通过 V类型 定义v
}

注意: main中的<>中填写的必须是类类型, 不能是基本类型!

3. 泛型类的使用

3.1 语法

泛型类<类型实参> 变量名;  // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参);  // 实例化一个泛型类对象

3.2 示例

MyArray<Integer> list = new MyArray<Integer>();

注意:泛型只能接受类,所有的基本数据类型必须使用包装类!

3.3 类型推导(Type Inference)

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写:

MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 String

4. 裸类型(Raw Type)

4.1 说明

裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型

MyArray list = new MyArray();

注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制

小结:

  1. 泛型是将数据类型参数化,进行传递
  2. 使用<T> 表示当前类是一个泛型类。
  3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换

5. 泛型如何编译的

5.1 擦除机制

以下代码是前文代码放到一起, 我们在IDEA中Build它, 然后查看jclasslib(需要安装 jclasslib bytecode viewer插件).
class MyArray<T> {
    //public T[] obj2 = new T[10]; // 把所有的Object改成T, 但是此时这里会报错
    public T[] obj = (T[]) new Object[10];

    public void setVal(int pos, T val) {
        obj[pos] = val;
    }

    public T getPos(int pos) {
        return obj[pos];
    }
}

public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<Integer>();
        myArray.setVal(0, 10);
        myArray.setVal(1, 2);
        myArray.setVal(2, 6);
        int a = myArray.getPos(1);

        MyArray<String> myArray2 = new MyArray<String>();
        myArray2.setVal(0, "hello");
        myArray2.setVal(1, "hello2");
        String str = myArray2.getPos(0);
    }
}

先来看setVal(), 描述符中, V代表的是返回值, I代表int, 后面的就是T的类型, 为Object.

可以看到T是Object, 而并不是其他的类型.

getPos()也是Object.

所以可以知道, 擦除机制就是在编译的时候, 把所有的T擦除成了Object.

Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。

有关泛型擦除机制的文章介绍:Java泛型擦除机制之答疑解惑 - 知乎


5.2 为什么不能实例化泛型类型数组

为什么T[] obj2 = new T[10];是不对的, 编译器会报错, 而T[] obj = (T[]) new Object[10];不报错? 那么其实后面这个代码也是不对的.

假设T[] obj2 = new T[10];编译器能够通过, 那么就意味着可以提供一个方法, 返回T[].

public T[] getobj2() {
    return obj;
}

T[] obj = (T[]) new Object[10];是不对的, 只不过这里编译器不会报错, 但是我们可以让它报错.

public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<Integer>();
        Integer[] tmp = myArray.getObj2();
    }
}

T[]擦除成了Object, 那么它要给到Integer[], 显然是不能的, 所以会报错.


数组和泛型之间的一个重要区别是它们如何强制执行类型检查。具体来说,数组在运行时存储和检查类型信息。然而,泛型在编译时检查类型错误。

通俗讲就是:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候,直接转给Integer类型的数组,编译器认为是不安全的。

正确的代码(了解):

class MyArray<T> {
    public T[] array;

    public MyArray() {
    }

    /**
    * 通过反射创建,指定类型的数组
    * @param clazz
    * @param capacity
    */
    public MyArray(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    public T getPos(int pos) {
        return this.array[pos];
    }

    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }

    public T[] getArray() {
        return array;
    }
}

public class Test {
    public static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>(Integer.class,10);
        Integer[] integers = myArray1.getArray();
    }
}

6. 泛型的上界

6.1 上界语法产生的背景

写一个泛型类,类中有一个方法 求一个数组当中的最大值?

我们先来看:

// 泛型类 ALg<T>
class Alg<T> {
    // 方法: 找到最大值, 传进来的数组为泛型数组T[]
    public T findMax(T[] array) {
        T max = array[0];   // 假设最大值是这个数组的 0下标
        for (int i = 0; i < array.length; i++) {
            if (max < array[i]) {    // 报错!!!
                max = array[i]; // max 更新为 array[i]
            }
        }
        return max; // 最后的最大值就在max中存
    }
}

在IDEA中写下以上代码之后, 会看到if (max < array[i]) {报错.

T一定是一个引用类型, 它不会是基本类型.

如果是基本类型的数组:

    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4};
        // 通过下标找到元素, 从而使用 大于小于 进行比较
        System.out.println(array[0] > array[1]);    
    }

但是, 对于findMax()来说, 传进来的一定是引用类型,这里不能通过大于小于进行比较.于是我们希望可以使用重写compareTo()方法的办法, 可是:

class Alg<T> {
    public T findMax(T[] array) {
        T max = array[0]; 
        for (int i = 0; i < array.length; i++) {
            //if (max < array[i]) {
            // T此时传的是Integer, 但是会发现没有compareTo
            if (max./* 没有compareTo? */ (array[i]) < 0) {
                max = array[i]; 
            }
        }
        return max; // 最后的最大值就在max中存
    }
}

// main代码同上, 省略...

由前文的擦除机制我们可以知道, T在编译的时候擦除成了Object, 而Object :

那么它就不具备compareTo这个功能. 所以在这里我们就需要来做一个校验, T如果要去比较, 那么就必须是实现了Comparable接口, 于是就产生了这样一个语法Alg<T extends Comparable<T>>.

修改代码:

class Alg<T extends Comparable<T>> {
    // 方法: 找到最大值, 传进来的数组为泛型数组T[]
    public T findMax(T[] array) {
        T max = array[0];   // 假设最大值是这个数组的 0下标
        for (int i = 0; i < array.length; i++) {
            //if (max < array[i]) {
            // T此时传的是Integer, 但是会发现没有compareTo
            if (max.compareTo(array[i]) < 0) {
                max = array[i]; // max 更新为 array[i]
            }
        }
        return max; // 最后的最大值就在max中存
    }
}

然后max就可以.compareTo了. 此时代码就不报错了. 即, 要比较大小, max要调用compareTo, 就要【使用泛型的上界这个语法】.

也就是说class Alg<T extends Comparable<T>> {含义是, T类型实现了Comparable接口, 这个接口也指定了类型是为T.

于是:

public class Test { 
   public static void main(String[] args) {
        Alg<Integer> alg = new Alg<>();
        Integer[] array = {1, 2, 3, 4};
        Integer ret = alg.findMax(array);
        System.out.println(ret);    // 4
    }
}


接下来我们再举一个反例:

class Person {}

public class Test {
    public static void main(String[] args) {
        Alg<Person> alg = new Alg<>();
    }
}

会发现, <Person>报错了.

因为在检查的时候发现, Alg有<T extends Comparable<T>>, 而Person没有实现Comparable接口.

所以要让它不报错:

class Person implements Comparable<Person> {

    public int age;

    public Person(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
}

public class Test {
    public static void main(String[] args) {
        Alg<Person> alg = new Alg<>();
        // 定义一个Person[]
        Person[] people = {new Person(10), new Person(15)};
        Person person = alg.findMax(people);    // 调用findMax的时候就会调用compareTo
        System.out.println(person);
    }
}


在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

6.2 语法

class 泛型类名称<类型形参 extends 类型边界> {
    // ...
}

6.3 示例

public class MyArray<E extends Number> {
    // ...
}

只接受 Number 的子类型作为 E 的类型实参

MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型

了解: 没有指定类型边界 E,可以视为 E extends Object

6.4 复杂示例

public class MyArray<E extends Comparable<E>> {
    // ...
}

E必须是实现了Comparable接口的

7. 泛型方法

对 找数组最大值 的优化:

我们发现, 需要调用findMax(), 必须要new Alg对象. 那么能否不new这个对象, 也就是说, 如果把这个方法变成static, 那么是否就可以不new这个对象?

class Alg2<T extends Comparable<T>> {
    public static T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            //if(max < array[i]) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}

会看到 , 整个代码的T都报错了.

为什么加了static报错? 或者说, 没加static之前, T是如何确定的?

没加static之前, new Alg传了实参Integer, 加了static之后, 意思就是, 要通过Alg.findMax()直接调用, 显然问题在于这里没有机会传实参, 所以Alg2上加<T extends Comparable<T>>或者不加都是没用的, 那, 不加<T extends Comparable<T>>, 不就更没用了吗, 所以, 这里从语法上要这么改:

class Alg2 {
    public static <T extends Comparable<T>> T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; i++) {
            //if(max < array[i]) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}

就不报错了. 所以当方法是静态的时候, 它不依赖于对象, 这个泛型的传参就要在static后加上<T extends Comparable<T>>, 所以此时这个方法就成为了一个泛型方法.


public class Test {
    public static void main(String[] args) {
        Integer[] array = {1, 2, 3, 4};
        // 给这个泛型方法传递一个 类型的实参
        Integer ret = Alg2.<Integer>findMax(array); // <Integer>实际上是省略的, 这里写了出来
        System.out.println(ret);
    }
}

泛型方法的语法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

8. 通配符

? 用于在泛型的使用,即为通配符

8.1 通配符解决什么问题

通配符是用来解决泛型无法协变的问题的,协变指的就是如果StudentPerson 的子类,那么List<Student> 也应该是List<Person> 的子类。但是泛型是不支持这样的父子类关系的。

泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围.
class Message<T> {
    private T message;  //消息

    public T getMessage() {
        return message;
    }

    public void setMessage(T message) {
        this.message = message;
    }
}

public class Test {
    public static void main(String[] args) {
        Message<String> message = new Message();
        message.setMessage("haha");
        fun(message);
    }

    public static void fun(Message<String> temp) {
        System.out.println(temp.getMessage());
    }
}

以上程序会带来新的问题,如果现在泛型的类型设置的不是String,而是Integer.

public class Test {
    public static void main(String[] args) {
        Message<Integer> message = new Message();
        message.setMessage(99);
        fun(message); // 出现错误,只能接收String
    }

    public static void fun(Message<String> temp) {
        System.out.println(temp.getMessage());
    }
}

我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符?来处理.

范例:使用通配符

此时使用通配符" ?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改


在"?"的基础上又产生了两个子通配符:

? extends 类:设置泛型上限
? super 类:设置泛型下限

8.2 通配符的上界

语法:

<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类

如图描述了一个继承关系, 当我们写出Plate<? extends Fruit>这样的代码的时候, 能在Plate中放的就只能有Fruit或者它的子类.

class Food {

}

class Fruit extends Food {

}

class Apple extends Fruit {

}

class Banana extends Fruit {

}

class Plate<T> { // 设置泛型 shift+f6
    private T plate;

    public T getPlate() {
        return plate;
    }

    public void setPlate(T plate) {
        this.plate = plate;
    }
}

public class Test {
    public static void main(String[] args) {
        Plate<Apple> plate1 = new Plate<>();
        plate1.setPlate(new Apple());
        fun1(plate1);

        Plate<Banana> plate2 = new Plate<>();
        plate2.setPlate(new Banana());
        fun1(plate2);
    } 
    
    // 此时无法在fun函数中对temp进行添加元素,
    // 因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定。
    // 所以添加会报错!但是可以获取元素。
    public static void fun(Plate<? extends Fruit> temp) {
        /*
        不能放东西
        temp.setPlate(new Apple());
        temp.setPlate(new Banana());
        temp.setPlate(new Fruit());
        */
        Fruit fruit = temp.getPlate();
    }
}

通配符的上界,不能进行写入数据,只能进行读取数据。

8.3 通配符下界

语法:

<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型

class Food {

}

class Fruit extends Food {

}

class Apple extends Fruit {

}

class Banana extends Fruit {

}

class Plate<T> { // 设置泛型 shift+f6
    private T plate;

    public T getPlate() {
        return plate;
    }

    public void setPlate(T plate) {
        this.plate = plate;
    }
}


public class Test {

    public static void main(String[] args) {
        Plate<Fruit> plate1 = new Plate<>();
        plate1.setPlate(new Fruit());
        fun(plate1);

        Plate<Food> plate2 = new Plate<>();
        plate2.setPlate(new Food());
        fun(plate2);
    }

    public static void fun(Plate<? super Fruit> temp) {
        temp.setPlate(new Apple());
        temp.setPlate(new Banana());
        temp.setPlate(new Fruit());
        //不能存放Fruit的父类

        //Fruit fruit = temp.getPlate();  不能取数据 因为无法知道取出的数据类型是什么?
    }
}

通配符的下界,不能进行读取数据,只能写入数据。

9. 包装类

在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。

9.1 基本数据类型和对应的包装类

基本数据类型

包装类

byte

Byte

short

Short

int

Integer

long

Long

float

Float

double

Double

char

Character

boolean

Boolean

除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。

9.2 装箱和拆箱

int i = 10;

// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer ii = Integer.valueOf(i);
Integer ij = new Integer(i);

// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
int j = ii.intValue();

9.3 自动装箱和自动拆箱

可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制。

int i = 10;

Integer ii = i; // 自动装箱
Integer ij = (Integer)i; // 自动装箱

int j = ii; // 自动拆箱
int k = (int)ii; // 自动拆箱

【面试题】

下列代码输出什么,为什么?

public static void main(String[] args) {
    Integer a = 127;
    Integer b = 127;

    Integer c = 128;
    Integer d = 128;

    System.out.println(a == b);
    System.out.println(c == d);
}

这是因为在Java中,对于 Integer`类型的对象,有一个内置的缓存范围为 -128 到 127。当你创建一个在这个范围内的`Integer 对象时,JVM 会尝试重用现有的对象而不是创建一个新的对象。这被称为整数缓存机制。

所以,当 a 和 b 的值都在 -128 到 127 的范围内时,它们会引用相同的对象,因此 a == b 会返回 true。而当 c 和 d 的值为 128 时,它们超出了缓存范围,因此会创建两个不同的对象,所以 c == d 返回 false。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/197249.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Node.js入门指南(完结)

目录 接口 介绍 RESTful json-server 接口测试工具 会话控制 介绍 cookie session token 上一篇文章我们介绍了MongoDB&#xff0c;这一篇文章是Node.js入门指南的最后一篇啦&#xff01;主要介绍接口以及会话控制。 接口 介绍 接口是前后端通信的桥梁 &#xff0…

elk日志分析系统:

elk日志分析系统: elk是一套完整的日志集中处理方案&#xff0c;由三个开源的软件简称组成&#xff1b; E:Easticsearch 简称ES是一个开源的&#xff0c;分布式的存储检索引擎&#xff0c;&#xff08;索引型的非关系数据库&#xff09;存储日志 由java代码开发的&#xff0…

麒麟V10桌面搭建FTP服务

1.1介绍 FTP&#xff1a;File transfer protocol &#xff08;文件传输协议&#xff09;是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分&#xff0c;其一为FTP服务器&#xff0c;其二为FTP客户端。其中FTP服务器用来存储文件&#xff0c;用户可以使用FTP客户端通过FT…

知识变现的未来:解析知识付费系统的核心

随着数字时代的发展&#xff0c;知识付费系统作为一种新兴的学习和知识分享模式&#xff0c;正逐渐引领着知识变现的未来。本文将深入解析知识付费系统的核心技术&#xff0c;揭示其在知识经济时代的重要性和潜力。 1. 知识付费系统的基本架构 知识付费系统的核心在于其灵活…

CorelDRAW Graphics Suite2023破解版含2024最新注册机下载

CorelDRAW Graphics Suite2023是Corel公司的平面设计软件&#xff1b;该软件是Corel出品的矢量图形制作工具软件&#xff0c;这个图形工具给设计师提供了矢量动画、页面设计、网站制作、位图编辑和网页动画等多种功能。在日常科研绘图中&#xff0c;若较为轻量&#xff0c;通常…

【Linux进阶之路】进程间通信

文章目录 一、原理二、方式1.管道1.1匿名管道1.1.1通信原理1.1.2接口使用 1.2命名管道 2.共享内存2.1原理2.2接口使用 3.消息队列原理 4.信号量引入原理 总结 一、原理 进程间的通信是什么&#xff1f;解释&#xff1a; 简单理解就是&#xff0c;不同进程之间进行数据的输入输出…

使用Tensorboard可视化 遇到无法访问此网站

问题&#xff1a; 使用Tensorboard可视化 遇到无法访问此网站 解决方法&#xff1a;后面加上服务器ip[参考] tensorboard --logdir目标目录 --hostxxx.xxx.xxx.xx

[学习记录]Node event loop 总结流程图

文章目录 文章来源根据内容输出的流程图待处理遗留的问题参考 文章来源 详解JavaScript中的Event Loop&#xff08;事件循环&#xff09;机制 根据内容输出的流程图 待处理 这里从polling阶段开始 好像有些问题 遗留的问题 为什么“在I/O事件的回调中&#xff0c;setImmediate…

Docker | 自定义Docker镜像

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;Docker系列 ✨特色专栏&#xff1a; My…

AB|如何正确从罗克韦尔官网下载资料?

哈喽呀&#xff0c;大家好&#xff0c;我是雷工&#xff01; 作为工控行业的从业者&#xff0c;可能要和各个厂家的中控系统、PLC、触摸屏、变频器、等软硬件产品打交道。 虽然从业十余年&#xff0c;但也不可能接触使用过所有的工控产品。还有海量的产品是没有接触过的。 但很…

sql注入靶场

第一关&#xff1a; 输入&#xff1a;http://127.0.0.1/sqli-labs-master/Less-1/?id1 http://127.0.0.1/sqli-labs-master/Less-1/?id1%27 http://127.0.0.1/sqli-labs-master/Less-1/?id1%27-- 使用--来闭合单引号&#xff0c;证明此处存在字符型的SQL注入。 使用order …

5.27每日一题(判断函数在那个区间上有界:充分条件不是必要条件)

若f(x)在(a , b)上连续&#xff0c;且f(a0)&#xff0c;f&#xff08;b-0&#xff09;存在&#xff08;及函数的左右极限存在&#xff09;>f(x)在(a,b)上有界

springboot中4级配置文件优先级

springboot中4级配置文件优先级

【AICFD案例教程】PCB多变量AI预测分析

AICFD是由天洑软件自主研发的通用智能热流体仿真软件&#xff0c;用于高效解决能源动力、船舶海洋、电子设备和车辆运载等领域复杂的流动和传热问题。软件涵盖了从建模、仿真到结果处理完整仿真分析流程&#xff0c;帮助工业企业建立设计、仿真和优化相结合的一体化流程&#x…

【视觉SLAM十四讲学习笔记】第三讲——四元数

专栏系列文章如下&#xff1a; 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——Eigen库 【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧…

【小白进阶】Linux 调试大法——gdb

初衷 gdb调试是每一个后端开发工程师所必备的技能&#xff0c;我们工作总是会用gdb协助我们去分析和调试问题。但是大部分同学的技能仅停留在最基础的查看问题。即gdb program -->r --> 问题复现 --> bt 查看源码中的哪一行出现了错误。再稍微熟练点的&#xff0c;可能…

Node.js 事件循环:定时任务、延迟任务和 I/O 事件的艺术

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

python多线程和多进程

1.多线程 线程是程序执行的最小单位&#xff0c;一个进程至少有一个线程。 提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。 进程之间不能共享内存&#xff0c;但线程之间共享内存非常容易。 Python 常用的多线程库有threading 和…

微信小程序Vue+nodejs教室自习室座位预约系统68u2m

本文从管理员、用户的功能要求出发&#xff0c;教室预约系统小程序中的功能模块主要是实现管理端&#xff1b;首页、个人中心、教室信息管理、教室设备管理、用户管理、教室预约管理、管理员管理、系统管理&#xff0c;微信端&#xff1b;首页、教室信息、教室设备、教室预约、…