Подтвердить что ты не робот

Чтение байтов из строки JavaScript

У меня есть строка, содержащая двоичные данные в JavaScript. Теперь я хочу прочитать, например, целое число из него. Поэтому я получаю первые 4 символа, использую charCodeAt, делаю некоторые изменения и т.д., Чтобы получить целое число.

Проблема в том, что строки в JavaScript - это UTF-16 (вместо ASCII), а charCodeAt часто возвращает значения выше 256.

В ссылка на Mozilla указано, что "первые 128 кодовых точек Unicode являются прямым совпадением кодировки символов ASCII". (как насчет значений ASCII > 128?).

Как преобразовать результат charCodeAt в значение ASCII? Или есть лучший способ преобразовать строку из четырех символов в 4-байтовое целое?

4b9b3361

Ответ 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; 
}

Ответ 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 );

Затем вы можете интерпретировать строку байтов, но вы хотите.

Надеюсь, это поможет! Ура!