Я уже пару месяцев использую CatML для сохранения данных приложения в файлы, передачи объектов по сети и даже для дампа объектов в лог.
Формат хорош. В своих проектах я отказался от XML/Json/Yaml и не жалею.
Но с ростом объемов данных все более заметными становятся фундаментальные недостатки текстовых форматов:
- они избыточно-огромные,
- они долго записываются и еще дольше парсятся.
Поэтому, в общем, вот новый бинарный формат BinaryCatML.
За magic следует описние объекта.
Объекты, которые могут храниться в файле:
Описание объекта начинается с тегового байта (и часто состоит из одного этого байта).
Теговый байт имеет следующий формат:
CTTT DDDD
D - data - четырехбитовое поле данных, его интерпретация зависит от поля TTT
C - бит продолжения.
T - трехбитное поле "тип объекта".
Байты продолжения имеют формат:
CEEE EEEE
где
E - 7-битное поле, задающее следующие биты числа
С - бит продолжения.
Поле DDDD из тегового байта кодируют 4 младших бита значения.
Каждый байт продолжения несет значения следующих семи бит числа в порядке от младщих байтов к старшим.
Числа от 0 до 15 кодируются одним байтом. Числа 16..2047 - двумя байтми, 2047..262143 тремя и т.д.
Пример трехбайтной последовательности: 1TTTAAAA 1BBBBBBB 0CCCCCCC - двоичное число CC_CCCC_CBBB_BBBB_AAAA
Примеры:
0ТТТ 1100 - единственный теговый байт, хранящий число 1100 = 6 в своем поле D.
1TTT 1010 0010 0000 - два байта, описывающие число 010_0000_1010 = 0x20a = 522
1TTT 1111 1111 1111 0111 1111 - три байта, описывающие число 11_1111_1111_1111_1111 = 0x3FFFF = 262143
Ссылка на новый объект добавляется в конец массива objects.
Ссылка на новую структуру добавляется в конец массива structs.
Имена полей кодируются не в виде обычных строк.
Младший бит длины установлен у всех полей, кроме последнего.
Длина строки смещена на один разряд влево.
Бинарный формат использует тот же самый DOM, что и текстовый CatML.
Это не самостоятельный проект, а пара классов в CatML пакете. Поэтому собственно запись и чтение - это BinaryWriter.encode и BinaryReader.read.
Формат хорош. В своих проектах я отказался от XML/Json/Yaml и не жалею.
Но с ростом объемов данных все более заметными становятся фундаментальные недостатки текстовых форматов:
- они избыточно-огромные,
- они долго записываются и еще дольше парсятся.
Поэтому, в общем, вот новый бинарный формат BinaryCatML.
- Он однозначно конвертируется в текстовый CatML и обратно. Для этого есть консольная утилитка. Вы можете открыть бинарный файл, просмотреть его содержимое, если надо исправить в любом текстовом редакторе и уаковать обратно в бинарный вид.
- Как и текстовый CatML, он имеет в себе всю метаинформацию и умеет кодировать произвольные графы объектов.
- Он разумно компактен. Любое имя поля, имя структуры или объект присутствуют в файле ровно один раз. Это не компрессия, компрессия - убирает избыточность, а BinaryCatML просто не вносит ненужной избыточности.
- Он быстро записывается и быстро загружается. Все идентификаторы - стркутур, объектов - просто индексы в массивах. Никих look-up-ов в словари, никаких сравнений текстовых строк.
- Кодек по минимуму использует память и может работать даже на очень слабых устройствах.
- Он не зависит от разрядности или порядка байт архитектуры, в нем нет ни одного зашитого в формат ограничения.
- Его кодек занимает меньше 300 строк на Java и может быть портирован на любой язык буквально за день.
Формат
Формат BinaryCatML начинается с однобайтового magic 0x8D - улыбка чеширского кота.За magic следует описние объекта.
Объекты, которые могут храниться в файле:
- знаковые и беззнаковые целые числа произвольной разрядности,
- числа с плафающей точкой произвольной точности,
- unicode-строки произвольной длины,
- бинарные данные - массивы байт произвольной длины,
- массивы объектов,
- структуры (при этом у каждой структуры есть именованный тип и набор именованных полей),
- перекрестные ссылки на структуры,
- специальные значения - null, undefined,
- глобальные имена объектов и записи импорта, чтобы связывать между собой данные, полученные из разных файлов.
Описание объекта начинается с тегового байта (и часто состоит из одного этого байта).
Теговый байт имеет следующий формат:
CTTT DDDD
D - data - четырехбитовое поле данных, его интерпретация зависит от поля TTT
C - бит продолжения.
T - трехбитное поле "тип объекта".
Поля D и C
C - бит проложения, установлен, если данные не поместились в четырех битах DDDD. В этом случае за теговым байтом следует один или несколько байт продолжения.Байты продолжения имеют формат:
CEEE EEEE
где
E - 7-битное поле, задающее следующие биты числа
С - бит продолжения.
Поле DDDD из тегового байта кодируют 4 младших бита значения.
Каждый байт продолжения несет значения следующих семи бит числа в порядке от младщих байтов к старшим.
Числа от 0 до 15 кодируются одним байтом. Числа 16..2047 - двумя байтми, 2047..262143 тремя и т.д.
Пример трехбайтной последовательности: 1TTTAAAA 1BBBBBBB 0CCCCCCC - двоичное число CC_CCCC_CBBB_BBBB_AAAA
Примеры:
0ТТТ 1100 - единственный теговый байт, хранящий число 1100 = 6 в своем поле D.
1TTT 1010 0010 0000 - два байта, описывающие число 010_0000_1010 = 0x20a = 522
1TTT 1111 1111 1111 0111 1111 - три байта, описывающие число 11_1111_1111_1111_1111 = 0x3FFFF = 262143
Поле T - "тип объекта":
- 000 положительнное число. Поле D кодирует целое число.
- 001 отрицательное число. Поле D кодирует его значение.
- 010 строка. Поле D - кодирует длину. За теговым байтом следуют unicode-символы в кодирвоании base 128 varint
- 011 бинарные данные. Поле D - кодирует длину. За теговым байтом следуют байты данных.
- 100 расширение типа. Поле D:
- 0 - null
- 1 - undefined
- 2 - import, за теговым байтом следует строка, см. "импорт и экспорт".
- 3 - export, за теговым байтом следует строка и определение объекта.
- 4 - число с плавающей точкой.
- 101 массив. Поле D задает длину. За теговым байтом следуют описания объектов - элементов массива.
- 110 мастер-ссылка на объект. Поле D кодирует идентификатор (id) объекта или идентификатор типа структуры.
- 111 weak-ссылка на объект. Кодируется аналогично типу 110.
Идентификаторы объектов.
При парсинге формата создаются и пополняются два массива - массив объектов (objects) и массив типов структур (structs).- Если id > structs.size, это ссылка на уже загруженный объект, objects[id - structs.size - 1]
- Если id == structs.size, далее следует описание новой структуры (имя и список полей) и описание объекта с этой структурой.
- Если id < structs.size, из массива structs[id] берется описание структуры и далее сделует описания объекта с этой структурой.
Ссылка на новый объект добавляется в конец массива objects.
Ссылка на новую структуру добавляется в конец массива structs.
Кодирование строк:
- длина строки base 128 varint.
- символы в base 128 varint.
Описание новой структуры:
- строка с именем структуры
- имена полей.
Имена полей кодируются не в виде обычных строк.
Младший бит длины установлен у всех полей, кроме последнего.
Длина строки смещена на один разряд влево.
Примеры
Пример1:
0 //число 0Кодируется
8D00Расшифровка
8d // magic=8d 00 // 0_000_0000 - нет байта продолжения. T=0 положительное целое. D=0
Пример2:
[1, [65536, 3]]
:
1
:
65536
3
Кодируется
8d52015280802003Расшифровка
8D // magic
52 // массив из двух элементов
01 // нулевой элемент массива - число int 1
52 // первый элемент массива - массив из двух элементов
80 80 20 // int 65536
03 // int 3
Пример3:
TBDПример4:
Point x 10 y 20Кодируется
8D6005506f696e74037802790a8401Расшифровка
8D // magic
60 // master reference. новая структура
// (структуре приписан id по порядку = 0)
05 'Point' // имя структуры - строка со счетчиком
03 'x' // имя поля, длина строки сдвинута на 1 бит влево,
// младший бит установлен - это признак, что есть еще поля
02 'y' // имя поля. младший бит сброшен. это последнее поле.
// конец описания структуры, начало описания экземпляра.
// эземпляр получает id по порядку = 0
0a // int 10
84 01 // int 20
Пример5:
Project
priority 4
tasks:
Task.t1
title "Analysis"
Task.t2
title "Coding"
depends:
=t1
Task.t3
title "Test Cases"
depends:
=t1
Task.t4
title "Test Cycles"
depends:
=t2
=t4
Кодируется
8D600750726f6a656374117072696f726974790a7461736b73045461045461736b 0b7469746c650e646570656e647328416e616c79736973416126436f64696e6751 74612a546573742043617365735174612b54657374204379636c6573527577Расшифровка
8d // magic
60 // master reference. Новая структура.
// StrucId=0
07 'Project' // имя структуры. строка со счетчиком
11 'priority' // имя поля. строка со счетчиком shr 1.
// признак не последнего поля установлен.
0a 'tasks' // имя поля. строка со счетчиком shr 1.
// признак не последнего поля = 0
// конец поределения структуры.
// начало определения экземпляра.
// Экpемпляр получает id=0
04 // поле proirity = int 4
54 // поле tasks, массив из 4 элементов
61 // нулевой элемент массива tasks.
// master reference на новую структуру,
// structId=1
04 'Task' // имя новой структуры. строка со счетчиком
0b 'title' // имя поля. не последнее
0e 'depends' // имя поля. последнее
// конец определения структуры.
// начало определения экземпляра.
// экземпляр получает id=1
28 'Analysis' // поле title. строка.
41 // depends. undefined
61 // первый элемент массива tasks.
// maser ref на структурy уже известного типа
// (structId=1)
// далее идет определение нового экемпляра этой структуры.
// он получает id=2
26 'Coding' // поле title. строка.
51 // поле depends. массив из одного элемента.
74 // элемент массива. weak reference
// на уже определенный экземпляр.
// id = 4-structs.count-1 = 1
61 // остальные элементы определяются аналогично.
2a 'Test Cases'
51
74
61
2b 'Test Cycles'
52
75
77
Библиотека и использование
Исходный код на Java можно забрать тут:CatML_src.zipБинарный формат использует тот же самый DOM, что и текстовый CatML.
Это не самостоятельный проект, а пара классов в CatML пакете. Поэтому собственно запись и чтение - это BinaryWriter.encode и BinaryReader.read.
Dom dom = new JavaDom();
Object root = new Object[]{1024, new Object[]{65536,24}};
OutputStream file = new FileOutputStream("test.8D");
// запись объекта в бинарный формат
BinaryWriter.encode(dom, root, file);
file.close();
// чтение из файла
Object r = BinaryReader.read(dom, new FileInputStream("test.8D"), dom);
// и вывод на консоль в текстовый CatML
CatWriter.encode(dom, r, new OutputStreamWriter(System.out));
No comments:
Post a Comment