Chuyển đổi giữa các chuỗi và ArrayBuffers


264

Có một kỹ thuật thường được chấp nhận để chuyển đổi hiệu quả các chuỗi JavaScript thành ArrayBuffers và ngược lại không? Cụ thể, tôi muốn có thể viết nội dung của ArrayBuffer localStoragevà đọc lại.


1
Tôi không có bất kỳ kinh nghiệm nào trong việc này, nhưng đánh giá từ tài liệu API ( khronos.org/registry/typedarray/specs/latest ) nếu bạn Int8Array ArrayBufferViewcó thể sử dụng ký hiệu ngoặc đơn để sao chép ký tự string[i] = buffer[i]và ngược lại.
FK82

2
@ FK82, có vẻ như là một cách tiếp cận hợp lý (sử dụng Uint16Arrays cho các ký tự 16 bit của JS), nhưng các chuỗi JavaScript là bất biến nên bạn không thể gán trực tiếp vào vị trí ký tự. Tôi vẫn sẽ cần sao chép String.fromCharCode(x)từng giá trị vào Uint16Arraymột bình thường Arrayvà sau đó gọi .join()vào Array.
kpozin

@kpozin: Đúng, không thực sự nghĩ rằng thông qua.
FK82

5
@kpozin Hóa ra hầu hết các công cụ JS hiện đại đã tối ưu hóa việc nối chuỗi đến mức rẻ hơn khi chỉ sử dụng string += String.fromCharCode(buffer[i]);. Có vẻ kỳ lạ là sẽ không có các phương thức tích hợp để chuyển đổi giữa các chuỗi và các mảng được gõ. Họ phải biết một cái gì đó như thế này sẽ xuất hiện.
tải xuống

mảngBuffer.toString () đang hoạt động tốt đối với tôi.
công dân

Câu trả lời:


128

Cập nhật 2016 - năm năm nay đã có các phương thức mới trong thông số kỹ thuật (xem hỗ trợ bên dưới) để chuyển đổi giữa các chuỗi và mảng được nhập bằng cách sử dụng mã hóa phù hợp.

Trình mã hóa văn bản

Các TextEncoderđại diện :

Các TextEncodergiao diện đại diện cho một bộ mã hóa cho một phương pháp cụ thể, đó là một mã hóa ký tự đặc biệt, giống như utf-8,iso-8859-2, koi8, cp1261, gbk, ... Một bộ mã hóa lấy một luồng các điểm mã làm đầu vào và phát ra một luồng byte.

Thay đổi ghi chú vì đã viết ở trên: (sđd.)

Lưu ý: Firefox, Chrome và Opera đã từng hỗ trợ các loại mã hóa khác ngoài utf-8 (chẳng hạn như utf-16, iso-8859-2, koi8, cp1261 và gbk). Kể từ Firefox 48 [...], Chrome 54 [...] và Opera 41, không có loại mã hóa nào khác ngoài utf-8, để phù hợp với thông số kỹ thuật. *

*) Thông số kỹ thuật được cập nhật (W3) và tại đây (whatwg).

Sau khi tạo một thể hiện của TextEncodernó, nó sẽ lấy một chuỗi và mã hóa nó bằng một tham số mã hóa đã cho:

if (!("TextEncoder" in window)) 
  alert("Sorry, this browser does not support TextEncoder...");

var enc = new TextEncoder(); // always utf-8
console.log(enc.encode("This is a string converted to a Uint8Array"));

Sau đó, bạn tất nhiên sử dụng .buffertham số trên kết quả Uint8Arrayđể chuyển đổi lớp lót ArrayBuffersang chế độ xem khác nếu cần.

Chỉ cần đảm bảo rằng các ký tự trong chuỗi tuân thủ lược đồ mã hóa, ví dụ, nếu bạn sử dụng các ký tự ngoài phạm vi UTF-8 trong ví dụ, chúng sẽ được mã hóa thành hai byte thay vì một byte.

Để sử dụng chung, bạn sẽ sử dụng mã hóa UTF-16 cho những thứ như localStorage.

Bộ giải mã văn bản

Tương tự, quá trình ngược lại sử dụngTextDecoder :

Các TextDecodergiao diện đại diện cho một bộ giải mã cho một phương pháp cụ thể, đó là một mã hóa ký tự cụ thể, như utf-8, iso-8859-2, koi8, cp1261, gbk, ... Một bộ giải mã có một dòng byte như là đầu vào và phát ra một dòng code point.

Tất cả các loại giải mã có sẵn có thể được tìm thấy ở đây .

if (!("TextDecoder" in window))
  alert("Sorry, this browser does not support TextDecoder...");

var enc = new TextDecoder("utf-8");
var arr = new Uint8Array([84,104,105,115,32,105,115,32,97,32,85,105,110,116,
                          56,65,114,114,97,121,32,99,111,110,118,101,114,116,
                          101,100,32,116,111,32,97,32,115,116,114,105,110,103]);
console.log(enc.decode(arr));

Thư viện MDN StringView

Một cách khác là sử dụng StringViewthư viện (được cấp phép là lgpl-3.0) với mục tiêu là:

  • để tạo giao diện giống như C cho các chuỗi (nghĩa là một mảng mã ký tự - ArrayBufferView trong JavaScript) dựa trên giao diện JavaScript ArrayBuffer
  • để tạo một thư viện có khả năng mở rộng cao mà bất kỳ ai cũng có thể mở rộng bằng cách thêm các phương thức vào đối tượng StringView.prototype
  • để tạo một tập hợp các phương thức cho các đối tượng giống như chuỗi đó (kể từ bây giờ: stringViews) hoạt động nghiêm ngặt trên các mảng số thay vì tạo các chuỗi JavaScript bất biến mới
  • để hoạt động với các bảng mã Unicode khác với DOMStrings UTF-16 mặc định của JavaScript

cho linh hoạt hơn nhiều. Tuy nhiên, nó sẽ yêu cầu chúng tôi liên kết hoặc nhúng thư viện này trong khi TextEncoder/ TextDecoderđang được tích hợp trong các trình duyệt hiện đại.

Ủng hộ

Kể từ tháng 7/2018:

TextEncoder (Thử nghiệm, theo dõi tiêu chuẩn)

 Chrome    | Edge      | Firefox   | IE        | Opera     | Safari
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |    19°    |     -     |     25    |     -

 Chrome/A  | Edge/mob  | Firefox/A | Opera/A   |Safari/iOS | Webview/A
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |    19°    |     ?     |     -     |     38

°) 18: Firefox 18 implemented an earlier and slightly different version
of the specification.

WEB WORKER SUPPORT:

Experimental, On Standard Track

 Chrome    | Edge      | Firefox   | IE        | Opera     | Safari
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |     20    |     -     |     25    |     -

 Chrome/A  | Edge/mob  | Firefox/A | Opera/A   |Safari/iOS | Webview/A
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |     20    |     ?     |     -     |     38

Data from MDN - `npm i -g mdncomp` by epistemex

2
Không hỗ trợ cho TextDecoder từ IE & Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete

1
Theo MS, nó đang được phát triển: developer.microsoft.com/en-us/microsoft-edge/pl
Maurice Müller

Không hỗ trợ cho Safari Mobile (ios) vào 2018-04-18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
người đàn ông bằng đồng vào

One-liner: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};vì vậy bạn chỉ có thểvar array = encoder.encode('hello');
Yeti

1
Vấn đề TextEncoderlà nếu bạn có dữ liệu nhị phân trong một chuỗi (như, hình ảnh), bạn không muốn sử dụng TextEncoder(rõ ràng). Các ký tự có điểm mã lớn hơn 127 tạo ra hai byte. Tại sao tôi có dữ liệu nhị phân trong một chuỗi? cy.fixture(NAME, 'binary')( cypress) tạo ra một chuỗi.
x-yuri

176

Mặc dù các giải pháp sử dụng Blob / FileReader của Dennis và gengkev, tôi không khuyên bạn nên thực hiện phương pháp đó. Đó là một cách tiếp cận không đồng bộ cho một vấn đề đơn giản, và nó chậm hơn nhiều so với giải pháp trực tiếp. Tôi đã tạo một bài đăng trong html5rocks với giải pháp đơn giản và (nhanh hơn nhiều): http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

Và giải pháp là:

function ab2str(buf) {
  return String.fromCharCode.apply(null, new Uint16Array(buf));
}

function str2ab(str) {
  var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
  var bufView = new Uint16Array(buf);
  for (var i=0, strLen=str.length; i<strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
}

BIÊN TẬP:

Các API Encoding giúp giải quyết các chuỗi chuyển đổi vấn đề. Kiểm tra phản hồi từ Jeff Posnik trên Html5Rocks.com cho bài viết gốc ở trên.

Trích đoạn:

API mã hóa giúp đơn giản hóa việc dịch giữa các byte thô và chuỗi JavaScript gốc, bất kể mã hóa nào trong số nhiều mã hóa tiêu chuẩn bạn cần để làm việc.

<pre id="results"></pre>

<script>
  if ('TextDecoder' in window) {
    // The local files to be fetched, mapped to the encoding that they're using.
    var filesToEncoding = {
      'utf8.bin': 'utf-8',
      'utf16le.bin': 'utf-16le',
      'macintosh.bin': 'macintosh'
    };

    Object.keys(filesToEncoding).forEach(function(file) {
      fetchAndDecode(file, filesToEncoding[file]);
    });
  } else {
    document.querySelector('#results').textContent = 'Your browser does not support the Encoding API.'
  }

  // Use XHR to fetch `file` and interpret its contents as being encoded with `encoding`.
  function fetchAndDecode(file, encoding) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', file);
    // Using 'arraybuffer' as the responseType ensures that the raw data is returned,
    // rather than letting XMLHttpRequest decode the data first.
    xhr.responseType = 'arraybuffer';
    xhr.onload = function() {
      if (this.status == 200) {
        // The decode() method takes a DataView as a parameter, which is a wrapper on top of the ArrayBuffer.
        var dataView = new DataView(this.response);
        // The TextDecoder interface is documented at http://encoding.spec.whatwg.org/#interface-textdecoder
        var decoder = new TextDecoder(encoding);
        var decodedString = decoder.decode(dataView);
        // Add the decoded file's text to the <pre> element on the page.
        document.querySelector('#results').textContent += decodedString + '\n';
      } else {
        console.error('Error while requesting', file, this);
      }
    };
    xhr.send();
  }
</script>

16
Thật không may, nhận xét của tôi về html5rocks vẫn chưa được chấp thuận. Vì vậy, một câu trả lời ngắn ở đây. Tôi vẫn nghĩ rằng, đây không phải là cách đúng đắn, bởi vì bạn bỏ lỡ rất nhiều ký tự, đặc biệt là vì hầu hết các trang đều ở dạng mã hóa UTF-8 ngày nay. Ở một bên, đối với các ký tự đặc biệt hơn (giả sử là người châu Á), hàm charCodeAt trả về giá trị 4 byte, do đó chúng sẽ bị cắt nhỏ. Mặt khác, các ký tự tiếng Anh đơn giản sẽ phát triển ArrayBuffer hai lần (bạn đang sử dụng 2 Byte cho mỗi ký tự 1 Byte). Hãy tưởng tượng gửi một văn bản tiếng Anh qua WebSocket, nó sẽ cần gấp đôi thời gian (không tốt trong môi trường thời gian thực).
Dennis

9
Ba ví dụ: (1) This is a cool text!20 Byte trong UTF8 - 40 Byte bằng Unicode. (2) ÄÖÜ6 byte trong UTF8 - 6 byte bằng Unicode. (3) ☐☑☒9 byte trong UTF8 - 6 byte bằng Unicode. Nếu bạn muốn lưu trữ chuỗi dưới dạng tệp UTF8 (thông qua API Blob và File Writer), bạn không thể sử dụng 2 phương thức này, vì ArrayBuffer sẽ ở dạng Unicode chứ không phải trong UTF8.
Dennis

3
Tôi gặp lỗi: Uncaught RangeError: Đã vượt quá kích thước ngăn xếp cuộc gọi tối đa. Điều gì có thể là vấn đề?
Jacob

6
@Dennis - Chuỗi JS sử dụng UCS2, không phải UTF8 (hoặc thậm chí UTF16) - có nghĩa là charCodeAt () luôn trả về các giá trị 0 -> 65535. Bất kỳ điểm mã UTF-8 nào yêu cầu kết thúc 4 byte sẽ được biểu diễn bằng các cặp thay thế (xem en.wikipedia .org / wiki / xoa ) - tức là hai giá trị UCS2 16 bit riêng biệt.
broalid

6
@jacob - Tôi tin rằng lỗi là do có giới hạn về độ dài của mảng có thể được truyền cho phương thức áp dụng (). Ví dụ: String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthhoạt động với tôi trong Chrome, nhưng nếu bạn sử dụng 246301 thay vào đó, tôi sẽ nhận được ngoại lệ RangeError của bạn
broofa

71

Bạn có thể sử dụng TextEncoderTextDecodertừ tiêu chuẩn Mã hóa , được thư viện chuỗi mã hóa , để chuyển đổi chuỗi sang và từ ArrayBuffers:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

2
Nhân tiện, cái này có sẵn trong Firefox theo mặc định: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard

2
Đồng ý với các API mới tốt hơn nhiều so với cách giải quyết kỳ lạ!
Tomáš Zato - Phục hồi Monica

1
Điều này sẽ không hoạt động với tất cả các loại nhân vật ngoài kia.
David

5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. Không, cám ơn.
Evan Hu

càu nhàu ... nếu tôi có một trình tạo mảng hiện có, tôi muốn viết một chuỗi vào tôi đoán tôi phải lấy uint8array và sao chép nó lần thứ 2 ??
shaunc

40

Blob chậm hơn nhiều String.fromCharCode(null,array);

nhưng điều đó không thành công nếu bộ đệm mảng trở nên quá lớn. Giải pháp tốt nhất tôi tìm thấy là sử dụngString.fromCharCode(null,array); và chia nó thành các hoạt động sẽ không thổi tung ngăn xếp, nhưng nhanh hơn một char mỗi lần.

Giải pháp tốt nhất cho bộ đệm mảng lớn là:

function arrayBufferToString(buffer){

    var bufView = new Uint16Array(buffer);
    var length = bufView.length;
    var result = '';
    var addition = Math.pow(2,16)-1;

    for(var i = 0;i<length;i+=addition){

        if(i + addition > length){
            addition = length - i;
        }
        result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
    }

    return result;

}

Tôi thấy điều này nhanh hơn khoảng 20 lần so với sử dụng blob. Nó cũng hoạt động cho các chuỗi lớn hơn 100mb.


3
Chúng ta nên đi với giải pháp này. Vì điều này giải quyết một trường hợp sử dụng nhiều hơn trường hợp được chấp nhận
sam

24

Dựa trên câu trả lời của gengkev, tôi đã tạo các hàm cho cả hai cách, bởi vì BlobBuilder có thể xử lý String và ArrayBuffer:

function string2ArrayBuffer(string, callback) {
    var bb = new BlobBuilder();
    bb.append(string);
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result);
    }
    f.readAsArrayBuffer(bb.getBlob());
}

function arrayBuffer2String(buf, callback) {
    var bb = new BlobBuilder();
    bb.append(buf);
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result)
    }
    f.readAsText(bb.getBlob());
}

Một bài kiểm tra đơn giản:

string2ArrayBuffer("abc",
    function (buf) {
        var uInt8 = new Uint8Array(buf);
        console.log(uInt8); // Returns `Uint8Array { 0=97, 1=98, 2=99}`

        arrayBuffer2String(buf, 
            function (string) {
                console.log(string); // returns "abc"
            }
        )
    }
)

Trong mảngBuffer2String (), ý bạn là gọi gọi lại (...) thay vì console.log ()? Nếu không, đối số gọi lại không được sử dụng.
Dan Philliffel

Điều này trông giống như con đường để đi - cảm ơn genkev và Dennis. Có vẻ ngớ ngẩn rằng không có cách đồng bộ để thực hiện điều này, nhưng bạn có thể làm gì ...
kpozin

JavaScript là một luồng đơn. Do đó, FileReader không đồng bộ vì hai lý do: (1) nó sẽ không chặn việc thực thi JavaScript khác trong khi tải tệp (rất lớn) (tưởng tượng một ứng dụng phức tạp hơn) và (2) nó sẽ không chặn UI / Browser (vấn đề phổ biến với mã JS thực thi dài). Rất nhiều API không đồng bộ. Ngay cả trong XMLHttpRequest 2, tính đồng bộ bị loại bỏ.
Dennis

Tôi đã thực sự hy vọng điều này sẽ làm việc cho tôi, nhưng việc chuyển đổi từ chuỗi sang ArrayBuffer không hoạt động đáng tin cậy. Tôi đang tạo một ArrayBuffer với 256 giá trị và có thể biến chuỗi đó thành một chuỗi có độ dài 256. Nhưng sau đó nếu tôi cố gắng chuyển đổi nó trở lại thành ArrayBuffer - tùy thuộc vào nội dung của ArrayBuffer ban đầu của tôi - tôi sẽ nhận được 376 phần tử. Nếu bạn muốn thử tái tạo vấn đề của mình, tôi đang xử lý ArrayBuffer của mình dưới dạng lưới 16x16 trong Uint8Array, với các giá trị được tính như a[y * w + x] = (x + y) / 2 * 16; tôi đã thử getBlob("x"), với nhiều mô hình khác nhau - không gặp may.
Matt Cruikshank

18
BlobBuilder không được dùng trong các trình duyệt mới hơn. Thay đổi new BlobBuilder(); bb.append(buf);thành new Blob([buf]), truyền ArrayBuffer trong hàm thứ hai thành UintArray thông qua new UintArray(buf)(hoặc bất cứ điều gì phù hợp với kiểu dữ liệu cơ bản), sau đó loại bỏ các getBlob()cuộc gọi. Cuối cùng, để sạch sẽ, đổi tên bb thành blob vì nó không còn là BlobBuilder nữa.
soworms

18

Tất cả những điều sau đây là về việc nhận chuỗi nhị phân từ bộ đệm mảng

Tôi khuyên bạn không nên sử dụng

var binaryString = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));

bởi vì nó

  1. sự cố trên bộ đệm lớn (ai đó đã viết về kích thước "ma thuật" 246300 nhưng tôi đã nhận đượcMaximum call stack size exceeded gặp lỗi trên bộ đệm 120000 byte (Chrome 29))
  2. nó có hiệu suất thực sự kém (xem bên dưới)

Nếu bạn thực sự cần giải pháp đồng bộ, hãy sử dụng một cái gì đó như

var
  binaryString = '',
  bytes = new Uint8Array(arrayBuffer),
  length = bytes.length;
for (var i = 0; i < length; i++) {
  binaryString += String.fromCharCode(bytes[i]);
}

nó chậm như cái trước nhưng hoạt động chính xác. Có vẻ như tại thời điểm viết bài này, không có giải pháp đồng bộ khá nhanh cho vấn đề đó (tất cả các thư viện được đề cập trong chủ đề này sử dụng cùng một cách tiếp cận cho các tính năng đồng bộ của chúng).

Nhưng những gì tôi thực sự khuyên là sử dụng phương pháp Blob+FileReader

function readBinaryStringFromArrayBuffer (arrayBuffer, onSuccess, onFail) {
  var reader = new FileReader();
  reader.onload = function (event) {
    onSuccess(event.target.result);
  };
  reader.onerror = function (event) {
    onFail(event.target.error);
  };
  reader.readAsBinaryString(new Blob([ arrayBuffer ],
    { type: 'application/octet-stream' }));
}

nhược điểm duy nhất (không phải cho tất cả) là nó không đồng bộ . Và nó nhanh hơn khoảng 8-10 lần so với các giải pháp trước đó! (Một số chi tiết: giải pháp đồng bộ trên môi trường của tôi mất 950-1050 ms cho bộ đệm 2,4Mb nhưng giải pháp với FileReader có thời gian khoảng 100-120 ms cho cùng một lượng dữ liệu. Và tôi đã thử nghiệm cả hai giải pháp đồng bộ trên bộ đệm 100Kb và họ đã thực hiện gần như cùng một lúc, do đó, vòng lặp không chậm hơn nhiều khi sử dụng 'áp dụng'.)

BTW tại đây: Cách chuyển đổi ArrayBuffer sang và từ String, tác giả so sánh hai cách tiếp cận như tôi và nhận được kết quả hoàn toàn trái ngược ( mã kiểm tra của anh ta ở đây ) Tại sao lại có kết quả khác nhau như vậy? Có lẽ vì chuỗi thử nghiệm của anh ta dài 1Kb (anh ta gọi nó là "VeryLongStr"). Bộ đệm của tôi là một hình ảnh JPEG thực sự lớn có kích thước 2,4Mb.


13

( Cập nhật Vui lòng xem nửa sau của câu trả lời này, nơi tôi có (hy vọng) đã cung cấp một giải pháp hoàn chỉnh hơn.)

Tôi cũng gặp vấn đề này, các công việc sau đây cho tôi trong FF 6 (cho một hướng):

var buf = new ArrayBuffer( 10 );
var view = new Uint8Array( buf );
view[ 3 ] = 4;
alert(Array.prototype.slice.call(view).join(""));

Thật không may, tất nhiên, bạn kết thúc bằng các biểu diễn văn bản ASCII của các giá trị trong mảng, thay vì các ký tự. Nó vẫn (nên) hiệu quả hơn nhiều so với một vòng lặp, mặc dù. ví dụ. Đối với ví dụ trên, kết quả là 0004000000, thay vì một vài ký tự null & một chr (4).

Biên tập:

Sau khi xem MDC ở đây , bạn có thể tạo một ArrayBuffertừ Arraynhư sau:

var arr = new Array(23);
// New Uint8Array() converts the Array elements
//  to Uint8s & creates a new ArrayBuffer
//  to store them in & a corresponding view.
//  To get at the generated ArrayBuffer,
//  you can then access it as below, with the .buffer property
var buf = new Uint8Array( arr ).buffer;

Để trả lời câu hỏi ban đầu của bạn, điều này cho phép bạn chuyển đổi ArrayBuffer<-> Stringnhư sau:

var buf, view, str;
buf = new ArrayBuffer( 256 );
view = new Uint8Array( buf );

view[ 0 ] = 7; // Some dummy values
view[ 2 ] = 4;

// ...

// 1. Buffer -> String (as byte array "list")
str = bufferToString(buf);
alert(str); // Alerts "7,0,4,..."

// 1. String (as byte array) -> Buffer    
buf = stringToBuffer(str);
alert(new Uint8Array( buf )[ 2 ]); // Alerts "4"

// Converts any ArrayBuffer to a string
//  (a comma-separated list of ASCII ordinals,
//  NOT a string of characters from the ordinals
//  in the buffer elements)
function bufferToString( buf ) {
    var view = new Uint8Array( buf );
    return Array.prototype.join.call(view, ",");
}
// Converts a comma-separated ASCII ordinal string list
//  back to an ArrayBuffer (see note for bufferToString())
function stringToBuffer( str ) {
    var arr = str.split(",")
      , view = new Uint8Array( arr );
    return view.buffer;
}

Để thuận tiện, đây là cách functionchuyển đổi Unicode thô Stringthành ArrayBuffer(sẽ chỉ hoạt động với các ký tự ASCII / một byte)

function rawStringToBuffer( str ) {
    var idx, len = str.length, arr = new Array( len );
    for ( idx = 0 ; idx < len ; ++idx ) {
        arr[ idx ] = str.charCodeAt(idx) & 0xFF;
    }
    // You may create an ArrayBuffer from a standard array (of values) as follows:
    return new Uint8Array( arr ).buffer;
}

// Alerts "97"
alert(new Uint8Array( rawStringToBuffer("abc") )[ 0 ]);

Ở trên cho phép bạn đi từ ArrayBuffer-> String& quay lại ArrayBufferlần nữa, trong đó chuỗi có thể được lưu trữ trong ví dụ. .localStorage:)

Hi vọng điêu nay co ich,

Dân


1
Tôi không nghĩ rằng đây là một phương pháp hiệu quả (về thời gian hoặc không gian) và đây là một cách rất bất thường để lưu trữ dữ liệu nhị phân.
kpozin

@kpozin: Theo như tôi biết, không có cách nào khác để lưu trữ dữ liệu nhị phân trong localStorage
Dan

1
Còn việc sử dụng mã hóa base64 thì sao?
Nick Sotiros

13

Không giống như các giải pháp ở đây, tôi cần chuyển đổi sang / từ dữ liệu UTF-8. Với mục đích này, tôi đã mã hóa hai hàm sau, sử dụng thủ thuật (un) esc / (en) decodeURIComponent. Chúng khá lãng phí bộ nhớ, phân bổ gấp 9 lần chiều dài của chuỗi utf8 được mã hóa, mặc dù chúng phải được gc khôi phục. Chỉ không sử dụng chúng cho văn bản 100mb.

function utf8AbFromStr(str) {
    var strUtf8 = unescape(encodeURIComponent(str));
    var ab = new Uint8Array(strUtf8.length);
    for (var i = 0; i < strUtf8.length; i++) {
        ab[i] = strUtf8.charCodeAt(i);
    }
    return ab;
}

function strFromUtf8Ab(ab) {
    return decodeURIComponent(escape(String.fromCharCode.apply(null, ab)));
}

Kiểm tra xem nó hoạt động:

strFromUtf8Ab(utf8AbFromStr('latinкирилицаαβγδεζηあいうえお'))
-> "latinкирилицаαβγδεζηあいうえお"

8

Trong trường hợp bạn có dữ liệu nhị phân trong một chuỗi (thu được từ nodejs+ readFile(..., 'binary'), hoặc cypress+ cy.fixture(..., 'binary'), v.v.), bạn không thể sử dụng TextEncoder. Nó chỉ hỗ trợ utf8. Mỗi byte có giá trị >= 128được chuyển thành 2 byte.

ES2015:

a = Uint8Array.from(s, x => x.charCodeAt(0))

Uint8Array (33) [2, 134, 140, 186, 82, 70, 108, 182, 233, 40, 143, 247, 29, 76, 245, 206, 29, 87, 48, 160, 78, 225, 242 , 56, 236, 201, 80, 80, 152, 118, 92, 144, 48

s = String.fromCharCode.apply(null, a)

"ºRFl¶é (÷ LõÎW0 Náò8ìÉPPv \ 0"


7

Tôi thấy tôi có vấn đề với cách tiếp cận này, về cơ bản vì tôi đang cố ghi đầu ra vào một tệp và nó không được mã hóa đúng cách. Vì JS dường như sử dụng mã hóa UCS-2 ( nguồn , nguồn ), chúng tôi cần kéo dài giải pháp này thêm một bước nữa, đây là giải pháp nâng cao của tôi hoạt động với tôi.

Tôi không gặp khó khăn với văn bản chung chung, nhưng khi nó xuống tiếng Ả Rập hoặc tiếng Hàn, tệp đầu ra không có tất cả các ký tự mà thay vào đó là hiển thị các ký tự lỗi

Đầu ra tệp: ","10k unit":"",Follow:"Õ©íüY‹","Follow %{screen_name}":"%{screen_name}U“’Õ©íü",Tweet:"ĤüÈ","Tweet %{hashtag}":"%{hashtag} ’ĤüÈY‹","Tweet to %{name}":"%{name}U“xĤüÈY‹"},ko:{"%{followers_count} followers":"%{followers_count}…X \Ì","100K+":"100Ì tÁ","10k unit":"Ì è",Follow:"\°","Follow %{screen_name}":"%{screen_name} Ø \°X0",K:"œ",M:"1Ì",Tweet:"¸","Tweet %{hashtag}":"%{hashtag}

Nguyên: ","10k unit":"万",Follow:"フォローする","Follow %{screen_name}":"%{screen_name}さんをフォロー",Tweet:"ツイート","Tweet %{hashtag}":"%{hashtag} をツイートする","Tweet to %{name}":"%{name}さんへツイートする"},ko:{"%{followers_count} followers":"%{followers_count}명의 팔로워","100K+":"100만 이상","10k unit":"만 단위",Follow:"팔로우","Follow %{screen_name}":"%{screen_name} 님 팔로우하기",K:"천",M:"백만",Tweet:"트윗","Tweet %{hashtag}":"%{hashtag}

Tôi lấy thông tin từ giải pháp của dennisbài đăng này tôi tìm thấy.

Đây là mã của tôi:

function encode_utf8(s) {
  return unescape(encodeURIComponent(s));
}

function decode_utf8(s) {
  return decodeURIComponent(escape(s));
}

 function ab2str(buf) {
   var s = String.fromCharCode.apply(null, new Uint8Array(buf));
   return decode_utf8(decode_utf8(s))
 }

function str2ab(str) {
   var s = encode_utf8(str)
   var buf = new ArrayBuffer(s.length); 
   var bufView = new Uint8Array(buf);
   for (var i=0, strLen=s.length; i<strLen; i++) {
     bufView[i] = s.charCodeAt(i);
   }
   return bufView;
 }

Điều này cho phép tôi lưu nội dung vào một tệp mà không gặp sự cố mã hóa.

Cách thức hoạt động: Về cơ bản, nó lấy các đoạn 8 byte đơn tạo thành một ký tự UTF-8 và lưu chúng thành các ký tự đơn (do đó, một ký tự UTF-8 được xây dựng theo cách này, có thể được tạo bởi 1-4 các ký tự này). UTF-8 mã hóa các ký tự theo định dạng có độ dài thay đổi từ 1 đến 4 byte. Những gì chúng ta làm ở đây là mã hóa sting trong một thành phần URI và sau đó lấy thành phần này và dịch nó theo ký tự 8 byte tương ứng. Theo cách này, chúng tôi không mất thông tin được cung cấp bởi các ký tự UTF8 dài hơn 1 byte.


6

nếu bạn đã sử dụng ví dụ mảng lớn, arr.length=1000000 bạn có thể mã này để tránh các vấn đề gọi lại ngăn xếp

function ab2str(buf) {
var bufView = new Uint16Array(buf);
var unis =""
for (var i = 0; i < bufView.length; i++) {
    unis=unis+String.fromCharCode(bufView[i]);
}
return unis
}

hàm ngược mangini trả lời từ đầu

function str2ab(str) {
    var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
    var bufView = new Uint16Array(buf);
    for (var i=0, strLen=str.length; i<strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

4

Chà, đây là một cách hơi phức tạp để làm điều tương tự:

var string = "Blah blah blah", output;
var bb = new (window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder)();
bb.append(string);
var f = new FileReader();
f.onload = function(e) {
  // do whatever
  output = e.target.result;
}
f.readAsArrayBuffer(bb.getBlob());

Chỉnh sửa: BlobBuilder từ lâu đã không được ủng hộ bởi nhà xây dựng Blob, vốn không tồn tại khi tôi viết bài đăng này lần đầu tiên. Đây là một phiên bản cập nhật. (Và vâng, đây luôn là một cách rất ngớ ngẩn để thực hiện chuyển đổi, nhưng nó chỉ để cho vui!)

var string = "Blah blah blah", output;
var f = new FileReader();
f.onload = function(e) {
  // do whatever
  output = e.target.result;
};
f.readAsArrayBuffer(new Blob([string]));

3

Sau khi chơi với giải pháp của mangini để chuyển đổi từ ArrayBuffersang String- ab2str(đây là giải pháp hữu ích và thanh lịch nhất mà tôi đã tìm thấy - cảm ơn!), Tôi đã gặp một số vấn đề khi xử lý các mảng lớn. Cụ thể hơn, gọi String.fromCharCode.apply(null, new Uint16Array(buf));ném lỗi:

arguments array passed to Function.prototype.apply is too large.

Để giải quyết nó (bỏ qua) tôi đã quyết định xử lý đầu vào ArrayBuffertheo từng khối. Vì vậy, giải pháp sửa đổi là:

function ab2str(buf) {
   var str = "";
   var ab = new Uint16Array(buf);
   var abLen = ab.length;
   var CHUNK_SIZE = Math.pow(2, 16);
   var offset, len, subab;
   for (offset = 0; offset < abLen; offset += CHUNK_SIZE) {
      len = Math.min(CHUNK_SIZE, abLen-offset);
      subab = ab.subarray(offset, offset+len);
      str += String.fromCharCode.apply(null, subab);
   }
   return str;
}

Kích thước khối được đặt thành 2^16vì đây là kích thước tôi đã tìm thấy để hoạt động trong bối cảnh phát triển của mình. Đặt giá trị cao hơn gây ra lỗi tương tự để lặp lại. Nó có thể được thay đổi bằng cách đặt CHUNK_SIZEbiến thành một giá trị khác. Điều quan trọng là có một số chẵn.

Lưu ý về hiệu suất - Tôi không thực hiện bất kỳ thử nghiệm hiệu suất nào cho giải pháp này. Tuy nhiên, vì nó dựa trên giải pháp trước đó và có thể xử lý các mảng lớn, tôi thấy không có lý do gì để không sử dụng nó.


bạn có thể sử dụng typedarray.subarray để có được một đoạn ở vị trí và kích thước được chỉ định, đây là những gì tôi làm để đọc các tiêu đề tắt định dạng nhị phân trong js
Nikos M.


2
  stringToArrayBuffer(byteString) {
    var byteArray = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      byteArray[i] = byteString.codePointAt(i);
    }
    return byteArray;
  }
  arrayBufferToString(buffer) {
    var byteArray = new Uint8Array(buffer);
    var byteString = '';
    for (var i = 0; i < byteArray.byteLength; i++) {
      byteString += String.fromCodePoint(byteArray[i]);
    }
    return byteString;
  }

mã này là lỗi nếu chuỗi chứa các ký tự unicode. ví dụ:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp

2

Đối với node.js và cả cho các trình duyệt sử dụng https://github.com/feross/buffer

function ab2str(buf: Uint8Array) {
  return Buffer.from(buf).toString('base64');
}
function str2ab(str: string) {
  return new Uint8Array(Buffer.from(str, 'base64'))
}

Lưu ý: Giải pháp ở đây không hiệu quả với tôi. Tôi cần hỗ trợ node.js và trình duyệt và chỉ cần tuần tự hóa UInt8Array thành một chuỗi. Tôi có thể tuần tự hóa nó thành một số [] nhưng nó chiếm không gian không cần thiết. Với giải pháp đó, tôi không cần phải lo lắng về mã hóa vì nó là cơ sở64. Chỉ trong trường hợp những người khác đấu tranh với cùng một vấn đề ... Hai xu của tôi


2

Giả sử bạn có một ArrayBuffer binaryStr:

let text = String.fromCharCode.apply(null, new Uint8Array(binaryStr));

và sau đó bạn gán văn bản cho nhà nước.


1

Chuỗi nhị phân "gốc" mà atob () trả về là Mảng 1 byte cho mỗi ký tự.

Vì vậy, chúng ta không nên lưu trữ 2 byte vào một ký tự.

var arrayBufferToString = function(buffer) {
  return String.fromCharCode.apply(null, new Uint8Array(buffer));
}

var stringToArrayBuffer = function(str) {
  return (new Uint8Array([].map.call(str,function(x){return x.charCodeAt(0)}))).buffer;
}

1

Đúng:

const encstr = (`TextEncoder` in window) ? new TextEncoder().encode(str) : Uint8Array.from(str, c => c.codePointAt(0));

0

Tôi khuyên bạn KHÔNG nên sử dụng các API không dùng nữa như BlobBuilder

BlobBuilder từ lâu đã bị đối tượng Blob phản đối. So sánh mã trong câu trả lời của Dennis - nơi BlobBuilder được sử dụng - với mã dưới đây:

function arrayBufferGen(str, cb) {

  var b = new Blob([str]);
  var f = new FileReader();

  f.onload = function(e) {
    cb(e.target.result);
  }

  f.readAsArrayBuffer(b);

}

Lưu ý rằng điều này sạch hơn và ít cồng kềnh hơn so với phương pháp không dùng nữa ... Vâng, đây chắc chắn là điều cần xem xét ở đây.


Ý tôi là, vâng, nhưng nhà xây dựng Blob đó thực sự không thể sử dụng được vào năm 2012;)
gengkev


0

Tôi đã sử dụng nó và làm việc cho tôi.

function arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
}



function base64ToArrayBuffer(base64) {
    var binary_string =  window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.