布局与结构体
描述原生内存布局
布局与结构体 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
描述本地内存
本地数据很少只有一个值。C 使用结构体、数组和联合体。FFM 使用内存布局来表示这些形状,从而安全地读写结构化的本地内存。
布局类型体系
布局类型构成了一个小型层次结构:
ValueLayout:基本类型(整数、长整数、双精度浮点数、地址)SequenceLayout:由某种布局构成的固定大小数组StructLayout:字段按顺序排列UnionLayout:字段相互重叠
值布局
最简单的布局是内置值布局。它们包含大小、对齐方式和字节序信息。
import java.lang.foreign.ValueLayout;
public class Main {
public static void main(String[] args) {
System.out.println("int size: " + ValueLayout.JAVA_INT.byteSize());
System.out.println("double size: " + ValueLayout.JAVA_DOUBLE.byteSize());
}
}定义结构体
使用 MemoryLayout.structLayout(...) 表示 C 结构体,并通过 withName 为每个字段命名。考虑下面的 C 代码:struct Point { int x; int y; }。
import java.lang.foreign.*;
public class Main {
public static void main(String[] args) {
StructLayout point = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y"));
System.out.println("Point size: " + point.byteSize());
}
}字段的 VarHandle
要读取或写入命名字段,请使用路径表达式派生一个 VarHandle。groupElement("x") 会按名称选择字段。
import java.lang.foreign.*;
import java.lang.invoke.VarHandle;
public class Main {
public static void main(String[] args) {
StructLayout point = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y"));
VarHandle xHandle = point.varHandle(
MemoryLayout.PathElement.groupElement("x"));
System.out.println("Built VarHandle for x: " + xHandle);
}
}读写结构体
分配一个大小与布局匹配的内存段,然后使用 VarHandle 设置和获取字段。这与填充 C 结构体的方式相似。
import java.lang.foreign.*;
import java.lang.invoke.VarHandle;
public class Main {
public static void main(String[] args) {
StructLayout point = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y"));
VarHandle xh = point.varHandle(MemoryLayout.PathElement.groupElement("x"));
VarHandle yh = point.varHandle(MemoryLayout.PathElement.groupElement("y"));
try (Arena arena = Arena.ofConfined()) {
MemorySegment p = arena.allocate(point);
xh.set(p, 0L, 3);
yh.set(p, 0L, 4);
System.out.println("Point = (" + xh.get(p, 0L) + ", " + yh.get(p, 0L) + ")");
}
}
}填充与对齐
C 编译器会插入填充,以确保字段正确对齐。FFM不会自动添加填充,因此必须加入 MemoryLayout.paddingLayout(n),以便与 C 布局完全匹配。
对齐方式错误会破坏间隔之后的每一个字段。
显式填充示例
对于 struct { char c; int n; },整数必须按 4 字节对齐,因此 char 后面要跟随 3 个填充字节。
import java.lang.foreign.*;
public class Main {
public static void main(String[] args) {
StructLayout s = MemoryLayout.structLayout(
ValueLayout.JAVA_BYTE.withName("c"),
MemoryLayout.paddingLayout(3),
ValueLayout.JAVA_INT.withName("n"));
System.out.println("Aligned struct size: " + s.byteSize());
}
}序列布局
SequenceLayout 用于描述数组。MemoryLayout.sequenceLayout(count, element) 可以表示例如包含 10 个双精度浮点数的数组。
import java.lang.foreign.*;
public class Main {
public static void main(String[] args) {
SequenceLayout arr = MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_DOUBLE);
System.out.println("Array bytes: " + arr.byteSize());
}
}嵌套布局
布局可以组合。结构体可以包含序列,序列也可以包含结构体。使用 sequenceElement() 和 groupElement(name) 这样的路径表达式,可以深入访问嵌套数据。
这样就能处理任意复杂程度的真实 C 数据结构。
偏移量与切片
您可以通过路径查询字段的 byteOffset,并使用 segment.asSlice(offset, size) 获取子内存段;这对于将结构体中的某个字段传递给本地函数很有用。
快速检查
回想一下,结构体中常见的陷阱是什么。
总结
您已经学会了描述本地数据:
- 布局类型体系:
ValueLayout、SequenceLayout、StructLayout、UnionLayout - 使用
withName为字段命名;通过VarHandle和路径元素访问字段 - 添加显式的
paddingLayout,以确保正确对齐 - 组合嵌套布局,并使用
asSlice创建切片
至此,外部函数与内存 API 课程就完成了。
常见问题解答
「布局与结构体」课时是免费的吗?
是的 — 「布局与结构体」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「布局与结构体」这节课中我会学到什么?
描述原生内存布局 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「布局与结构体」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。