Java 何时会触发一个类的初始化

Java 何时会触发一个类的初始化?

  • 使用new关键字创建对象
  • 访问类的静态成员变量 或 对类的静态成员变量进行赋值
  • 调用类的静态方法
  • 反射调用类时,如 Class.forName()
  • 初始化子类时,会先初始化其父类(如果父类还没有进行过初始化的话)
  • 遇到启动类时,如果一个类被标记为启动类(即包含main方法),虚拟机会先初始化这个主类。
  • 实现带有默认方法的接口的类被初始化时(拥有被default关键字修饰的接口方法的类)
  • 使用 JDK7 新加入的动态语言支持时 MethodHandle

虚拟机在何时加载类

关于在什么情况下需要开始类加载的第一个阶段,《Java虚拟机规范》中并没有进行强制约束,留给虚拟机自由发挥。但对于初始化阶段,虚拟机规范则严格规定:当且仅当出现以下六种情况时,必须立即对类进行初始化,而加载、验证、准备自然需要在此之前进行。虚拟机规范中对这六种场景中的行为称为对一个类型进行主动引用。除此之外,所有引用类型的方式都不会触发初始化,称为被动引用

1. 遇到指定指令时

在程序执行过程中,遇到 new、getstatic、putstatic、invokestatic 这4条字节码执行时,如果类型没有初始化,则需要先触发其初始化阶段。

new

这没什么好说的,使用new关键字创建对象,肯定会触发该类的初始化。

getstatic 与 putstatic

当访问某个类或接口的静态变量,或对该静态变量进行赋值时,会触发类的初始化。首先来看第一个例子:

// 示例1
public class Demo {public static void main(String[] args) {System.out.println(Bird.a);}
}class Bird {static int a = 2;// 在类初始化过程中不仅会执行构造方法,还会执行类的静态代码块// 如果静态代码块里的语句被执行,说明类已开始初始化static {System.out.println("bird init");}
}

执行后会输出:

bird init
2

同样地,如果直接给Bird.a进行赋值,也会触发Bird类的初始化:

public class Demo {public static void main(String[] args) {Bird.a = 2;}
}class Bird {static int a;static {System.out.println("bird init");}
}

执行后会输出:

bird init

接着再看下面的例子:

public class Demo {public static void main(String[] args) {Bird.a = 2;}
}class Bird {// 与前面的例子不同的是,这里使用 final 修饰static final int a = 2;static {System.out.println("bird init");}
}

执行后不会有输出。

本例中,a不再是一个静态变量,而变成了一个常量,运行代码后发现,并没有触发Bird类的初始化流程。常量在编译阶段会存入到调用这个常量的方法所在类的常量池中本质上,调用类并没有直接引用定义常量的类,因此并不会触发定义常量的类的初始化。即这里已经将常量a=2存入到Demo类的常量池中,这之后,Demo类与Bird类已经没有任何关系,甚至可以直接把Bird类生成的class文件删除,Demo仍然可以正常运行。使用javap命令反编译一下字节码:

// 前面已省略无关部分public static void main(java.lang.String[]);Code:0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: iconst_24: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V7: return
}

从反编译后的代码中可以看到:Bird.a已经变成了助记符iconst_2(将int类型2推送至栈顶),和Bird类已经没有任何联系,这也从侧面证明,只有访问类的静态变量才会触发该类的初始化流程,而不是其他类型的变量

关于Java助记符,如果将上面一个示例中的常量修改为不同的值,会生成不同的助记符,比如:

// bipush  20
static int a = 20; 
// 3: sipush        130
static int a = 130
// 3: ldc #4   // int 327670
static int a = 327670;

其中:
iconst_n:将int类型数字n推送至栈顶,n取值0~5
lconst_n:将long类型数字n推送至栈顶,n取值0,1,类似的还有fconst_ndconst_n
bipush:将单字节的常量值(-128~127) 推送至栈顶
sipush:将一个短整类型常量值(-32768~32767) 推送至栈顶
ldc:将intfloatString类型常量值从常量池中推送至栈顶

再看下一个实例:

public class Demo {public static void main(String[] args) {System.out.println(Bird.a);}
}class Bird {static final String a = UUID.randomUUID().toString();static {System.out.println("bird init");}
}

执行后会输出:

bird init
d01308ed-8b35-484c-b440-04ce3ecb7c0e

在本例中,常量a的值在编译时不能确定,需要进行方法调用,这种情况下,编译后会产生getstatic指令,同样会触发类的初始化,所以才会输出bird init。看下反编译字节码后的代码:

// 已省略部分无关代码
public static void main(java.lang.String[]);Code:0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: getstatic     #3                  // Field com/hicsc/classloader/Bird.a:Ljava/lang/String;6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V9: return

invokestatic

调用类的静态方法时,也会触发该类的初始化。比如:

public class Demo {public static void main(String[] args) {Bird.fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}

执行后会输出:

bird init
bird fly

通过本例可以证明,调用类的静态方法,确实会触发类的初始化。

2. 反射调用时

使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化。来看下面的例子:

ublic class Demo {public static void main(String[] args) throws Exception {ClassLoader loader = ClassLoader.getSystemClassLoader();Class clazz = loader.loadClass("com.hicsc.classloader.Bird");System.out.println(clazz);System.out.println("——————");clazz = Class.forName("com.hicsc.classloader.Bird");System.out.println(clazz);}
}class Bird {static {System.out.println("bird init");}
}

执行后输出结果:

class com.hicsc.classloader.Bird
------------
bird init
class com.hicsc.classloader.Bird

本例中,调用ClassLoader方法load一个类,并不会触发该类的初始化,而使用反射包中的forName方法,则触发了类的初始化。

3. 初始化子类时

当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。比如:

public class Demo {public static void main(String[] args) throws Exception {Pigeon.fly();}
}class Bird {static {System.out.println("bird init");}
}class Pigeon extends Bird {static {System.out.println("pigeon init");}static void fly() {System.out.println("pigeon fly");}
}

执行后输出:

bird init
pigeon init
pigeon fly

本例中,在main方法调用Pigeon类的静态方法,最先初始化的是父类Bird,然后才是子类Pigeon。因此,在类初始化时,如果发现其父类并未初始化,则会先触发父类的初始化。

对子类调用父类中存在的静态方法,只会触发父类初始化而不会触发子类的初始化。

看下面的例子,可以先猜猜运行结果:

public class Demo {public static void main(String[] args) {Pigeon.fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}class Pigeon extends Bird {static {System.out.println("pigeon init");}
}

输出:

bird init
bird fly

本例中,由于fly方法是定义在父类中,那么方法的拥有者就是父类,因而,使用Pigeno.fly()并不是表示对子类的主动引用,而是表示对父类的主动引用,所以,只会触发父类的初始化。

4. 遇到启动类时

当虚拟机启动时,如果一个类被标记为启动类(即:包含main方法),虚拟机会先初始化这个主类。比如:

public class Demo {static {System.out.println("main init");}public static void main(String[] args) throws Exception {Bird.fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}

执行后输出:

main init
bird init
bird fly

5. 实现带有默认方法的接口的类被初始化时

当一个接口中定义了 JDK8 新加入的默认方法(被default关键字修饰的接口方法) 时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化

由于接口中没有static{}代码块,怎么判断一个接口是否初始化?来看下面这个例子:

public class Demo {public static void main(String[] args) throws Exception {Pigeon pigeon = new Pigeon();}
}interface Bird {// 如果接口被初始化,那么这句代码一定会执行// 那么Intf类的静态代码块一定会被执行public static Intf intf = new Intf();default void fly() {System.out.println("bird fly");}
}class Pigeon implements Bird {static {System.out.println("pigeon init");}
}class Intf {{System.out.println("interface init");}
}

执行后输出:

interface init
pigeon init

可知,接口确实已被初始化,如果把接口中的default方法去掉,那么不会输出interface init,即接口未被初始化。

6. 使用JDK7新加入的动态语言支持时

当使用JDK7新加入的动态类型语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。

简单点来说,当初次调用MethodHandle实例时,如果其指向的方法所在类没有进行过初始化,则需要先触发其初始化

什么是动态类型语言:

  • 动态类型语言的关键特性是它的类型检查的主体过程是在运行期进行的,常见的语言比如:JavaScript、PHP、Python等,相对地,在编译期进行类型检查过程的语言,就是静态类型语言,比如 Java 和 C# 等。
  • 简单来说,对于动态类型语言,变量是没有类型的,变量的值才具有类型,在编译时,编译器最多只能确定方法的名称、参数、返回值这些,而不会去确认方法返回的具体类型以及参数类型。
  • 而Java等静态类型语言则不同,你定义了一个整型的变量x,那么x的值也只能是整型,而不能是其他的,编译器在编译过程中就会坚持定义变量的类型与值的类型是否一致,不一致编译就不能通过。因此,「变量无类型而变量值才有类型」是动态类型语言的一个核心特征。

关于MethodHandle与反射的区别,可以参考周志明著「深入理解Java虚拟机」第8.4.3小节,这里引用部分内容,方便理解。

  1. Reflection 和 MethodHandle 机制本质上都是在模拟方法调用,但是 Reflection 是在模拟 Java 代码层次的方法调用,而 MethodHandle 是在模拟字节码层次的方法调用。
  2. 反射中的 Method 对象包含了方法签名、描述符以及方法属性列表、执行权限等各种信息,而 MethodHandle 仅包含执行该方法的相关信息,通俗来讲:Reflection 是重量级,而 MethodHandle 是轻量级。

总的来说,反射是为 Java 语言服务的,而 MethodHandle 则可为所有 Java 虚拟机上的语言提供服务。

来看一个简单的示例:

public class Demo {public static void main(String[] args) throws Exception {new Pigeon().fly();}
}class Bird {static {System.out.println("bird init");}static void fly() {System.out.println("bird fly");}
}class Pigeon {void fly() {try {MethodHandles.Lookup lookup = MethodHandles.lookup();// MethodType.methodType 方法的第一个参数是返回值// 然后按照目标方法接收的参数的顺序填写参数类型// Bird.fly() 方法返回值是空, 没有参数MethodType type = MethodType.methodType(void.class);MethodHandle handle = lookup.findStatic(Bird.class, "fly", type);handle.invoke();} catch (Throwable a) {a.printStackTrace();}}
}

Pigeon类中,使用MethodHandle来调用Bird类中的静态方法fly,按照前面所述,初次调用MethodHandle实例时,如果其指向的方法所在类没有进行过初始化,则需要先触发其初始化。所以,这里一定会执行Bird类中的静态代码块。而最终的运行结果也与我们预计的一致:

bird init
bird fly

虚拟机如何加载类 - 类的加载过程

类的加载全过程包括:加载、验证、准备、解析初始化 5 个阶段,是一个非常复杂的过程。

在这里插入图片描述

在这里插入图片描述

加载 Loading

Loading 阶段主要是找到类的class文件,并把文件中的二进制字节流读取到内存,然后在内存中创建一个java.lang.Class对象。

加载完成后,就进入连接阶段,但需要注意的是,加载阶段与连接阶段的部分动作(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的一部分,这两个阶段的开始时间仍然保持着固定的先后顺序,也就是只有加载阶段开始后,才有可能进入连接阶段。

验证 Verification

验证是连接阶段的首个步骤,其目的是确保被加载的类的正确性,即要确保加载的字节流信息要符合《Java虚拟机规范》的全部约束要求,确保这些信息被当做代码运行后不会危害虚拟机自身的安全。

其实,Java 代码在编译过程中,已经做了很多安全检查工作,比如,不能将一个对象转型为它未实现的类型、不能使用未初始化的变量(赋值除外)、不能跳转到不存在的代码行等等。但 JVM 仍要对这些操作作验证,这是因为 Class 文件并不一定是由 Java 源码编译而来,甚至你都可以通过键盘自己敲出来。如果 JVM 不作校验的话,很可能就会因为加载了错误或有恶意的字节流而导致整个系统受到攻击或崩溃。所以,验证字节码也是 JVM 保护自身的一项必要措施。

整个验证阶段包含对文件格式、元数据、字节码、符号引用等信息的验证

准备 Preparation

这一阶段主要是为类的静态变量分配内存,并将其初始化为默认值。这里有两点需要注意:

  • 仅为类的静态变量分配内存并初始化,并不包含实例变量
  • 初始化为默认值,比如int0,引用类型初始化为null

需要注意的是,准备阶段的主要目的并不是为了初始化,而是为了为静态变量分配内存,然后再填充一个初始值而已。就比如:

// 在准备阶段是把静态类型初始化为 0,即默认值
// 在初始化阶段才会把 a 的值赋为 1
public static int a = 1;

来看一个实例加深印象,可以先考虑一下运行结果。

public class StaticVariableLoadOrder {public static void main(String[] args) {Singleton singleton = Singleton.getInstance();System.out.println("counter1:" + Singleton.counter1);System.out.println("counter2:" + Singleton.counter2);}
}class Singleton {public static Singleton instance = new Singleton();private Singleton() {counter1++;counter2++;System.out.println("构造方法里:counter1:" + counter1 + ", counter2:" + counter2);}public static int counter1;public static int counter2 = 0;public static Singleton getInstance() {return instance;}
}

其运行结果是:

构造方法里:counter1:1, counter2:1
counter1:1
counter2:0

在准备阶段counter1counter2都被初始化为默认值0,因此,在构造方法中自增后,它们的值都变为1,然后继续执行初始化,仅为counter2赋值为0counter1的值不变。

如果你理解了这段代码,再看下面这个例子,想想会输出什么?

// main 方法所在类的代码不变
// 修改了 counter1 的位置,并为其初始化为 1
class Singleton {public static int counter1 = 1;public static Singleton instance = new Singleton();private Singleton() {counter1++;counter2++;System.out.println("构造方法里:counter1:" + counter1 + ", counter2:" + counter2);}public static int counter2 = 0;public static Singleton getInstance() {return instance;}
}

运行后输出:

构造方法里:counter1:2, counter2:1
counter1:2
counter2:0

counter2并没有任何变化,为什么counter1的值会变成2?其实是因为类在初始化的时候,是按照代码的顺序来的,就比如上面的示例中,为counter1赋值以及执行构造方法都是在初始化阶段执行的,但谁先谁后呢?按照顺序来,因此,在执行构造方法时,counter1已经被赋值为1,执行自增后,自然就变为2了。

解析 Resolution

解析阶段是将常量池类的符号引用替换为直接引用的过程。在编译时,Java 类并不知道所引用的类的实际地址,只能使用符号引用来代替。符号引用存储在class文件的常量池中,比如类和接口的全限定名、类引用、方法引用以及成员变量引用等,如果要使用这些类和方法,就需要把它们转化为 JVM 可以直接获取的内存地址或指针,即直接引用。

因此,解析的动作主要是针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符这 7 类符号引用进行的。

初始化 Initialization

准备阶段我们只是给静态变量设置了类似0的初值,在这一阶段,则会根据我们的代码逻辑去初始化类变量和其他资源。

更直观的说初始化过程就是执行类构造器<clinit>方法的过程

类的初始化是类加载过程的最后一个步骤,直到这一个步骤,JVM 才真正开始执行类中编写的 Java 代码。初始化完也就差不多是类加载的全过程了,什么时候需要初始化也就是我们最前面讲到的几种情况。

类初始化是懒惰的,不会导致类初始化的情况,也就是前面讲到的被动引用类型,再讲全一点:

  • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
  • 访问类对象.class不会触发初始化
  • 创建该类的数组不会触发初始化
  • 执行类加载器的 loadClass 方法不会触发初始化
  • Class.forName(反射)的参数2为false时(为true才会初始化)

在编译生成class文件时,编译器会产生两个方法加于class文件中,一个是类的初始化方法clinit, 另一个是实例的初始化方法init

1. 类初始化方法:<clinit>()

  • Java 编译器在编译过程中,会自动收集类中所有静态变量赋值语句静态代码块中的语句,将其合并到类构造器<clinit>()方法收集的顺序由源代码文件中出现的顺序决定。类初始化方法一般在类初始化阶段执行。
  • 如果两个类存在父子关系,那么在执行子类的<clinit>()方法之前,会确保父类的方法已执行完毕,因此,父类的静态代码块会优先于子类的静态代码块

例子:

public class ClassDemo {static {i = 20;}static int i = 10;static {i = 30;}// init 方法收集后里面的代码就是这个,当然你是看不到该方法的init() {i = 20;i = 10;i = 30;}
}
  • <clinit>()方法不需要显示调用,类解析完了会立即调用,且父类的<clinit>()永远比子类的先执行,因此在jvm中第一个执行的肯定是Object中的<clinit>()方法。
  • <clinit>()方法不是必须的,如果没有静态代码块和变量赋值就没有
  • 接口也有变量复制操作,因此也会生成<clinit>(),但是只有当父接口中定义的变量被使用时才会初始化。

这里有一点需要特别强调,JVM 会保证一个类的<clinit>()方法在多线程环境中被正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的<clinit>()方法,其它线程都需要等待,直到<clinit>()方法执行完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,那么可能会造成多个线程阻塞,在实际应用中这种阻塞往往是很隐蔽的。因此,在实际开发过程中,我们都会强调,不要在类的构造方法中加入过多的业务逻辑,甚至是一些非常耗时的操作。

另外,静态代码块中只能访问定义它之前的变量,定义在它之后的变量可以赋值但不能访问:

class Class{static {c = 2; // 赋值操作可以正常编译通过System.out.println(c);//编译器提示 Illegal forward reference,非法向前引用}static int c = 1;
}

2. 对象初始化方法:init()

  • init()是实例对象自动生成的方法。编译器会按照从上至下的顺序,收集 「类成员变量」赋值语句、普通代码块,最后收集构造函数的代码,最终组成对象初始化方法。对象初始化方法一般在实例化类对象的时候执行。

例子:

public class ClassDemo {int a = 1;{a = 2;System.out.println(2);}{b = "b2";System.out.println("b2");}String b = "b1";public ClassDemo(int a, String b) {System.out.println("构造器赋值前:"+this.a+" "+this.b);this.a = a;this.b = b;}public static void main(String[] args) {ClassDemo demo = new ClassDemo(3, "b3");System.out.println("构造结束后:"+demo.a+" "+demo.b);
//        2
//        b2
//        构造器赋值前:2 b1
//        构造结束后:3 b3}
}

上面的代码的init()方法实际为:

public init(int a, String b){super(); // 不要忘记在底层还会加上父类的构造方法this.a = 1;this.a = 2;System.out.println(2);this.b = "b2";System.out.println("b2");this.b = "b1";System.out.println("构造器赋值前:" + this.a + " " + this.b); // 构造方法在最后this.a = a;this.b = b;
}

类执行过程小结:

  1. 确定类变量的初始值。在类加载的准备阶段JVM 会为「类变量」初始化默认值,这时候类变量会有一个初始的零值。如果是被 final 修饰的类变量,则直接会被初始成用户想要的值。
  2. 初始化入口方法。当进入类加载的初始化阶段后,JVM 会寻找整个 main 方法入口,从而初始化 main 方法所在的整个类。当需要对一个类进行初始化时,会首先初始化类构造器,之后初始化对象构造器。
  3. 初始化类构造器。JVM 会按顺序收集「类变量」的赋值语句、静态代码块,将它们组成类构造器,最终由 JVM 执行。
  4. 初始化对象构造器。JVM 会按顺序收集「类成员变量」的赋值语句、普通代码块,最后收集构造方法,将它们组成对象构造器,最终由 JVM 执行。

如果在初始化 「类变量」时,类变量是一个其他类的对象引用,那么就先加载对应的类,然后实例化该类对象,再继续初始化其他类变量。


参考:

  • 深入理解JVM类加载机制
  • jvm深入理解类加载机制

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

找不到WMVCORE.dll怎么办?一键解决WMVCORE.dll缺失的详细方法分享

当打开软件时提示wmvcore.dll丢失&#xff0c;这可能是由于以下几个原因导致的&#xff1a; 系统文件损坏&#xff1a;wmvcore.dll是系统文件&#xff0c;可能会因为各种原因&#xff08;如病毒感染、系统错误、软件卸载等&#xff09;而损坏。 软件依赖问题&#xff1a;某些…

用 Python 自动创建 Markdown 表格

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Markdown表格是文档中整理和展示数据的重要方式之一。然而&#xff0c;手动编写大型表格可能会费时且容易出错。本文将介绍如何使用Python自动创建Markdown表格&#xff0c;通过示例代码详细展示各种场景下的创建…

Linux基础指令详解(1)

操作系统的概念 百度百科 操作系统&#xff08;英语&#xff1a;Operating System&#xff0c;缩写&#xff1a;OS&#xff09;是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序。根据运行的环境&#xff0c;操作系…

【Python网络爬虫入门教程1】成为“Spider Man”的第一课:HTML、Request库、Beautiful Soup库

Python 网络爬虫入门&#xff1a;Spider man的第一课 写在最前面背景知识介绍蛛丝发射器——Request库智能眼镜——Beautiful Soup库 第一课总结 写在最前面 有位粉丝希望学习网络爬虫的实战技巧&#xff0c;想尝试搭建自己的爬虫环境&#xff0c;从网上抓取数据。 前面有写一…

论文阅读——Deformable ConvNets v2

论文&#xff1a;https://arxiv.org/pdf/1811.11168.pdf 代码&#xff1a;https://github.com/chengdazhi/Deformable-Convolution-V2-PyTorch 1. 介绍 可变形卷积能够很好地学习到发生形变的物体&#xff0c;但是论文观察到当尽管比普通卷积网络能够更适应物体形变&#xff…

Numpy数组的去重 np.unique()(第15讲)

Numpy数组的去重 np.unique()(第15讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

Linux权限详解

Linux权限 文章目录 Linux权限一、root账号与普通账号二、Linux权限管理三、权限权值表示方法四、文件访问权限的设置方法五、粘滞位六、权限总结 前言&#xff1a; 我们在学习Linux的时候&#xff0c;我们知道在Linux下一切皆文件&#xff0c;而不同的文件对于不同的用户有不同…

第二十一章总结。。

计算机网络实现了堕胎计算机间的互联&#xff0c;使得它们彼此之间能够进行数据交流。网络应用程序就是再已连接的不同计算机上运行的程序&#xff0c;这些程序借助于网络协议&#xff0c;相互之间可以交换数据&#xff0c;编写网络应用程序前&#xff0c;首先必须明确网络协议…

掌握iText:轻松处理PDF文档-基础篇

关于iText iText是一个强大的PDF处理库&#xff0c;可以用于创建、读取和操作PDF文件。它支持PDF表单、加密和签署等操作&#xff0c;同时支持多种字体和编码。maven的中央仓库中的最新版本是5.X&#xff0c;且iText5不是完全免费的&#xff0c;但是基础能力是免费使用的&…

2023-12-10 LeetCode每日一题(爬楼梯)

2023-12-10每日一题 一、题目编号 70. 爬楼梯二、题目链接 点击跳转到题目位置 三、题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 示例 2&#xff1a; 提…

gin投票系统2

投票系统 数据库的建立 先分析需求&#xff0c;在sql中建立数据库&#xff0c;关于项目数据库如何建立可以在“goweb项目创建流程分析中看如何去建表” 成功后目前有四个表&#xff1a; vote&#xff0c;user&#xff0c;vote_opt,vote_opt_user 建立数据库&#xff0c;可以…

Flink基本转换算子map/filter/flatmap

map map是大家非常熟悉的大数据操作算子&#xff0c;主要用于将数据流中的数据进行转换&#xff0c;形成新的数据流。简单来说&#xff0c;就是一个“一一映射”&#xff0c;消费一个元素就产出一个元素。 我们只需要基于DataStream调用map()方法就可以进行转换处理。方法需要…

案例026:基于微信小程序的原创音乐系统的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

汽车网络安全--关于UN R155认证的思考

1.UN R155概述 2020年6月25日,联合国颁布了全球首个汽车网络安全强制性法规 -- UN 155,详细规定了关于评估网络安全措施的审核条款、制造商和供应商降低网络安全风险的方法以及实施风险评估的义务等。 法规适用于与信息安全相关的M类(4轮及以上载客汽车)、N类(四轮载货汽车)…

SpringBoot项目连接Graylog

直接用logback将控制台输出的日志发送到graylog上 1.导入logback依赖 <dependency> <groupId>de.siegmar</groupId> <artifactId>logback-gelf</artifactId> <version>1.1.0</version> </dependency> 2.创建logback-spring.x…

golang学习笔记——编写最简单的命令行工具

编写最简单的命令行工具 用户输入bufio 使用go语言编写最简单的命令行工具 mkdir hello-cli-demo cd hello-cli-demo # 查看环境变量 go envgo mod初始化 go mod init gitcode.com/m打开vscode&#xff0c;创建main.go package mainimport ("fmt""bufio&qu…

快速测试 3节点的redis sentinel集群宕机2个节点以后是否仍能正常使用

有同事问我&#xff0c;三个redis sentinel节点&#xff0c;宕机两个节点以后&#xff0c;是否还能够正常的通过redis sentinel正常访问redis的数据。我想了想&#xff0c;理论上是可以的&#xff0c;但是我没试过&#xff0c;今天有时间就测试了一下。搭建环境和测试代码的过程…

Java并发(十七)----变量的线程安全分析

1、成员变量和静态变量是否线程安全 如果它们没有共享&#xff0c;则线程安全 如果它们被共享了&#xff0c;根据它们的状态是否能够改变&#xff0c;又分两种情况 如果只有读操作&#xff0c;则线程安全 如果有读写操作&#xff0c;则这段代码是临界区&#xff0c;需要考虑线…

深入了解Python pydash库

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在数据处理和分析领域&#xff0c;Python一直是一种强大的编程语言。然而&#xff0c;在处理大规模数据集和执行复杂操作时&#xff0c;有时候需要更高效的工具。在本文中&#xff0c;我们将深入探讨pydash库&am…

语义分割 简介及数据集简介

参考文章 MS COCO数据集介绍以及pycocotools简单使用-CSDN博客