Сложные данные и MemoryLayout

Последнее обновление: 21.12.2025

Ранее мы рассмотрели, как работать с сегментами памяти, которые содержат строки UTF-8 и массивы значений примитивного типа. Но возникает вопрос, как быть с более сложными по структуре данными? В языке C для описания таких данных применяются структуры. В Java для преобразования в нативный формат и обратно применяются кастомные структуры памяти, которые определяются с помощью метода MemoryLayout.structLayout():

static StructLayout structLayout(MemoryLayout... elements)

В этот метод передается набор значений типа MemoryLayout.

К примеру, если на Си у нас есть следующая структура Point, которая представляет точку:

typedef struct {
    int x;
    int y;
} Point;

То в Java этой структуре будет соответствовать следующая компоновка памяти:

StructLayout POINT = MemoryLayout.structLayout(
    ValueLayout.JAVA_INT.withName("x"),
    ValueLayout.JAVA_INT.withName("y")
);

Интерфейс MemoryLayout предоставляет ряд специальных методов для управления выделенной памятью, в частности, метод withName(String name) возвращает структуру памяти (объект MemoryLayout) с теми же характеристиками, что и данная структура, но с заданным именем. Таким образом, с помощью выражения

ValueLayout.JAVA_INT.withName("x")

определяем сегмент памяти размером в 4 байта (размера типа int в Java), который сопоставлен с именем "x".

Выделение сегмента памяти для данной структуры:

import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;

public class Program {

    public static void main(String[] args) {

        StructLayout POINT = MemoryLayout.structLayout(
            ValueLayout.JAVA_INT.withName("x"),
            ValueLayout.JAVA_INT.withName("y")
        );
        try (Arena arena = Arena.ofConfined()) {

            // выделяем память для 1 объекта POINT
            MemorySegment point_seg = arena.allocate(POINT);

            System.out.println(point_seg);  // MemorySegment{ kind: native, address: 0x72760c180400, byteSize: 8 }
        } 
    }
}

При сопоставлении типов C и Java длины байтов должны совпадать. Но беззнаковые типы C сопоставляются с их эквивалентами в Java. При обработке этих значений в Java необходимо самостоятельно обрабатывать их как беззнаковые значения.

В примере выше мы не просто так установили для выделенных сегментов памяти имена ("x" и "y"). Используя методы сегментов памяти, мы можем затем получить или установить компоненты структуры:

import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;

public class Program {

    public static void main(String[] args) {

        // определяем структуру
        StructLayout POINT = MemoryLayout.structLayout(
            ValueLayout.JAVA_INT.withName("x"),
            ValueLayout.JAVA_INT.withName("y")
        );
        try (Arena arena = Arena.ofConfined()) {

            // выделяем память для 1 объекта POINT
            MemorySegment point_seg = arena.allocate(POINT);

            // устанавливаем значения x и y
            point_seg.set(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("x")), 20);
            point_seg.set(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("y")), 15);

            // получаем значения x и y
            int x = point_seg.get(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("x")));
            int y = point_seg.get(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("y")));

            System.out.println("x: " + x);  // 20
            System.out.println("y: " + y);  // 15
        } 
    }
}

Методы get/set обращаются к определенным участкам памяти, используя смещения. Для получения смещения применяется метод byteOffset(), в который передается путь к элементу в памяти. Для получения пути используется имя. Например, выражение

POINT.byteOffset(PathElement.groupElement("x"))

позволяет получить смещение относительно начала структуры для сегмента, который сопоставляется с именем "x".

В принципе мы могли бы передать и числовое значение смещения, если оно нам известно:

// поле y смещено на 4 байта относительно начала структуры
point_seg.set(ValueLayout.JAVA_INT, 4, 17);

// получаем значения  y
int y = point_seg.get(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("y")));

System.out.println("y: " + y);  // y: 17

Объединения

Подобно тому, как StructLayout описывает расположение структур C, тип UnionLayout описывает объединение (union) языка C. Работа с UnionLayout во многом аналогична работе с StructLayout.

Составная компоновка SequenceLayout

Говоря о компоновке памяти, следует отметить интерфейс SequenceLayout, который предоставляет составную компоновку - однородное повторение заданной компоновки элементов или последовательность. Количество повторений называется количеством элементов в последовательности. Последовательность можно рассматривать как структурную компоновку, в которой количество повторений элементов равно количеству элементов в последовательности.

Для создания этого типа компоновки памяти применяется метод MemoryLayout.sequenceLayout()

static SequenceLayout sequenceLayout(long elementCount, MemoryLayout elementLayout)

Метод принимает количество элементов и компоновку элемента в виде значения MemoryLayout

Например, используем SequenceLayout для определения массива структур:

// массив структур
SequenceLayout points_layout = MemoryLayout.sequenceLayout(count, 
    MemoryLayout.structLayout( 
        JAVA_INT.withName("x"),
        JAVA_INT.withName("y")
    )
);

Для обращения к компоненту y структуры с некоторым индексом i, нам нужно вычислить смещение в байтах:

long offset = points_layout.byteOffset( 
    PathElement.sequenceElement(i), 
    PathElement.groupElement("y")
);

Здесь метод byteOffset получает последовательность объектов MemoryLayout.PathElement, которые позволяют получить смещение к нужному компоненту.

Получив соответствующий сегмент памяти, мы можем получить или установить само значение компонента:

MemorySegment points = Arena.global().allocate(point_layout);
int value = 6;  // значение для y
points.set(JAVA_FLOAT, offset, value, y);

Пример программы полностью:

import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;

public class Program {

    public static void main(String[] args) {

        int count = 4;  // количество структур
        // массив структур
        SequenceLayout points_layout = MemoryLayout.sequenceLayout(count, 
            MemoryLayout.structLayout( 
                JAVA_INT.withName("x"),
                JAVA_INT.withName("y")
            )
        );
        try (Arena arena = Arena.ofConfined()) {

            // выделяем память для массива структур
            MemorySegment points = arena.allocate(points_layout);

            int index = 1;      // индекс нужной структуры в массиве

            // получаем смещение поля y для структуры с индексом index
            long offset = points_layout.byteOffset( 
                PathElement.sequenceElement(index), 
                PathElement.groupElement("y")
            );
            // устанавливаем значение y
            points.set(ValueLayout.JAVA_INT, offset, 24);

            // получаем значение y для структуры с индексом index
            int y = points.get(ValueLayout.JAVA_INT, offset);

            System.out.println("y: " + y);  // 24
        } 
    }
}
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.