Як перетворити рядок у Bytearray


90

Як я можу перетворити рядок у bytearray за допомогою JavaScript. Результат повинен бути еквівалентний наведеному нижче коду C #.

UnicodeEncoding encoding = new UnicodeEncoding();
byte[] bytes = encoding.GetBytes(AnyString);

Оскільки UnicodeEncoding за замовчуванням UTF-16 з малою ендіанністю.

Редагувати: у мене є вимога збігати байтовий масив, згенерований на стороні клієнта, з тим, який генерується на стороні сервера, використовуючи наведений вище код C #.


3
javascript не зовсім відомий тим, що його легко використовувати з BLOB-файлами - чому б вам просто не надіслати рядок у JSON?
Марк Гравелл

Можливо, ви можете подивитися тут ..
V4Vendetta

2
Рядок Javascript - UTF-16, або ви це вже знали?
Кевін

2
Перш за все, чому вам потрібно перетворити це в javascript?
BreakHead

17
Рядки не кодуються. Так, внутрішньо вони представлені у вигляді байтів і мають кодування, але це, по суті, безглуздо на рівні сценаріїв. Рядки - це логічні набори символів. Для кодування символу потрібно чітко вибрати схему кодування, за допомогою якої кожен код символу можна перетворити в послідовність одного або декількох байтів. Відповіді на це запитання нижче - сміття, оскільки вони називають charCodeAt і вписують його значення в масив, який називається "байти". Привіт! charCodeAt може повертати значення більше 255, тому це не байт!
Трійко

Відповіді:


21

У C # це працює

UnicodeEncoding encoding = new UnicodeEncoding();
byte[] bytes = encoding.GetBytes("Hello");

Створить масив за допомогою

72,0,101,0,108,0,108,0,111,0

байтовий масив

Для символу, код якого перевищує 255, це буде виглядати так

байтовий масив

Якщо ви хочете дуже подібної поведінки в JavaScript, ви можете зробити це (v2 - трохи більш надійне рішення, тоді як оригінальна версія буде працювати лише для 0x00 ~ 0xff)

var str = "Hello竜";
var bytes = []; // char codes
var bytesv2 = []; // char codes

for (var i = 0; i < str.length; ++i) {
  var code = str.charCodeAt(i);
  
  bytes = bytes.concat([code]);
  
  bytesv2 = bytesv2.concat([code & 0xff, code / 256 >>> 0]);
}

// 72, 101, 108, 108, 111, 31452
console.log('bytes', bytes.join(', '));

// 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 220, 122
console.log('bytesv2', bytesv2.join(', '));


1
Я вже пробував це, але це дає мені інший результат, ніж наведений вище код C #. Як і в цьому випадку, вихідний масив байтів коду C # дорівнює = 72,0,101,0,108,0,108,0,111,0. У мене є вимога збігати обидва, щоб це не працювало.
shas

2
@shas Я протестував попередню лише на Firefox 4. Оновлену версію протестували на Firefox 4, Chrome 13 та IE9.
BrunoLM

40
Зверніть увагу, що якщо рядок містить символи Unicode, charCodeAt (i) буде> 255, що, мабуть, не те, що ви хочете.
broofa

23
Так, це неправильно. charCodeAt не повертає байт. Немає сенсу записувати значення більше 255 в масив, який називається "байти"; дуже вводить в оману. Ця функція взагалі не виконує кодування, вона просто вставляє коди символів у масив.
Трійко

1
Я не розумію, чому цю відповідь позначено як правильну, оскільки вона нічого не кодує.
AB

32

Якщо ви шукаєте рішення, яке працює в node.js, ви можете використовувати це:

var myBuffer = [];
var str = 'Stack Overflow';
var buffer = new Buffer(str, 'utf16le');
for (var i = 0; i < buffer.length; i++) {
    myBuffer.push(buffer[i]);
}

console.log(myBuffer);

3
Це для node.js, але я думаю, що питання шукає рішення, яке працює в браузері. Проте він працює правильно, на відміну від більшості інших відповідей на це запитання, тому +1.
Даніель Кассіді,

Це працює, але набагато простіший код - це функція convertString (myString) {var myBuffer = new Buffer (myString, 'utf16le'); console.log (myBuffer); повернути myBuffer; }
Філіп Рутовіц

16

Я припускаю, що C # та Java створюють рівнобайтові масиви. Якщо у вас є символи, що не є ASCII, недостатньо додати додаткових 0. Мій приклад містить кілька спеціальних символів:

var str = "Hell ö € Ω 𝄞";
var bytes = [];
var charCode;

for (var i = 0; i < str.length; ++i)
{
    charCode = str.charCodeAt(i);
    bytes.push((charCode & 0xFF00) >> 8);
    bytes.push(charCode & 0xFF);
}

alert(bytes.join(' '));
// 0 72 0 101 0 108 0 108 0 32 0 246 0 32 32 172 0 32 3 169 0 32 216 52 221 30

Я не знаю, чи C # розміщує специфікацію (позначення порядку байтів), але якщо використовується UTF-16, Java String.getBytesдодає такі байти: 254 255.

String s = "Hell ö € Ω ";
// now add a character outside the BMP (Basic Multilingual Plane)
// we take the violin-symbol (U+1D11E) MUSICAL SYMBOL G CLEF
s += new String(Character.toChars(0x1D11E));
// surrogate codepoints are: d834, dd1e, so one could also write "\ud834\udd1e"

byte[] bytes = s.getBytes("UTF-16");
for (byte aByte : bytes) {
    System.out.print((0xFF & aByte) + " ");
}
// 254 255 0 72 0 101 0 108 0 108 0 32 0 246 0 32 32 172 0 32 3 169 0 32 216 52 221 30

Редагувати:

Додано спеціальний символ (U + 1D11E) MUSICAL SYMBOL G CLEF (за межами BPM, тому в UTF-16 береться не лише 2 байти, а й 4.

Поточні версії JavaScript внутрішньо використовують "UCS-2", тому цей символ займає простір 2 звичайних символів.

Я не впевнений, але при використанні charCodeAt здається, що ми отримуємо саме сурогатні кодові точки, які також використовуються в UTF-16, тому символи, не пов'язані з BPM, обробляються правильно.

Ця проблема абсолютно нетривіальна. Це може залежати від використовуваних версій та механізмів JavaScript. Отже, якщо ви хочете отримати надійні рішення, вам слід поглянути на:


1
Все ще не повна відповідь. UTF16 - це кодування змінної довжини, яке використовує 16-бітові фрагменти для представлення символів. Один символ буде закодований як 2 байти або 4 байти, залежно від того, наскільки велике значення коду символу. Оскільки ця функція записує щонайбільше 2 байти, вона не може обробляти всі точки коду символів Unicode, і не є повною реалізацією кодування UTF16, ані довгим знімком.
Трійко

@Triynko після мого редагування та тестування, ти все ще вважаєш, що це не повна відповідь? Якщо так, чи маєте ви відповідь?
hgoebl

2
@Triynko Ви напів праві, але насправді ця відповідь працює правильно. Рядки JavaScript насправді не є послідовностями точок коду Unicode, це послідовності одиниць коду UTF-16. Незважаючи на назву, charCodeAtповертає UTF-16 одиницю коду в діапазоні 0-65535. Символи поза 2-байтовим діапазоном представлені у вигляді сурогатних пар, як і в UTF-16. (До речі, це стосується рядків кількома іншими мовами, включаючи Java та C #.)
Даніель Кассіді,

До речі, (charCode & 0xFF00) >> 8зайвий, вам не потрібно маскувати його перед перемиканням.
Патрік Робертс,

15

Найпростішим способом у 2018 році повинен бути TextEncoder, але повернутий елемент - це не байтовий масив, це Uint8Array. (І не всі браузери це підтримують)

let utf8Encode = new TextEncoder();
utf8Encode.encode("eee")
> Uint8Array [ 101, 101, 101 ]

Це своєрідно. Я не думаю, що використання різних імен змінних як utf8Decode та utf8Encode буде працювати.
Уніедрон

Ви можете використовувати TextDecoder для декодування: new TextDecoder().decode(new TextEncoder().encode(str)) == str.
Фонс

Ось таблиці підтримки TextEncoder: caniuse
Fons

11

Байтовий масив UTF-16

JavaScript кодує рядки як UTF-16 , так само, як і C # UnicodeEncoding, тому масиви байтів повинні точно збігатися charCodeAt(), і розділяючи кожну повернену пару байтів на 2 окремі байти, як у:

function strToUtf16Bytes(str) {
  const bytes = [];
  for (ii = 0; ii < str.length; ii++) {
    const code = str.charCodeAt(ii); // x00-xFFFF
    bytes.push(code & 255, code >> 8); // low, high
  }
  return bytes;
}

Наприклад:

strToUtf16Bytes('🌵'); 
// [ 60, 216, 53, 223 ]

Однак, якщо ви хочете отримати байтовий масив UTF-8, ви повинні перекодувати байти.

Байтовий масив UTF-8

Рішення здається дещо нетривіальним, але я використовував наведений нижче код у виробничому середовищі з великим трафіком з великим успіхом ( оригінальне джерело ).

Також для зацікавленого читача я опублікував свої помічники Unicode, які допомагають мені працювати з довжинами рядків, про які повідомляють інші мови, такі як PHP.

/**
 * Convert a string to a unicode byte array
 * @param {string} str
 * @return {Array} of bytes
 */
export function strToUtf8Bytes(str) {
  const utf8 = [];
  for (let ii = 0; ii < str.length; ii++) {
    let charCode = str.charCodeAt(ii);
    if (charCode < 0x80) utf8.push(charCode);
    else if (charCode < 0x800) {
      utf8.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
    } else if (charCode < 0xd800 || charCode >= 0xe000) {
      utf8.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
    } else {
      ii++;
      // Surrogate pair:
      // UTF-16 encodes 0x10000-0x10FFFF by subtracting 0x10000 and
      // splitting the 20 bits of 0x0-0xFFFFF into two halves
      charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (str.charCodeAt(ii) & 0x3ff));
      utf8.push(
        0xf0 | (charCode >> 18),
        0x80 | ((charCode >> 12) & 0x3f),
        0x80 | ((charCode >> 6) & 0x3f),
        0x80 | (charCode & 0x3f),
      );
    }
  }
  return utf8;
}

і що обернено до цього?
simbo1905

Я б описав зворотну функцію як "перетворити байтовий масив UTF-8 в рідний рядок UTF-16". Я ніколи не видавав зворотного. У myc env я видалив цей код, змінивши вивід API на діапазон символів замість діапазону байтів, а потім використовував руни для синтаксичного аналізу діапазонів.
jchook

Я вважаю, що це має бути прийнятою відповіддю на це питання.
LeaveTheCapital

10

Натхненний відповіддю @ hgoebl. Його код призначений для UTF-16, і мені потрібно було щось для US-ASCII. Отже, ось більш повна відповідь, яка охоплює US-ASCII, UTF-16 та UTF-32.

/**@returns {Array} bytes of US-ASCII*/
function stringToAsciiByteArray(str)
{
    var bytes = [];
   for (var i = 0; i < str.length; ++i)
   {
       var charCode = str.charCodeAt(i);
      if (charCode > 0xFF)  // char > 1 byte since charCodeAt returns the UTF-16 value
      {
          throw new Error('Character ' + String.fromCharCode(charCode) + ' can\'t be represented by a US-ASCII byte.');
      }
       bytes.push(charCode);
   }
    return bytes;
}
/**@returns {Array} bytes of UTF-16 Big Endian without BOM*/
function stringToUtf16ByteArray(str)
{
    var bytes = [];
    //currently the function returns without BOM. Uncomment the next line to change that.
    //bytes.push(254, 255);  //Big Endian Byte Order Marks
   for (var i = 0; i < str.length; ++i)
   {
       var charCode = str.charCodeAt(i);
       //char > 2 bytes is impossible since charCodeAt can only return 2 bytes
       bytes.push((charCode & 0xFF00) >>> 8);  //high byte (might be 0)
       bytes.push(charCode & 0xFF);  //low byte
   }
    return bytes;
}
/**@returns {Array} bytes of UTF-32 Big Endian without BOM*/
function stringToUtf32ByteArray(str)
{
    var bytes = [];
    //currently the function returns without BOM. Uncomment the next line to change that.
    //bytes.push(0, 0, 254, 255);  //Big Endian Byte Order Marks
   for (var i = 0; i < str.length; i+=2)
   {
       var charPoint = str.codePointAt(i);
       //char > 4 bytes is impossible since codePointAt can only return 4 bytes
       bytes.push((charPoint & 0xFF000000) >>> 24);
       bytes.push((charPoint & 0xFF0000) >>> 16);
       bytes.push((charPoint & 0xFF00) >>> 8);
       bytes.push(charPoint & 0xFF);
   }
    return bytes;
}

UTF-8 має змінну довжину і не включається, тому що мені довелося б писати кодування самостійно. UTF-8 та UTF-16 мають змінну довжину. UTF-8, UTF-16 та UTF-32 мають мінімальну кількість бітів, як вказує їх назва. Якщо символ UTF-32 має кодову точку 65, це означає, що є 3 провідних 0. Але той же код для UTF-16 має лише 1 провідний 0. US-ASCII, з іншого боку, є фіксованою шириною 8 біт, що означає, що його можна безпосередньо перевести в байти.

String.prototype.charCodeAtповертає максимальну кількість 2 байт і точно відповідає UTF-16. Однак необхідний UTF-32 String.prototype.codePointAt, який є частиною пропозиції ECMAScript 6 (Harmony). Оскільки charCodeAt повертає 2 байти, що є більш можливими символами, ніж US-ASCII, функція stringToAsciiByteArrayбуде кидати в таких випадках, замість того, щоб розділити символ навпіл і взяти один або обидва байти.

Зверніть увагу, що ця відповідь нетривіальна, оскільки кодування символів є нетривіальним. Який тип байтового масиву ви хочете, залежить від того, яке кодування символів ви хочете, щоб ці байти представляли.

javascript має можливість внутрішнього використання або UTF-16, або UCS-2, але оскільки у нього є методи, які діють так, як це UTF-16, я не розумію, чому будь-який браузер буде використовувати UCS-2. Також дивіться: https://mathiasbynens.be/notes/javascript-encoding

Так, я знаю, що питанню 4 роки, але мені потрібна була сама відповідь.


Результати буфера вузла для '02', [ 48, 0, 50, 0 ]де як stringToUtf16ByteArrayповертається ваша функція [ 0, 48, 0, 50 ]. який з них правильний?
pkyeck

@pkyeck Моя функція stringToUtf16ByteArray повертає UTF-16 BE без специфікації. Приклад, який ви дали з вузла, - UTF-16 LE без специфікації. Я думав, що біг-ендіан є більш нормальним, ніж мало-ендіан, але може бути помилковим.
SkySpiral7,

2

Оскільки я не можу коментувати відповідь, я б спирався на відповідь Джин Іззраеля

var myBuffer = [];
var str = 'Stack Overflow';
var buffer = new Buffer(str, 'utf16le');
for (var i = 0; i < buffer.length; i++) {
    myBuffer.push(buffer[i]);
}

console.log(myBuffer);

сказавши, що ви можете використовувати це, якщо хочете використовувати буфер Node.js у своєму браузері.

https://github.com/feross/buffer

Отже, заперечення Тома Стікеля не є дійсним, і відповідь справді є слушною відповіддю.


1
String.prototype.encodeHex = function () {
    return this.split('').map(e => e.charCodeAt())
};

String.prototype.decodeHex = function () {    
    return this.map(e => String.fromCharCode(e)).join('')
};

4
Було б корисно, якщо ви надасте якийсь текст, щоб разом із кодом пояснити, чому можна вибрати такий підхід, а не одну з інших відповідей.
NightOwl888

цей підхід простіший за інші, але робить те саме, тому я нічого не писав.
Фабіо Маціель,

encodeHexповерне масив 16-розрядних чисел, а не байтів.
Павло

0

Найкращим рішенням, яке я придумав на місці (хоча, швидше за все, сирим), було б:

String.prototype.getBytes = function() {
    var bytes = [];
    for (var i = 0; i < this.length; i++) {
        var charCode = this.charCodeAt(i);
        var cLen = Math.ceil(Math.log(charCode)/Math.log(256));
        for (var j = 0; j < cLen; j++) {
            bytes.push((charCode << (j*8)) & 0xFF);
        }
    }
    return bytes;
}

Хоча я помічаю, що це питання тут уже більше року.


2
Це працює неправильно. Логіка символів змінної довжини неправильна, в UTF-16 немає 8-бітових символів. Незважаючи на назву, charCodeAtповертає 16-розрядну одиницю коду UTF-16, тому вам не потрібна логіка змінної довжини. Ви можете просто зателефонувати charCodeAt, розділити результат на два 8-бітові байти та вкласти їх у вихідний масив (спочатку байт найнижчого порядку, оскільки запитання задає UTF-16LE).
Даніель Кассіді,

0

Я знаю, що питанню вже майже 4 роки, але це те, що зі мною справно працювало:

String.prototype.encodeHex = function () {
  var bytes = [];
  for (var i = 0; i < this.length; ++i) {
    bytes.push(this.charCodeAt(i));
  }
  return bytes;
};

Array.prototype.decodeHex = function () {    
  var str = [];
  var hex = this.toString().split(',');
  for (var i = 0; i < hex.length; i++) {
    str.push(String.fromCharCode(hex[i]));
  }
  return str.toString().replace(/,/g, "");
};

var str = "Hello World!";
var bytes = str.encodeHex();

alert('The Hexa Code is: '+bytes+' The original string is:  '+bytes.decodeHex());

або, якщо ви хочете працювати лише з рядками, а не з масивом, ви можете використовувати:

String.prototype.encodeHex = function () {
  var bytes = [];
  for (var i = 0; i < this.length; ++i) {
    bytes.push(this.charCodeAt(i));
  }
  return bytes.toString();
};

String.prototype.decodeHex = function () {    
  var str = [];
  var hex = this.split(',');
  for (var i = 0; i < hex.length; i++) {
    str.push(String.fromCharCode(hex[i]));
  }
  return str.toString().replace(/,/g, "");
};

var str = "Hello World!";
var bytes = str.encodeHex();

alert('The Hexa Code is: '+bytes+' The original string is:  '+bytes.decodeHex());


2
Цей вид працює, але вкрай вводить в оману. bytesМасив не містить «байти», він містить 16-розрядні числа, які представляють собою рядок в UTF-16 одиниць коди. Це майже те, про що задавалось питання, але насправді лише випадково.
Даніель Кассіді,

-1

Ось та сама функція, яку @BrunoLM розмістив, перетворену на функцію прототипу String:

String.prototype.getBytes = function () {
  var bytes = [];
  for (var i = 0; i < this.length; ++i) {
    bytes.push(this.charCodeAt(i));
  }
  return bytes;
};

Якщо ви визначите функцію як таку, тоді ви можете викликати метод .getBytes () для будь-якого рядка:

var str = "Hello World!";
var bytes = str.getBytes();

31
Це все ще неправильно, як і відповідь, на яку посилається. charCodeAt не повертає байт. Немає сенсу записувати значення більше 255 в масив, який називається "байти"; дуже вводить в оману. Ця функція взагалі не виконує кодування, вона просто вставляє коди символів у масив. Щоб виконати кодування UTF16, вам слід вивчити код символу, вирішити, чи потрібно буде представляти його 2 байтами або 4 байтами (оскільки UTF16 кодування змінної довжини), а потім записати кожен байт в масив окремо.
Трійко

8
Також поганою практикою є модифікація прототипу власних типів даних.
Ендрю Лундін,

@AndrewLundin, це цікаво ... каже хто?
Джертер


-3

Вам не потрібно підкреслювати, просто використовуйте вбудовану карту:

var string = 'Hello World!';

document.write(string.split('').map(function(c) { return c.charCodeAt(); }));


1
Це повертає масив 16-розрядних чисел, що представляють рядок як послідовність кодових точок UTF-16. Це не те, про що просив ОП, але принаймні це призводить до того, що ви туди потрапили.
Даніель Кассіді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.