การแปลงระหว่างสตริงและ ArrayBuffers


264

มีเทคนิคที่ยอมรับกันโดยทั่วไปสำหรับการแปลงสตริง JavaScript ให้เป็นArrayBuffersและในทางกลับกันได้อย่างมีประสิทธิภาพหรือไม่? โดยเฉพาะฉันต้องการที่จะสามารถเขียนเนื้อหาของ ArrayBuffer ไปที่localStorageและอ่านมันกลับมา


1
ฉันไม่มีประสบการณ์ใด ๆ ในเรื่องนี้ แต่พิจารณาจากเอกสาร API ( khronos.org/registry/typedarray/specs/latest ) หากคุณสร้างInt8Array ArrayBufferViewมันอาจเป็นไปได้ที่จะใช้เครื่องหมายวงเล็บเพื่อคัดลอกตัวอักษรstring[i] = buffer[i]และในทางกลับกัน
FK82

2
@ FK82 ซึ่งดูเหมือนว่าเหมาะสม (ใช้Uint16Arrays สำหรับอักขระ 16 บิตของ JS) แต่สตริง JavaScript นั้นไม่เปลี่ยนรูปดังนั้นคุณจึงไม่สามารถกำหนดตำแหน่งอักขระได้โดยตรง ฉันยังคงต้องคัดลอกString.fromCharCode(x)ของแต่ละค่าในการUint16Arrayที่จะปกติArrayแล้วโทรบน.join() Array
kpozin

@kpozin: จริงไม่ได้คิดอย่างนั้น
FK82

5
@kpozin ปรากฎว่าส่วนใหญ่เครื่องมือ JS string += String.fromCharCode(buffer[i]);ที่ทันสมัยมีการเพิ่มประสิทธิภาพสตริงไปยังจุดที่มันถูกกว่าการใช้เพียง ดูเหมือนแปลกที่ไม่มีวิธีการในตัวสำหรับการแปลงระหว่างสตริงและอาร์เรย์ที่พิมพ์ พวกเขาต้องรู้อะไรแบบนี้จะเกิดขึ้น
ดาวน์โหลด

arrayBuffer.toString () ทำงานได้ดีสำหรับฉัน
พลเมือง conn

คำตอบ:


128

อัปเดต 2559 - ห้าปีที่ผ่านมาขณะนี้มีวิธีการใหม่ในสเป็ค (ดูการสนับสนุนด้านล่าง) เพื่อแปลงระหว่างสตริงและอาร์เรย์ที่พิมพ์โดยใช้การเข้ารหัสที่เหมาะสม

TextEncoder

TextEncoderหมายถึง :

TextEncoderอินเตอร์เฟซที่แสดงให้เห็นถึงการเข้ารหัสสำหรับวิธีการเฉพาะที่เป็นการเข้ารหัสตัวอักษรที่เฉพาะเจาะจงเช่นutf-8,iso-8859-2, koi8, cp1261, gbk... เครื่องเข้ารหัสใช้กระแสของจุดรหัสเป็นอินพุตและปล่อยกระแสข้อมูลของไบต์

บันทึกการเปลี่ยนแปลงตั้งแต่เขียนข้างต้น: (ibid.)

หมายเหตุ: Firefox, Chrome และ Opera เคยรองรับการเข้ารหัสประเภทอื่นที่ไม่ใช่ utf-8 (เช่น utf-16, iso-8859-2, koi8, cp1261 และ gbk) ตั้งแต่ Firefox 48 [... ], Chrome 54 [... ] และ Opera 41 ไม่มีการเข้ารหัสประเภทอื่นใดนอกจาก utf-8 เพื่อให้ตรงกับข้อมูลจำเพาะ *

*) อัพเดตรายละเอียด (W3) และที่นี่ (whatwg)

หลังจากสร้างอินสแตนซ์ของTextEncoderมันจะใช้สตริงและเข้ารหัสโดยใช้พารามิเตอร์การเข้ารหัสที่กำหนด:

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

แน่นอนว่าคุณใช้.bufferพารามิเตอร์ที่อยู่บนผลลัพธ์Uint8Arrayเพื่อแปลงการปูพื้นArrayBufferให้เป็นมุมมองอื่นหากจำเป็น

ตรวจสอบให้แน่ใจว่าอักขระในสตริงเป็นไปตามสคีมาการเข้ารหัสตัวอย่างเช่นหากคุณใช้อักขระที่อยู่นอกช่วง UTF-8 ในตัวอย่างพวกเขาจะถูกเข้ารหัสเป็นสองไบต์แทนหนึ่งตัว

สำหรับการใช้งานทั่วไปที่คุณจะใช้เข้ารหัส UTF-16 localStorageสำหรับสิ่งที่ต้องการ

TextDecoder

กระบวนการตรงกันข้ามใช้TextDecoder :

TextDecoderอินเตอร์เฟซที่แสดงให้เห็นถึงถอดรหัสสำหรับวิธีการเฉพาะที่เป็นการเข้ารหัสอักขระที่เฉพาะเจาะจงเช่นutf-8, iso-8859-2, koi8, cp1261, gbk... ถอดรหัสต้องใช้กระแสของไบต์เป็น input และส่งเสียงกระแสของจุดรหัส

ทุกประเภทถอดรหัสใช้ได้สามารถพบได้ที่นี่

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

ไลบรารี MDN StringView

ทางเลือกในการเหล่านี้คือการใช้StringViewห้องสมุด (ใบอนุญาตเป็น LGPL-3.0) ซึ่งเป้าหมายคือ:

  • เพื่อสร้างอินเตอร์เฟส C-like สำหรับสตริง (เช่นอาร์เรย์ของรหัสอักขระ - ArrayBufferView ใน JavaScript) โดยยึดตามอินเตอร์เฟส JavaScript ArrayBuffer ของ JavaScript
  • เพื่อสร้างไลบรารีที่สามารถขยายได้สูงซึ่งทุกคนสามารถขยายได้โดยการเพิ่มเมธอดไปยังวัตถุ StringView.prototype
  • เพื่อสร้างคอลเลกชันของวิธีการสำหรับวัตถุที่คล้ายสตริง (ตั้งแต่ตอนนี้: stringViews) ซึ่งทำงานอย่างเคร่งครัดในอาร์เรย์ของตัวเลขแทนที่จะสร้างสตริง JavaScript ที่ไม่เปลี่ยนรูปแบบใหม่
  • ทำงานกับการเข้ารหัส Unicode นอกเหนือจาก DOMStrings UTF-16 ที่เป็นค่าเริ่มต้นของ JavaScript

ให้ความยืดหยุ่นมากขึ้น อย่างไรก็ตามเราจำเป็นต้องเชื่อมโยงหรือฝังไลบรารีนี้ในขณะที่TextEncoder/ TextDecoderกำลังติดตั้งอยู่ในเบราว์เซอร์สมัยใหม่

สนับสนุน

ณ กรกฎาคม / 2018:

TextEncoder (ทดลองบนแทร็กมาตรฐาน)

 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
ไม่รองรับ TextDecoder จาก IE & Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete

1
ตามที่ MS อยู่ในการพัฒนา: developer.microsoft.com/en-us/microsoft-edge/platform/status/
......

ไม่มีการสนับสนุนสำหรับ Safari มือถือ (iOS) ที่ 2018/04/18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
บรอนซ์ชาย

One-liner: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};ดังนั้นคุณสามารถทำได้var array = encoder.encode('hello');
Yeti

1
สิ่งที่มีTextEncoderคือถ้าคุณมีข้อมูลไบนารีในสตริง (เช่นรูปภาพ) คุณไม่ต้องการใช้TextEncoder(ชัด) อักขระที่มีรหัสจุดมากกว่า 127 สร้างสองไบต์ ทำไมฉันถึงมีข้อมูลเลขฐานสองในสตริง? cy.fixture(NAME, 'binary')( cypress) สร้างสตริง
x-yuri

176

แม้ว่า Dennis และ gengkev จะมีวิธีแก้ปัญหาในการใช้งาน Blob / FileReader แต่ฉันก็ไม่แนะนำให้ใช้วิธีการนั้น มันเป็นวิธีการแบบอะซิงโครนัสสำหรับปัญหาง่าย ๆ และช้ากว่าโซลูชันโดยตรงมาก ฉันโพสต์ใน html5rocks ด้วยวิธีที่ง่ายและ (เร็วกว่า): http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

และการแก้ปัญหาคือ:

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

แก้ไข:

เข้ารหัส API จะช่วยให้การแก้สตริงแปลงปัญหา ลองอ่านคำตอบจากJeff Posnikบน Html5Rocks.com กับบทความต้นฉบับด้านบน

ข้อความที่ตัดตอนมา:

API การเข้ารหัสทำให้ง่ายต่อการแปลระหว่างไบต์ดิบและสตริง JavaScript ดั้งเดิมโดยไม่คำนึงถึงการเข้ารหัสมาตรฐานที่คุณต้องใช้

<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
น่าเสียดายที่ความคิดเห็นของฉันเกี่ยวกับ html5rocks ยังไม่ได้รับการอนุมัติ ดังนั้นคำตอบสั้น ๆ ที่นี่ ฉันยังคงคิดว่านี่ไม่ใช่วิธีที่ถูกต้องเพราะคุณพลาดตัวละครมากมายโดยเฉพาะอย่างยิ่งเพราะส่วนใหญ่หน้าอยู่ในการเข้ารหัส UTF-8 วันนี้ ในอีกด้านหนึ่งสำหรับอักขระพิเศษเพิ่มเติม (สมมุติว่าเป็นภาษาเอเชีย) ฟังก์ชัน charCodeAt จะส่งกลับค่า 4 ไบต์ดังนั้นพวกเขาจะถูกสับ ในอีกด้านหนึ่งอักขระภาษาอังกฤษแบบง่ายจะเพิ่ม ArrayBuffer สองครั้ง (คุณใช้ 2 ไบต์สำหรับอักขระ 1 ไบต์ทุกตัว) ลองนึกภาพการส่งข้อความภาษาอังกฤษผ่าน WebSocket มันจะต้องสองครั้ง (ไม่ดีในสภาพแวดล้อมเรียลไทม์)
Dennis

9
ตัวอย่างที่สาม: (1) This is a cool text!20 ไบต์ใน UTF8 - 40 ไบต์ใน Unicode (2) ÄÖÜ6 ไบต์ใน UTF8 - 6 ไบต์ใน Unicode (3) ☐☑☒9 ไบต์ใน UTF8 - 6 ไบต์ใน Unicode หากคุณต้องการเก็บสตริงเป็นไฟล์ UTF8 (ผ่าน Blob และ File Writer API) คุณไม่สามารถใช้ 2 วิธีนี้ได้เนื่องจาก ArrayBuffer จะเป็น Unicode ไม่ใช่ UTF8
เดนนิส

3
ฉันได้รับข้อผิดพลาด: Uncaught RangeError: เกินขนาดสแต็กการโทรสูงสุด มีปัญหาอะไร
จาค็อบ

6
@Dennis - สตริง JS ใช้ UCS2 ไม่ใช่ UTF8 (หรือแม้แต่ UTF16) - หมายถึง charCodeAt () จะส่งคืนค่า 0 -> 65535 เสมอจุดรหัส UTF-8 ใด ๆ ที่ต้องการ 4 ไบต์สิ้นสุดจะแสดงด้วยคู่ตัวแทน (ดูen.wikipedia .org / wiki / … ) - เช่นสองค่า UCS2 แบบ 16 บิตแยกกัน
broofa

6
@jacob - ฉันเชื่อว่าข้อผิดพลาดเป็นเพราะมีข้อ จำกัด เกี่ยวกับความยาวของอาร์เรย์ที่สามารถส่งผ่านไปยังวิธีการใช้ () เช่นString.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthทำงานให้ฉันใน Chrome แต่ถ้าคุณใช้ 246301 แทนฉันจะได้รับข้อยกเว้น RangeError ของคุณ
broofa

71

คุณสามารถใช้TextEncoderและTextDecoderจากมาตรฐานการเข้ารหัสซึ่งเป็นโพลีฟิลโดยไลบรารีสตริงการเข้ารหัสเพื่อแปลงสตริงเป็นและจาก ArrayBuffers:

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

2
โดยวิธีการนี้มีให้ใน Firefox โดยค่าเริ่มต้น: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard

2
ยกนิ้วให้กับ API ใหม่ที่ดีกว่าการแก้ไขปัญหาแปลก ๆ !
Tomáš Zato - Reinstate Monica

1
สิ่งนี้จะไม่สามารถใช้ได้กับตัวละครทุกประเภท
David

5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. ไม่เป็นไรขอบคุณ.
Evan Hu

บ่น ... ถ้าฉันมี arraybuffer ที่มีอยู่ฉันต้องการเขียนสตริงลงไปฉันคิดว่าฉันต้องใช้ uint8array และคัดลอกมันเป็นครั้งที่สอง ??
shaunc

40

หยดช้ากว่ามาก String.fromCharCode(null,array);

แต่นั่นจะล้มเหลวหากบัฟเฟอร์อาร์เรย์มีขนาดใหญ่เกินไป ทางออกที่ดีที่สุดที่ฉันพบคือการใช้String.fromCharCode(null,array);และแยกออกเป็นการดำเนินการที่จะไม่ระเบิดกอง แต่จะเร็วกว่าถ่านครั้งเดียว

ทางออกที่ดีที่สุดสำหรับบัฟเฟอร์อาร์เรย์ขนาดใหญ่คือ:

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;

}

ฉันพบว่านี่จะเร็วกว่าการใช้ Blob ประมาณ 20 เท่า นอกจากนี้ยังใช้งานได้กับสตริงขนาดใหญ่กว่า 100mb


3
เราควรไปด้วยวิธีนี้ เช่นนี้จะช่วยแก้ปัญหากรณีใช้มากกว่าหนึ่งกรณีที่ยอมรับ
sam

24

จากคำตอบของ gengkev ฉันได้สร้างฟังก์ชั่นสำหรับทั้งสองวิธีเนื่องจากBlobBuilderสามารถจัดการสตริงและ 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());
}

การทดสอบอย่างง่าย:

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

ใน arrayBuffer2String () คุณหมายถึงการโทรกลับ (... ) แทน console.log () หรือไม่ มิฉะนั้นอาร์กิวเมนต์โทรกลับจะไม่ได้ใช้
Dan Phillimore

ดูเหมือนว่าจะเป็นอย่างไร - ขอบคุณ genkev และ Dennis ดูเหมือนว่างี่เง่าว่าไม่มีวิธีแบบซิงโครนัสในการทำสิ่งนี้ให้สำเร็จ แต่สิ่งที่คุณสามารถทำได้ ...
kpozin

JavaScript เป็นเธรดเดียว ดังนั้น FileReader แบบอะซิงโครนัสด้วยเหตุผลสองประการ: (1) มันจะไม่บล็อกการใช้งานจาวาสคริปต์อื่นในขณะที่โหลดไฟล์ (ขนาดใหญ่) (จินตนาการแอปพลิเคชันที่ซับซ้อนมากขึ้น) และ (2) มันจะไม่ปิดกั้น UI / เบราว์เซอร์ ด้วยรหัส JS ยาว) API จำนวนมากไม่ตรงกัน แม้ใน XMLHttpRequest 2 การซิงโครนัสจะถูกลบออก
Dennis

ฉันหวังว่าสิ่งนี้จะได้ผลสำหรับฉัน แต่การเปลี่ยนจากสตริงเป็น ArrayBuffer นั้นใช้งานไม่ได้อย่างน่าเชื่อถือ ฉันกำลังสร้าง ArrayBuffer ด้วยค่า 256 และสามารถเปลี่ยนให้เป็นสตริงที่มีความยาว 256 แต่ถ้าฉันลองแปลงกลับไปเป็น ArrayBuffer - ขึ้นอยู่กับเนื้อหาของ ArrayBuffer เริ่มต้นของฉัน - ฉันได้รับองค์ประกอบ 376 รายการ หากคุณต้องการลองทำซ้ำปัญหาของฉันฉันจัดการ ArrayBuffer ของฉันเป็นกริด 16x16 ใน Uint8Array ด้วยค่าที่คำนวณตามที่a[y * w + x] = (x + y) / 2 * 16; ฉันได้ลองgetBlob("x")ด้วย mimetypes ที่แตกต่างกันมากมาย - ไม่มีโชค
Matt Cruikshank

18
BlobBuilder เลิกใช้แล้วในเบราว์เซอร์ใหม่ เปลี่ยนnew BlobBuilder(); bb.append(buf);เป็นnew Blob([buf])โยน ArrayBuffer ในฟังก์ชั่นที่สองเป็น UintArray ผ่านnew UintArray(buf)(หรืออะไรก็ตามที่เหมาะสมสำหรับประเภทข้อมูลพื้นฐาน) แล้วกำจัดการgetBlob()โทรออก ในที่สุดเพื่อความสะอาดเปลี่ยนชื่อ bb เป็นหยดเพราะไม่ใช่ BlobBuilder อีกต่อไป
sowbug

18

ทั้งหมดต่อไปนี้เป็นเรื่องเกี่ยวกับการรับสตริงไบนารีจากบัฟเฟอร์อาร์เรย์

ฉันไม่แนะนำให้ใช้

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

เพราะมัน

  1. เกิดปัญหากับบัฟเฟอร์ขนาดใหญ่ (บางคนเขียนเกี่ยวกับ "เวทมนต์" ขนาด 246300 แต่ฉันมีMaximum call stack size exceededข้อผิดพลาดในบัฟเฟอร์ 120000 ไบต์ (Chrome 29))
  2. มันมีประสิทธิภาพต่ำมาก (ดูด้านล่าง)

หากคุณต้องการโซลูชันแบบซิงโครนัสให้ใช้สิ่งที่ต้องการ

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

มันช้าเท่ากับรุ่นก่อนหน้า แต่ทำงานได้อย่างถูกต้อง ดูเหมือนว่าในขณะที่เขียนสิ่งนี้ไม่มีวิธีแก้ปัญหาแบบซิงโครนัสที่รวดเร็วสำหรับปัญหานั้น (ไลบรารีทั้งหมดที่กล่าวถึงในหัวข้อนี้ใช้วิธีการเดียวกันสำหรับคุณลักษณะแบบซิงโครนัส)

แต่สิ่งที่ฉันแนะนำจริงๆคือใช้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' }));
}

ข้อเสียเท่านั้น (ไม่ทั้งหมด) ก็คือว่ามันไม่ตรงกัน และเร็วกว่าโซลูชั่นก่อนหน้าประมาณ8-10 เท่า ! (รายละเอียดบางอย่าง: โซลูชันแบบซิงโครนัสในสภาพแวดล้อมของฉันใช้เวลา 950-1050 มิลลิวินาทีสำหรับบัฟเฟอร์ 2.4Mb แต่โซลูชันที่มี FileReader มีเวลาประมาณ 100-120 มิลลิวินาทีสำหรับข้อมูลจำนวนเท่ากันและฉันได้ทดสอบทั้งสองซิงโครนัสกับบัฟเฟอร์ 100Kb เกือบในเวลาเดียวกันดังนั้นการวนซ้ำจึงไม่ช้ากว่าการใช้ 'นำไปใช้')

BTW ที่นี่: วิธีการแปลง ArrayBuffer เป็นและจากผู้เขียนStringเปรียบเทียบสองวิธีเช่นฉันและได้ผลลัพธ์ที่ตรงข้ามอย่างสมบูรณ์ ( รหัสทดสอบของเขาอยู่ที่นี่ ) ทำไมผลลัพธ์ที่แตกต่างกันอย่างไร อาจเป็นเพราะสตริงทดสอบของเขาที่มีความยาว 1Kb (เขาเรียกมันว่า "veryLongStr") บัฟเฟอร์ของฉันเป็นภาพ JPEG ขนาดใหญ่จริง ๆ ขนาด 2.4Mb


13

( อัปเดตโปรดดูครึ่งหลังของคำตอบนี้ที่ฉันมี (หวังว่า) จะให้โซลูชันที่สมบูรณ์ยิ่งขึ้น)

ฉันพบปัญหานี้ด้วยการทำงานต่อไปนี้สำหรับฉันใน FF 6 (สำหรับทิศทางเดียว):

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

น่าเสียดายที่คุณจบลงด้วยการแทนข้อความ ASCII ของค่าในอาร์เรย์แทนที่จะเป็นตัวอักษร แม้ว่าจะยังคงมีประสิทธิภาพมากกว่าลูปก็ตาม เช่น. สำหรับตัวอย่างด้านบนผลลัพธ์คือ0004000000แทนที่จะเป็น null หลาย chars & a chr (4)

แก้ไข:

หลังจากดูMDC ที่นี่คุณสามารถสร้างรายการArrayBufferจากArrayดังนี้:

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;

ในการตอบคำถามเดิมของคุณคุณสามารถแปลงArrayBuffer<-> Stringดังนี้:

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

เพื่อความสะดวกนี่คือการfunctionแปลง Unicode แบบ raw Stringให้เป็นArrayBuffer(จะใช้ได้กับอักขระ ASCII / หนึ่งไบต์เท่านั้น)

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

ด้านบนอนุญาตให้คุณเปลี่ยนจากArrayBuffer-> String& กลับไปArrayBufferอีกครั้งซึ่งอาจเก็บสตริงไว้ใน .localStorage:)

หวังว่าจะช่วยได้

แดน


1
ฉันไม่คิดว่านี่เป็นวิธีที่มีประสิทธิภาพ (ในแง่ของเวลาหรือพื้นที่) และนี่เป็นวิธีที่ผิดปกติมากในการจัดเก็บข้อมูลไบนารี
kpozin

@kpozin: เท่าที่ฉันรู้ไม่มีวิธีอื่นในการจัดเก็บข้อมูลไบนารีใน localStorage
Dan Phillimore

1
สิ่งที่เกี่ยวกับการใช้การเข้ารหัส base64
Nick Sotiros

13

ไม่เหมือนโซลูชันที่นี่ฉันต้องการแปลงเป็น / จากข้อมูล UTF-8 สำหรับจุดประสงค์นี้ฉันเขียนโค้ดสองฟังก์ชั่นต่อไปนี้โดยใช้ (un) escape / (en) decodeURIComponent หน่วยความจำเหล่านี้ค่อนข้างสิ้นเปลืองโดยจัดสรรความยาวของสตริง utf8-encoded เป็น 9 เท่าแม้ว่า gc จะสามารถกู้คืนได้ อย่าใช้มันกับข้อความขนาด 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)));
}

ตรวจสอบว่ามันใช้งานได้:

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

8

ในกรณีที่คุณมีข้อมูลเลขฐานสองในสตริง (ที่ได้จากnodejs+ readFile(..., 'binary')หรือcypress+ cy.fixture(..., 'binary')ฯลฯ ) คุณจะไม่สามารถใช้งานTextEncoderได้ utf8มันสนับสนุนเท่านั้น ไบต์ที่มีค่า>= 128แต่ละค่าจะถูกเปลี่ยนเป็น 2 ไบต์

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õÎW0Náò8ìÉPPv \ 0"


7

ฉันพบว่าฉันมีปัญหากับวิธีการนี้เนื่องจากฉันพยายามเขียนผลลัพธ์ไปยังไฟล์และมันไม่ได้เข้ารหัสอย่างถูกต้อง ตั้งแต่ JS ดูเหมือนว่าจะใช้ UCS 2 การเข้ารหัส ( แหล่งที่มา , แหล่งที่มา ) เราต้องการที่จะยืดการแก้ปัญหานี้ขั้นตอนต่อไปนี่คือทางออกที่เพิ่มขึ้นของฉันที่ผลงานกับผม

ฉันไม่มีปัญหากับข้อความทั่วไป แต่เมื่อลงไปเป็นอาหรับหรือเกาหลีแล้วไฟล์เอาต์พุตไม่ได้มีตัวอักษรทั้งหมด แต่กลับแสดงอักขระข้อผิดพลาดแทน

ไฟล์ที่ส่งออก: ","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}

เดิม: ","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}

ฉันเอาข้อมูลจากวิธีแก้ปัญหาของเดนนิสและพบโพสต์นี้

นี่คือรหัสของฉัน:

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

สิ่งนี้ทำให้ฉันสามารถบันทึกเนื้อหาไปยังไฟล์โดยไม่มีปัญหาการเข้ารหัส

วิธีการทำงาน: โดยทั่วไปจะใช้ชิ้นส่วน 8 ไบต์เดียวที่ประกอบด้วยอักขระ UTF-8 และบันทึกเป็นอักขระเดี่ยว (ดังนั้นอักขระ UTF-8 ที่สร้างขึ้นในลักษณะนี้อาจประกอบด้วย 1-4 ของอักขระเหล่านี้) UTF-8 เข้ารหัสอักขระในรูปแบบที่มีความยาวตั้งแต่ 1 ถึง 4 ไบต์ สิ่งที่เราทำที่นี่คือการเข้ารหัสต่อยในองค์ประกอบ URI แล้วนำองค์ประกอบนี้และแปลมันในตัวอักษร 8 ไบต์ที่สอดคล้องกัน ด้วยวิธีนี้เราจะไม่สูญเสียข้อมูลที่กำหนดโดยตัวอักษร UTF8 ที่มีความยาวมากกว่า 1 ไบต์


6

ถ้าคุณใช้ตัวอย่างอาร์เรย์ขนาดใหญ่arr.length=1000000 คุณสามารถใช้รหัสนี้เพื่อหลีกเลี่ยงปัญหาการโทรกลับสแต็ค

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
}

ฟังก์ชั่นย้อนกลับ Mangini คำตอบจากด้านบน

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

นี่เป็นวิธีที่ค่อนข้างซับซ้อนในการทำสิ่งเดียวกัน:

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

แก้ไข: BlobBuilder เลิกใช้มานานแล้วในความโปรดปรานของตัวสร้าง Blob ซึ่งไม่มีอยู่เมื่อฉันเขียนบทความนี้เป็นครั้งแรก นี่คือรุ่นที่ปรับปรุงแล้ว (และใช่นี่เป็นวิธีที่โง่มากที่จะทำเรื่องเปลี่ยนใจเลื่อมใส แต่มันก็เพื่อความสนุก!)

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

หลังจากเล่นกับคำตอบของ mangini สำหรับการแปลงจากArrayBufferเป็นString- ab2strซึ่งเป็นสิ่งที่สวยงามและมีประโยชน์ที่สุดที่ฉันได้พบ - ขอบคุณ!) ฉันมีปัญหาบางอย่างเมื่อจัดการกับอาร์เรย์ขนาดใหญ่ มากกว่า specefivally โทรString.fromCharCode.apply(null, new Uint16Array(buf));โยนข้อผิดพลาด:

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

เพื่อที่จะแก้ปัญหานี้ (บายพาส) ฉันได้ตัดสินใจจัดการอินพุตเป็นArrayBufferชิ้น ๆ ดังนั้นวิธีแก้ไขคือ:

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

ขนาดก้อนถูกตั้งค่าเป็น2^16เพราะนี่คือขนาดที่ฉันได้พบว่าทำงานในแนวการพัฒนาของฉัน การตั้งค่าที่สูงกว่าทำให้เกิดข้อผิดพลาดซ้ำอีกครั้ง มันสามารถเปลี่ยนแปลงได้โดยการตั้งค่าCHUNK_SIZEตัวแปรให้เป็นค่าที่แตกต่างกัน สิ่งสำคัญคือต้องมีเลขคู่

หมายเหตุเกี่ยวกับประสิทธิภาพ - ฉันไม่ได้ทำการทดสอบประสิทธิภาพใด ๆ สำหรับโซลูชันนี้ อย่างไรก็ตามเนื่องจากเป็นไปตามวิธีแก้ไขปัญหาก่อนหน้านี้และสามารถจัดการกับอาร์เรย์ขนาดใหญ่ได้ฉันจึงไม่เห็นเหตุผลที่จะไม่ใช้มัน


คุณสามารถใช้typedarray.subarrayเพื่อรับชิ้นงานตามตำแหน่งและขนาดที่ระบุนี่คือสิ่งที่ฉันทำเพื่ออ่านส่วนหัวของรูปแบบไบนารีใน js
Nikos M.

2

ดูที่นี่: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (อินเตอร์เฟส C-like สำหรับสตริงที่อิงกับอินเตอร์เฟส JavaScript ArrayBuffer)


2
รหัสนั้นอยู่ภายใต้ GPLv3 ฉันคิดว่ามันค่อนข้างไม่เป็นมืออาชีพของ Mozilla ที่จะผสมผสานรหัสนั้นเข้ากับเอกสารที่เป็นไปตามมาตรฐาน
user239558

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

รหัสนี้เป็นรถหากสตริงมีอักขระ Unicode ตัวอย่าง:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp

2

สำหรับ node.js และสำหรับเบราว์เซอร์ที่ใช้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'))
}

หมายเหตุ: การแก้ปัญหาที่นี่ไม่ได้ผลสำหรับฉัน ฉันต้องการสนับสนุน node.js และเบราว์เซอร์และเพียงแค่ซีเรียลไลซ์ UInt8Array ให้เป็นสตริง ฉันสามารถทำให้มันเป็นตัวเลข [] แต่มันใช้พื้นที่ที่ไม่จำเป็น ด้วยโซลูชันนั้นฉันไม่ต้องกังวลเกี่ยวกับการเข้ารหัสเนื่องจากเป็น base64 ในกรณีที่คนอื่นต่อสู้กับปัญหาเดียวกัน ... สองเซนต์ของฉัน


2

สมมติว่าคุณมี arrayBuffer binaryStr:

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

และจากนั้นคุณกำหนดข้อความให้กับสถานะ


1

สตริงไบนารี "ดั้งเดิม" ที่ atob () ส่งคืนคืออาร์เรย์ 1 ไบต์ต่ออักขระ

ดังนั้นเราไม่ควรเก็บ 2 ไบต์เป็นตัวอักษร

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


0

ฉันไม่แนะนำให้ใช้ API ที่เลิกใช้แล้วเช่น BlobBuilder

BlobBuilder เลิกใช้มานานแล้วโดยวัตถุ Blob เปรียบเทียบรหัสในคำตอบของ Dennis - โดยใช้ BlobBuilder - กับรหัสด้านล่าง:

function arrayBufferGen(str, cb) {

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

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

  f.readAsArrayBuffer(b);

}

โปรดทราบว่านี่เป็นวิธีที่สะอาดกว่าและป่องน้อยกว่าเมื่อเทียบกับวิธีที่เลิกใช้แล้วใช่แล้วนี่เป็นสิ่งที่ควรพิจารณาที่นี่


ผมหมายถึง แต่ก็ใช่ว่าหยดคอนสตรัคก็ไม่ได้กลับมาใช้งานได้จริงในปี 2012;)
gengkev


0

ฉันใช้มันและทำงานให้ฉัน

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;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.