Ile bajtów w ciągu JavaScript?


99

Mam ciąg javascript, który jest wysyłany z serwera w UTF-8, który wynosi około 500K. Jak mogę określić jego rozmiar w JavaScript?

Wiem, że JavaScript używa UCS-2, więc oznacza to 2 bajty na znak. Czy jednak zależy to od implementacji JavaScript? Albo o kodowaniu strony, a może typie zawartości?


Około. odpowiedzią byłoby length * charsize, więc twoje przypuszczenie jest bliskie.
glasnt

1
Nowoczesny JavaScript, na przykład ES6, nie tylko używa UCS-2, więcej szczegółów tutaj: stackoverflow.com/a/46735247/700206
whitneyland

Odpowiedzi:


37

Stringwartości nie są zależne od implementacji, zgodnie ze specyfikacją ECMA-262 3rd Edition , każdy znak reprezentuje pojedynczą 16-bitową jednostkę tekstu UTF-16 :

4.3.16 Wartość ciągu

Wartość ciągu należy do typu String i jest skończoną uporządkowaną sekwencją zera lub więcej 16-bitowych liczb całkowitych bez znaku.

UWAGA Chociaż każda wartość zwykle reprezentuje pojedynczą 16-bitową jednostkę tekstu UTF-16, język nie nakłada żadnych ograniczeń ani wymagań dotyczących wartości, z wyjątkiem tego, że są to 16-bitowe liczby całkowite bez znaku.


8
Moja lektura tego fragmentu nie oznacza niezależności wdrażania.
Paul Biggar,

4
UTF-16 nie jest gwarantowany, tylko fakt, że łańcuchy są przechowywane jako 16-bitowe int.
bjornl

To zależy tylko od implementacji w odniesieniu do UTF-16. 16-bitowy opis znaków jest uniwersalny.
Panzercrisis

1
Myślę wewnętrznie Firefox mógłby nawet użyć 1 bajt na znak dla niektórych ciągów .... blog.mozilla.org/javascript/2014/07/21/...
Michal Charemza

1
UTF-16 jest wyraźnie zabroniony tak, jak go czytam. Znaki UTF-16 mogą mieć do 4 bajtów, ale specyfikacja mówi, że „wartości muszą być 16-bitowymi liczbami całkowitymi bez znaku”. Oznacza to, że wartości ciągów JavaScript są podzbiorem UTF-16, jednak żaden ciąg znaków UTF-16 zawierający 3 lub 4 bajty znaków nie byłby dozwolony.
whitneyland

71

Ta funkcja zwróci rozmiar bajtu dowolnego przekazanego do niej ciągu znaków UTF-8.

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

Źródło

Silniki JavaScript mogą wewnętrznie używać UCS-2 lub UTF-16. Większość silników, które znam, używa UTF-16, ale bez względu na wybór, jest to tylko szczegół implementacji, który nie wpłynie na charakterystykę języka.

Jednak sam język ECMAScript / JavaScript udostępnia znaki zgodnie z UCS-2, a nie UTF-16.

Źródło


9
Użyj .split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./)zamiast tego. Twój fragment kodu nie działa w przypadku ciągów kodujących jako „% uXXXX”.
Rob W

Używany do obliczania rozmiaru ramek Websocket, daje taki sam rozmiar ramce String, jak chrome dev tools.
user85155

2
Używany do ciągów javascript przesłanych do s3, s3 wyświetla dokładnie ten sam rozmiar [(byteCount (s)) / 1024) .toFixed (2) + "KiB"]
user85155


46

Możesz użyć obiektu Blob, aby uzyskać rozmiar ciągu w bajtach.

Przykłady:

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


3
Dzięki Bogu za plamy! To prawdopodobnie powinna być akceptowana odpowiedź w przypadku nowoczesnych przeglądarek.
prasanthv

jak zaimportować Blob w Node.js?
Alexander Mills,

5
Ahh, z Node.js używamy na przykład buforaBuffer.from('😂').length
Alexander Mills,

19

Wypróbuj tę kombinację z użyciem funkcji unescape js:

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

Przykład pełnego kodowania:

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
Funkcja unescapeJavaScript jest przestarzała i nie powinna być używana do dekodowania jednolitych identyfikatorów zasobów (URI). Źródło
Lauri Oherd,

@LauriOherd Wiem, że komentarz jest stary, ale: w tej odpowiedzi unescapenie jest używany do dekodowania URI. Służy do konwersji %xxsekwencji na pojedyncze znaki. As encodeURIComponentkoduje łańcuch jako UTF-8, reprezentujący jednostki kodu jako odpowiadający mu znak ASCII lub jako %xxsekwencję, wywołanie unescape(encodeURIComponent(...))daje w wyniku ciąg binarny zawierający reprezentację oryginalnego ciągu w formacie UTF-8. .lengthPrawidłowe wywołanie podaje rozmiar w bajtach ciągu zakodowanego jako UTF-8.
TS

I tak ( un) escapejest przestarzałe od 1999 roku, ale nadal jest dostępne w każdej przeglądarce ... - To powiedziawszy, jest dobry powód, aby go wycofać. W zasadzie nie ma sposobu, aby ich poprawnie używać (z wyjątkiem kodowania / dekodowania UTF8 w połączeniu z en- / decodeURI( Component) - lub przynajmniej nie znam żadnej innej użytecznej aplikacji dla ( un) escape). A dziś są lepsze alternatywy dla kodowania / dekodowania UTF8 ( TextEncoderitp.)
TS

10

Pamiętaj, że jeśli kierujesz reklamy na node.js, możesz użyć Buffer.from(string).length:

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


7

UTF-8 koduje znaki przy użyciu od 1 do 4 bajtów na punkt kodowy. Jak wskazał CMS w zaakceptowanej odpowiedzi, JavaScript będzie przechowywać każdy znak wewnętrznie przy użyciu 16 bitów (2 bajty).

Jeśli przeanalizujesz każdy znak w ciągu za pomocą pętli i policzysz liczbę użytych bajtów na punkt kodowy, a następnie pomnożymy całkowitą liczbę przez 2, użycie pamięci JavaScript powinno być w bajtach dla tego zakodowanego ciągu UTF-8. Może coś takiego:

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

Przykłady:

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

6

Rozmiar ciągu JavaScript to

  • Pre-ES6 : 2 bajty na znak
  • ES6 i nowsze: 2 bajty na znak lub 5 lub więcej bajtów na znak

Pre-ES6
Zawsze 2 bajty na znak. UTF-16 nie jest dozwolony, ponieważ specyfikacja mówi, że „wartości muszą być 16-bitowymi liczbami całkowitymi bez znaku”. Ponieważ łańcuchy UTF-16 mogą używać 3 lub 4-bajtowych znaków, naruszyłoby to wymaganie 2-bajtowe. Co najważniejsze, chociaż UTF-16 nie może być w pełni obsługiwany, standard wymaga, aby używane znaki dwubajtowe były poprawnymi znakami UTF-16. Innymi słowy, ciągi JavaScript w wersjach wcześniejszych niż ES6 obsługują podzbiór znaków UTF-16.

ES6 i później
2 bajty na znak lub 5 lub więcej bajtów na znak. Dodatkowe rozmiary wchodzą w grę, ponieważ ES6 (ECMAScript 6) dodaje obsługę ucieczki punktów kodowych Unicode . Użycie znaku ucieczki Unicode wygląda następująco: \ u {1D306}

Praktyczne notatki

  • Nie dotyczy to wewnętrznej implementacji konkretnego silnika. Na przykład niektóre silniki używają struktur danych i bibliotek z pełną obsługą UTF-16, ale to, co zapewniają zewnętrznie, nie musi w pełni obsługiwać UTF-16. Silnik może również zapewniać zewnętrzne wsparcie dla UTF-16, ale nie jest do tego upoważniony.

  • W przypadku ES6 praktycznie rzecz biorąc, znaki nigdy nie będą dłuższe niż 5 bajtów (2 bajty dla punktu ucieczki + 3 bajty dla punktu kodowego Unicode), ponieważ najnowsza wersja Unicode ma tylko 136755 możliwych znaków, co z łatwością mieści się w 3 bajtach. Jednak nie jest to technicznie ograniczone przez standard, więc w zasadzie pojedynczy znak może używać powiedzmy 4 bajty na punkt kodowy i łącznie 6 bajtów.

  • Większość przykładów kodu służących do obliczania rozmiaru bajtów wydaje się nie uwzględniać ucieczki punktu kodowego ES6 Unicode, więc w niektórych przypadkach wyniki mogą być nieprawidłowe.


1
Po prostu zastanawiasz się, czy rozmiar jest 2 bajty na znak, dlaczego Buffer.from('test').lengthi Buffer.byteLength('test')równa 4 (w węźle) i new Blob(['test']).sizerównież jest równa 4?
user1063287

Pre-ES6: UTF-16 jest dozwolony: patrz ECMA-262 3. wydanie (od 1999) : Strona pierwsza mówi, że UCS2 lub UTF-16 są dozwolone. Strona 5, definicja wartości ciągu: "... Chociaż każda wartość zwykle reprezentuje pojedynczą 16-bitową jednostkę tekstu UTF-16, ...". Na stronie 81 znajduje się tabela, która pokazuje, jak pasujące pary zastępcze muszą być kodowane jako cztery bajty UTF-8.
TS

„na znak” - jeśli przez to rozumiesz, na „znak postrzegany przez użytkownika” ( specyfikacja , prostsze wyjaśnienie ) może to być dowolna liczba 16-bitowych jednostek kodu. Jeśli chodziło Ci o „punkt kodowy”, może to być jedna lub dwie 16-bitowe jednostki kodu w UTF-16 . (Nie może to być 2,5 jednostki kodu (lub jak uzyskać 5 bajtów?))
TS

To, czy każdy element w ciągu javascript ( 16-bitowe wartości całkowite bez znaku („elementy”) ) jest w rzeczywistości wewnętrznie reprezentowane przez dwa bajty, nie jest zdefiniowane w standardzie. (I jak to może być - Dopóki interfejs dostarczany do programu javascript następujące standardowe wszystko działa zgodnie z przeznaczeniem.) Mozilla na przykład można użyć tylko jeden bajt na punkt kodowy jeśli ciąg zawiera tylko latin1
TS

Ucieczki punktów kodu Unicode nie mają nic wspólnego z długością łańcucha - to tylko nowy sposób reprezentowania ciągów znaków w kodzie źródłowym. ( '\u{1F600}'.length===2, '\u{1F600}'==='\uD83D\uDE00', '\u{1F600}'==='😀')
TS

3

Pojedynczy element w ciągu JavaScript jest uważany za pojedynczą jednostkę kodu UTF-16. Oznacza to, że ciągi znaków są przechowywane w formacie 16-bitowym (1 jednostka kodu), a 16-bitowy jest równy 2 bajtom (8-bitów = 1 bajt).

charCodeAt()Metoda może być zastosowana, aby powrócić liczbę całkowitą od 0 do 65535, przedstawiający urządzenie kodu UTF-16 przy danym wskaźniku.

codePointAt()Może być stosowany do całej wartości powrotu punkt kodowy znaki unikodowe np UTF-32.

Gdy znak UTF-16 nie może być reprezentowany w pojedynczej 16-bitowej jednostce kodu, będzie miał parę zastępczą, a zatem użyje dwóch jednostek kodu (2 x 16-bit = 4 bajty)

Zobacz kodowania Unicode dla różnych kodowań i ich zakresów kodów.


To, co mówisz o surogatach, wydaje się naruszać specyfikację skryptu ECMA. Jak skomentowałem powyżej, specyfikacja wymaga dwóch bajtów na znak, a dopuszczenie par zastępczych naruszałoby to.
whitneyland

Silniki Javascript ES5 są wewnętrznie wolne i mogą używać USC-2 lub UTF-16, ale to, co faktycznie używa, to coś w rodzaju UCS-2 z surogatami. Dzieje się tak, ponieważ umożliwia eksponowanie zastępczych połówek jako oddzielnych znaków, pojedynczych liczb całkowitych bez znaku UTF-16. Jeśli w kodzie źródłowym używasz znaku Unicode, który wymaga reprezentacji więcej niż jednej 16-bitowej jednostki kodu, zostanie użyta para zastępcza. To zachowanie nie narusza specyfikacji, patrz rozdział 6 tekst źródłowy: ecma-international.org/ecma-262/5.1
holmberd

2

Odpowiedź od Lauri Oherd działa dobrze dla większości ciągów występujących w środowisku naturalnym, ale zakończy się niepowodzeniem, jeśli ciąg zawiera pojedyncze znaki z zakresu par zastępczych, od 0xD800 do 0xDFFF. Na przykład

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

Ta dłuższa funkcja powinna obsługiwać wszystkie ciągi:

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

Na przykład

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

Prawidłowo obliczy rozmiar ciągów zawierających pary zastępcze:

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

Wyniki można porównać z wbudowaną funkcją Node Buffer.byteLength:

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

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

1

Pracuję z wbudowaną wersją silnika V8. Przetestowałem pojedynczy ciąg. Wypychanie każdego kroku 1000 znaków. UTF-8.

Pierwszy test z jednobajtowym (8-bitowym, ANSI) znakiem „A” (szesnastkowo: 41). Drugi test ze znakiem dwubajtowym (16-bitowy) „Ω” (szesnastkowo: CE A9) i trzeci test ze znakiem trzy bajtowym (24-bitowy) „☺” (szesnastkowo: E2 98 BA).

We wszystkich trzech przypadkach urządzenie drukuje z pamięci przy 888 000 znaków i przy użyciu ok. 26 348 kb w pamięci RAM.

Wynik: znaki nie są przechowywane dynamicznie. I to nie tylko z 16-bitowym. - Ok, może tylko w moim przypadku (wbudowane 128 MB pamięci RAM, silnik V8 C ++ / QT) - Kodowanie znaków nie ma nic wspólnego z rozmiarem w pamięci RAM silnika javascript. Np. EncodingURI itp. Jest użyteczne tylko do wysokopoziomowej transmisji i przechowywania danych.

Osadzone czy nie, faktem jest, że postacie są przechowywane nie tylko w 16 bitach. Niestety nie mam 100% odpowiedzi, co robi Javascript na niskim poziomie. Przy okazji. Przetestowałem to samo (pierwszy test powyżej) z tablicą znaków „A”. Podano 1000 pozycji na każdym kroku. (Dokładnie ten sam test. Po prostu zamieniłem ciąg na tablicę) I system wyprowadził z pamięci (pożądany) po 10 416 KB i długości tablicy 1 337 000. Tak więc silnik javascript nie jest po prostu ograniczony. To bardziej złożone.


0

Możesz spróbować tego:

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

U mnie to zadziałało.


1
Z pewnością zakłada się, że wszystkie znaki mają maksymalnie 2 bajty? Jeśli są 3 lub 4 znaki bajtowe (które są możliwe w UTF-8), to ta funkcja policzy je tylko jako znaki 2-bajtowe?
Adam Burley
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.