2009-08-06 14 views
31

Ich habe eine Zeichenfolge mit binären Daten in JavaScript. Jetzt möchte ich zum Beispiel eine ganze Zahl daraus lesen. Also bekomme ich die ersten 4 Zeichen, benutze charCodeAt, mache etwas Shifting, etc. um eine ganze Zahl zu erhalten.Bytes aus einer JavaScript-Zeichenfolge lesen

Das Problem ist, dass Strings in JavaScript ist UTF-16 (anstelle von ASCII) und charCodeAt oft gibt Werte höher als 256.

Die Mozilla reference heißt es: „Die ersten 128 Unicode-Codepunkte eine direkte Übereinstimmung der ARE ASCII-Zeichencodierung. " (Was ist mit ASCII-Werten> 128?).

Wie kann ich das Ergebnis von charCodeAt in einen ASCII-Wert konvertieren? Oder gibt es eine bessere Möglichkeit, eine Zeichenfolge aus vier Zeichen in eine 4-Byte-Ganzzahl zu konvertieren?

+0

Können Sie ein Beispiel geben? – Gumbo

+12

ASCII hat * NO * Werte> 128 (eigentlich hat es keine Werte> 127: es definiert NUR Codes zwischen 0 und 127). Die Frage nach "ASCII-Werten> 128" macht also keinen Sinn; Sie müssen eine andere Zeichencodierung meinen (ISO-8859-x für einen Wert von x, vielleicht?) –

+4

Es gibt erweiterte ASCII-Codes (von 128 bis 255) http://www.asciitable.com/ –

Antwort

2

Ich werde für eine Sekunde annehmen, dass Ihr Ziel ist, beliebige Bytes aus einer Zeichenfolge zu lesen. Mein erster Vorschlag wäre, Ihre String-Repräsentation eine hexadezimale Darstellung der binären Daten zu machen.

Sie können die Werte mit Conversions Zahlen von Hex lesen:

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

Die Funktionen dann ausgelesen werden können, was Sie wollen:

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

Sie können dann die Bytestring jedoch interpretieren Sie wollen.

Hoffe, das hilft! Prost!

+0

Nur vermisse 'BITS_PER_CHAR';) – yckart

33

Ich glaube, dass man mit relativ einfachen Bit-Operationen kann dies tun kann:

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] 

Es soll eine einfache Sache sein, die Ausgabe zu summieren durch den Byte-Array zu lesen, als ob es Speicher waren und das Hinzufügen es oben in größere Zahlen:

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

Die Codierung, die du ausgibst, ist nicht einmal gut definiert. In den meisten Fällen können Sie zwischen dieser fiktiven Kodierung und einer Zeichenfolge nicht hin- und herwechseln. – rspeer

3

Wie haben Sie die Binärdaten in die Zeichenfolge an erster Stelle? Wie die Binärdaten in eine Zeichenfolge codiert werden, ist eine WICHTIGE Überlegung, und Sie benötigen eine Antwort auf diese Frage, bevor Sie fortfahren können.

Eine Möglichkeit, binäre Daten in eine Zeichenfolge zu erhalten, besteht darin, das XHR-Objekt zu verwenden und UTF-16 zu erwarten.

Sobald es in utf-16 ist, können Sie 16-Bit-Zahlen aus dem String abrufen mit "....".charCodeAt(0)

, die eine Zahl zwischen 0 und 65535

Dann wird, wenn Sie möchten, können Sie konvertieren, dass Nummer in zwei Zahlen zwischen 0 und 255 wie folgt:

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

Borgars Antwort scheint richtig.

Ich wollte nur einen Punkt klarstellen. Javascript behandelt bitweise Operationen als '32-Bit-Vorzeichen int', wobei das letzte (am weitesten links liegende) Bit das Vorzeichenbit ist.Dh,

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

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

jedoch für Oktett-Datenverarbeitung (zB Netzwerk-Stream, usw.), will in der Regel die 'unsigned int' Darstellung. Dies kann durch Hinzufügen eines ">>> 0" (Zero-Fill Right-Shift) -Operators erreicht werden, der Javascript intern anweist, dies als unsigniert zu behandeln.

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

Anstatt sich auf einen seltsamen Trick (>>> 0) zu verlassen, können wir die problematische << 24 durch * Math.pow (2, 24) ersetzen. Anstatt als 32bit unsigned int behandelt zu werden, wird dies als 64bit double betrachtet. Dies könnte jedoch ein wenig langsamer sein. Rückkehr (b3 * Math.pow (2, 24)) + (b2 << 16) + (b1 << 8) + b0; Danke für die Lösung, ich jDataView dank, dass gepatcht :) https://github.com/vjeux/jsDataView/commit/2ee4906e8d3fcc954a263c7b161cf3dbf2d4804f – Vjeux

13

Es gibt zwei Methoden zum Codieren und Decodieren von utf-8-Strings zu einem Byte-Array und zurück.

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

ich diese Lösung gefällt. Ich habe dafür gestimmt. Ich verstehe nicht, warum Sie '127' nicht eingeschlossen haben und das hexadezimale' 0x7F' gewählt haben. Ich verwende das, um eine Multibyte-Zeichenfolge in JavaScript zu erkennen. z.B. '' Şerban ".length! = ToByteArray (" Şerban "). Length ' –

3

borgars Lösung Verbesserung:

... 
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

wichtig ist, dass dies viel schneller als push & reverse läuft – tomasb

+0

Anmerkung: hängt von der Größe des Arrays ab, verlangsamt sich mit größeren Arrays nach einigen Grenzen dort ist überhaupt kein Unterschied – tomasb

9

Während @Borgar die Frage richtig beantwortet, seine Lösung ist ziemlich langsam. Ich brauchte eine Weile, um es aufzuspüren (ich benutzte seine Funktion irgendwo in einem größeren Projekt), also dachte ich, ich würde meine Erkenntnisse teilen.

Ich endete mit etwas wie @Kadm. Es ist nicht ein paar Prozent schneller, es ist 500-mal schneller (keine Übertreibung!). Ich schrieb einen little benchmark, so können Sie es selbst sehen :)

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

Es scheint einige Probleme mit diesem für chinesische Schriftzeichen zu geben. Codepoint ist nicht identisch mit der Codierung. – tofutim

+0

Das scheint jedoch nicht für UTF8-Strings zu funktionieren, da Kadm's das tut ... – whoughton

+0

Borgar und Kadm haben verschiedene Lösungen geliefert, die unterschiedliche Ergebnisse liefern. Diese (wirklich viel schnellere) Version von Borgars Code gibt das gleiche Ergebnis wie Borgars Code zurück. Es liefert nicht das gleiche Ergebnis wie der Code von Kadm, und der Autor hat dies nie behauptet. Die Borgar-Methode extrahiert rohe Bytes (in der gleichen Weise wie ein Hex-Editor, oder xxd würde tun). Es hat keine Kenntnis von "Codepunkten" oder Unicode. Die Kadm-Methode dekonstruiert sie mithilfe von encodeUriComponent, das ** Unicode-bewusst ist, und die Ausgabe unterscheidet sich daher - obwohl ich den tatsächlichen Unterschied nicht erklären konnte. – Orwellophile

3

Eine nette und schnelle Hack ist eine Kombination aus encodeURI und unescape zu verwenden:

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] 

Vielleicht eine Erklärung notwendig ist, warum die Heck es funktioniert, so lassen sie es mich in einzelne Schritte aufgeteilt:

encodeURI("zażółć gęślą jaźń") 

kehrt

"za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84" 

die - wenn Sie genau hinsehen - die ursprüngliche Zeichenfolge ist, in der alle Zeichen mit Werten> 127 durch (möglicherweise mehr als) hexadezimale Byte-Darstellungen ersetzt wurden. Zum Beispiel wurde der Buchstabe "ż" zu "% C5% BC". Tatsache ist, dass encodeURI auch einige normale Ascii-Zeichen wie Leerzeichen entzieht, aber das macht nichts. Es kommt darauf an, dass an dieser Stelle jedes Byte der ursprünglichen Zeichenfolge entweder wörtlich (wie bei "z", "a", "g" oder "j") oder als prozentcodierte Bytefolge (wie es bei "ż" der Fall war, der ursprünglich zwei Bytes 197 und 188 war und in% C5 und% BC umgewandelt wurde. Jetzt

wenden wir unescape:

unescape("za%C5%BC%C3%B3%C5%82%C4%87%20g%C4%99%C5%9Bl%C4%85%20ja%C5%BA%C5%84") 

die

"zażóÅÄ gÄÅlÄ jaźÅ" 

gibt Wenn Sie nicht heimisch sind polnische Lautsprecher Sie vielleicht nicht bemerken, dass dieses Ergebnis in der Tat ist viel anders als das Original " zażółć gęślą jaźń ". Für den Anfang hat es eine andere Anzahl von Charakteren :) Sicher können Sie sagen, dass diese seltsamen Versionen von Großbuchstabe A nicht zum Standard-ASCII-Set gehören. Tatsächlich hat dieses "Å" den Wert 197. (was genau hexadezimal C5 ist).

Nun, wenn Sie wie ich sind, würden Sie sich fragen: Warten Sie eine Minute ... Wenn dies wirklich eine Folge von Bytes mit den Werten 122, 97, 197, 188 ist, und JS wirklich UTF dann warum verwenden Ich sehe diese "ż" -Zeichen und nicht das Original "ż"?

Nun, die Sache (ich glaube), dass diese Sequenz 122, 97, 197, 188 (die wir sehen, wenn die Anwendung charCodeAt) ist nicht eine Folge von Bytes, sondern eine Folge von Codes. Das Zeichen "Å" hat einen Code 197, aber seine eigentlich zwei Byte lange Sequenz: C3 85.

Also funktioniert der Trick, weil unescape Zahlen behandelt, die in Prozent-kodierten Strings als Codes auftreten, nicht als Byte-Werte - oder Um genauer zu sein: Unescape weiß nichts über Multibyte-Zeichen, also wenn man Bytes byteweise decodiert, sind Werte von weniger als 128 einfach großartig, aber nicht so gut, wenn sie in solchen Fällen über 127 und Multibyte liegen gibt einfach ein Multibyte-Zeichen zurück, das zufällig einen Code aufweist, der dem angeforderten Bytewert entspricht. Dieser "Bug" ist eigentlich eine nützliche Funktion.

Verwandte Themen