Скільки байтів у рядку JavaScript?


97

У мене є рядок javascript, який становить близько 500 тис. При надсиланні з сервера в UTF-8. Як я можу визначити його розмір у JavaScript?

Я знаю, що JavaScript використовує UCS-2, тож це означає 2 байти на символ. Однак чи залежить це від реалізації JavaScript? Або на кодуванні сторінки чи, можливо, типу вмісту?


Прибл. відповідь буде довжиною * charsize, тому ваша здогадка близька.
glasnt

1
Сучасні JavaScript, наприклад ES6, не тільки використовувати UCS-2, більш докладно тут: stackoverflow.com/a/46735247/700206
whitneyland

Відповіді:


36

Stringзначення не залежать від реалізації, згідно специфікації ECMA-262 3rd Edition , кожен символ представляє одну 16-бітову одиницю тексту UTF-16 :

4.3.16 Значення рядка

Значення рядка є членом типу String і є скінченною впорядкованою послідовністю з нуля або більше 16-розрядних цілих беззнакових значень.

ПРИМІТКА Хоча кожне значення зазвичай представляє одну 16-бітову одиницю тексту UTF-16, мова не встановлює жодних обмежень чи вимог до значень, за винятком того, що це 16-бітові цілі числа без знака.


8
Моє прочитання цього уривку не означає незалежності реалізації.
Paul Biggar

4
UTF-16 не гарантується, лише факт, що рядки зберігаються як 16-розрядні ints.
bjornl

Це залежить лише від реалізації стосовно UTF-16. Опис 16-бітового символу є універсальним.
Panzercrisis

1
Я думаю, що внутрішньо Firefox може навіть використовувати 1 байт на символ для деяких рядків .... blog.mozilla.org/javascript/2014/07/21/…
Міхал Шаремза

1
UTF-16 явно заборонено так, як я його читаю. Символи UTF-16 можуть мати до 4 байт, але в специфікації сказано, що "значення повинні бути 16-бітовими цілими беззнаками". Це означає, що значення рядків JavaScript є підмножиною UTF-16, однак будь-який рядок UTF-16, що використовує 3 або 4 байтові символи, не буде дозволено.
Whitneyland

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".
Роб W

Використовується для обчислення розміру на фреймах веб-сокетів, дає такий самий розмір для кадру String, як і інструменти розробки chrome.
user85155

2
Використовується для рядків javascript, завантажених у s3, s3 відображає абсолютно однаковий розмір [(byteCount (s)) / 1024) .toFixed (2) + "KiB"]
user85155


41

Ви можете використовувати 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
Слава Богу за краплі! Це, мабуть, має бути прийнятою відповіддю для сучасних браузерів.
prasanthv

як імпортувати Blob в Node.js?
Олександр Міллс,

4
Ааа, з Node.js ми використовуємо буфер, наприкладBuffer.from('😂').length
Alexander 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
Функція unescapeJavaScript застаріла і не повинна використовуватися для декодування єдиних ідентифікаторів ресурсів (URI). Джерело
Lauri Oherd

@LauriOherd Я знаю, що коментар старий, але: У цій відповіді unescapeне використовується для декодування URI. Він використовується для перетворення %xxпослідовностей в окремі символи. Як encodeURIComponentкодує рядок як UTF-8, представляючи кодоодиниці або як відповідний символ ASCII, або як %xxпослідовність, unescape(encodeURIComponent(...))результати виклику отримують двійковий рядок, що містить представлення UTF-8 вихідного рядка. .lengthПравильний виклик дає розмір у байтах рядка, закодованого як UTF-8.
TS

І yes ( un) escapeє застарілим з 1999 року, але він все ще доступний у кожному браузері ... - Тим не менш, є поважна причина для його припинення. В основному немає можливості їх правильно використовувати (за винятком кодування / декодування UTF8 у поєднанні з en- / decodeURI( Component) - або, принаймні, я не знаю жодної іншої корисної програми для ( un) escape). І сьогодні є кращі альтернативи кодування / декодування UTF8 ( TextEncoderтощо)
TS

10

Зверніть увагу, що якщо ви націлюєтесь на node.js, ви можете використовувати Buffer.from(string).length:

var str = "\u2620"; // => "☠"
str.length; // => 1 (character)
Buffer.from(str).length // => 3 (bytes)

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

6

Це 3 способи, якими я користуюся:

  1. TextEncoder ()

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

  2. Крапля

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

  3. Буфер

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


4

Розмір рядка 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 виглядає так: \ 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

"на символ" - якщо ви маєте на увазі, на "символ, сприйнятий користувачем" ( специфікація , простіше пояснення ), це може бути будь-яка кількість 16-бітових одиниць коду. Якщо ви мали на увазі "кодову точку", це може бути одна або дві 16-бітові одиниці коду в UTF-16 . (Це не може бути 2,5 одиниці коду (або як ви отримуєте 5 байт?))
TS

Чи справді кожен елемент у рядку javascript ( 16-розрядні цілі цілі без знака ("елементи") внутрішньо представлений двома байтами, не визначено в стандарті. (І як це могло б бути - поки інтерфейс, наданий програмі javascript, відповідає стандарту, все працює за призначенням.) Mozilla, наприклад, може використовувати лише один байт на
TS

Екрани точок коду Unicode не мають нічого спільного з довжиною рядка - це лише новий спосіб представити рядки у вихідному коді. ( '\u{1F600}'.length===2, '\u{1F600}'==='\uD83D\uDE00', '\u{1F600}'==='😀')
TS

3

Окремий елемент у рядку JavaScript вважається єдиною кодовою одиницею UTF-16. Тобто символи рядків зберігаються у 16-бітах (1 кодова одиниця), а 16-біт дорівнює 2 байтам (8-біт = 1 байт).

charCodeAt()Метод може бути використаний , щоб повертати ціле число в діапазоні від 0 до 65535 , що становить коду блоку UTF-16 з даного індексу.

codePointAt()Може бути використано , щоб повернути все значення точки коди для символів Unicode, наприклад UTF-32.

Коли символ UTF-16 не може бути представлений в одній 16-бітовій кодовій одиниці, він матиме сурогатну пару і, отже, використовуватиме дві одиниці коду (2 х 16-бітних = 4 байти)

Див. Кодування Unicode щодо різних кодувань та їх діапазонів кодів.


Те, що ви говорите про сурогати, здається, порушує специфікацію сценарію ECMA. Як я вже коментував вище, специфікація вимагає два байти на символ, і дозволення сурогатних пар це порушить.
Whitneyland

Двигуни Javascript ES5 можуть безкоштовно використовувати USC-2 або UTF-16, але насправді він використовує своєрідний UCS-2 із сурогатами. Це тому, що це дозволяє виставити сурогатні половинки як окремі символи, одиничні цілі числа без підпису UTF-16. Якщо у вихідному коді ви використовуєте символ юнікоду, якому потрібно представити більше, ніж одну 16-бітову кодову одиницю, буде використана сурогатна пара. Ця поведінка не є порушенням специфікацій, див. Розділ 6 - вихідний текст: ecma-international.org/ecma-262/5.1
Холмберд,

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)

Результати можна порівняти із вбудованою функцією Node Buffer.byteLength:

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

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

1

Я працюю із вбудованою версією V8 Engine. Я протестував один рядок. Натискання кожного кроку 1000 символів. UTF-8.

Перший тест з однобайтовим (8-бітним, ANSI) символом "A" (шістнадцятковий: 41). Другий тест із двобайтовим символом (16 біт) "Ω" (шістнадцятковий: CE A9) і третій тест із трибайтовим символом (24 біт) "☺" (шістнадцятковий: E2 98 BA).

У всіх трьох випадках пристрій друкує з пам'яті на 888 000 символів із використанням ca. 26 348 кб в оперативній пам'яті.

Результат: символи не зберігаються динамічно. І не лише з 16 бітами. - Гаразд, можливо, лише для мого випадку (вбудований пристрій оперативної пам'яті 128 Мб, движок V8 C ++ / QT) - Кодування символів не має нічого спільного з розміром оперативної пам'яті механізму javascript. Наприклад, кодуванняURI тощо корисно лише для високорівневої передачі та зберігання даних.

Вбудовано чи ні, факт полягає в тому, що символи зберігаються не тільки в 16-бітній. На жаль, я не маю 100% відповіді, що Javascript робить на низькому рівні. До речі. Я тестував те саме (перший тест вище) з масивом символів "А". Кожен крок штовхав 1000 предметів. (Точно такий же тест. Просто замінено рядок на масив) І система виводить пам’ять (потрібна) після 10 416 КБ з використанням і довжиною масиву 1 337 000. Отже, механізм javascript не є простим обмеженням. Це набагато складніше.


0

Ви можете спробувати це:

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

У мене це спрацювало.


1
Звичайно, це передбачає, що всі символи мають максимум 2 байти? Якщо є 3 або 4 байтові символи (які можливі в UTF-8), то ця функція враховуватиме їх лише як 2-байтові символи?
Адам Берлі,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.