Как преобразовать строку в Bytearray

90

Как преобразовать строку в массив байтов с помощью JavaScript. Вывод должен быть эквивалентом приведенного ниже кода C #.

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

Поскольку UnicodeEncoding по умолчанию - UTF-16 с Little-Endianness.

Изменить: у меня есть требование сопоставить клиентскую сторону, сгенерированную байтовым массивом, с той, которая была создана на стороне сервера, с использованием приведенного выше кода C #.

шас
источник
3
javascript не совсем известен тем, что его легко использовать с большими двоичными объектами - почему бы вам просто не отправить строку в JSON?
Марк Грейвелл
Может быть, вы можете взглянуть здесь ..
V4Vendetta
2
Строка Javascript - это UTF-16, или вы это уже знали?
Кевин
2
Прежде всего, почему вам нужно преобразовать это в javascript?
BreakHead 03
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(', '));

BrunoLM
источник
1
Я уже пробовал это, но это дает мне другой результат, чем приведенный выше код C #. Как и в этом случае, выходной байтовый массив кода C # равен 72,0,101,0,108,0,108,0,111,0. У меня есть требование соответствовать обоим, поэтому это не работает.
shas
2
@shas Я тестировал предыдущую только на Firefox 4. Обновленная версия была протестирована на Firefox 4, Chrome 13 и IE9.
BrunoLM 03
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); return 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 # BOM (метки порядка байтов), но при использовании 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", поэтому этот символ занимает пространство из двух обычных символов.

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

Эта проблема совершенно нетривиальная. Это может зависеть от используемых версий и движков JavaScript. Поэтому, если вам нужны надежные решения, вам следует взглянуть на:

Hgoebl
источник
1
Все еще не полный ответ. UTF16 - это кодировка переменной длины, в которой для представления символов используются 16-битные блоки. Один символ будет закодирован как 2 байта или 4 байта, в зависимости от того, насколько велико значение кода символа. Так как эта функция записывает не более 2 байтов, она не может обрабатывать все кодовые точки символов Юникода и не является полной реализацией кодировки UTF16, не в последнюю очередь.
Трийнко
@Triynko после моего редактирования и тестирования, вы все еще думаете, что это не полный ответ? Если да, есть ли у вас ответ?
hgoebl 09
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 ]
code4j
источник
Это странно. Я не думаю, что использовать разные имена переменных, так как utf8Decode и utf8Encode будут работать.
Unihedron
Вы можете использовать TextDecoder для декодирования: new TextDecoder().decode(new TextEncoder().encode(str)) == str.
Fons
Вот таблицы поддержки 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

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

Кроме того, для заинтересованного читателя я опубликовал свои помощники юникода, которые помогают мне работать с длинами строк, сообщаемыми другими языками, такими как 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;
}
Jchook
источник
а что обратное?
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 ведущих нуля. Но тот же код для UTF-16 имеет только 1 ведущий 0. US-ASCII, с другой стороны, имеет фиксированную ширину 8 бит, что означает, что он может быть напрямую преобразован в байты.

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

Обратите внимание, что этот ответ нетривиален, потому что кодировка символов нетривиальна. Какой тип массива байтов вам нужен, зависит от того, какую кодировку символов вы хотите представлять в этих байтах.

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

Да, я знаю, что этому вопросу 4 года, но мне нужен был этот ответ для себя.

НебоСпираль7
источник
Результаты узла Buffer для '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;
}

Хотя я заметил, что этот вопрос был здесь больше года.

Whosdr
источник
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, это интересно ... кто говорит?
Джертер
2
@Jerther: stackoverflow.com/questions/14034180/…
Эндрю Лундин,
-3

Вам не нужно подчеркивание, просто используйте встроенную карту:

var string = 'Hello World!';

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

Кристиан Гутьеррес Сьерра
источник
1
Это возвращает массив 16-битных чисел, представляющих строку как последовательность кодовых точек UTF-16. Это не то, о чем просил OP, но, по крайней мере, он помогает вам достичь цели.
Дэниел Кэссиди,