สตริง JavaScript มีกี่ไบต์?


97

ฉันมีสตริงจาวาสคริปต์ซึ่งมีขนาดประมาณ 500K เมื่อถูกส่งจากเซิร์ฟเวอร์ใน UTF-8 ฉันจะบอกขนาดของมันใน JavaScript ได้อย่างไร?

ฉันรู้ว่า JavaScript ใช้ UCS-2 ดังนั้นหมายความว่า 2 ไบต์ต่ออักขระ อย่างไรก็ตามมันขึ้นอยู่กับการใช้งาน JavaScript หรือไม่? หรือในการเข้ารหัสหน้าหรืออาจจะเป็นประเภทเนื้อหา?


ประมาณ. คำตอบจะมีความยาว * ตัวอักษรดังนั้นการคาดเดาของคุณจึงใกล้เคียง
glasnt

1
JavaScript สมัยใหม่เช่น ES6 ไม่เพียง แต่ใช้ UCS-2 เท่านั้นดูรายละเอียดเพิ่มเติมได้ที่นี่: stackoverflow.com/a/46735247/700206
whitneyland

คำตอบ:


36

Stringค่าไม่ได้ขึ้นอยู่กับการนำไปใช้งานตามข้อกำหนดของ ECMA-262 3rd Editionอักขระแต่ละตัวแทนข้อความ UTF-16 หน่วย 16 บิตเดียว :

4.3.16 ค่าสตริง

ค่าสตริงเป็นสมาชิกของชนิด String และเป็นลำดับที่ จำกัด ของค่าจำนวนเต็มศูนย์หรือมากกว่า 16 บิต

หมายเหตุแม้ว่าโดยปกติแล้วค่าแต่ละค่าจะแสดงถึงข้อความ UTF-16 หน่วย 16 บิตเพียงหน่วยเดียว แต่ภาษาจะไม่กำหนดข้อ จำกัด หรือข้อกำหนดใด ๆ กับค่ายกเว้นว่าจะเป็นจำนวนเต็ม 16 บิตที่ไม่ได้ลงชื่อ


8
การอ่านข้อความนั้นของฉันไม่ได้หมายความถึงความเป็นอิสระในการนำไปปฏิบัติ
Paul Biggar

4
ไม่รับประกัน UTF-16 เฉพาะความจริงของสตริงที่จัดเก็บเป็น ints 16 บิต
bjornl

ขึ้นอยู่กับการนำไปใช้งานที่เกี่ยวกับ UTF-16 เท่านั้น คำอธิบายอักขระ 16 บิตเป็นสากล
Panzercrisis

1
ฉันคิดว่าภายใน Firefox สามารถใช้ 1 ไบต์ต่ออักขระสำหรับบางสตริง .... blog.mozilla.org/javascript/2014/07/21/…
Michal Charemza

1
UTF-16 ไม่ได้รับอนุญาตอย่างชัดเจนในแบบที่ฉันอ่าน อักขระ UTF-16 อาจมีได้ถึง 4 ไบต์ แต่ข้อมูลจำเพาะระบุว่า "ค่าต้องเป็นจำนวนเต็ม 16 บิตที่ไม่ได้ลงชื่อ" ซึ่งหมายความว่าค่าสตริง JavaScript เป็นชุดย่อยของ UTF-16 อย่างไรก็ตามไม่อนุญาตให้ใช้สตริง UTF-16 ที่ใช้อักขระ 3 หรือ 4 ไบต์
แลนด์

71

ฟังก์ชันนี้จะส่งคืนขนาดไบต์ของสตริง UTF-8 ที่คุณส่งผ่านไป

function byteCount(s) {
    return encodeURI(s).split(/%..|./).length - 1;
}

ที่มา

เอ็นจิ้น JavaScript ใช้งานภายใน UCS-2 หรือ UTF-16 ได้ฟรี เอ็นจิ้นส่วนใหญ่ที่ฉันรู้จักใช้ UTF-16 แต่ไม่ว่าพวกเขาจะเลือกอะไรก็ตามมันเป็นเพียงรายละเอียดการใช้งานที่ไม่ส่งผลต่อลักษณะของภาษา

อย่างไรก็ตามภาษา ECMAScript / JavaScript นั้นแสดงอักขระตาม UCS-2 ไม่ใช่ UTF-16

ที่มา


9
ใช้.split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./)แทน ข้อมูลโค้ดของคุณล้มเหลวสำหรับสตริงที่เข้ารหัสเป็น "% uXXXX"
Rob W

ใช้สำหรับการคำนวณขนาดบนเฟรม websocket ให้ขนาดของเฟรมสตริงเท่ากับเครื่องมือ Chrome dev
user85155

2
ใช้สำหรับสตริงจาวาสคริปต์ที่อัปโหลดไปยัง s3 โดย s3 จะแสดงขนาดเท่ากันทุกประการ [(byteCount (s)) / 1024) .toFixed (2) + "KiB"]
user85155


42

คุณสามารถใช้Blobเพื่อรับขนาดสตริงเป็นไบต์

ตัวอย่าง:

console.info(
  new Blob(['😂']).size,                             // 4
  new Blob(['👍']).size,                             // 4
  new Blob(['😂👍']).size,                           // 8
  new Blob(['👍😂']).size,                           // 8
  new Blob(['I\'m a string']).size,                  // 12

  // from Premasagar correction of Lauri's answer for
  // strings containing lone characters in the surrogate pair range:
  // https://stackoverflow.com/a/39488643/6225838
  new Blob([String.fromCharCode(55555)]).size,       // 3
  new Blob([String.fromCharCode(55555, 57000)]).size // 4 (not 6)
);


2
ขอบคุณพระเจ้าสำหรับ blobs! นี่น่าจะเป็นคำตอบที่ได้รับการยอมรับสำหรับเบราว์เซอร์สมัยใหม่
prasanthv

วิธีการนำเข้า Blob ใน Node.js
Alexander Mills

4
อ่ากับ Node.js เราใช้บัฟเฟอร์ตัวอย่างเช่นBuffer.from('😂').length
อเล็กซานเด Mills

19

ลองใช้ชุดนี้ร่วมกับการใช้ฟังก์ชันunescape js:

const byteAmount = unescape(encodeURIComponent(yourString)).length

ตัวอย่างการเข้ารหัสแบบเต็ม:

const s  = "1 a ф № @ ®"; //length is 11
const s2 = encodeURIComponent(s); //length is 41
const s3 = unescape(s2); //length is 15 [1-1,a-1,ф-2,№-3,@-1,®-2]
const s4 = escape(s3); //length is 39
const s5 = decodeURIComponent(s4); //length is 11

4
unescapeฟังก์ชัน JavaScript จะเลิกและไม่ควรนำมาใช้ในการถอดรหัส Uniform Resource Identifiers (URI) ที่มา
Lauri Oherd

@LauriOherd ฉันรู้ว่าความคิดเห็นนั้นเก่า แต่: ในคำตอบunescapeนี้ไม่ได้ใช้เพื่อถอดรหัส URI ใช้เพื่อแปลง%xxลำดับเป็นอักขระเดี่ยว เมื่อencodeURIComponentเข้ารหัสสตริงเป็น UTF-8 ซึ่งแสดงโค้ดยูนิตเป็นอักขระ ASCII ที่สอดคล้องกันหรือเป็น%xxลำดับการเรียกunescape(encodeURIComponent(...))ผลลัพธ์ในสตริงไบนารีที่มีการแสดง UTF-8 ของสตริงดั้งเดิม การโทร.lengthอย่างถูกต้องให้ขนาดเป็นไบต์ของสตริงที่เข้ารหัสเป็น UTF-8
TS

และใช่ ( un) escapeเลิกใช้งานตั้งแต่ปี 2542 แต่ยังคงมีให้บริการในทุกเบราว์เซอร์ ... - นั่นคือเหตุผลที่ดีที่จะเลิกใช้ โดยพื้นฐานแล้วไม่มีวิธีใดที่จะใช้อย่างถูกต้อง (ยกเว้น en- / ถอดรหัส UTF8 ร่วมกับen- / decodeURI( Component) - หรืออย่างน้อยฉันก็ไม่รู้จักแอปพลิเคชันที่มีประโยชน์อื่น ๆ สำหรับ ( un) escape) และวันนี้มีทางเลือกอื่นที่ดีกว่าในการเข้ารหัส / ถอดรหัส UTF8 ( TextEncoderฯลฯ )
TS


7

UTF-8 เข้ารหัสอักขระโดยใช้ 1 ถึง 4 ไบต์ต่อจุดรหัส ดังที่ CMS ระบุไว้ในคำตอบที่ยอมรับ JavaScript จะเก็บอักขระแต่ละตัวไว้ภายในโดยใช้ 16 บิต (2 ไบต์)

หากคุณแยกวิเคราะห์อักขระแต่ละตัวในสตริงผ่านการวนซ้ำและนับจำนวนไบต์ที่ใช้ต่อจุดโค้ดจากนั้นคูณจำนวนทั้งหมดด้วย 2 คุณควรใช้หน่วยความจำของ JavaScript เป็นไบต์สำหรับสตริงที่เข้ารหัส UTF-8 นั้น อาจจะเป็นเช่นนี้:

      getStringMemorySize = function( _string ) {
        "use strict";

        var codePoint
            , accum = 0
        ;

        for( var stringIndex = 0, endOfString = _string.length; stringIndex < endOfString; stringIndex++ ) {
            codePoint = _string.charCodeAt( stringIndex );

            if( codePoint < 0x100 ) {
                accum += 1;
                continue;
            }

            if( codePoint < 0x10000 ) {
                accum += 2;
                continue;
            }

            if( codePoint < 0x1000000 ) {
                accum += 3;
            } else {
                accum += 4;
            }
        }

        return accum * 2;
    }

ตัวอย่าง:

getStringMemorySize( 'I'    );     //  2
getStringMemorySize( '❤'    );     //  4
getStringMemorySize( '𠀰'   );     //  8
getStringMemorySize( 'I❤𠀰' );     // 14

7

นี่คือ 3 วิธีที่ฉันใช้:

  1. TextEncoder ()

    (new TextEncoder().encode("myString")).length)

  2. หยด

    new Blob(["myString"]).size)

  3. กันชน

    Buffer.byteLength("myString", 'utf8'))


5

ขนาดของสตริง JavaScript คือ

  • Pre-ES6 : 2 ไบต์ต่ออักขระ
  • ES6และใหม่กว่า: 2 ไบต์ต่ออักขระหรือ 5 ไบต์หรือมากกว่าต่ออักขระ

Pre-ES6
เสมอ 2 ไบต์ต่ออักขระ ไม่อนุญาตให้ใช้ UTF-16 เนื่องจากข้อมูลจำเพาะระบุว่า "ค่าต้องเป็นจำนวนเต็ม 16 บิตที่ไม่ได้ลงชื่อ" เนื่องจากสตริง UTF-16 สามารถใช้อักขระ 3 หรือ 4 ไบต์ได้จึงจะละเมิดข้อกำหนด 2 ไบต์ สำคัญอย่างยิ่งในขณะที่ UTF-16 ไม่สามารถรองรับได้อย่างสมบูรณ์ แต่มาตรฐานต้องการให้อักขระสองไบต์ที่ใช้เป็นอักขระ UTF-16 ที่ถูกต้อง กล่าวอีกนัยหนึ่งสตริง JavaScript Pre-ES6 รองรับชุดย่อยของอักขระ UTF-16

ES6 และใหม่กว่า
2 ไบต์ต่ออักขระหรือ 5 ไบต์หรือมากกว่าต่ออักขระ ขนาดเพิ่มเติมเข้ามาเล่นเพราะ ES6 (ECMAScript 6) เพิ่มการสนับสนุนสำหรับUnicode หนีจุดรหัส การใช้ Unicode Escape จะมีลักษณะดังนี้ \ u {1D306}

บันทึกปฏิบัติ

  • สิ่งนี้ไม่เกี่ยวข้องกับการใช้งานภายในของเครื่องยนต์เฉพาะ ตัวอย่างเช่นเอ็นจิ้นบางตัวใช้โครงสร้างข้อมูลและไลบรารีที่รองรับ UTF-16 เต็มรูปแบบ แต่สิ่งที่ให้ภายนอกไม่จำเป็นต้องรองรับ UTF-16 เต็มรูปแบบ นอกจากนี้เครื่องยนต์อาจให้การสนับสนุน UTF-16 ภายนอกเช่นกัน แต่ไม่ได้รับคำสั่งให้ทำเช่นนั้น

  • สำหรับ ES6 อักขระที่พูดได้จริงจะมีความยาวไม่เกิน 5 ไบต์ (2 ไบต์สำหรับจุดหลบหนี + 3 ไบต์สำหรับจุดรหัส Unicode) เนื่องจาก Unicode เวอร์ชันล่าสุดมีอักขระที่เป็นไปได้ 136,755 ตัวเท่านั้นซึ่งพอดีกับ 3 ไบต์ได้อย่างง่ายดาย อย่างไรก็ตามในทางเทคนิคนี้ไม่ได้ถูก จำกัด โดยมาตรฐานดังนั้นโดยหลักแล้วอักขระเดี่ยวสามารถใช้พูดได้ 4 ไบต์สำหรับจุดโค้ดและ 6 ไบต์ทั้งหมด

  • ตัวอย่างโค้ดส่วนใหญ่ที่นี่สำหรับการคำนวณขนาดไบต์ดูเหมือนจะไม่คำนึงถึงการหลบหนีของรหัส ES6 Unicode ดังนั้นผลลัพธ์อาจไม่ถูกต้องในบางกรณี


1
เพียงแค่สงสัยว่าถ้าขนาด 2 ไบต์ต่อตัวละครทำไมBuffer.from('test').lengthและBuffer.byteLength('test')เท่ากับ 4 (ใน Node) และnew Blob(['test']).sizeยังเท่ากับ 4?
user1063287

Pre-ES6: UTF-16 ได้รับอนุญาต: ดูECMA-262 ฉบับที่ 3 (จากปี 1999) : หน้าหนึ่งระบุว่าอนุญาตให้ใช้ UCS2 หรือ UTF-16 หน้า 5 คำจำกัดความของค่าสตริง: "... แม้ว่าแต่ละค่ามักจะแทนหน่วย 16 บิตเดียวของข้อความ UTF-16, ... " ในหน้า 81 เป็นตารางที่แสดงให้เห็นว่าคู่ตัวแทนที่ตรงกันต้องถูกเข้ารหัสเป็นสี่ไบต์ UTF-8 สี่ไบต์
TS

"ต่อตัวอักษร" - ถ้าโดยที่คุณหมายถึงต่อ "ใช้การรับรู้ของตัวอักษร" ( สเป็ค , คำอธิบายง่าย ) มันอาจจะเป็นจำนวนหน่วยรหัส 16bit ใด ๆ หากคุณหมายถึง "จุดรหัส" อาจเป็นหน่วยรหัส 16 บิตหนึ่งหรือสองหน่วยใน UTF-16ก็ได้ (ไม่สามารถเป็น 2.5 หน่วยรหัส (หรือคุณจะได้รับ 5 ไบต์ได้อย่างไร))
TS

ไม่ว่าแต่ละองค์ประกอบในสตริงจาวาสคริปต์ ( ค่าจำนวนเต็ม 16 บิตที่ไม่ได้ลงชื่อ ("องค์ประกอบ") ) จะถูกแทนภายในด้วยสองไบต์หรือไม่นั้นไม่ได้กำหนดไว้ในมาตรฐาน (และเป็นไปได้อย่างไร - ตราบใดที่อินเทอร์เฟซที่ให้กับโปรแกรมจาวาสคริปต์เป็นไปตามมาตรฐานทุกอย่างทำงานได้ตามที่ตั้งใจไว้) ตัวอย่างเช่น Mozilla สามารถใช้เพียงหนึ่งไบต์ต่อ codepoint หากสตริงมีเฉพาะ latin1
TS

การหลีกเลี่ยงจุดรหัส Unicode ไม่มีส่วนเกี่ยวข้องกับความยาวสตริงเป็นเพียงวิธีใหม่ในการแสดงสตริงในซอร์สโค้ด ( '\u{1F600}'.length===2, '\u{1F600}'==='\uD83D\uDE00', '\u{1F600}'==='😀')
TS

3

องค์ประกอบเดียวในสตริง JavaScript ถือเป็นหน่วยรหัส UTF-16 เดียว กล่าวคืออักขระ Strings จะถูกเก็บไว้ใน 16 บิต (1 หน่วยรหัส) และ 16 บิตเท่ากับ 2 ไบต์ (8-bit = 1 ไบต์)

charCodeAt()วิธีสามารถนำมาใช้เพื่อกลับจำนวนเต็มระหว่าง 0 และ 65535 เป็นตัวแทนของหน่วยรหัส UTF-16 ในดัชนีที่กำหนด

codePointAt()สามารถใช้ในการส่งกลับค่าจุดรหัสทั้งหมดสำหรับอักขระ Unicode เช่น UTF-32

เมื่ออักขระ UTF-16 ไม่สามารถแสดงในหน่วยรหัส 16 บิตเดียวได้จะมีคู่ตัวแทนดังนั้นจึงใช้หน่วยรหัสสองหน่วย (2 x 16-bit = 4 ไบต์)

ดูการเข้ารหัส Unicodeสำหรับการเข้ารหัสที่แตกต่างกันและช่วงรหัส


สิ่งที่คุณพูดเกี่ยวกับตัวแทนดูเหมือนจะละเมิดข้อกำหนดของสคริปต์ ECMA ดังที่ฉันแสดงความคิดเห็นไว้ข้างต้นข้อมูลจำเพาะต้องใช้สองไบต์ต่ออักขระและการอนุญาตให้คู่ตัวแทนจะละเมิดสิ่งนี้
แลนด์

เอ็นจิ้น Javascript ES5 มีอิสระในการใช้ USC-2 หรือ UTF-16 ภายใน แต่สิ่งที่ใช้จริงคือประเภทของ UCS-2 ที่มีตัวแทน นั่นเป็นเพราะอนุญาตให้เปิดเผยครึ่งหนึ่งของตัวแทนเป็นอักขระแยกกันซึ่งเป็นจำนวนเต็ม UTF-16 ที่ไม่ได้ลงนามเดียว หากคุณใช้อักขระ Unicode ในซอร์สโค้ดของคุณที่ต้องการแสดงหน่วยรหัส 16 บิตมากกว่าหนึ่งหน่วยจะใช้คู่ตัวแทน พฤติกรรมนี้ไม่ได้ละเมิดข้อกำหนดโปรดดูที่มาของบทที่ 6: ecma-international.org/ecma-262/5.1
holmberd

2

คำตอบจาก Lauri Oherd ใช้ได้ดีกับสตริงส่วนใหญ่ที่เห็นในไวลด์ แต่จะล้มเหลวหากสตริงมีอักขระเดี่ยวในช่วงคู่ตัวแทนคือ 0xD800 ถึง 0xDFFF เช่น

byteCount(String.fromCharCode(55555))
// URIError: URI malformed

ฟังก์ชันที่ยาวขึ้นนี้ควรจัดการกับสตริงทั้งหมด:

function bytes (str) {
  var bytes=0, len=str.length, codePoint, next, i;

  for (i=0; i < len; i++) {
    codePoint = str.charCodeAt(i);

    // Lone surrogates cannot be passed to encodeURI
    if (codePoint >= 0xD800 && codePoint < 0xE000) {
      if (codePoint < 0xDC00 && i + 1 < len) {
        next = str.charCodeAt(i + 1);

        if (next >= 0xDC00 && next < 0xE000) {
          bytes += 4;
          i++;
          continue;
        }
      }
    }

    bytes += (codePoint < 0x80 ? 1 : (codePoint < 0x800 ? 2 : 3));
  }

  return bytes;
}

เช่น

bytes(String.fromCharCode(55555))
// 3

จะคำนวณขนาดของสตริงที่มีคู่ตัวแทนได้อย่างถูกต้อง:

bytes(String.fromCharCode(55555, 57000))
// 4 (not 6)

ผลลัพธ์สามารถเปรียบเทียบกับฟังก์ชันในตัวของโหนดBuffer.byteLength:

Buffer.byteLength(String.fromCharCode(55555), 'utf8')
// 3

Buffer.byteLength(String.fromCharCode(55555, 57000), 'utf8')
// 4 (not 6)

1

ฉันกำลังทำงานกับ V8 Engine เวอร์ชันฝังตัว ฉันได้ทดสอบสตริงเดี่ยวแล้ว การผลักดันแต่ละขั้นตอน 1,000 อักขระ UTF-8

ทดสอบครั้งแรกด้วย single byte (8bit, ANSI) Character "A" (hex: 41) การทดสอบครั้งที่สองด้วยอักขระสองไบต์ (16 บิต) "Ω" (ฐานสิบหก: CE A9) และการทดสอบครั้งที่สามด้วยอักขระสามไบต์ (24 บิต) "☺" (ฐานสิบหก: E2 98 BA)

ในทั้งสามกรณีอุปกรณ์จะพิมพ์ออกมาจากหน่วยความจำที่ 888,000 ตัวอักษรและใช้ ca. 26348 kb ใน RAM

ผลลัพธ์: อักขระจะไม่ถูกจัดเก็บแบบไดนามิก และไม่ใช่แค่ 16 บิตเท่านั้น - โอเคอาจเป็นเพียงกรณีของฉัน (อุปกรณ์ RAM ขนาด 128 MB ที่ฝังตัว, V8 Engine C ++ / QT) - การเข้ารหัสอักขระไม่มีส่วนเกี่ยวข้องกับขนาดใน ram ของ javascript engine เช่นการเข้ารหัส URI เป็นต้นมีประโยชน์สำหรับการส่งและจัดเก็บข้อมูลระดับสูงเท่านั้น

ฝังตัวหรือไม่ความจริงก็คือตัวอักษรไม่ได้ถูกเก็บไว้ใน 16 บิตเท่านั้น โชคไม่ดีที่ฉันไม่มีคำตอบ 100% สิ่งที่ Javascript ทำในพื้นที่ระดับต่ำ Btw. ฉันได้ทดสอบแบบเดียวกัน (การทดสอบครั้งแรกด้านบน) ด้วยอาร์เรย์ของอักขระ "A" ผลักดัน 1,000 รายการทุกขั้นตอน (เป็นการทดสอบแบบเดียวกันเพียงแค่เปลี่ยนสตริงเป็นอาร์เรย์) และระบบนำหน่วยความจำออกมา (ต้องการ) หลังจาก 10 416 KB โดยใช้และความยาวอาร์เรย์ 1 337,000 ดังนั้นโปรแกรมจาวาสคริปต์จึงไม่ถูก จำกัด มันซับซ้อนมากขึ้น


0

คุณสามารถลองสิ่งนี้:

  var b = str.match(/[^\x00-\xff]/g);
  return (str.length + (!b ? 0: b.length)); 

มันได้ผลสำหรับฉัน


1
แน่นอนว่านี่ถือว่าอักขระทั้งหมดมีขนาดไม่เกิน 2 ไบต์? หากมีอักขระ 3 หรือ 4 ไบต์ (ซึ่งเป็นไปได้ใน UTF-8) ฟังก์ชันนี้จะนับเป็นอักขระ 2 ไบต์เท่านั้น?
Adam Burley
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.