Чтение байтов из строки JavaScript - PullRequest
31 голосов
/ 06 августа 2009

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

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

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

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

Ответы [ 9 ]

35 голосов
/ 07 августа 2009

Я считаю, что вы можете сделать это с помощью относительно простых битовых операций:

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"
15 голосов
/ 02 июня 2010

Ответ Боргара кажется правильным.

Просто хотел уточнить один момент. Javascript обрабатывает побитовые операции как 32-битные знаковые целые, где последний (самый левый) бит является знаковым битом. Т.е.,

getIntAt([0x7f,0,0,0],0).toString(16)  //  "7f000000"

getIntAt([0x80,0,0,0],0).toString(16)  // "-80000000"

Однако для обработки октетных данных (например, сетевого потока и т. Д.) Обычно требуется представление «unsigned int». Это может быть достигнуто добавлением оператора «>>> 0» (смещение вправо с нулевым заполнением), который внутренне сообщает Javascript, что следует рассматривать это как беззнаковое.

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"
14 голосов
/ 24 декабря 2010

Существует два метода кодирования и декодирования строки 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)); // Да!
9 голосов
/ 27 декабря 2011

Пока @ Боргар отвечает на вопрос правильно, его решение довольно медленное. Мне потребовалось некоторое время, чтобы отследить его (я использовал его функцию где-то в более крупном проекте), поэтому я решил поделиться своим пониманием.

У меня получилось что-то вроде @ 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 голосов
/ 14 ноября 2010

Решение Борги работает отлично. Если вам нужна более конкретная реализация, вы можете взглянуть на класс BinaryReader от vjeux (который, для записей, основан на классе двоичного синтаксического анализатора от Jonas Raoni Soares Silva ).

3 голосов
/ 27 сентября 2013

Один приятный и быстрый способ - использовать комбинацию 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 ​​и% до н.э).

Теперь мы применяем 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źń". Для начала у него разное количество символов :) Конечно, вы можете сказать, что эти странные версии большой буквы A не относятся к стандартному набору ascii. На самом деле это «Å» имеет значение 197. (что в шестнадцатеричном формате точно C5).

Теперь, если вы похожи на меня, вы спросите себя: подождите минуту ... если это действительно последовательность байтов со значениями 122, 97, 197, 188, и JS действительно использует UTF, тогда почему я видите символы "ż", а не оригинал "ż"?

Хорошо, дело в том (я верю), что эта последовательность 122, 97, 197, 188 (которую мы видим при применении charCodeAt) - это не последовательность байтов , а последовательность коды . Символ «Å» имеет код 197, но на самом деле его последовательность длиной два байта: C3 85.

Итак, хитрость работает, потому что unescape обрабатывает числа, встречающиеся в процентно-кодированной строке, как коды, а не как байтовые значения, или, если быть более точным: unescape ничего не знает о многобайтовых символах, поэтому, когда он декодирует байты один за другим обработка значений ниже 128 просто великолепна, но не так хороша, когда они выше 127, и многобайтовая - unescape в таких случаях просто возвращает многобайтовый символ, который имеет код, равный запрошенному значению байта. Эта «ошибка» на самом деле полезная функция.

3 голосов
/ 27 августа 2011

решение 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 );
...
3 голосов
/ 07 августа 2009

Как вы взяли двоичные данные в строку? Как двоичные данные кодируются в строку, это ВАЖНОЕ соображение, и вам нужно ответить на этот вопрос, прежде чем вы сможете продолжить.

Один из известных мне способов получения двоичных данных в строку - это использовать объект XHR и настроить его на ожидание UTF-16.

Как только он находится в utf-16, вы можете извлечь 16-битные числа из строки, используя "....".charCodeAt(0)

, который будет числом от 0 до 65535

Затем, если хотите, вы можете преобразовать это число в два числа от 0 до 255 следующим образом:

var leftByte = mynumber>>>8;
var rightByte = mynumber&255;
2 голосов
/ 06 августа 2009

Я собираюсь на секунду предположить, что ваша цель - прочитать произвольные байты из строки. Моим первым предложением было бы сделать ваше строковое представление шестнадцатеричным представлением двоичных данных.

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

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 );

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...