2009-08-06 2 views
31

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

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

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

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

+0

Можете привести пример? – Gumbo

+12

ASCII имеет * NO * значения> 128 (фактически, он не имеет значений> 127: он определяет коды от 0 до 127 включительно, ТОЛЬКО). Поэтому вопрос о «значениях ASCII> 128» не имеет смысла; вы должны означать некоторую другую кодировку символов (ISO-8859-x для некоторого значения x, может быть?) –

+4

Имеются расширенные коды ASCII (от 128 до 255) http://www.asciitable.com/ –

ответ

2

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

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

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

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

Надеюсь, это поможет! Приветствия!

+0

Просто пропустите 'BITS_PER_CHAR';) – yckart

33

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

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" 
+1

Кодировка, которую вы выводите, даже не определена. В большинстве случаев вы не сможете совершить двустороннее путешествие между этой фиктивной кодировкой и строкой. – rspeer

3

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

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

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

, который будет представлять собой число от 0 до 65535

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

var leftByte = mynumber>>>8; 
var rightByte = mynumber&255; 
15

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

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

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

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

Однако для обработки октета данных (например, сетевого потока и т.д.), как правило, хочет «без знака 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" 
+0

Вместо того чтобы полагаться на странный трюк (>>> 0), заменим проблематичный << 24, на * Math.pow (2, 24). Вместо того, чтобы рассматриваться как 32-битный беззнаковый int, это будет считаться 64-битным двойным. Это может быть немного медленнее, хотя. return (b3 * Math.pow (2, 24)) + (b2 << 16) + (b1 << 8) + b0; Спасибо за решение, я пропатчен jDataView благодаря тому, что :) https://github.com/vjeux/jsDataView/commit/2ee4906e8d3fcc954a263c7b161cf3dbf2d4804f – Vjeux

13

Существует два способа кодирования и декодирования строки 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)); // Да! 
+0

Мне нравится это решение. Я проголосовал за это. Я не понимаю, почему вы не включили '127' и выбрали шестнадцатеричный« 0x7F ». Я использую это для обнаружения многобайтовой строки в JavaScript. например. '" Şerban ".length! = ToByteArray (" Şerban "). Length' –

3

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); 
... 
+0

Важно то, что это выполняется намного быстрее, чем push & reverse – tomasb

+0

edit note: зависит от размера массива, замедляется с большими массивами после некоторого ограничения там вообще не имеет значения – tomasb

9

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

В итоге у меня было что-то вроде @Kadm. Это не какой-то небольшой процент быстрее, это как в 500 раз быстрее (без преувеличения!). Я написал little benchmark, так что вы можете видеть это для себя :)

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; 
} 
+0

Кажется, что некоторые проблемы с этим для китайских иероглифов. Codepoint не совпадает с кодировкой. – tofutim

+0

Но это, похоже, не работает для строк UTF8, поскольку Kadm's делает ... – whoughton

+0

Borgar и Kadm предоставили различные решения, которые обеспечивают разные результаты. Эта (действительно намного более быстрая) версия кода Боргара возвращает тот же результат, что и код Боргара. Он не возвращает тот же результат, что и код Кадма, и автор никогда не утверждал, что это так. Метод Borgar извлекает необработанные байты (так же, как и шестнадцатеричный редактор, или xxd). Он не знает «кодовых точек» или unicode. Метод Кадма деконструирует их с помощью encodeUriComponent, который ** является ** unicode, а результат отличается в результате - хотя я не мог объяснить объяснение фактической разницы. – Orwellophile

3

Один хороший и быстрый хак использовать комбинацию encodeURI и: экранирования в

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("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 в таких случаях просто возвращает многобайтовый символ, который имеет код, равный запрошенному значению байта. Эта «ошибка» на самом деле является полезной функцией.