字节码学习之常见java语句的底层原理
文章目录
- 前言
- 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,如果是则跳转到指令177: getstatic #2 // 获取System类的out字段,类型是PrintStream10: ldc #3 // 将字符串"a is positive"压入栈顶12: invokevirtual #4 // 调用PrintStream的println方法输出字符串15: goto 23 // 无条件跳转到指令2318: getstatic #2 // 获取System类的out字段,类型是PrintStream21: 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),则跳转到指令198: getstatic #2 // 获取System类的out字段,类型是PrintStream11: new #3 // 创建一个StringBuilder类的对象14: dup // 复制栈顶元素,此时栈顶有两个相同的StringBuilder对象引用15: invokespecial #4 // 调用StringBuilder类的构造函数初始化对象18: ldc #5 // 将字符串"i ="压入栈顶20: invokevirtual #6 // 调用StringBuilder的append方法将字符串添加到StringBuilder23: iload_1 // 将局部变量表的第1个位置的元素(0)入栈24: invokevirtual #7 // 调用StringBuilder的append方法将数字添加到StringBuilder27: invokevirtual #8 // 调用StringBuilder的toString方法将StringBuilder转化为字符串30: invokevirtual #9 // 调用PrintStream的println方法输出字符串33: iinc 1, 1 // 将局部变量表的第1个位置的元素(0)增加136: goto 2 // 无条件跳转到指令2,形成循环39: return // 方法返回LineNumberTable:line 3: 0line 4: 8line 3: 33line 6: 39StackMapTable: number_of_entries = 2frame_type = 252 /* append */offset_delta = 2locals = [ 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_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_01: istore_12: iload_13: bipush 105: if_icmpge 198: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;11: new #3 // class java/lang/StringBuilder14: dup15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V18: ldc #5 // String i =20: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;23: iload_124: 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;)V33: iinc 1, 136: goto 239: returnLineNumberTable:line 3: 0line 4: 2line 5: 8line 6: 33line 5: 36line 8: 39StackMapTable: number_of_entries = 2frame_type = 252 /* append */offset_delta = 2locals = [ 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_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_21: istore_12: iload_13: tableswitch { // 1 to 31: 282: 403: 52default: 64}28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;31: ldc #3 // String i is 133: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V36: goto 7140: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;43: ldc #5 // String i is 245: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V48: goto 7152: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;55: ldc #6 // String i is 357: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V60: goto 7164: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;67: invokevirtual #7 // Method java/io/PrintStream.println:()V70: return71: returnLineNumberTable:line 3: 0line 4: 2line 5: 28line 6: 40line 7: 52line 8: 64line 9: 70line 7: 71StackMapTable: number_of_entries = 5frame_type = 252 /* append */offset_delta = 28locals = [ int ]frame_type = 252 /* append */offset_delta = 11locals = [ int ]frame_type = 252 /* append */offset_delta = 12locals = [ int ]frame_type = 252 /* append */offset_delta = 12locals = [ int ]frame_type = 252 /* append */offset_delta = 3locals = [ 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_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_01: newarray int3: astore_14: aload_15: iconst_46: iconst_57: iastore8: goto 1911: astore_112: 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;)V20: returnException table:from to target type0 8 11 Class java/lang/ArrayIndexOutOfBoundsExceptionLineNumberTable:line 3: 0line 4: 4line 5: 11line 6: 12line 7: 20StackMapTable: number_of_entries = 2frame_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 指令之后的代码
这段字节码中使用了 jsr 和 ret 指令,这两个指令主要用于实现 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. 参考文档
- 张亚 《深入理解JVM字节码》
- https://www.jonesjalapat.com/2021/09/11/internal-working-of-java-virtual-machine/
相关文章:
字节码学习之常见java语句的底层原理
文章目录 前言1. if语句字节码的解析 2. for循环字节码的解析 3. while循环4. switch语句5. try-catch语句6. i 和i的字节码7. try-catch-finally8. 参考文档 前言 上一章我们聊了《JVM字节码指令详解》 。本章我们学以致用,聊一下我们常见的一些java语句的特性底层…...
Godot C#连接信号不能像GDScirpt一样自动添加代码
前言 我网上找了好久,发现Godot 对于C# 的支持还有待增强 使用c#脚本有办法像gds那样连接节点自带信号时自动生成信号吗? 百度贴吧 Godot C# How To, Episode 9. Signals With Parameters | Godot Mono 解决方案 把信号拉长,看他的属性 修…...
快速自动化处理JavaScript渲染页面
在进行网络数据抓取时,许多网站使用了JavaScript来动态加载内容,这给传统的网络爬虫带来了一定的挑战。本文将介绍如何使用Selenium和ChromeDriver来实现自动化处理JavaScript渲染页面,并实现有效的数据抓取。 1、Selenium和ChromeDriver简介…...
通过API接口进行商品价格监控,可以按照以下步骤进行操作
要实现通过API接口进行商品价格监控,可以按照以下步骤进行操作: 申请平台账号并选择API接口:根据需要的功能,选择相应的API接口,例如商品API接口、店铺API接口、订单API接口等,这一步骤通常需要我们在相应…...
(vue3)大事记管理系统 文章管理页
[element-plus进阶] 文章列表渲染(带搜索&到分页) 表单架设:当前el-form标签配置一个inline属性,里面的元素就会在一行显示了 中英国际化处理:App.vue中el-config-provider标签包裹组件,意味着整个组…...
springboot 使用RocketMQ客户端生产消费消息DEMO
创建springboot项目省略 项目依赖 注意:当前客户端版本是 5.1.3 ,安装的rocketmq服务的版本要与其对应 <properties><java.version>11</java.version><rocketmq-client-java-version>5.1.3</rocketmq-client-java-version&…...
第三章 内存管理 四、连续分配管理方式
目录 一、内存空间的分配与回收 1、连续分配管理方式 (1)、单一连续分配 优点: 缺点: (2)、固定分区分配 分区大小相等: 分区大小不等: (3)、动态分区…...
npm install报--4048错误和ERR_SOCKET_TIMEOUT问题解决方法之一
一、问题描述 学习vue数字大屏加载动漫效果时,在项目终端页面输入全局下载指令 npm install -g json-server 问题1、报--4048错误 会报如下错误 operation not permitted......errno: -4048code:EPERMsyscall: mkdir......The operation was reiected by your op…...
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意:最终,合并后数组…...
自动泊车系统设计学习笔记
1 概述 1.1 自动泊车系统研究现状 目前对于自动泊车系统的研究方法通常有两种实现方式: 整个泊车操作可以分为四个阶段:第一阶段车辆向前行驶进行车位识别,第二阶段车辆行驶到准备泊车时的待泊车区域,第三阶段车辆按照规划好的…...
基于Java的家电销售网站管理系统设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域…...
设计模式~备忘录模式(memento)-22
目录 (1)优点: (2)缺点: (3)使用场景: (4)注意事项: (5)应用实例: 代码 备忘录模式(memento) 备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对…...
【Agora UID 踩坑记录 Java 数据类型】
目录 负数二进制表示Java中32位无符号数的取法项目踩坑记录Java 0xffffffff隐式类型转换的坑 负数二进制表示 由于计算机中数据都以二进制表示,而负数的二级制是根据正数二进制取补码(补码就是先取反码,然后加1)得到,…...
ESP8285 RTOS SDK OTA
一、官方资源说明 官方指南:空中升级 (OTA) - ESP32 - — ESP-IDF 编程指南 v4.3.6 文档,虽然是正对ESP32的,但是原理是一样的。 官方参考例程:esp-idf\ESP8266_RTOS_SDK\examples\system\ota\,其中包含两个例程&…...
Hadoop3教程(四):HDFS的读写流程及节点距离计算
文章目录 (55)HDFS 写数据流程(56) 节点距离计算(57)机架感知(副本存储节点选择)(58)HDFS 读数据流程参考文献 (55)HDFS 写数据流程 …...
[0xGameCTF 2023] web题解
文章目录 [Week 1]signinbaby_phphello_httprepo_leakping [Week 2]ez_sqli方法一(十六进制绕过)方法二(字符串拼接) ez_upload [Week 1] signin 打开题目,查看下js代码 在main.js里找到flag baby_php <?php /…...
Qt之submodule编译
工作中会遇到这样一种情况:qt应用程序在运行时提示找不到某个qt的动态库。我遇到的是缺少libQt5Websocket.so,因为应用程序是在x86平台银河麒麟v10上开发,能够正常编译运行,然后移植到rk3588(aarch64架构)上…...
Python实现带图形界面的计算器
Python实现带图形界面的计算器 在本文中,我们将使用Python编写一个带有图形用户界面的计算器程序。这个程序将允许用户通过点击按钮或键盘输入数字和操作符,并在显示屏上显示计算结果。 开发环境准备 要运行这个计算器程序,您需要安装Pyth…...
$ vue -Vbash: vue: command not found
$ vue -V bash: vue: command not found报这个错,我们需要找到vue安装路径,添加在环境变量的用户变量中: 1、vue安装路径 2、编辑环境变量 然后重新打开命令框,就可以了...
专业音视频领域中,Pro AV的崛起之路
编者按:在技术进步的加持下,AV行业发展得如何了?本文采访了两位深耕于广播电视行业的技术人,为我们介绍了专业音视频的进展:一位冉冉升起的新星:Pro AV以及FPGA在其中发挥的作用。 美国,拉斯维加…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
