Чтение байтов из строки JavaScript
У меня есть строка, содержащая двоичные данные в JavaScript. Теперь я хочу прочитать, например, целое число из него. Поэтому я получаю первые 4 символа, использую charCodeAt
, делаю некоторые изменения и т.д., Чтобы получить целое число.
Проблема в том, что строки в JavaScript - это UTF-16 (вместо ASCII), а charCodeAt
часто возвращает значения выше 256.
В ссылка на Mozilla указано, что "первые 128 кодовых точек Unicode являются прямым совпадением кодировки символов ASCII". (как насчет значений ASCII > 128?).
Как преобразовать результат charCodeAt
в значение ASCII? Или есть лучший способ преобразовать строку из четырех символов в 4-байтовое целое?
Ответы
Ответ 1
Я считаю, что вы можете сделать это с помощью относительно простых операций с битами:
function stringToBytes ( str ) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++ ) {
ch = str.charCodeAt(i); // get char
st = []; // set up "stack"
do {
st.push( ch & 0xFF ); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
}
while ( ch );
// add stack contents to result
// done because chars have "wrong" endianness
re = re.concat( st.reverse() );
}
// return an array of bytes
return re;
}
stringToBytes( "A\u1242B\u4123C" ); // [65, 18, 66, 66, 65, 35, 67]
Это должно быть простой задачей, чтобы суммировать вывод, читая байтовый массив, как если бы он был памятью и добавлял его в большие числа:
function getIntAt ( arr, offs ) {
return (arr[offs+0] << 24) +
(arr[offs+1] << 16) +
(arr[offs+2] << 8) +
arr[offs+3];
}
function getWordAt ( arr, offs ) {
return (arr[offs+0] << 8) +
arr[offs+1];
}
'\\u' + getWordAt( stringToBytes( "A\u1242" ), 1 ).toString(16); // "1242"
Ответ 2
Ответ Боргара кажется правильным.
Просто хотел прояснить один момент. Javascript рассматривает побитовые операции как "32-битные подписанные int, где последний (самый левый) бит является битом знака. Т.е.,
getIntAt([0x7f,0,0,0],0).toString(16) // "7f000000"
getIntAt([0x80,0,0,0],0).toString(16) // "-80000000"
Однако для обработки октетов (например, сетевого потока и т.д.) обычно требуется представление "unsigned int". Это можно сделать, добавив оператор " → > 0" (нулевой заполняющий правый сдвиг), который внутренне сообщает Javascript, чтобы рассматривать это как unsigned.
function getUIntAt ( arr, offs ) {
return (arr[offs+0] << 24) +
(arr[offs+1] << 16) +
(arr[offs+2] << 8) +
arr[offs+3] >>> 0;
}
getUIntAt([0x80,0,0,0],0).toString(16) // "80000000"
Ответ 3
Существует два метода кодирования и декодирования строки utf-8 в массив байтов и обратно.
var utf8 = {}
utf8.toByteArray = function(str) {
var byteArray = [];
for (var i = 0; i < str.length; i++)
if (str.charCodeAt(i) <= 0x7F)
byteArray.push(str.charCodeAt(i));
else {
var h = encodeURIComponent(str.charAt(i)).substr(1).split('%');
for (var j = 0; j < h.length; j++)
byteArray.push(parseInt(h[j], 16));
}
return byteArray;
};
utf8.parse = function(byteArray) {
var str = '';
for (var i = 0; i < byteArray.length; i++)
str += byteArray[i] <= 0x7F?
byteArray[i] === 0x25 ? "%25" : // %
String.fromCharCode(byteArray[i]) :
"%" + byteArray[i].toString(16).toUpperCase();
return decodeURIComponent(str);
};
// sample
var str = "Да!";
var ba = utf8.toByteArray(str);
alert(ba); // 208, 148, 208, 176, 33
alert(ba.length); // 5
alert(utf8.parse(ba)); // Да!
Ответ 4
В то время как @Borgar отвечает на вопрос правильно, его решение довольно медленно. Мне потребовалось некоторое время, чтобы отследить его (я использовал его функцию где-то в более крупном проекте), поэтому я решил поделиться своим пониманием.
У меня получилось что-то вроде @Kadm. Это не какой-то небольшой процент быстрее, это как в 500 раз быстрее (без преувеличения!). Я написал маленький тест, поэтому вы можете увидеть это сами:)
function stringToBytesFaster ( str ) {
var ch, st, re = [], j=0;
for (var i = 0; i < str.length; i++ ) {
ch = str.charCodeAt(i);
if(ch < 127)
{
re[j++] = ch & 0xFF;
}
else
{
st = []; // clear stack
do {
st.push( ch & 0xFF ); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
}
while ( ch );
// add stack contents to result
// done because chars have "wrong" endianness
st = st.reverse();
for(var k=0;k<st.length; ++k)
re[j++] = st[k];
}
}
// return an array of bytes
return re;
}
Ответ 5
Решение Borga работает отлично. Если вам нужна более конкретная реализация, вы можете захотеть взглянуть на класс BinaryReader из vjeux (который для записей основан на класс двоично-парсер от Джонаса Рани Соареса Сильвы).
Ответ 6
решение borgars :
...
do {
st.unshift( ch & 0xFF ); // push byte to stack
ch = ch >> 8; // shift value down by 1 byte
}
while ( ch );
// add stack contents to result
// done because chars have "wrong" endianness
re = re.concat( st );
...
Ответ 7
Как вы получили двоичные данные в строке в первую очередь? Как двоичные данные кодируются в строку, ВАЖНОЕ соображение, и вам нужно ответить на этот вопрос, прежде чем продолжить.
Один из способов, которым я знаю, чтобы получить двоичные данные в строку, - это использовать объект XHR и настроить его на ожидание UTF-16.
После этого в utf-16 вы можете извлечь 16-битные числа из строки, используя "....".charCodeAt(0)
который будет числом от 0 до 65535
Затем, если хотите, вы можете преобразовать это число в два числа от 0 до 255 следующим образом:
var leftByte = mynumber>>>8;
var rightByte = mynumber&255;
Ответ 8
Одним хорошим и быстрым взломом является использование комбинации encodeURI и unescape:
t=[];
for(s=unescape(encodeURI("zażółć gęślą jaźń")),i=0;i<s.length;++i)
t.push(s.charCodeAt(i));
t
[122, 97, 197, 188, 195, 179, 197, 130, 196, 135, 32, 103, 196, 153, 197, 155, 108, 196, 133, 32, 106, 97, 197, 186, 197, 132]
Возможно, какое-то объяснение необходимо, почему это работает, поэтому позвольте мне разбить его на шаги:
encodeURI("zażółć gęślą jaźń")
возвращает
"za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84"
который - если вы внимательно посмотрите - это исходная строка, в которой все символы со значениями > 127 заменены (возможно, более чем одним) шестнадцатеричным представлениями байтов.
Например, буква "ż" стала "% C5% BC". Дело в том, что encodeURI также выполняет некоторые обычные символы ascii, такие как пробелы, но это не имеет значения. Важно то, что в этот момент каждый байт исходной строки либо представлен дословно (как в случае с "z", "a", "g", или "j" ), либо как процентная последовательность байтов ( как это было в случае с "ż" , который был первоначально двумя байтами 197 и 188 и преобразован в% C5 и% BC).
Теперь применим unescape:
unescape("za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84")
который дает
"zażóÅÄ gÄÅlÄ jaźÅ"
Если вы не являетесь носителем польского языка, вы можете не заметить, что этот результат на самом деле отличается от оригинального "zażółć gęślą jaźń". Для начала у него есть другое количество символов:)
Конечно, вы можете сказать, что эти странные версии большой буквы А не относятся к стандартному набору ascii. Фактически это "Å" имеет значение 197. (это точно C5 в шестнадцатеричном виде).
Теперь, если вы похожи на меня, вы спросите себя: подождите минуту... если это действительно последовательность байтов со значениями 122, 97, 197, 188 и JS, действительно использует UTF, то почему я см. эти символы "¼", а не оригинальные "ż" ?
Хорошо, дело в том, что эта последовательность 122, 97, 197, 188 (которую мы видим при применении charCodeAt) не является последовательностью байтов, а последовательностью кодов. Символ "Å" имеет код 197, но его фактически двухбайтная длинная последовательность: C3 85.
Итак, трюк работает, потому что unescape обрабатывает числа, происходящие в процентной кодированной строке в виде кодов, а не как байтовые значения - или, если быть более конкретными: unescape ничего не знает о многобайтовых символах, поэтому при декодировании байтов один за другим, обработка значений ниже 128 просто отлично, но не очень хорошая, когда они выше 127 и многобайтовые - unescape в таких случаях просто возвращает многобайтовый символ, который имеет код, равный запрашиваемому байтовому значению. Эта "ошибка" действительно полезная функция.
Ответ 9
Я собираюсь предположить на секунду, что ваша цель - прочитать произвольные байты из строки.
Моим первым предложением было бы сделать ваше строковое представление шестнадцатеричным представлением двоичных данных.
Вы можете прочитать значения с помощью преобразований в числа из hex:
var BITS_PER_BYTE = 8;
function readBytes(hexString, numBytes) {
return Number( parseInt( hexString.substr(0, numBytes * (BITS_PER_BYTE/4) ),16 ) );
}
function removeBytes(hexString, numBytes) {
return hexString.substr( numBytes * (BITS_PER_BYTE/BITS_PER_CHAR) );
}
Затем эти функции можно использовать для чтения всего, что вы хотите:
var hex = '4ef2c3382fd';
alert( 'We had: ' + hex );
var intVal = readBytes(hex,2);
alert( 'Two bytes: ' + intVal.toString(2) );
hex = removeBytes(hex,2);
alert( 'Now we have: ' + hex );
Затем вы можете интерпретировать строку байтов, но вы хотите.
Надеюсь, это поможет!
Ура!