Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Как сохранить двоичный код в файл? (https://javascript.ru/forum/misc/84848-kak-sokhranit-dvoichnyjj-kod-v-fajjl.html)

webgraph 15.01.2023 12:47

Как сохранить двоичный код в файл?
 
Как сохранить число или иную информацию в файл в виде двоичного кода?

//1673357377000 — весит целых 13 байтов

console.log((1673357377000 >>> 0).toString(2));

//10011011110111110011110111101000 — весит всего 4 байта




//340282366920938463463374607431768211455 — весит целых 39 байт

console.log((340282366920938463463374607431768211455n).toString(2))

//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 — весит всего 16 байт


В данном примере 1673357377000 — это дата в секундах от 1 янв 1970. Допустим, надо сохранить много дат в файл — каждая дата с новой строки. Как же это сделать?


Попробовали такой код, но нули и единицы всё равно сохраняются как байты:

var sampleBits = (1673357377000 >>> 0).toString(2);

    var saveBitArray = (function () {
        var a = document.createElement("a");
        document.body.appendChild(a);
        a.style = "display: none";
        return function (data, name) {
            var blob = new Blob(data, {type: "octet/stream"}),
                url = window.URL.createObjectURL(blob);
            a.href = url;
            a.download = name;
            a.click();
            window.URL.revokeObjectURL(url);
        };
    }());

    saveBitArray([sampleBits], 'example');

voraa 15.01.2023 13:51

Двоичные данные надо сохранять через типизированные массивы

<body>
<script>
const sampleBits = 1673357377000;
 
   const saveBitArray = function (data, filename) {
		const buffer = new ArrayBuffer(8);
		const bindata = new Float64Array(buffer);
		bindata[0] = data;
		
        const a = document.createElement("a");
        a.download = filename;
        document.body.append(a);
        
        a.style = "display: none";
        
        const blob = new Blob([buffer], {type: "octet/stream"});
        const url = window.URL.createObjectURL(blob);
        a.href = url;
        a.click();
        window.URL.revokeObjectURL(url);
        a.remove();
    };
 
    saveBitArray(sampleBits, 'examplebin');
</script>
</body>

voraa 15.01.2023 14:02

Цитата:

Сообщение от webgraph
надо сохранить много дат в файл — каждая дата с новой строки. Как же это сделать?

Бинарные данные не хранятся по строкам. Будет непонятно, как искать разделитель строк.

webgraph 15.01.2023 14:20

Цитата:

Сообщение от voraa
const buffer = new ArrayBuffer(8);

А почему вы создали массив из 8 байтов если требуется 4?

voraa 15.01.2023 14:31

Цитата:

Сообщение от webgraph
//10011011110111110011110111101000 — весит всего 4 байта

1673357377000
В 2 это 00000001 10000101 10011011 11011111 00111101 11101000
В 16 это 01 85 9B DF 3D E8
Ну никак не 4 байта, а 6

Такие числа представляются как double64
В js целые числа меньшие 2**53 - 1 хранятся как денормализованные double64

В windows есть калькулятор. Проверьте в режиме "программист"

webgraph 15.01.2023 14:47

Цитата:

Сообщение от voraa (Сообщение 549994)
1673357377000
В 2 это 00000001 10000101 10011011 11011111 00111101 11101000
В 16 это 01 85 9B DF 3D E8
Ну никак не 4 байта, а 6

Такие числа представляются как double64
В js целые числа меньшие 2**53 - 1 хранятся как денормализованные double64

В windows есть калькулятор. Проверьте в режиме "программист"

Да, видимо из-за сдвига >>> 0 уменьшилось итоговое количество битов (Зачем его там тогда вообще применяли??). Число 1673357377000 (в миллисекундах) в двоичной системе имеет значение 11000010110011011110111110011110111101000 (41 бит).

Получается, если хранить в секундах — 1673357377, то это поместится в 4 байта 1100011101111010110100001000001 (31 бит).


А как тогда сохранить в файл большое беззнаковое целое число?

const bigInt = 340282366920938463463374607431768211455n;

console.log(bigInt.toString(2))

voraa 15.01.2023 14:50

Цитата:

Сообщение от webgraph
Да, видимо из-за сдвига >>> 0 уменьшилось итоговое количество битов

Конечно. При всех побитовых операциях в js числа переводятся в формат int32 с отбрасыванием старших бит.

voraa 15.01.2023 14:51

Цитата:

Сообщение от webgraph
А как тогда сохранить в файл большое беззнаковое целое число?

Только в виде строки.
Ну или самим писать функции преобразования их в двоичные и обратно.

voraa 15.01.2023 15:15

Что то вроде такого
<body>
<script>
const sampleBits = 340282366920938463463374607431768211455n;
const toBinArr = (data) => {
	const binstr = data.toString(2);
	const len = binstr.length;
	const arr = [];
	let bb = 0;
	let shift = 0;
	for (let i=0; i<len; i++) {
		const bit = binstr.at(-i-1);
		if (bit === '1') 
			bb |= 1<<shift;
		shift ++;
		if (i%8 == 7) {
			arr.unshift(bb);
			bb=0;
			shift = 0;
		}
	}
	if (bb) arr.unshift(bb);
	return arr;
}
 
const saveBitArray = function (data, filename) {
	const binar = toBinArr(data);
	const buffer = new ArrayBuffer(binar.length);
	let bindata = new Uint8Array(buffer);
	for (let i=0; i<binar.length; i++) 
		bindata[i] = binar[i];
		
    const a = document.createElement("a");
    a.download = filename;
    document.body.append(a);
        
    a.style = "display: none";
        
    const blob = new Blob([buffer], {type: "octet/stream"});
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
};
 
saveBitArray(sampleBits, 'examplebin');
</script>
</body>

webgraph 15.01.2023 15:16

Цитата:

Сообщение от voraa (Сообщение 549997)
Только в виде строки.
Ну или самим писать функции преобразования их в двоичные и обратно.

Ну, преобразование-то и так работает же.

// Псевдопример 

const bigInt = 340282366920938463463374607431768211455n;

const binaryBigInt = bigInt.toString(2);

console.log(binaryBigInt)
// выведет 11111111....11111111 (128 единиц, т.е. 128 бит = 16 байт)

const arrayBuffer = new ArrayBuffer(16);

const binaryData = new Uint128Array(arrayBuffer); // Uint128Array не существует — типа надо эту функцию создать?

binaryData[0] = binaryBigInt;

voraa 15.01.2023 15:20

Цитата:

Сообщение от webgraph
Ну, преобразование-то и так работает же.

Это преобразовывает в строку. Каждый символ '0' или '1'. а нам эти нули и единицы надо преобразовать в биты и распихать их по байтам.
И еще решить, как хранить младший байт первым или старший.
В примере выше, в Uint8Array первым идет старший байт. Но не уверен, что это правильно.

ЗЫ
Посмотрев всякие материалы убедился, что именно так правильно (первыми идут старшие байты). Такой порядок обычно применяется при передаче по сети и межкомпьютерного обмена данными.

webgraph 15.01.2023 16:39

Цитата:

Сообщение от voraa (Сообщение 550000)
Это преобразовывает в строку. Каждый символ '0' или '1'. а нам эти нули и единицы надо преобразовать в биты и распихать их по байтам.
И еще решить, как хранить младший байт первым или старший.
В примере выше, в Uint8Array первым идет старший байт. Но не уверен, что это правильно.

ЗЫ
Посмотрев всякие материалы убедился, что именно так правильно (первыми идут старшие байты). Такой порядок обычно применяется при передаче по сети и межкомпьютерного обмена данными.

А как это сделать не для числа, а для hex-строки? Например, для 2d60a05493265733ff38002c9b80d359ddd9707f98f1c72ce3 8f08fbb057e6ee (64 байта)

// выведет 00101101011000001010000001010100100100110010011001 01011100110011111111110011100000000000001011001001 10111000000011010011010110011101110111011001011100 00011111111001100011110001110001110010110011100011 10001111000010001111101110110000010101111110011011 101110 (256 бит = 32 байта)


ЗЫ
Понятное дело, что hex-строка может иметь произвольную длину))

voraa 15.01.2023 17:00

Как то так
<body>
<script>
const sampleXStr = '2d60a05493265733ff38002c9b80d359ddd9707f98f1c72ce38f08fbb057e6ee';
const xStrtoBinArr = (data) => {
	data = data.padStart(((data.length/2+0.5)|0)*2, '0')
	const len = data.length;
	const arr = [];
	for (let i=0; i<len; i+=2) {
		const x = data[i]+data[i+1]
		arr.push(parseInt(x,16));
	}
	return arr;
}
 
const saveBitArray = function (data, filename) {
	const binar = xStrtoBinArr(data);
	const buffer = new ArrayBuffer(binar.length);
	let bindata = new Uint8Array(buffer);
	for (let i=0; i<binar.length; i++) 
		bindata[i] = binar[i];
		
    const a = document.createElement("a");
    a.download = filename;
    document.body.append(a);
        
    a.style = "display: none";
        
    const blob = new Blob([buffer], {type: "octet/stream"});
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
};
 
saveBitArray(sampleXStr, 'examplebin');
</script>
</body>

webgraph 15.01.2023 17:44

Цитата:

Сообщение от voraa
data = data.padStart(((data.length/2+0.5)|0)*2, '0')

Несколько в замешательство ввела эта строка кода. Можете подробнее рассказать что в ней происходит?

В то же время при поиске в интернете был найден иной способ — что вы можете сказать на счёт него?)

/* 

Альтернативная функция перевода из hex в bin 

*/
function hex2bin(hex){
    return (parseInt(hex, 16).toString(2)).padStart(8, '0');
}

voraa 15.01.2023 18:01

Цитата:

Сообщение от webgraph
Несколько в замешательство ввела эта строка кода. Можете подробнее рассказать что в ней происходит?

Добавляю '0' в начало строки, если длина нечетна
Яснее было бы
data = data.padStart(data.length + data.length%2, '0')
Цитата:

Сообщение от webgraph
В то же время при поиске в интернете был найден иной способ — что вы можете сказать на счёт него?)

он возвращает строку из '0' и '1',
И только с величиной от 0 до 255.
Просто переводит его в строку с записью битов.

webgraph 15.01.2023 18:25

Цитата:

Сообщение от voraa
data = data.padStart(data.length + data.length%2, '0')

Ну вот, это выглядит понятнее и ощущение, что выполняется быстрее (за счет меньшего числа математических действий).

Или тот вариант быстрее?

webgraph 15.01.2023 18:55

voraa,

Хм, не знаю почему, но только сейчас пришла эта мысль — раз у нас большая величина в шестнадцатеричной системе счисления (32 байта хэш против 16 байтов число), то может оптимальнее сразу всё хранить в hex ?)

webgraph 15.01.2023 19:23

voraa,

Получается BigInt -> Hex надо вот так?

const number = 340282366920938463463374607431768211455n;

const toBinArr = (data) => {
	data = data.toString(16).padStart(data.length + data.length%2, '0'); // Чувствую, что padStart здесь не нужен?
	//да и вообще логично, что для hex строк обязательное должно быть условие, чтобы они делились на 2 без остатка
	const len = data.length;
	const arr = [];
	for (let i=0; i<len; i+=2) {
		const x = data[i]+data[i+1]
		arr.push(parseInt(x,16));
	}
	return arr;
}

const saveBitArray = function (data, filename) {
	const binar = toBinArr(data);
	const buffer = new ArrayBuffer(binar.length);
	let bindata = new Uint8Array(buffer);
	for (let i=0; i<binar.length; i++) 
		bindata[i] = binar[i];
		
    const a = document.createElement("a");
    a.download = filename;
    document.body.append(a);
        
    a.style = "display: none";
        
    const blob = new Blob([buffer], {type: "octet/stream"});
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
};
 
saveBitArray(number, 'examplebin');

voraa 15.01.2023 19:40

Цитата:

const sampleBits = 340282366920938463463374607431768211455n;
 
const toBinArr = (data) => {
    data = data.toString(16).padStart(data.length + data.length%2, '0');
Неужто это работает?
Это не в 16-ричную переводит, а в массив байт
И где хранить? В ОП или на диске?

webgraph 15.01.2023 19:46

Цитата:

Сообщение от voraa (Сообщение 550008)
Неужто это работает?
Это не в 16-ричную переводит, а в массив байт
И где хранить? В ОП или на диске?

На диске)

webgraph 15.01.2023 19:47

Цитата:

Сообщение от voraa
Неужто это работает?

Ну ваще да, вроде)) ахха файл такой же как и при бинарном методе

webgraph 15.01.2023 19:51

Цитата:

Сообщение от voraa
Это не в 16-ричную переводит, а в массив байт

В бинарном варианте вы делали data = data.toString(2).

А здесь сделано data = data.toString(16).

voraa 15.01.2023 21:24

Цитата:

Сообщение от webgraph
Ну ваще да, вроде))

Не может это работать. у bigInt нет свойства length.
Попробуйте с нечетным количеством 16-ричных цифр и убедитесь, что 0 в начале не добавляется

let data = 384n; // 0x180
data = data.toString(16).padStart(data.length + data.length%2, '0');
console.log(data);

Цитата:

Сообщение от webgraph
В бинарном варианте вы делали data = data.toString(2).

А здесь сделано data = data.toString(16).

Результат одинаковый. Что быстрее - надо проверять. Может через 16-ричные будет быстрее

webgraph 15.01.2023 22:22

Цитата:

Сообщение от voraa
Не может это работать. у bigInt нет свойства length.

Так а как тогда? Типа сначала:

// сначала надо преобразовать в 16?

data = data.toString(16); 

// а уже потом работать с ней

data = data.padStart(data.length + data.length%2, '0');


Так получается?

voraa 15.01.2023 22:46

Цитата:

Сообщение от webgraph
Так получается?

Так получится.

webgraph 15.01.2023 23:18

Цитата:

Сообщение от voraa
И где хранить? В ОП или на диске?

voraa,
а этот вопрос к чему был?))

webgraph 24.01.2023 00:39

voraa,
а если для хранения в ОП?)

voraa 24.01.2023 07:35

Цитата:

Сообщение от webgraph
а если для хранения в ОП?)

Если только в typed Array.
Иначе памяти потребуется в разы больше. В памяти каждое число хранится как 8 байтовое вещественное (double64), а в строках по 2 байта на символ (utf-16)/

webgraph 24.01.2023 09:17

Цитата:

Сообщение от voraa (Сообщение 550238)
Если только в typed Array.
Иначе памяти потребуется в разы больше. В памяти каждое число хранится как 8 байтовое вещественное (double64), а в строках по 2 байта на символ (utf-16)/

Полагаю что двоичные данные только в typed array и могут храниться))

Изначально эта тема поднималась для записи именно на диск. Но сейчас интересен вопрос и про оперативную память.

Как считаете, насколько актуально принимать на сервер данные в формате JSON, конвертировать их в двоичные данные и записывать в память (map, set и т.д.) — в типизированные массивы?

Взять тот же Map — значение ключа может быть совершенно любым — мы можем создать new ArrayBuffer() и записать в него UUID участника.

Получается, что мы можем оптимизировать использование объёма оперативной памяти — уменьшить его как минимум 2 в раза.

Насколько верны данные размышления?)

voraa 24.01.2023 10:37

Мне трудно рассуждать абстрактно, не понимая всей задачи. Что и сколько передается, что где хранится, какие объемы?
Сделать то можно, что угодно.
Количество записей у вас только растет.
Как долго оно растет? Днями, месяцами, годами...?
Нужен все равно какой то предел количества записей при хранении в ОП. Она не резиновая.
Какие операции с ключами? Только сравнение на равенство?
Сравнение типизированных массивов не мгновенная операция. Она требует перебора - преобразование каждого элемента массива в обычное числовое значение и только тогда сравнивать. Сколько это займет по времени?

webgraph 24.01.2023 12:06

Цитата:

Сообщение от voraa (Сообщение 550240)
Мне трудно рассуждать абстрактно, не понимая всей задачи. Что и сколько передается, что где хранится, какие объемы?
Сделать то можно, что угодно.
Количество записей у вас только растет.
Как долго оно растет? Днями, месяцами, годами...?
Нужен все равно какой то предел количества записей при хранении в ОП. Она не резиновая.
Какие операции с ключами? Только сравнение на равенство?
Сравнение типизированных массивов не мгновенная операция. Она требует перебора - преобразование каждого элемента массива в обычное числовое значение и только тогда сравнивать. Сколько это займет по времени?

Ммм, недавно гуглили информацию о том, каким образом "компьютер складывает" числа. Как выяснилось — он складывает нули и единицы.

В данном случае у меня складывалось впечатление, что математические операции должны работать подобным образом.

Например, было проверено:

let arr = new ArrayBuffer(2);
let data = new Uint8Array(arr);
let map = new Map();

map.set(data, 'вот это сила!');

// Проверим есть ли в мапе типизированный массив data
if(map.has(data)) alert('Map has Typed Array. Key: ' + map.get(data));

voraa 24.01.2023 12:12

let arr = new ArrayBuffer(2);
let data = new Uint8Array(arr);
let data1 = new Uint8Array(arr);
let map = new Map();
 
map.set(data, 'вот это сила!');
 
// Проверим есть ли в мапе типизированный массив data
if(map.has(data)) alert('Map has Typed Array. Key: ' + map.get(data));
if(!map.has(data1)) alert('Map has not Typed Array');

Ключи-объекты равны, только если это тот же самый объект

{a:'aa'} !== {a:'aa'}


Часовой пояс GMT +3, время: 11:47.