字节码进阶之java Instrumentation原理详解
文章目录
- 0. 前言
- 1. 基础
- 2. Java Instrumentation API
- 使用示例
- 3. Java Agent
- 4. 字节码操作库
- 5. 实际应用
- 6. 注意事项和最佳实践

0. 前言
Java Instrumentation是Java API的一部分,它允许开发人员在运行时修改类的字节码。使用此功能,可以实现许多高级操作,例如性能监控、代码覆盖率分析等。
通过Java提供的Instrumentation接口,我们能够在运行时改变和监控Java程序,这种能力在许多场景中都非常有用,比如性能调优、故障排查、代码覆盖率分析等。然而,Instrumentation接口的使用和字节码操作本身都是高级主题,需要深入理解JVM和字节码的工作原理。
本文我们一起深入讲解Java Instrumentation的原理,并通过实际的例子展示如何使用字节码操作库来实现类的修改。
在字节码的世界里,一切皆可能。让我们开搞!
1. 基础
Java Instrumentation的概念和用途
Java Instrumentation是Java编程语言的一个特性,它允许开发者在Java程序运行时检查和修改应用程序的行为,特别是类和对象的行为。Java提供了一个名为java.lang.instrument
的包,其中包含API用于在运行时更改和监控Java类。
Java Instrumentation的主要功能包括:
- 在类文件加载到JVM之前,改变类文件的字节码。
- 在运行时计算应用程序中对象的大小。
- 在运行时更改类的定义。
- 为JVM提供一种获取加载到Java应用程序的类文件的方式。
Java Instrumentation的主要用途包括性能监控(例如,计算方法调用的时间)、故障排查、代码覆盖率分析、内存分析、线程分析等。
Java Agent的介绍
Java Agent是Java Instrumentation的一种应用,是一种特殊的Java程序,它能够通过Java Instrumentation API修改其他Java程序的字节码。Java Agent在主程序之前启动,可以在类加载到JVM之前改变类的字节码。
Java Agent主要有两种类型:静态agent和动态agent。静态agent在JVM启动时通过命令行参数指定,并在主程序启动之前运行。动态agent则可以在JVM运行时随时加载。
Java Agent可以用来实现各种复杂的任务,例如性能监控、日志记录、代码审计等。一些常见的Java诊断和监控工具(如JProfiler、VisualVM等)就是通过Java Agent实现的。
2. Java Instrumentation API
Java Instrumentation API
Java Instrumentation API位于java.lang.instrument
包内,这个包提供了类和接口供开发者进行字节码操作。
java.lang.instrument包中的关键类和接口
-
Instrumentation
:此接口提供了用于实施字节码转换和获取对象的相关信息的方法。 -
ClassFileTransformer
:这是一个接口,其中定义了一个transform方法,允许我们在类加载到JVM之前对其进行转换。 -
UnmodifiableClassException
:这是一个异常,会在尝试修改或重定义它的状态时,由Instrumentation.retransformClasses方法和Instrumentation.redefineClasses方法抛出。
Instrumentation接口及其方法
Instrumentation接口提供了许多方法,这些方法可以用来改变和检查应用程序的行为,包括:
addTransformer(ClassFileTransformer transformer, boolean canRetransform)
: 添加一个类文件转换器。removeTransformer(ClassFileTransformer transformer)
: 移除一个类文件转换器。redefineClasses(ClassDefinition... definitions)
: 重新定义类。retransformClasses(Class<?>... classes)
: 改变已经加载到JVM的类。getObjectSize(Object objectToSize)
: 返回对象的大小(以字节为单位)。
ClassFileTransformer接口
ClassFileTransformer接口是Java Instrumentation API中的重要组成部分,它让我们在类加载到虚拟机之前,可以修改类的字节码。
transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
: 这个方法会在类被加载到JVM之前被调用,它可以修改类的字节码。
使用示例
假设我们想要跟踪每个方法调用的执行时间,我们可以使用Java Instrumentation和ASM(一个常用的Java字节码操作库)来实现。以下是一个如何实现的例子:
- 需要一个
ClassFileTransformer
来转换类文件:
import org.objectweb.asm.*;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;public class ProfilingTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if (className.startsWith("my/package/")) { // only transform classes in my packageClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr, 0);ClassVisitor cv = new ProfilingClassAdapter(cw);cr.accept(cv, 0);return cw.toByteArray();} else {return classfileBuffer;}}
}
- 需要一个
ClassVisitor
来开始转换类,并一个MethodVisitor
来插入我们的代码
import org.objectweb.asm.*;public class ProfilingClassAdapter extends ClassVisitor {public ProfilingClassAdapter(ClassVisitor cv) {super(Opcodes.ASM5, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);return new ProfilingMethodAdapter(mv, access, name, desc);}
}public class ProfilingMethodAdapter extends MethodVisitor {private String methodName;public ProfilingMethodAdapter(MethodVisitor mv, int access, String name, String desc) {super(Opcodes.ASM5, mv);this.methodName = name;}@Overridepublic void visitCode() {mv.visitCode();mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);mv.visitVarInsn(Opcodes.LSTORE, 1); // store start time to local variable}@Overridepublic void visitInsn(int opcode) {if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);mv.visitVarInsn(Opcodes.LLOAD, 1); // load start timemv.visitInsn(Opcodes.LSUB); // get elapsed time by subtracting start time from current timemv.visitVarInsn(Opcodes.LSTORE, 3); // store elapsed time to local variablemv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitLdcInsn("execution time of " + methodName + ": ");mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitVarInsn(Opcodes.LLOAD, 3); // load elapsed timemv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false); }mv.visitInsn(opcode);}
}
- 需要创建一个Java Agent来使用这个
ClassFileTransformer
:
import java.lang.instrument.Instrumentation;public class ProfilingAgent {public static void premain(String agentArgs, Instrumentation inst) {inst.addTransformer(new ProfilingTransformer());}
}
以上代码在每个方法的开始和结束时,插入了代码来获取当前时间,计算出方法的执行时间,并将执行时间打印出来。此代码编译后,需要打包为JAR文件,并在JAR的MANIFEST.MF文件中指定Premain-Class
属性为ProfilingAgent
,然后就可以使用-javaagent:ProfilingAgent.jar
参数启动JVM了。
3. Java Agent
Java Agent的概念
Java Agent是一种特殊的Java应用程序,它能够通过Java Instrumentation API修改其他Java程序的字节码。Java Agent在主程序之前启动,可以在类加载到JVM之前改变类的字节码。Java Agent可以用来实现各种复杂的任务,例如性能监控、日志记录、代码审计等。
如何创建Java Agent
要创建Java Agent,你需要创建一个Java类,并实现premain
方法。这个方法在主程序的main
方法之前执行,它的签名如下:
public static void premain(String agentArgs, Instrumentation inst)
你可以使用Instrumentation
参数来添加你的ClassFileTransformer
。
创建Agent的步骤:实现premain方法、创建MANIFEST.MF文件、打包为JAR
- 实现
premain
方法:在你的Java Agent类中,实现premain
方法。 - 创建
MANIFEST.MF
文件:在MANIFEST.MF
文件中,设置Premain-Class
属性为你的Java Agent类的全名。 - 打包为JAR:将你的类和
MANIFEST.MF
文件打包为一个JAR文件。
在JVM启动时附加Java Agent
当启动JVM时,你可以使用-javaagent
参数来指定你的Java Agent JAR文件,如下:
java -javaagent:myAgent.jar -jar myApplication.jar
在运行时动态附加Java Agent(Attach API)
从Java 6开始,你也可以在运行时动态地附加Java Agent。这需要你再实现一个agentmain
方法,这个方法在动态附加Java Agent时会被调用。其签名如下:
public static void agentmain(String agentArgs, Instrumentation inst)
要动态附加Java Agent,你需要使用Java的Attach API,例如:
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentJarFilePath, agentArgs);
vm.detach();
上述代码会将Java Agent附加到运行中的JVM中,其中pid
是你要附加的JVM的进程ID,agentJarFilePath
是你的Java Agent JAR文件的路径,agentArgs
是传递给agentmain
方法的参数。
4. 字节码操作库
使用字节码操作库实现类的修改
字节码操作库能够帮助我们操作和修改Java类的字节码。这些库提供了一种在运行时修改Java类的方法,例如添加、修改或删除类的字段和方法,改变类的继承结构等。这些操作是通过直接修改类的字节码来实现的。
ASM
ASM是一个通用的Java字节码操纵和分析框架。它可以用来修改现有类或者动态生成新的类。ASM提供了一些核心API用于直接操作字节码,这使得ASM非常强大,但也意味着使用ASM需要了解Java字节码的详细知识。
ByteBuddy
Byte Buddy是一个新的库,用于创建和修改Java类。Byte Buddy的API设计得非常友好,适合那些不熟悉Java字节码的开发者使用。Byte Buddy也提供了一些高级特性,如方法调用代理和Java Agent的支持。
CGLIB
CGLIB(Code Generation Library)是一个开源的项目,它提供了一些强大的高级功能,如方法拦截和创建代理类。CGLIB通过使用ASM框架在运行时动态生成和加载新的Java类。CGLIB常常被用在许多流行的开源项目中,如Spring和Hibernate。
Javassist
Javassist(Java Programming Assistant)使Java字节码的编辑变得简单。它是一个类库,为修改字节码提供了两级的接口:源码级和字节码级。源码级接口允许以源码形式(如字符串)修改类的字段和方法,字节码级接口允许直接修改字节码。
对比和选择适合的库
库名称 | 描述 | 优点 | 缺点 |
---|---|---|---|
ASM | 一个通用的Java字节码操纵和分析框架 | 提供了一些核心API用于直接操作字节码,非常强大 | 使用ASM需要了解Java字节码的详细知识 |
ByteBuddy | 用于创建和修改Java类的库 | API设计得非常友好,适合那些不熟悉Java字节码的开发者使用,提供了一些高级特性 | 相比于ASM,可能无法进行一些更底层的操作 |
CGLIB | 提供了一些强大的高级功能,如方法拦截和创建代理类 | 常常被用在许多流行的开源项目中,如Spring和Hibernate | 使用ASM框架,需要一定的字节码知识 |
Javassist | 使Java字节码的编辑变得简单 | 提供了源码级和字节码级接口,使得操作更直观 | 虽然方便,但在性能和灵活性上可能不如ASM |
这只是一个大概的比较,具体使用哪个库还是要根据个人的实际需求和偏好来决定。每个库都有各自的优点和特色,了解它们的特性和优缺点,可以帮助你做出更好的决定。
- 如果你需要最大的灵活性,并且不介意处理底层的字节码细节,那么ASM可能是最好的选择。
- 如果你希望有一个简单、易用的API,并且需要一些高级特性,如方法调用代理,那么Byte Buddy可能是最好的选择。
- 如果你需要生成代理类,或者你正在使用依赖于CGLIB的库(如Spring和Hibernate),那么CGLIB可能是最好的选择。
- 如果你希望能够以源代码形式修改类,那么Javassist可能是最好的选择。
5. 实际应用
如何使用Java Instrumentation实现性能监控
为了使用Java Instrumentation实现性能监控,可以创建一个Java Agent,这个Agent会添加代码来跟踪每个方法的执行时间。
如何使用Java Instrumentation实现代码覆盖率分析
代码覆盖率分析是检查你的测试用例覆盖了多少代码的一种方法。可以使用Java Agent来修改类的字节码,添加代码来跟踪每个方法和代码块的执行情况。
public class CodeCoverageMethodAdapter extends MethodVisitor {private String className;private String methodName;public CodeCoverageMethodAdapter(MethodVisitor mv, String className, String methodName) {super(Opcodes.ASM5, mv);this.className = className;this.methodName = methodName;}// 添加代码来跟踪方法的执行情况@Overridepublic void visitCode() {mv.visitCode();mv.visitFieldInsn(Opcodes.GETSTATIC, "my/package/CodeCoverage", "executedMethods", "Ljava/util/Set;");mv.visitLdcInsn(className + "." + methodName);mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Set", "add", "(Ljava/lang/Object;)Z", true);mv.visitInsn(Opcodes.POP);}
}
CodeCoverage
是一个你自己创建的类,它有一个Set
类型的静态字段executedMethods
,用于存储已经执行过的方法。然后,你需要在每个方法的开始处添加代码,将当前方法添加到executedMethods
中。
如何使用Java Instrumentation实现故障排查和诊断工具
故障排查和诊断工具通常需要获取一些低级别的信息,例如对象的创建和销毁,方法的调用情况等。可以使用Java Agent来收集这些信息。下面是一个跟踪对象创建的例子:
public class ObjectCreationClassAdapter extends ClassVisitor {public ObjectCreationClassAdapter(ClassVisitor cv) {super(Opcodes.ASM5, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);if ("<init>".equals(name)) { // 如果是构造方法mv = new ObjectCreationMethodAdapter(mv);}return mv;}
}public class ObjectCreationMethodAdapter extends MethodVisitor {public ObjectCreationMethodAdapter(MethodVisitor mv) {super(Opcodes.ASM5, mv);}// 在对象创建后添加代码来跟踪对象的创建@Overridepublic void visitInsn(int opcode) {if (opcode == Opcodes.RETURN) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, "my/package/Tracker", "trackObjectCreation", "()V", false);}mv.visitInsn(opcode);}
}
<>Tracker
是一个创建的类,它有一个静态方法trackObjectCreation
,用于跟踪对象的创建。然后,你需要在每个构造方法的结束处添加代码,调用trackObjectCreation
方法。
6. 注意事项和最佳实践
避免破坏类的结构和逻辑
字节码操作是一项强大的功能,但如果使用不当,可能会导致一些意想不到的问题。在进行字节码操作时,你需要确保不要破坏类的结构和逻辑。例如,不要删除或更改类的重要方法,不要添加破坏类行为的代码等。
优雅地处理异常
在字节码操作的过程中,可能会出现各种各样的异常。你需要确保在你的代码中妥善处理这些异常。一般来说,你应该尽量减少对异常的静默处理,而是应该将异常记录下来,便于后续的问题排查。
注意性能影响
虽然字节码操作可以实现强大的功能,但它同时也可能对应用程序的性能产生影响。你需要确保你的字节码操作不会对性能产生太大的影响。在进行性能敏感的字节码操作时,你可能需要对你的代码进行性能测试,以确保它不会成为性能瓶颈。
一些常见的性能影响因素包括:增加过多的方法调用,添加大量的同步代码,过度使用反射等。
相关文章:

字节码进阶之java Instrumentation原理详解
文章目录 0. 前言1. 基础2. Java Instrumentation API使用示例 3. Java Agent4. 字节码操作库5. 实际应用6. 注意事项和最佳实践 0. 前言 Java Instrumentation是Java API的一部分,它允许开发人员在运行时修改类的字节码。使用此功能,可以实现许多高级操…...
Android 13.0 锁屏页面禁止下拉状态栏
1.概述 在13.0的系统产品定制化中,在默认的锁屏界面的时候原生系统是可以下拉状态栏的,但是定制的产品是需要禁用下拉状态栏的,所以需要在锁屏页面的时候禁用下拉状态栏,需要从两部分查看下拉状态栏流程然后禁用状态栏 接下来就来分析下看这个功能怎么实现 2.锁屏页面禁止…...

Windows10 Docker 安装教程
Docker Desktop是什么? Docker Desktop是适用于Windows的Docker桌面,是Docker设计用于在Windows 10上运行。它是一个本地 Windows 应用程序,为构建、交付和运行dockerized应用程序提供易于使用的开发环境。Docker Desktop for Windows 使用 …...

JWT认证
目录 前言 JWT组成部分 JWT工作原理 在Express中使用JWT 安装JWT相关的包 导入JWT相关的包 定义密钥 登录成功后调用jwt.sign()生成JWT字符串 将JWT字符串还原为JSON对象 捕获解析JWT失败后产生的错误 结尾 前言 Session 认证机制需要配合 Cookie 才能实现。由于 Co…...

【网络安全 --- xss-labs靶场通关(1-10关)】详细的xss-labs靶场通关思路及技巧讲解,让你对xss漏洞的理解更深刻
靶场安装: 靶场安装请参考以下博客,既详细有提供工具: 【网络安全 --- xss-labs靶场】xss-labs靶场安装详细教程,让你巩固对xss漏洞的理解及绕过技巧和方法(提供资源)-CSDN博客【网络安全 --- xss-labs通…...

Mathematics-Vocabulary·数学专业英语词汇
点击查看: Mathematics-Vocabulary数学专业英语词汇点击查看: Mathematics-Vocabulary-Offline数学专业英语词汇离线版本 Chinese-English translation英译汉The study of mathematics in English requires understanding the subject-specific vocabulary and terminology. Ma…...

画程序流程图
一。在线程序流程图。类图和时序图 Integrations | Mermaid 二。VSCODE画UML图和各种种 1.下载plantuml.jarReleases plantuml/plantuml GitHubGenerate diagrams from textual description. Contribute to plantuml/plantuml development by creating an account on GitHu…...

C++ 模板进阶
非类型模板参数 模板参数分为:类型模板参数与非类型模板参数 类型模板参数即:出现在模板参数列表中,跟在class或者typename之后的参数类型名称非类型模板参数即:用一个常量作为类(函数)模板的一个参数,在类(函数)模…...

jenkins 安装与使用、用户权限划分
jenkins 安装与使用 安装插件: 开启该插件功能 验证用户管理 创建web01~02 使用web01登录 用户权限划分 安装 Role-Based Strategy 插件后,系统管理 中多了如图下所示的一个功能,用户权限的划分就是靠他来做的 创建角色 重新访问 创建项目…...

Hadoop3教程(三十三):(生产调优篇)慢磁盘监控与小文件归档
文章目录 (161)慢磁盘监控(162)小文件归档小文件过多的问题如何对小文件进行归档 参考文献 (161)慢磁盘监控 慢磁盘,是指写入数据时特别慢的一类磁盘。这种磁盘并不少见,当机器运行…...

物联网知识复习
物联网的内涵和体系结构 物联网的基本内涵 物联网的基本内涵在于物联,物物相连或者物和人相连的互联网。 也就是说,它是要由物主动发起的,物物互联的互联网。 它的第一层意思是说物和物相连;第二层意思是说物和人相连。 物联网的…...
Golang爬虫入门指南
引言 网络爬虫是一种自动化程序,用于从互联网上收集信息。随着互联网的迅速发展,爬虫技术在各行各业中越来越受欢迎。Golang作为一种高效、并发性好的编程语言,也逐渐成为爬虫开发的首选语言。本文将介绍使用Golang编写爬虫的基础知识和技巧…...

1024渗透测试如何暴力破解其他人主机的密码(第十一课)
1024渗透测试如何暴力破解其他人主机的密码(第十一课) 1 crunch 工具 crunch是一个密码生成器,一般用于在渗透测试中生成随机密码或者字典攻击。下面是常见的一些使用方法: 生成密码字典 生成6位数字的字典:crunch 6 6 -t -o dict.txt …...

记录一次线下渗透电气照明系统(分析与实战)
项目地址:https://github.com/MartinxMax/S-Clustr 注意 本次行动未造成任何设备损坏,并在道德允许范围内测试 >ethical hacking< 发现过程 在路途中,发现一个未锁的配电柜,身为一个电工自然免不了好奇心(非专业人士请勿模仿,操作不当的话220V人就直了) 根据照片,简…...
Android ADB 常用命令及详解
Android ADB 常用命令及详解 Android Debug Bridge(ADB)是 Android 开发工具包(SDK)的一部分,用于与 Android 设备通信和执行各种任务。无论你是 Android 开发者还是普通用户,了解 ADB 命令是非常有用的&a…...
GO 工程下载依赖操作流程(go mod)
1. 写一个main.go文件 package main import ("fmt""net/http""github.com/ClickHouse/clickhouse-go" ) func main() {fmt.Println("服务启动......")http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Requ…...

[Linux打怪升级之路]-system V共享内存
前言 作者:小蜗牛向前冲 名言:我可以接受失败,但我不能接受放弃 如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标&…...

STM32不使用 cubeMX实现外部中断
这篇文章将介绍如何不使用 cubeMX完成外部中断的配置和实现。 文章目录 前言一、文件加入工程二、代码解析exti.cexti.hmain.c 注意:总结 前言 实验开发板:STM32F103C8T6。所需软件:keil5 , cubeMX 。实验目的:如何不…...

Nautilus Chain 与 Coin98 生态达成合作,加速 Zebec 生态亚洲战略进
目前,行业内首个模块化 Layer3 架构公链 Nautilus Chain 已经上线主网,揭示了模块化区块链领域迎来了全新的进程。在主网上线后,Nautilus Chain 将扮演 Zebec 生态中最重要的底层设施角色,并将为 Zebec APP 以及 Zebec Payroll 规…...

method.isAnnotationPresent(Xxx.class)一直为null
package com.dj.springtest.aspect;import com.dj.springtest.annotation.RequireRoles; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.s…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...