jvm之字节码

写在前面

java字节码由单字节的指令(也叫做操作码)组成,但一个 byte 最多能够存储 256 个指令,够用吗?截止到目前是够的,因为指令的个数是200多一点,指令分为如下四类:

1:栈操作指令(load store)
2:流程控制指令(goto )
3:对象操作指令(new invokestatic)
4:算数运算,类型转换指令(iadd i2d)

接下来我们来通过一些字节码分析的例子,来实际的了解下各种指令,以及其作用。在正式开始之前我们先来看下方法栈,栈帧,局部变量表等相关概念。

JVM会为每个线程分配一个独属于自己的线程栈,如下图:

在这里插入图片描述

接着当线程执行一个方法的时候,则会为该方法生成一个栈帧,并将栈帧压入线程栈,假定有如下代码:

void A() {
    B();
}
void B() {
    C();
}

则会分别生成A的栈帧,B的栈帧,C的栈帧,并压入线程栈,如下图:

在这里插入图片描述

栈帧中包含如下信息:

局部变量表:存储方法内部的局部变量
操作数栈:存储程序当前运行状态下需要用到的变量
Class引用:方法所属Class在常量池的引用

即如下:

在这里插入图片描述

这里局部变量表中的信息,其实就是我们在方法中定义的局部变量,比如如下代码:

package javadeveloper.mkmoney;

public class TestByte {
	public static void main(String[] args) {
		int a1 = 20;
		String a2 = "uuuu"; 
	}
}

则局部变量表信息可通过如下方式(注意编译时需要增加-g)查看:

bogon:temp xb$ javac -g -d . TestByte.java 
bogon:temp xb$ javap -c -verbose javadeveloper/mkmoney/TestByte.class 
...
  public static void main(java.lang.String[]);
      ...
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  args   [Ljava/lang/String;
            3       4     1    a1   I
            6       1     2    a2   Ljava/lang/String;
}

可以看到主函数main局部变量表中有3个变量,第一个是主函数的入参String数组,第二个和第三个使我们在代码中定义的局部变量a1和a2。

最后因为后续分析还需要用到常量池,我们也来看下,常量池保存的是我们在程序中定义的常量,以及在程序运行过程中需要用到的类权限定名称信息等,比如定义如下代码:

package javadeveloper.mkmoney;

public class TestByte {
	final static int c1 = 900;
	final static String sportName = "羽毛球";
}

查看常量池:

bogon:temp xb$ javap -c -verbose javadeveloper/mkmoney/TestByte.class | grep 'Constant pool' -A 30
Constant pool:
   #1 = Methodref          #3.#17         // java/lang/Object."<init>":()V
   #2 = Class              #18            // javadeveloper/mkmoney/TestByte
   #3 = Class              #19            // java/lang/Object
   #4 = Utf8               c1
   #5 = Utf8               I
   #6 = Utf8               ConstantValue
   #7 = Integer            900
   #8 = Utf8               sportName
   #9 = Utf8               Ljava/lang/String;
  #10 = String             #20            // 羽毛球
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               SourceFile
  #16 = Utf8               TestByte.java
  #17 = NameAndType        #11:#12        // "<init>":()V
  #18 = Utf8               javadeveloper/mkmoney/TestByte
  #19 = Utf8               java/lang/Object
  #20 = Utf8               羽毛球

我们只来分析和我们定义的两个常量c1sportName相关的部分:

常量c1分析:
    #4 = Utf8               c1  --》常量名称c1
    #7 = Integer            900  --》常量值是900
常量sportName分析:
    #8 = Utf8               sportName  --》常量名称sportName
    #10 = String             #20            // 羽毛球  -》String类型的值,在常量池20的位置
    #20 = Utf8               羽毛球  --》常量值是羽毛球

1:简单的java类字节码分析

假定我们有如下的java源码:

package javadeveloper.mkmoney;

public class TestByte {
	public static void main(String[] args) {
		TestByte testByte = new TestByte();
	}
}

首先我们来将源码通过javac编译为class字节码文件,如下:

bogon:temp xb$ javac -d . TestByte.java 
bogon:temp xb$ tree
.
|____javadeveloper
| |____mkmoney
| | |____TestByte.class
|____.DS_Store
|____TestByte.java

接下来我们通过javap查看字节码信息:

bogon:temp xb$ javap -c javadeveloper/mkmoney/TestByte.class 
Compiled from "TestByte.java"
public class javadeveloper.mkmoney.TestByte {
  public javadeveloper.mkmoney.TestByte();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class javadeveloper/mkmoney/TestByte
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: return
}

我们可以看到默认的构造函数TestByte()以及我们定义的主函数main,方法下面的第一列的整数是字节的偏移量,数字的右侧就是操作码+操作数(可能没有),接下来我们分别来看下。

1.1:构造函数TestByte()

0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return

首先第一行0: aload_0,0代表偏移量是0,即从0字节开始是该指令的内容,aload_0中的a是代表一个对象,load是代表加载本地变量表中的变量到栈中,_0代表的是从本地变量表中加载变量的位置,因此,整个意思就是,从本地变量表的0位置加载一个对象类型的值压到栈中,如下图:

在这里插入图片描述

第二行1: invokespecial #1代表偏移量是1,即从第一个字节开始是该指令的内容,invokespecial代表是调用一个类实例方法,#1代表是要调用的类实例方法信息在常量池的1位置。

第三行4: return中4代表偏移量是4,即从第四个字节是该指令的信息(说明前一个指令占用了3个字节,这是因为invokespecial占用了2个字节的操作数,只不过这里只使用了字节存储了#1),return指令,代表方法结束,弹出栈帧。

1.2:主函数main

0: new           #2                  // class javadeveloper/mkmoney/TestByte
3: dup
4: invokespecial #3                  // Method "<init>":()V
7: astore_1
8: return

第一行0: new #2 ,指令的偏移量是0,new指令代表是创建一个对象,#2代表要创建的对象的信息在常量池2的位置。

第二行3: dup,指令的偏移量是3,dup指令是压栈,即将第一行指令创建的对象压到栈顶。

第三行4: invokespecial #3,指令偏移量是4,调用常量池中位置为3的方法,即构造函数。

第四行7: astore_1,指令偏移量是7(说明上个指令占用了3个字节,因为有2字节的的操作数),astore_1中的a代表对象,store,代表存储到本地变量表中,_1代表存储的局部变量表的位置是1,即将当前栈顶的引用类型元素出栈并存储到局部变量表的1位置,如下图:

在这里插入图片描述

第五行8: return,指令偏移量是8,return指令,返回,栈帧弹出,方法结束。

2:局部变量表分析

首先我们准备两个类,如下:

package monkey.study;

public class MovieAverage {
	private int count = 0;
	private double sum = 0.0D;

	public void submit(double value) {
		this.count++;
		this.sum += value;
	}

	public double getAvg() {
		if (0 == this.count) { return sum; }
		return this.sum / this.count;
	}
}
package monkey.study;

public class LocalVariableTest {
	public static void main(String[] args) {
		MovieAverage ma = new MovieAverage();
		int num1 = 1;
		int num2 = 2;
		ma.submit(num1);
		ma.submit(num2);
		double avg = ma.getAvg();
	}
}

其中LocalVariableTest类重点分析,MovieAverage类作为辅助。

接着我们来编译(增加-g参数保留变量名称信息,方便我们阅读字节码),如下:

D:\test>javac -g -d . MovieAverage.java
D:\test>javac -g -d . LocalVariableTest.java

编译后结构如下:

D:\test>tree
Folder PATH listing for volume 新加卷
Volume serial number is 0023-BF0B
D:.
└───monkey
    └───study
    	└───MovieAverage.class
    	└───LocalVariableTest.class

接下来你通过javap命令查看LocalVariableTest字节码信息:

D:\test>javap -verbose -c monkey.study.LocalVariableTest
...
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=6, args_size=1
         0: new           #2                  // class monkey/study/MovieAverage
         3: dup
         4: invokespecial #3                  // Method monkey/study/MovieAverage."<init>":()V
         7: astore_1
         8: iconst_1
         9: istore_2
        10: iconst_2
        11: istore_3
        12: aload_1
        13: iload_2
        14: i2d
        15: invokevirtual #4                  // Method monkey/study/MovieAverage.submit:(D)V
        18: aload_1
        19: iload_3
        20: i2d
        21: invokevirtual #4                  // Method monkey/study/MovieAverage.submit:(D)V
        24: aload_1
        25: invokevirtual #5                  // Method monkey/study/MovieAverage.getAvg:()D
        28: dstore        4
        30: return
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 7: 10
        line 8: 12
        line 9: 18
        line 10: 24
        line 11: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  args   [Ljava/lang/String;
            8      23     1    ma   Lmonkey/study/MovieAverage;
           10      21     2  num1   I
           12      19     3  num2   I
           30       1     4   avg   D
}

LocalVariableTable就是局部局部变量表信息,其他源码对应关系如下图:

在这里插入图片描述

接着我们看下指令是如何同本地变量表发生关系的:

0: new           #2                  // class monkey/study/MovieAverage
    创建对象MovieAverage,并将其引用值压入栈顶
3: dup
    复制栈顶元素并压入栈顶,即创建MovieAverage对象应用被复制一份
4: invokespecial #3                  // Method monkey/study/MovieAverage."<init>":()V
    操作数栈出栈,获取MovieAverage对象应用,并调用其构造函数
7: astore_1
    操作数栈出栈,获取MovieAverage对象应用(因为dup复制了一份,所以这里还有一份),并存储到本地变量表slot 1的位置,即变量ma,对应代码MovieAverage ma = new MovieAverage();
8: iconst_1
    将常量1压入栈帧操作数栈栈顶
9: istore_2
    将操作数栈栈顶元素出栈,并存储到本地变量表slot 2的位置,即变量num1,对应代码int num1 = 1;
10: iconst_2
    将常量1压入栈帧操作数栈栈顶
11: istore_3
    将操作数栈栈顶元素出栈,并存储到本地变量表slot 3的位置,即变量num2,对应代码int num2 = 2;
12: aload_1
    从局部变量表slot 1位置加载变量,即加载变量ma,即获取代码ma.submit(num1);中的ma
13: iload_2
    从局部变量表slot 2位置加载变量,即加载变量num1,即获取代码ma.submit(num1);中的num1
14: i2d
    弹出操作数栈栈顶元素,并装换为double,因为MovieAverage.submit方法的入参是double的,所以这里执行强转
15: invokevirtual #4                  // Method monkey/study/MovieAverage.submit:(D)V
    弹出操作数栈栈顶元素,并执行其实例方法submit,使用上一个指令的结果作为参数
18: aload_1
    从局部变量表slot 1位置加载变量,即加载变量ma,即获取代码ma.submit(num2);中的ma
19: iload_3
    从局部变量表slot 2位置加载变量,即加载变量num2,即获取代码ma.submit(num2);中的num2
20: i2d
    弹出操作数栈栈顶元素,并装换为double,因为MovieAverage.submit方法的入参是double的,所以这里执行强转
21: invokevirtual #4                  // Method monkey/study/MovieAverage.submit:(D)V
    弹出操作数栈栈顶元素,并执行其实例方法submit,使用上一个指令的结果作为参数
24: aload_1
    从局部变量表slot 1位置加载变量,即加载变量ma,即获取代码double avg = ma.getAvg();中的ma
25: invokevirtual #5                  // Method monkey/study/MovieAverage.getAvg:()D
    弹出操作数栈栈顶元素,即ma,并执行其实例方法getAvg,并将操作结果压入操作数栈栈顶,该方法不需要参数
28: dstore        4
    将操作数栈栈顶的double类型数据出栈,并存入本地变量表slot 4的位置,即赋值给avg变量
30: return
    栈帧弹出线程栈,方法执行完毕

3:行号表分析

行号表用来存储字节码指令和源代码位置的对应关系,如下测试代码:

package monkey.study;

public class LineNumTableTest {
	public static void main(String[] args) {
		int a = 1;
		int b = 2;
	}
}

接着编译:

D:\test>javac -g -d . LineNumTableTest.java

javap 查看字节码只保留LineNumberTable

D:\test>javap -c -verbose monkey.study.LineNumTableTest
Classfile /D:/test/monkey/study/LineNumTableTest.class
  ...
    Code:
      stack=1, locals=3, args_size=1
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: return
     LineNumberTable:
      line 5: 0
      line 6: 2
      line 7: 4
      LocalVariableTable:
        ...
}

行号表line 5: 0意思是指令码偏移量0位置对应的源码的行号是5,如下图:

在这里插入图片描述

其他类似,完整的如下图:

在这里插入图片描述

4:循环控制分析

测试代码如下:

package monkey.study;

public class MovieAverage {
	private int count = 0;
	private double sum = 0.0D;

	public void submit(double value) {
		this.count++;
		this.sum += value;
	}

	public double getAvg() {
		if (0 == this.count) { return sum; }
		return this.sum / this.count;
	}
}
package monkey.study;

public class ForLoopTest {
	private static int[] numbers = {1, 6, 8};
	public static void main(String[] args) {
		MovieAverage ma = new MovieAverage();
		for (int number : numbers) {
			ma.submit(number);
		}
	}
}

接下来编译:

D:\test>javac -d . MovieAverage.java

D:\test>javac -d . ForLoopTest.java

查看字节码如下:

D:\test>javap -c -verbose monkey/study/ForLoopTest
... 
    public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=6, args_size=1
             0: new           #2                  // class monkey/study/MovieAverage
             3: dup
             4: invokespecial #3                  // Method monkey/study/MovieAverage."<init>":()V
             7: astore_1
             8: getstatic     #4                  // Field numbers:[I
            11: astore_2
            12: aload_2
            13: arraylength
            14: istore_3
            15: iconst_0
            16: istore        4
            18: iload         4
            20: iload_3
            21: if_icmpge     43
            24: aload_2
            25: iload         4
            27: iaload
            28: istore        5
            30: aload_1
            31: iload         5
            33: i2d
            34: invokevirtual #5                  // Method monkey/study/MovieAverage.submit:(D)V
            37: iinc          4, 1
            40: goto          18
            43: return
...

指令分析如下:

0: new           #2                  // class monkey/study/MovieAverage
    创建对象,类信息在常量池2位置,并将创建的对象压入栈帧的操作数栈栈顶
3: dup
    复制栈顶元素,并压入操作数栈栈顶
4: invokespecial #3                  // Method monkey/study/MovieAverage."<init>":()V
    栈顶元素出栈,并调用其<init>方法
7: astore_1
    栈顶对象类型(a)元素出栈,并赋值到局部变量表slot 1位置的变量ma
8: getstatic     #4                  // Field numbers:[I
    获取常量并压栈都操作数栈栈顶,常量信息通过常量池4位置获取,即private static int[] numbers = {1, 6, 8};
11: astore_2
    操作数栈出栈,并存储在本地变量表2位置
12: aload_2
    从本地变量表2位置获取变量,并压栈到操作数栈栈顶
13: arraylength
    获取操作数栈栈顶元素,并获取其长度,并将长度值压栈到操作数栈栈顶
14: istore_3
    获取操作数栈栈顶元素并存储到本地变量表3位置
15: iconst_0
    将整形常量0,压入操作数栈栈顶
16: istore        4
    获取栈顶整形值并存储到本地变量表4位置
18: iload         4
    从本地变量表4位置加载对应变量,并压栈到操作数栈栈顶(即for循环的初始值0)
20: iload_3
    从本地变量表3位置加载对应变量,并压栈到操作数栈栈顶(即数组numbers = {1, 6, 8}的长度3)
21: if_icmpge     43
    从操作数栈中出栈两个整数,并比较,如果第一个大于第二个,则跳转到指令43的位置,这里43就是43: return,即方法结束
24: aload_2
    加载本地变量表2位置对象类型变量到操作数栈栈顶,即数组(即数组numbers = {1, 6, 8})
25: iload         4
    加载本地变量表4位置整数类型变量到操作数栈栈顶,第一次获取的就是0
27: iaload
    出栈2次,使用第一个出栈结果作为索引,从第二出栈结果中获取整数类型的数据,即执行numbers[0]
28: istore        5
    将获取的整数结果存储到本地变量表5位置,即赋值到number变量,此时for循环里的number值就是0
30: aload_1
    从局部变量表1位置获取对象类型变量,即ma,并压栈到操作数栈的栈顶
31: iload         5
    从局部变量表5位置获取整数类型变量,即number 0,并压栈到操作数栈栈顶
33: i2d
    栈顶元素出栈,并强转为double,之后重新入栈
34: invokevirtual #5                  // Method monkey/study/MovieAverage.submit:(D)V
    执行MovieAverage.submit,并出栈操作数栈栈顶元素作为参数,首次就是ma.submit(0d);
37: iinc          4, 1
    将本地变量表4位置的值+1,即for循环中的number变为1
40: goto          18
    回到指令18的位置,继续整个循环的过程
43: return

5:一个动态例子

在开始之前先补充一个知识点,程序计数器,用来存储程序当前执行的位置,即字节码指令的偏移量。

如下代码:

package monkey.study;

public class DynamicExample {

	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		int c = (a + b) + 3;
	}
}

编译并查看字节码:

D:\test>javac -d . -g DynamicExample.java

D:\test>javap -c -verbose monkey.study.DynamicExample
...
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: iconst_3
         8: iadd
         9: istore_3
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 2
        line 8: 4
        line 9: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            2       9     1     a   I
            4       7     2     b   I
           10       1     3     c   I
}

接下来我们通过程序计数器,局部变量表,操作数栈3个元素的变化情况来分析下整个执行过程,初始化时如下:

在这里插入图片描述

执行指令0: iconst_1,将整数常量1压到操作数栈:

在这里插入图片描述

执行指令1: istore_1,将栈顶元素出栈,并赋值到局部变量表1位置:

在这里插入图片描述

这样a就被赋值1,接着执行指令2: iconst_2,将整数常量2压到操作数栈:

在这里插入图片描述

接着执行指令3: istore_2,操作数栈出栈,并赋值给局部变量表2位置:

在这里插入图片描述

这样b就赋值2,执行指令4: iload_1,加载局部变量表1位置值到操作数栈,即a变量:

在这里插入图片描述

执行指令5: iload_2,将局部变量表2位置值压到操作数栈栈顶,即变量b:

在这里插入图片描述

执行指令6: iadd,将操作数栈顶2个元素出栈,并执行加法运算,最后将结果重新压到操作数栈栈顶:

在这里插入图片描述

执行指令7: iconst_3,加载整形常量3到操作数栈栈顶:

在这里插入图片描述

执行指令8: iadd,将操作数栈栈顶元素出栈2次,并执行加法运算,将结果重新压到操作数栈栈顶:

在这里插入图片描述

执行指令9: istore_3,操作数栈出栈,并赋值到局部变量表的3位置的整型变量中,即c:

在这里插入图片描述

执行指令10: return,方法结束,方法对应栈帧弹出线程栈。

写在后面

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

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

相关文章

SmartEngine流程引擎之Custom模式

目录 一、为什么选用SmartEngine 二、各类流程引擎框架简单对比 1、流程设计器推荐 2、什么是BPMN 流程定义解释说明 三、SmartEngine之Custom实操 1、引入依赖 2、典型的初始化代码如下 3、节点如何流转以及流程实例存储问题 4、定义Delegation 关键类 一、为什么选用…

Java 基础入门篇(四)—— 方法的重载与参数传递机制

文章目录 一、方法的定义二、方法的参数传递机制 ★2.1 基本类型的参数传递2.2 引用类型的参数传递 三、方法重载 一、方法的定义 方法的作用&#xff1a;封装一段代码的语法结构&#xff0c;可以被重复调用&#xff0c;以此提高代码的复用性&#xff0c;提高开发效率&#xf…

ChatGPT Plus价格太贵,可以约上三五知己一起上车体验一下,这个项目就能帮到你

对于想体验ChatGPT PLus的小伙伴&#xff0c;可能觉得自己一个人一个月花费20美元&#xff0c;相对于人民币每月137多&#xff0c;确实是一个不少的开支&#xff0c;如果&#xff0c;几个人合作一个账号&#xff0c;这样负担就减少了。刚好&#xff0c;最近逛github发现刚好有一…

小记Java调用C++开发的动态链接库(DLL)

一、背景 五一快乐吖&#xff01;死肥宅正趁着五一这段时间&#xff0c;努力提升自己&#xff01; 最近使用Java拦截Windows系统中一些默认事件时&#xff0c;发现了一些瓶颈。 我用Java操作浏览器、用Java最小化其他应用窗口&#xff0c;但是我发现这个操作&#xff0c;他都…

几十个简要的游戏案例分析

文章目录 一、 介绍二、 影响游戏体验的因素三、 游戏能爆火的因素1.影响游戏爆火因素的排名2.玩游戏的两种经典心理3.经典案例分析Qq农场植物大战僵尸水果忍者召唤神龙羊了个羊 4.游戏公司可借鉴的经验 四、 几十款游戏的多方面分析FC红白游戏机十二人街霸热血高校系列魂斗罗系…

趣说数据结构(练习1) —— 顺序表/链表力扣刷题

练习 1 —— 顺序表/链表力扣刷题 1. 合并两个有序链表 力扣题目地址&#xff1a;https://leetcode.cn/problems/merge-two-sorted-lists/ 问题描述&#xff1a;将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例&#x…

Java——Java面向对象

该系列博文会告诉你如何从入门到进阶&#xff0c;一步步地学习Java基础知识&#xff0c;并上手进行实战&#xff0c;接着了解每个Java知识点背后的实现原理&#xff0c;更完整地了解整个Java技术体系&#xff0c;形成自己的知识框架。 概述&#xff1a; Java是面向对象的程序…

@Autowired与@Resource原理知识点详解

文章目录 前言springIOC依赖注入的三种方式属性注入&#xff08;字段注入&#xff09;构造方法注入setter注入用哪个&#xff1f; Autowired实现原理 Resource实现原理结论 Autowired与Resource的不同来源不同参数不同使用不同装配顺序 前言 现在spring可以说是一统天下了&…

【Unity-UGUI控件全面解析】| Canvas 画布组件详解

🎬【Unity-UGUI控件全面解析】| Canvas 画布组件详解一、组件介绍1.1 绘制元素的顺序二、组件属性面板2.1 Canvas :画布,控制UI的渲染模式2.2 Canvas Scaler:画布缩放器,控制UI画布的放大缩放的比例2.3 Graphic Raycaster:图形射线投射器,控制是否让UI响应射线点击三、…

【干货分享】一文说透分布式一致性协议(上)

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注"慕课网"&#xff01; 作者&#xff1a;大熊老师 | 慕课网讲师 在常见的分布式系统中&#xff0c;总会发生诸如机器宕机或网络异常&#xff08;包括消息的延迟…

数据备份系列:Rsync 备份详解(一)

一、Rsync 简介 1.1 Rsync 是一个远程增量文件备份软件工具 1.2 Rsync 的特性 支持拷贝特殊文件&#xff0c;如连接文件、设备等。可以有排除指定文件或目录同步的功能&#xff0c;相当于打包命令 tar 的排除功能。可以做到保持原文件或目录的权限、时间、软硬链接、属主、组…

Python每日一练(20230502)

目录 1. 被围绕的区域 &#x1f31f;&#x1f31f; 2. 两数之和 II &#x1f31f; 3. 二叉树展开为链表 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1…

react native ios 添加启动页 xcode14 react-native-splash-screen

最近更新xcode&#xff0c;有些配置有些不同&#xff0c;网上查的方法都是过时的&#xff0c;导致配了一段时间卡在这里&#xff0c;最后访问官网才弄好了&#xff0c;所以以后解决问题的办法先看官网再查其他各路神仙的办法。 官网的步骤&#xff1a;https://github.com/crazy…

颜色空间转换RGB-YCbCr

颜色空间 颜色空间&#xff08;Color Space&#xff09;是描述颜色的一种方式&#xff0c;它是一个由数学模型表示的三维空间&#xff0c;通常用于将数字表示的颜色转换成可见的颜色。颜色空间的不同取决于所选的坐标轴和原点&#xff0c;以及用于表示颜色的色彩模型。在计算机…

【C++入门】一篇搞懂auto关键字

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】 目录 作用不那么大的场景auto真正的价值auto和指针结合使用注意点auto不能推导的场景范围for范围for的使用条件 作用不那么大的…

海尔牵头IEEE P2786国际标准通过Sponsor投票并连任工作组主席

01 海尔牵头IEEE P2786国际标准 通过Sponsor投票 并连任工作组主席 海尔牵头制定的全球首个服装物联网国际标准IEEE P2786《Standard for General Requirements and Interoperability for Internet of Clothing》通过Sponsor投票&#xff0c;标志着该国际标准草案得到了行业…

2.6 浮点运算方法和浮点运算器

学习目标&#xff1a; 以下是一些具体的学习目标&#xff1a; 理解浮点数的基本概念和表示方法&#xff0c;包括符号位、指数和尾数。学习浮点数的运算规则和舍入规则&#xff0c;包括加、减、乘、除、开方等。了解浮点数的常见问题和误差&#xff0c;例如舍入误差、溢出、下…

FPGA实现10G万兆网UDP通信 10G Ethernet Subsystem替代网络PHY芯片 提供工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、详细设计方案传统 FPGA UDP 方案本 FPGA 10G UDP 方案(牛逼)10G Ethernet 框图10G Ethernet 发送解析10G Ethernet 接收解析10G Ethernet 寄存器配置10G Ethernet UI 配置 4、vivado工程详解5、上板调试验证并演示ping功能测试数据收发测…

一款支持全文检索、工作流审批、知识图谱的企事业知识库

一、项目介绍 一款全源码&#xff0c;可二开&#xff0c;可基于云部署、私有部署的企业级知识库云平台&#xff0c;一款让企业知识变为实打实的数字财富的系统&#xff0c;应用在需要进行文档整理、分类、归集、检索、分析的场景。 获取方式q:262086839 为什么建立知识库平台&…

perf record对C++程序耗时进行分析

本节将介绍如何使用perf工具的perf record对C代码进行性能分析&#xff0c;一切操作都是在ubuntu 20下进行。 perf工具安装 由于perf工具和内核版本有关&#xff0c;因此直接安装容易出错&#xff0c;建议直接通过如下指令安装&#xff1a; sudo apt-get install linux-tool…