ความยาวสตริงเป็นไบต์ใน JavaScript


105

ในโค้ด JavaScript ของฉันฉันต้องเขียนข้อความไปยังเซิร์ฟเวอร์ในรูปแบบนี้:

<size in bytes>CRLF
<data>CRLF

ตัวอย่าง:

3
foo

ข้อมูลอาจมีอักขระ Unicode ฉันต้องการส่งเป็น UTF-8

ฉันกำลังมองหาวิธีข้ามเบราว์เซอร์ที่มากที่สุดในการคำนวณความยาวของสตริงเป็นไบต์ใน JavaScript

ฉันได้ลองทำสิ่งนี้เพื่อสร้างน้ำหนักบรรทุกของฉัน:

return unescape(encodeURIComponent(str)).length + "\n" + str + "\n"

แต่มันไม่ได้ให้ผลลัพธ์ที่ถูกต้องสำหรับฉันสำหรับเบราว์เซอร์รุ่นเก่า (หรืออาจเป็นสตริงในเบราว์เซอร์เหล่านั้นใน UTF-16?)

เบาะแสใด ๆ ?

อัปเดต:

ตัวอย่าง: ความยาวเป็นไบต์ของสตริงЭЭХ! Naïve?ใน UTF-8 คือ 15 ไบต์ แต่บางเบราว์เซอร์รายงาน 23 ไบต์แทน


1
อาจซ้ำกัน? stackoverflow.com/questions/2219526/…
Eli

@Eli: ไม่มีคำตอบในคำถามที่คุณเชื่อมโยงกับงานของฉัน
Alexander Gladysh

เมื่อคุณพูดถึง "ЭЭХ! Naïve?" คุณใส่มันลงในรูปแบบปกติหรือไม่? unicode.org/reports/tr15
Mike Samuel

@ ไมค์: ฉันพิมพ์ลงในโปรแกรมแก้ไขข้อความแบบสุ่ม (ในโหมด UTF-8) และบันทึกไว้ เช่นเดียวกับที่ผู้ใช้ห้องสมุดของฉันจะทำ อย่างไรก็ตามดูเหมือนว่าฉันจะรู้ว่ามีอะไรผิดพลาด - ดูคำตอบของฉัน
Alexander Gladysh

คำตอบ:


89

ไม่มีวิธีทำใน JavaScript โดยกำเนิด (ดูคำตอบของ Riccardo Galliสำหรับแนวทางสมัยใหม่)


สำหรับการอ้างอิงประวัติศาสตร์หรือที่ TextEncoder API ที่มียังคงสามารถใช้งานได้

หากคุณรู้จักการเข้ารหัสอักขระคุณสามารถคำนวณได้ด้วยตัวเอง

encodeURIComponent ถือว่า UTF-8 เป็นการเข้ารหัสอักขระดังนั้นหากคุณต้องการการเข้ารหัสนั้นคุณสามารถทำได้

function lengthInUtf8Bytes(str) {
  // Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
  var m = encodeURIComponent(str).match(/%[89ABab]/g);
  return str.length + (m ? m.length : 0);
}

สิ่งนี้ควรใช้งานได้เนื่องจากวิธีที่ UTF-8 เข้ารหัสลำดับแบบหลายไบต์ ไบต์แรกที่เข้ารหัสจะเริ่มต้นด้วยบิตสูงของศูนย์สำหรับลำดับไบต์เดียวหรือไบต์ที่มีเลขฐานสิบหกหลักแรกคือ C, D, E หรือ F ไบต์ที่สองและตามมาคือบิตที่มีสองบิตแรกคือ 10 นี่คือไบต์พิเศษที่คุณต้องการนับใน UTF-8

ตารางในwikipediaทำให้ชัดเจนขึ้น

Bits        Last code point Byte 1          Byte 2          Byte 3
  7         U+007F          0xxxxxxx
 11         U+07FF          110xxxxx        10xxxxxx
 16         U+FFFF          1110xxxx        10xxxxxx        10xxxxxx
...

หากคุณจำเป็นต้องเข้าใจการเข้ารหัสเพจแทนคุณสามารถใช้เคล็ดลับนี้:

function lengthInPageEncoding(s) {
  var a = document.createElement('A');
  a.href = '#' + s;
  var sEncoded = a.href;
  sEncoded = sEncoded.substring(sEncoded.indexOf('#') + 1);
  var m = sEncoded.match(/%[0-9a-f]{2}/g);
  return sEncoded.length - (m ? m.length * 2 : 0);
}

ฉันจะทราบการเข้ารหัสอักขระของข้อมูลได้อย่างไร ฉันต้องการเข้ารหัสผู้ใช้สตริง (โปรแกรมเมอร์) ที่ให้มาในไลบรารี JS ของฉัน
Alexander Gladysh

@Alexander เมื่อคุณส่งข้อความไปยังเซิร์ฟเวอร์คุณกำลังระบุการเข้ารหัสเนื้อหาของเนื้อหาข้อความผ่านส่วนหัว HTTP หรือไม่?
Mike Samuel

1
@ อเล็กซานเดอร์เจ๋ง. หากคุณกำลังสร้างโปรโตคอลการกำหนด UTF-8 เป็นแนวคิดที่ดีสำหรับการแลกเปลี่ยนข้อความ ตัวแปรน้อยกว่าหนึ่งตัวที่อาจทำให้เกิดความไม่ตรงกัน UTF-8 ควรเป็นลำดับเครือข่ายไบต์ของการเข้ารหัสอักขระ
Mike Samuel

4
@MikeSamuel: lengthInUtf8Bytesฟังก์ชันจะคืนค่า 5 สำหรับอักขระที่ไม่ใช่ BMP str.lengthสำหรับผลตอบแทนเหล่านี้ 2 ฉันจะเขียนเวอร์ชันแก้ไขของฟังก์ชันนี้เพื่อตอบส่วน
Lauri Oherd

2
โซลูชันนี้ยอดเยี่ยม แต่ไม่ได้พิจารณา utf8mb4 ยกตัวอย่างเช่นเป็นencodeURIComponent('🍀') '%F0%9F%8D%80'
albert

119

หลายปีผ่านไปและทุกวันนี้คุณสามารถทำได้โดยกำเนิด

(new TextEncoder().encode('foo')).length

โปรดทราบว่า IE (หรือ Edge) ยังไม่รองรับ (คุณอาจใช้ polyfill ก็ได้ )

เอกสาร MDN

ข้อกำหนดมาตรฐาน


4
ช่างเป็นแนวทางที่ทันสมัยและยอดเยี่ยมจริงๆ ขอบคุณ!
Con Antonakos

โปรดสังเกตว่าตามเอกสาร MDN Safari (WebKit) ยังไม่รองรับ TextEncoder
มอร์

TextEncodeรองรับเฉพาะutf-8ตั้งแต่ Chrome 53
Jehong Ahn

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

67

นี่คือเวอร์ชันที่เร็วกว่ามากซึ่งไม่ใช้นิพจน์ทั่วไปหรือencodeURIComponent () :

function byteLength(str) {
  // returns the byte length of an utf8 string
  var s = str.length;
  for (var i=str.length-1; i>=0; i--) {
    var code = str.charCodeAt(i);
    if (code > 0x7f && code <= 0x7ff) s++;
    else if (code > 0x7ff && code <= 0xffff) s+=2;
    if (code >= 0xDC00 && code <= 0xDFFF) i--; //trail surrogate
  }
  return s;
}

นี่คือผลการดำเนินงานเปรียบเทียบ

เพียงแค่คำนวณความยาวใน UTF8 ของแต่ละจุดรหัส Unicode ที่ส่งคืนโดยcharCodeAt () (ตามคำอธิบายของUTF8และอักขระตัวแทน UTF16 ของวิกิพีเดีย)

เป็นไปตามRFC3629 (โดยที่อักขระ UTF-8 มีความยาวสูงสุด 4 ไบต์)


47

สำหรับการเข้ารหัส UTF-8 แบบธรรมดาที่มีความเข้ากันได้ดีกว่าเล็กน้อยTextEncoderBlob ทำเคล็ดลับ จะไม่ทำงานในเบราว์เซอร์รุ่นเก่ามาก

new Blob(["😀"]).size; // -> 4  

29

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

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

ที่มา


ใช้ไม่ได้กับสตริง 'ユーザーコード' คาดว่าจะมีความยาว 14 แต่ 21
พฤษภาคมอากาศ VN

1
@MayWeatherVN คุณมีユーザーコードความยาวผิดเป็นไบต์คือ 21 เสมอฉันทดสอบด้วยเครื่องมือ differents กรุณาเพิ่มเติมกับความคิดเห็นของคุณ)
Capitex

สตริงนี้ฉันจำได้ว่าทดสอบกับ php คือ 14
พฤษภาคม

23

อีกวิธีง่ายๆโดยใช้Buffer(สำหรับ NodeJS เท่านั้น):

Buffer.byteLength(string, 'utf8')

Buffer.from(string).length

1
คุณสามารถข้ามการสร้างบัฟเฟอร์ด้วยBuffer.byteLength(string, 'utf8')ไฟล์.
โจ

1
@ โจขอบคุณสำหรับข้อเสนอแนะฉันเพิ่งทำการแก้ไขเพื่อรวมไว้
IvánPérez

6

ฉันใช้เวลาสักพักเพื่อหาวิธีแก้ปัญหาสำหรับReact Nativeดังนั้นฉันจะใส่ไว้ที่นี่:

ติดตั้งbufferแพ็คเกจก่อน:

npm install --save buffer

จากนั้นใช้วิธีโหนด:

const { Buffer } = require('buffer');
const length = Buffer.byteLength(string, 'utf-8');

4

อันที่จริงฉันคิดว่ามีอะไรผิดปกติ เพื่อให้โค้ดทำงานหน้า<head>ควรมีแท็กนี้:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

หรือตามที่แนะนำในความคิดเห็นหากเซิร์ฟเวอร์ส่งContent-Encodingส่วนหัวHTTP ก็ควรใช้งานได้เช่นกัน

จากนั้นผลลัพธ์จากเบราว์เซอร์ต่างๆจะมีความสอดคล้องกัน

นี่คือตัวอย่าง:

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  <title>mini string length test</title>
</head>
<body>

<script type="text/javascript">
document.write('<div style="font-size:100px">' 
    + (unescape(encodeURIComponent("ЭЭХ! Naïve?")).length) + '</div>'
  );
</script>
</body>
</html>

หมายเหตุ: ผมสงสัยว่าการระบุใด ๆ (ที่ถูกต้อง) การเข้ารหัสจะแก้ไขปัญหาการเข้ารหัส มันเป็นเรื่องบังเอิญที่ฉันต้องการ UTF-8


2
unescapeฟังก์ชัน JavaScript ไม่ควรนำมาใช้ในการถอดรหัส Uniform Resource Identifiers (URI)
Lauri Oherd

1
unescapeไม่ควรใช้@LauriOherd เพื่อถอดรหัส URI อย่างไรก็ตามในการแปลงข้อความเป็น UTF-8 ก็ใช้ได้ดี
TS

unescape(encodeURIComponent(...)).lengthmeta http-equiv ... utf8มักจะคำนวณระยะเวลาที่ถูกต้องมีหรือไม่มี หากไม่มีข้อกำหนดการเข้ารหัสบางเบราว์เซอร์อาจมีข้อความที่แตกต่างออกไป (หลังจากเข้ารหัสไบต์ของเอกสารเป็นข้อความ html จริง) ซึ่งมีการคำนวณความยาว เราสามารถทดสอบสิ่งนี้ได้อย่างง่ายดายโดยการพิมพ์ไม่เพียง แต่ความยาวเท่านั้น แต่ยังรวมถึงข้อความด้วย
TS

3

นี่คือวิธีการที่เป็นอิสระและมีประสิทธิภาพในการนับสตริง UTF-8 ไบต์

//count UTF-8 bytes of a string
function byteLengthOf(s){
	//assuming the String is UCS-2(aka UTF-16) encoded
	var n=0;
	for(var i=0,l=s.length; i<l; i++){
		var hi=s.charCodeAt(i);
		if(hi<0x0080){ //[0x0000, 0x007F]
			n+=1;
		}else if(hi<0x0800){ //[0x0080, 0x07FF]
			n+=2;
		}else if(hi<0xD800){ //[0x0800, 0xD7FF]
			n+=3;
		}else if(hi<0xDC00){ //[0xD800, 0xDBFF]
			var lo=s.charCodeAt(++i);
			if(i<l&&lo>=0xDC00&&lo<=0xDFFF){ //followed by [0xDC00, 0xDFFF]
				n+=4;
			}else{
				throw new Error("UCS-2 String malformed");
			}
		}else if(hi<0xE000){ //[0xDC00, 0xDFFF]
			throw new Error("UCS-2 String malformed");
		}else{ //[0xE000, 0xFFFF]
			n+=3;
		}
	}
	return n;
}

var s="\u0000\u007F\u07FF\uD7FF\uDBFF\uDFFF\uFFFF";
console.log("expect byteLengthOf(s) to be 14, actually it is %s.",byteLengthOf(s));

โปรดทราบว่าวิธีการนี้อาจทำให้เกิดข้อผิดพลาดหากสตริงอินพุต UCS-2 ผิดรูปแบบ


3

ใน NodeJS Buffer.byteLengthเป็นวิธีการเฉพาะสำหรับวัตถุประสงค์นี้:

let strLengthInBytes = Buffer.byteLength(str); // str is UTF-8

โปรดทราบว่าโดยค่าเริ่มต้นวิธีการถือว่าสตริงอยู่ในการเข้ารหัส UTF-8 หากต้องการการเข้ารหัสอื่นให้ส่งผ่านเป็นอาร์กิวเมนต์ที่สอง


เป็นไปได้ไหมที่จะคำนวณstrLengthInBytesเพียงแค่รู้ค่า 'จำนวน' ของอักขระในสตริง กล่าวคือvar text = "Hello World!; var text_length = text.length; // pass text_length as argument to some method?. และเพียงสำหรับการอ้างอิงใหม่Buffer- ฉันเพิ่งมาข้ามคำตอบนี้ที่กล่าวถึง และในโหนดnew Blob(['test string']).size Buffer.from('test string').lengthบางทีสิ่งเหล่านี้อาจช่วยบางคนได้ด้วย?
user1063287

1
@ user1063287 ปัญหาคือจำนวนอักขระไม่ได้เทียบเท่ากับจำนวนไบต์เสมอไป ตัวอย่างเช่นการเข้ารหัส UTF-8 ทั่วไปคือการเข้ารหัสความกว้างตัวแปรซึ่งอักขระเดียวอาจมีขนาด 1 ไบต์ถึง 4 ไบต์ นั่นเป็นเหตุผลที่จำเป็นต้องใช้วิธีพิเศษเช่นเดียวกับการเข้ารหัสที่ใช้
Boaz

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

1

สิ่งนี้ใช้ได้กับอักขระ BMP และ SIP / SMP

    String.prototype.lengthInUtf8 = function() {
        var asciiLength = this.match(/[\u0000-\u007f]/g) ? this.match(/[\u0000-\u007f]/g).length : 0;
        var multiByteLength = encodeURI(this.replace(/[\u0000-\u007f]/g)).match(/%/g) ? encodeURI(this.replace(/[\u0000-\u007f]/g, '')).match(/%/g).length : 0;
        return asciiLength + multiByteLength;
    }

    'test'.lengthInUtf8();
    // returns 4
    '\u{2f894}'.lengthInUtf8();
    // returns 4
    'سلام علیکم'.lengthInUtf8();
    // returns 19, each Arabic/Persian alphabet character takes 2 bytes. 
    '你好,JavaScript 世界'.lengthInUtf8();
    // returns 26, each Chinese character/punctuation takes 3 bytes. 

0

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

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

มันใช้ได้กับฉัน


ส่งคืน 1 สำหรับ "â" ใน chrome
Rick

ปัญหาแรกสามารถแก้ไขได้โดยเปลี่ยน \ xff เป็น \ x7f แต่นั่นไม่ได้แก้ไขความจริงที่ว่าจุดรหัสระหว่าง 0x800-0xFFFF จะถูกรายงานว่าใช้เวลา 2 ไบต์เมื่อใช้เวลา 3
Rick
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.