Maksymalna długość stringa w JavaScript

Trochę o tym, jak JavaScript liczy długość ciągów znakowych.

Opis problemu

Ile maksymalnie może mieć wartość łańcucha znaków w JavaScript i skąd to wynika? Ktoś kiedyś powiedział, że 10k, czy to prawda?

Rozwiązanie problemu

Nie określono, ile może mieć maksymalnie ciąg znaków w JavaScript, ale ...

Znaki są przechowywane na 16 bitach zgodnie ze specyfikacją utf-16 lub 32 zgodnie z utf-32.

Gdy zobaczysz, że 256 * 2 ** 20 znaków jest w ciągu, nie oznacza to, że przydzielono 256 megabajtów pamięci. JavaScript przechowuje każdy znak w dwóch bajtach (ponieważ każdy znak jest zgodny z UTF-16).

Dzisiejsze przeglądarki (nawet IE) przechowują ciągi znaków w zaawansowany sposób, najczęściej przy użyciu struktury linowej. Oznacza to, że:

  • Poszczególne liny nie wymagają przydzielania spójnego regionu pamięci
  • Struktura potrafi nawet deduplikować podciągi, co oznacza, że s + s niekoniecznie używa podwójnej pamięci jak s
  • Łączenie jest bardzo szybkie
  • Dostęp do elementów jest nieco wolniejszy

Badając niektóre przebiegi w IE i Chrome, powiedziałbym, że obaj używają leniwej oceny ciągów i od czasu do czasu próbują je rozwinąć. Nawet podczas manipulacji na dużych ciągach znaków nie odczuwa się zbytnio spowolnienia przeglądarki. Ale jeśli spróbuję manipulować przechowywanym oknem za pomocą window.LONGEST_STRING w konsoli, IE wyrzuci błąd braku pamięci, a Chrome zamrozi na krótki czas i zużyje dużo pamięci (> 2 GB).

Jeśli nie wiesz, ile maksymalnie może mieć ciąg znaków w Twojej przeglądarce, to użyj następującego skryptu do testów:

var real_console_log = console.log;
console.log = function(x) {
  real_console_log.apply(console, arguments);
  var d = document,b=d.body,p=d.createElement('pre');
  p.style.margin = "0";
  p.appendChild(d.createTextNode(''+x));
  b.appendChild(p);
  window.scrollTo(0, b.scrollHeight);
};


function alloc(x) {
    if (x < 1) return '';
    var halfi = Math.floor(x/2);
    var half = alloc(halfi);
    return 2*halfi < x ? half + half + 'a' : half + half;
}

function test(x) {
    try {
        return alloc(x);
    } catch (e) {
        return null;
    }
}

function binsearch(predicateGreaterThan, min, max) {
    while (max > min) {
        var mid = Math.floor((max + min) / 2);
        var val = predicateGreaterThan(mid);
        if (val) {
            min = mid + 1;
        } else {
            max = mid;
        }
    }
    return max;
}

var maxStrLen = binsearch(test, 10, Math.pow(2, 52)) - 1;
console.log('Max string length is:');
console.log(maxStrLen + ' characters');
console.log(2*maxStrLen + ' bytes');
console.log(2*maxStrLen/1024/1024 + ' megabytes');
console.log('');
console.log('Store longest string');
window.LONGEST_STRING = alloc(maxStrLen);

console.log('Try to read first char');
console.log(window.LONGEST_STRING.charAt(0));
console.log('Try to read last char');
console.log(window.LONGEST_STRING.charAt(maxStrLen - 1));
console.log('Try to read length');
console.log(window.LONGEST_STRING.length);