字节码学习之常见java语句的底层原理

news2024/11/25 20:17:09

文章目录

  • 前言
  • 1. if语句
    • 字节码的解析
  • 2. for循环
    • 字节码的解析
  • 3. while循环
  • 4. switch语句
  • 5. try-catch语句
  • 6. i++ 和++i的字节码
  • 7. try-catch-finally
  • 8. 参考文档

前言

上一章我们聊了《JVM字节码指令详解》 。本章我们学以致用,聊一下我们常见的一些java语句的特性底层是如何实现。
在这里插入图片描述

1. if语句

if语句是我们最常用的判断语句之一,它的底层实现原理是什么呢?可以通过反编译字节码来分析一下。

假设我们有以下的java代码:

public class IfStatement {
    public static void main(String[] args) {
        int a = 10;
        if (a > 0) {
            System.out.println("a is positive");
        } else {
            System.out.println("a is negative or zero");
        }
    }
}

可以使用javap命令来反编译字节码:

javap -c IfStatement.class

输出结果如下:

public class IfStatement {
  // 构造方法
  public IfStatement();
    Code:
       0: aload_0                     // 将局部变量表中的第0个元素(通常是this引用)入栈
       1: invokespecial #1            // 调用父类Object的构造方法
       4: return                      // 方法返回

  // main方法
  public static void main(java.lang.String[]);
    Code:
       0: bipush        10            // 将10压入栈顶
       2: istore_1                    // 将栈顶元素(10)存入局部变量表的第1个位置
       3: iload_1                     // 将局部变量表的第1个位置的元素(10)入栈
       4: ifle          17            // 判断栈顶元素(10)是否小于等于0,如果是则跳转到指令17
       7: getstatic     #2            // 获取System类的out字段,类型是PrintStream
      10: ldc           #3            // 将字符串"a is positive"压入栈顶
      12: invokevirtual #4            // 调用PrintStream的println方法输出字符串
      15: goto          23            // 无条件跳转到指令23
      18: getstatic     #2            // 获取System类的out字段,类型是PrintStream
      21: invokevirtual #5            // 调用PrintStream的println方法输出局部变量表第1个位置的元素(10)
      24: return                      // 方法返回
}

字节码的解析

  • 在main方法中,首先将10压入栈顶,然后将其存入局部变量表的第1个位置。然后将局部变量表的第1个位置的元素(10)入栈,判断其是否小于等于0,如果是则跳转到指令17,否则执行下一条指令。在指令7-15中,它获取System的out字段,将字符串"a is positive"压入栈顶,然后调用println方法输出这个字符串,最后无条件跳转到指令23。在指令18-21中,它获取System的out字段,然后调用println方法输出局部变量表第1个位置的元素(10)。最后,main方法返回。

2. for循环

for循环是我们常用的循环语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。

假设我们有以下的java代码:

public class ForLoop {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println("i = " + i);
        }
    }
}

可以使用javap命令来反编译字节码:

javap -c ForLoop.class

这是一个包含for循环的Java类ForLoop的字节码输出,下面是中文注释:

public class ForLoop {
  // 构造方法
  public ForLoop();
    Code:
       0: aload_0                     // 将局部变量表中的第0个元素(通常是this引用)入栈
       1: invokespecial #1            // 调用父类Object的构造方法
       4: return                      // 方法返回

  // main方法
  public static void main(java.lang.String[]);
    Code:
       0: iconst_0                    // 将0压入栈顶
       1: istore_1                    // 将栈顶元素(0)存入局部变量表的第1个位置
       2: iload_1                     // 将局部变量表的第1个位置的元素(0)入栈
       3: bipush        10            // 将10压入栈顶
       5: if_icmpge     19            // 如果局部变量表的第1个位置的元素(0)大于等于栈顶元素(10),则跳转到指令19
       8: getstatic     #2            // 获取System类的out字段,类型是PrintStream
      11: new           #3            // 创建一个StringBuilder类的对象
      14: dup                         // 复制栈顶元素,此时栈顶有两个相同的StringBuilder对象引用
      15: invokespecial #4            // 调用StringBuilder类的构造函数初始化对象
      18: ldc           #5            // 将字符串"i ="压入栈顶
      20: invokevirtual #6            // 调用StringBuilder的append方法将字符串添加到StringBuilder
      23: iload_1                     // 将局部变量表的第1个位置的元素(0)入栈
      24: invokevirtual #7            // 调用StringBuilder的append方法将数字添加到StringBuilder
      27: invokevirtual #8            // 调用StringBuilder的toString方法将StringBuilder转化为字符串
      30: invokevirtual #9            // 调用PrintStream的println方法输出字符串
      33: iinc          1, 1          // 将局部变量表的第1个位置的元素(0)增加1
      36: goto          2             // 无条件跳转到指令2,形成循环
      39: return                      // 方法返回
    LineNumberTable:
      line 3: 0
      line 4: 8
      line 3: 33
      line 6: 39
    StackMapTable: number_of_entries = 2
      frame_type = 252 /* append */
        offset_delta = 2
        locals = [ int, int ]
        stack = []
      frame_type = 250 /* chop */
        offset_delta = 36
}

字节码的解析

可以看到,for循环的底层实现是通过if_icmpge指令来实现的。在本例中,当i小于10时,会执行第8行的输出语句;否则,会跳转到第39行,结束循环。

  • 在main方法中,首先将0压入栈顶,然后将其存入局部变量表的第1个位置。接下来是一个循环,循环条件是局部变量表的第1个位置的元素小于10。在循环体中,它首先获取System的out字段,然后创建一个StringBuilder对象并初始化,然后将字符串"i ="和局部变量表的第1个位置的元素添加到StringBuilder,然后将StringBuilder转化为字符串,然后调用println方法输出字符串。在循环体结束时,它将局部变量表的第1个位置的元素加1,然后无条件跳转到指令2,形成循环。当循环结束时,main方法返回。

3. while循环

while循环是我们常用的循环语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。

假设我们有以下的java代码:

public class WhileLoop {
    public static void main(String[] args) {
        int i = 0;
        while (i < 10) {
            System.out.println("i = " + i);
            i++;
        }
    }
}

可以使用javap命令来反编译字节码:

javap -c WhileLoop.class

输出结果如下:

public class WhileLoop {
  public WhileLoop();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: bipush        10
       5: if_icmpge     19
       8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: new           #3                  // class java/lang/StringBuilder
      14: dup
      15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      18: ldc           #5                  // String i =
      20: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      23: iload_1
      24: invokevirtual #7                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      27: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      30: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      33: iinc          1, 1
      36: goto          2
      39: return
    LineNumberTable:
      line 3: 0
      line 4: 2
      line 5: 8
      line 6: 33
      line 5: 36
      line 8: 39
    StackMapTable: number_of_entries = 2
      frame_type = 252 /* append */
        offset_delta = 2
        locals = [ int, int ]
        stack = []
      frame_type = 250 /* chop */
        offset_delta = 36
}

可以看到,while循环的底层实现也是通过if_icmpge指令来实现的。在本例中,当i小于10时,会执行第8行的输出语句;否则,会跳转到第39行,结束循环。

4. switch语句

switch语句是我们常用的分支语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。

假设我们有以下的java代码:

public class SwitchStatement {
    public static void main(String[] args) {
        int i = 2;
        switch (i) {
            case 1:
                System.out.println("i is 1");
                break;
            case 2:
                System.out.println("i is 2");
                break;
            case 3:
                System.out.println("i is 3");
                break;
            default:
                System.out.println("i is neither 1, 2 nor 3");
                break;
        }
    }
}

可以使用javap命令来反编译字节码:

javap -c SwitchStatement.class

输出结果如下:

public class SwitchStatement {
  public SwitchStatement();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_2
       1: istore_1
       2: iload_1
       3: tableswitch   { // 1 to 3
                     1: 28
                     2: 40
                     3: 52
               default: 64
          }
      28: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: ldc           #3                  // String i is 1
      33: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36: goto          71
      40: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      43: ldc           #5                  // String i is 2
      45: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      48: goto          71
      52: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      55: ldc           #6                  // String i is 3
      57: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: goto          71
      64: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      67: invokevirtual #7                  // Method java/io/PrintStream.println:()V
      70: return
      71: return
    LineNumberTable:
      line 3: 0
      line 4: 2
      line 5: 28
      line 6: 40
      line 7: 52
      line 8: 64
      line 9: 70
      line 7: 71
    StackMapTable: number_of_entries = 5
      frame_type = 252 /* append */
        offset_delta = 28
        locals = [ int ]
      frame_type = 252 /* append */
        offset_delta = 11
        locals = [ int ]
      frame_type = 252 /* append */
        offset_delta = 12
        locals = [ int ]
      frame_type = 252 /* append */
        offset_delta = 12
        locals = [ int ]
      frame_type = 252 /* append */
        offset_delta = 3
        locals = [ int ]

可以看到,switch语句的底层实现是通过tableswitch指令来实现的。在本例中,当i等于1时,会执行第28行的输出语句;当i等于2时,会执行第40行的输出语句;当i等于3时,会执行第52行的输出语句;否则,会执行第64行的输出语句。

5. try-catch语句

try-catch语句是我们常用的异常处理语句之一,它的底层实现原理是什么呢?我们还是可以通过反编译字节码来分析一下。

假设我们有以下的java代码:

public class TryCatchStatement {
    public static void main(String[] args) {
        try {
            int[] arr = new int[3];
            arr[4] = 5;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Array index out of bounds!");
        }
    }
}

可以使用javap命令来反编译字节码:

javap -c TryCatchStatement.class

输出结果如下:

public class TryCatchStatement {
  public TryCatchStatement();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_4
       6: iconst_5
       7: iastore
       8: goto          19
      11: astore_1
      12: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      15: ldc           #3                  // String Array index out of bounds!
      17: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: return
    Exception table:
       from    to  target type
           0     8    11   Class java/lang/ArrayIndexOutOfBoundsException
    LineNumberTable:
      line 3: 0
      line 4: 4
      line 5: 11
      line 6: 12
      line 7: 20
    StackMapTable: number_of_entries = 2
      frame_type = 34 /* same */
      frame_type = 1 /* same_locals_1_stack_item */
        stack = [ class java/lang/ArrayIndexOutOfBoundsException ]
}

可以看到,try-catch语句的底层实现是通过异常表来实现的。在本例中,当数组下标越界时,会执行第12行的输出语句;否则,会跳转到第20行,继续执行。

6. i++ 和++i的字节码

i++++i 在语义上有些许不同,在字节码层面也有所体现。下面是它们的字节码层面的解释:

假设 i 是局部变量表的索引为1的变量。

i++ 的伪字节码:

iload_1                 // 从局部变量表中加载变量 i 到操作数栈顶
iinc 1 by 1             // 将局部变量表中的变量 i 增加1

++i 的伪字节码:

iinc 1 by 1             // 将局部变量表中的变量 i 增加1
iload_1                 // 从局部变量表中加载变量 i 到操作数栈顶

可以看到,i++++i 的主要区别在于加载和增加操作的顺序不同。i++ 是先将 i 加载到操作数栈顶,然后再增加 i 的值;而 ++i 是先增加 i 的值,然后再将 i 加载到操作数栈顶。这就解释了 i++++i 在语义上的不同:i++ 是先取值后加1,++i 是先加1后取值。

7. try-catch-finally

在 Java 字节码中,try-catch-finally 结构主要通过异常表(Exception Table)来实现。Java 字节码并没有专门的指令来表示 try、catch 或者 finally 块。相反,它通过在异常表中记录 try 块的开始和结束位置、catch 块的开始位置和要捕获的异常类型,以实现异常处理的流程。

下面是一个简单的 try-catch-finally 代码例子:

void test() {
    try {
        System.out.println("try block");
        throw new Exception();
    } catch (Exception e) {
        System.out.println("catch block");
    } finally {
        System.out.println("finally block");
    }
}

对应的字节码指令

0: getstatic     #2   // 获取 java/lang/System 类的 out 字段,是 PrintStream 类型
3: ldc           #3   // 将常量池中的 "try block" 字符串压入栈顶
5: invokevirtual #4   // 调用 PrintStream 类的 println 方法输出字符串
8: new           #5   // 创建一个 java/lang/Exception 类的对象
11: dup           // 复制栈顶的元素,此时栈顶有两个相同的异常对象引用
12: invokespecial #6   // 调用 Exception 类的构造函数初始化对象
15: athrow         // 抛出栈顶的异常对象
16: astore_1       // 捕获异常并存入局部变量表的第1个位置
17: getstatic     #2   // 获取 java/lang/System 类的 out 字段,是 PrintStream 类型
20: ldc           #7   // 将常量池中的 "catch block" 字符串压入栈顶
22: invokevirtual #4   // 调用 PrintStream 类的 println 方法输出字符串
25: jsr           26   // 无条件跳转到指令26(finally块的起始位置)
28: goto          34   // 执行完finally块后,跳过 catch 块剩下的代码,进入下一个处理流程
31: astore_2       // 捕获从finally块抛出的异常并存入局部变量表的第2个位置
32: jsr           26   // 无条件跳转到指令26(finally块的起始位置)
35: aload_2        // 从局部变量表的第2个位置加载异常对象至栈顶
36: athrow         // 再次抛出该异常对象
37: astore_3       // 捕获异常并存入局部变量表的第3个位置
38: getstatic     #2   // 获取 java/lang/System 类的 out 字段,是 PrintStream 类型
41: ldc           #8   // 将常量池中的 "finally block" 字符串压入栈顶
43: invokevirtual #4   // 调用 PrintStream 类的 println 方法输出字符串
46: ret           3    // 返回到 astore_3 指令之后的代码

这段字节码中使用了 jsrret 指令,这两个指令主要用于实现 finally 块的逻辑。jsr 指令会跳转到 finally 块的代码,然后 ret 指令用于返回到 finally 块之前的代码继续执行。

字节码的解释

  • 行0-15:这部分对应 try 块的内容。在这个例子中,它首先通过 getstatic 指令获取 System.out 对象,然后通过 ldc 指令加载常量 “try block”,最后调用 println 方法输出这个字符串。然后,它创建一个 Exception 对象并抛出。

  • 行16-25:这部分对应 catch 块的内容。当 try 块抛出异常时,执行流程会跳转到这部分。在这个例子中,它首先通过 astore 指令将异常对象存储到局部变量表,然后类似于 try 块的处理,输出 “catch block” 字符串。

  • 行37-46:这部分对应 finally 块的内容。无论 try 块是否抛出异常,这部分代码总是会被执行。在这个例子中,它输出 “finally block” 字符串。

  • 行25-32和行35-36:这部分是对异常处理的一些额外控制。jsr 和 ret 指令用于实现无条件的跳转,确保 finally 块总是会被执行。

8. 参考文档

  1. 张亚 《深入理解JVM字节码》
  2. https://www.jonesjalapat.com/2021/09/11/internal-working-of-java-virtual-machine/

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

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

相关文章

苍穹外卖(四) AOP切面公共字段自动填充及文件上传

一.AOP切面公共字段填充 问题分析 如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐&#xff0c;那能不能对于这些公共字段在某个地方统一处理&#xff0c;来简化开发呢&#xff1f; 答案是可以的&#xff0c;我们使用AOP切…

Redis订阅和发布

Redis订阅和发布 一、订阅者和发布者二、使用示例三、常用命令 一、订阅者和发布者 发布者&#xff1a;publish&#xff0c;发送消息订阅者&#xff1a;subscribe&#xff0c;接收消息 如下图所示&#xff0c;可以有多个订阅者订阅同一个频道&#xff0c;如果该频道发送消息&…

【RocketMQ系列一】初识RocketMQ

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

thinkphp6入门(9)-- 获取url路径中的应用名、控制器名、操作名

如果使用了多应用模式&#xff0c;可以通过下面的方法来获取当前应用 app(http)->getName(); 获取当前控制器 Request::controller(); 获取当前操作 Request::action(); 在中间件middleware中是无法获取控制器和操作的 需要将middleware的引入修改为 config 目录下的 ro…

计算机网络 实验六 Wireshark 抓包实验

实验目的&#xff1a; 通过实验掌握下列知识&#xff1a; 1. 掌握Wireshark抓包工具的使用。 2. 掌握建立telnet用户密码登录方式&#xff0c;并通过wireshark抓取相关信息。 3. 理解TCP的包结构&#xff0c;并掌握TCP建立连接的过程。 4. 掌握使用tftp工具&#xff0c;上…

一文带你了解以色列的当红38家网络安全公司

2014年&#xff0c;以色列出口的网络安全产品总值达60亿美金&#xff0c;占领了全球10%的网络安全市场。2014-2016年&#xff0c;微软用3.2亿美元买下数据隐私公司Adallom&#xff1b;Facebook花了1.5亿美元收购移动端数据分析公司Onavo&#xff1b;PayPal以6千万美元收下CyAct…

2023面试知识点二

1、vue双向绑定是如何实现的 原理主要通过数据劫持和发布订阅模式实现的通过Object.defineProperty()来劫持各个属性的setter&#xff0c;getter&#xff0c;监听数据的变化在数据变动时发布消息给订阅者(watcher)&#xff0c;订阅者触发响应的回调(update)更新视图。 2、ing…

非一线工程管理者的一对一沟通

一线工程管理者主要管理/领导工程师完成实际的工程任务&#xff0c;而非一线的工程管理者需要领导一线管理者&#xff0c;更多的关注团队本身&#xff0c;因此两者在做一对一沟通的时候需要有不同的关注点。原文: One-on-Ones for (Engineering) Manager of Managers 如果没有耐…

Linux该如何学习,给你支招

如果你已经确定对 Linux 产生了兴趣&#xff0c;那么接下来我们介绍一下学习 Linux 的方法。这只是自己关于学习Linux的建议。 一、如何去学习 学习大多类似庖丁解牛&#xff0c;对事物的认识一般都是由浅入深、由表及里的过程&#xff0c;循序才能渐进。学习 Linux 同样要有一…

复旦MBA魏文童:构建完备管理知识体系,助力企业数字化发展

日月光华&#xff0c;旦复旦兮&#xff01;复旦MBA如同一个巨大的磁场&#xff0c;吸引了诸多来自五湖四海、各行各业的职场精英。从初入职场的青涩懵懂到如今的独当一面专业干练&#xff0c;他们逐渐成长为职场的中坚力量&#xff0c;在各自领域内发光发热。作为新时代的青年&…

怒刷LeetCode的第27天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;位运算 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;贪心算法 第三题 题目来源 题目内容 解决方法 方法一&#xff1a;二分查找 方法二&#xff1a;牛顿迭代法 方法三&#xff1a;位操作…

基于Pytest接口自动化的requests模块项目实战以及接口关联方法详解

1、基于pytest单元测试框架的规则 1.1 模块名&#xff08;即文件名&#xff09;必须以test_开头或者_test结尾 1.2 类名必须以Test开头且不能有init方法 1.3 用例名&#xff08;测试方法&#xff09;必须以test开头 同时&#xff0c;我也为大家准备了一份软件测试视频教程&…

【算法-动态规划】零钱兑换 II-力扣 518

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

Smartforms 打印出现的问题

上半年ECC做了升级 程序代码从ECC迁移到S4 有用户反馈 打印不能用了 经过调试发现在打印程序中 竟然返回2&#xff0c;但是 smartforms ZRPT_CO_YFLL_DY又是存在的 。 然后去激活 并与 ECC对比发现问题 S4的页大小竟然这么小 找到对应的页格式 对比ECC和S4 果然是这个…

2023年中国商用服务机器人行业发展概况分析:国产机器人厂商向海外进军[图]

商用服务机器人指在非制造业的商用服务场景中&#xff0c;用来替代或辅助人类进行服务性质工作的机器人&#xff1b;常见的商用场景中&#xff0c;商用服务机器人主要分为终端配送类机器人&#xff0c;商用清洁类机器人&#xff0c;引导讲解类机器人等&#xff0c;被广泛应用在…

7+线粒体相关基因预后模型+肿瘤微环境+免疫细胞浸润+实验验证

今天给同学们分享一篇线粒体预后模型实验的生信文章“Constructing a novel mitochondrial-related gene signature for evaluating the tumor immune microenvironment and predicting survival in stomach adenocarcinoma”&#xff0c;这篇文章于2023年3月13日发表在J Trans…

Spring Boot 生成二维码

效果图 1.maven依赖 <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId>

力扣 095. 最长公共子序列(C语言+动态规划)

1. 题目 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08…

强化学习

文章目录 强化学习概念Model-FreeValue-based learningDQN State-based learning蒙特卡洛近似 Actor-Critic LearningModel-BasedMonte Carlo TreeSearch(MCTS)-AlphaGo 强化学习概念 Terminologies&#xff08;术语&#xff09; State s&#xff1a;环境的一个状态Agent &…

程 控 电 源1761程控模块电源

程 控 电 源 1761程控模块电源 1761程控模块电源是在自动测试环境中提供偏置功率和对部件或最终产品提供激励的理想设备&#xff0c;是测试系统必备的测试仪器。适用于研发、设计、生产制造等自动测试领域。 1761程控模块电源为用户选配电源提供了灵活性&#xff0c;根据需要…