Чи є масиви Javascript рідкими?


97

Тобто, якщо я використовую поточний час як індекс масиву:

array[Date.getTime()] = value;

чи інтерпретатор створить екземпляр усіх елементів від 0 до тепер? Чи різні браузери роблять це по-різному?

Я пам’ятаю, раніше в ядрі AIX була помилка , яка створювала псевдо-ttys за запитом, але якщо ви зробили, скажімо, "echo> / dev / pty10000000000", це створило б / dev / pty0, / dev / pty1, .... а потім падають мертвими. На виставках було весело, але я не хочу, щоб це траплялося з моїми клієнтами.


1
Можливим недоліком цього є труднощі налагодження у Firebug. оператор журналу в масиві буде перераховувати лише перші 1000 елементів у масиві, які всі будуть "невизначеними". Крім того, array.length скаже вам, що у вашому масиві є n елементів, хоча n-1 - це просто невизначені значення "привид".
Michael Butler

Налагодження тепер у порядку в Chrome - ось приклад виводу консолі: [порожній × 9564, Об’єкт, порожній × 105, Об’єкт, порожній × 10, Об’єкт, порожній × 12, Об’єкт, порожній × 9, Об’єкт, порожній × 21, Об'єкт, порожній × 9, Об'єкт]
джсалвата

Відповіді:


40

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

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

Дивіться цю відповідь для більш детального опису olliej.


1
Я не думаю, що ви насправді отримуєте щільний масив, якщо скажете щось подібне foo = new Array(10000). Однак, це повинно працювати: foo = Array.apply(null, {length: 10});.
doubleOrt

70

Так, вони є. Вони насправді є хеш-таблицями внутрішньо, тому ви можете використовувати не тільки великі цілі числа, але також рядки, плаваючі чи інші об’єкти. Усі ключі перетворюються на рядки за допомогою toString()перед додаванням до хешу. Ви можете підтвердити це за допомогою тестового коду:

<script>
  var array = [];
  array[0] = "zero";
  array[new Date().getTime()] = "now";
  array[3.14] = "pi";

  for (var i in array) {
      alert("array["+i+"] = " + array[i] + ", typeof("+i+") == " + typeof(i));
  }
</script>

Відображення:

array[0] = zero, typeof(0) == string
array[1254503972355] = now, typeof(1254503972355) == string
array[3.14] = pi, typeof(3.14) == string

Зверніть увагу, як я використовував for...inсинтаксис, який надає лише ті індекси, які фактично визначені. Якщо ви використовуєте більш поширений for (var i = 0; i < array.length; ++i)стиль ітерації, тоді у вас, очевидно, будуть проблеми з нестандартними індексами масивів.


9
більшість реалізацій JS зберігають числово індексовані властивості у фактичному масиві, якщо це можливо; це, мабуть, кулуарна магія: з мовної точки зору масиви є звичайними об'єктами з магічною lengthвластивістю
Крістоф,

7
@John: lengthневидимий лише в for..inциклах, оскільки має встановлений DontEnumпрапор; в ES5 викликається атрибут властивість, який enumerableможна явно встановити черезObject.defineProperty()
Крістоф

14
Усі об'єктні ключі в JavaScript завжди String; все інше, що ви вводите в індекс, отримує toString()-ed. Поєднайте це з цілочисловою неточністю великого числа, і це означає, що якщо ви встановите a[9999999999999999]=1, a[10000000000000000]буде 1 (і багато інших дивовижних особливостей поведінки). Використовувати нецілі числа в якості ключів дуже нерозумно, а довільні об’єкти - прямо.
bobince

71
Тоді ви будете використовувати лише рядки як ключі об’єктів, ні більше, ні менше. Рядок має бути типом, який ви будете використовувати, а тип ключа - Рядок. Ціле число ти не використовуєш і не використовуєш нецілі числа, за винятком того, що потім ти приступиш до передачі в String. Довільні об'єкти виходять прямо.
Crescent Fresh

8
Індекси масивів мають бути цілими числами. array [3.14] = pi працює, оскільки Array передає його від Object. Приклад: var x = []; x [.1] = 5; Тоді x має довжину 0 нерухомих.
Mike Blandford 02

10

Ви можете уникнути проблеми, використовуючи синтаксис javascript, розроблений для такого роду речей. Ви можете розглядати це як словник, але синтаксис "for ... in ..." дозволить вам захопити їх усіх.

var sparse = {}; // not []
sparse["whatever"] = "something";

7

Об'єкти Javascript є розрідженими, а масиви - це лише спеціалізовані об'єкти з властивістю автоматичної підтримки довжини (яка насправді на одну більша за найбільший індекс, а не за кількістю визначених елементів) та деякими додатковими методами. У будь-якому випадку ви в безпеці; використовуйте масив, якщо вам потрібні його додаткові функції, а об’єкт - інакше.


4
це з мовної точки зору; реалізації насправді використовують реальні масиви для зберігання щільних числових властивостей
Крістоф,

6

Відповідь, як це зазвичай справедливо для JavaScript, - "це трохи дивніше ..."

Використання пам’яті не визначено, і будь-яка реалізація може бути дурною. Теоретично, const a = []; a[1000000]=0;міг спалити мегабайт пам’яті, як міг const a = [];. На практиці навіть Microsoft уникає таких реалізацій.

Джастін Лав зазначає, атрибут length - це найвищий встановлений індекс. АЛЕ його оновлюється лише якщо індекс є цілим числом.

Отже, масив розріджений. АЛЕ вбудовані функції, такі як reduce (), Math.max () та "for ... of", пройдуть весь діапазон можливих цілочисельних індексів від 0 до довжини, відвідуючи багато з них, які повертаються "undefined". АЛЕ цикли "for ... in" можуть робити те, що ви очікували, відвідуючи лише визначені клавіші.

Ось приклад використання Node.js:

"use strict";
const print = console.log;

let a = [0, 10];
// a[2] and a[3] skipped
a[4] = 40;
a[5] = undefined;  // which counts towards setting the length
a[31.4] = 'ten pi';  // doesn't count towards setting the length
a['pi'] = 3.14;
print(`a.length= :${a.length}:, a = :${a}:`);
print(`Math.max(...a) = :${Math.max(a)}: because of 'undefined values'`);
for (let v of a) print(`v of a; v=:${v}:`);
for (let i in a) print(`i in a; i=:${i}: a[i]=${a[i]}`);

даючи:

a.length= :6:, a = :0,10,,,40,:
Math.max(...a) = :NaN: because of 'undefined values'
v of a; v=:0:
v of a; v=:10:
v of a; v=:undefined:
v of a; v=:undefined:
v of a; v=:40:
v of a; v=:undefined:
i in a; i=:0: a[i]=0
i in a; i=:1: a[i]=10
i in a; i=:4: a[i]=40
i in a; i=:5: a[i]=undefined
i in a; i=:31.4: a[i]=ten pi
i in a; i=:pi: a[i]=3.14

Але. Є ще кутові справи з Arrays, про які ще не згадувалося.


2

Розрідженість (або щільність) можна підтвердити емпірично для NodeJS за допомогою нестандартного process.memoryUsage () .

Іноді вузол достатньо розумний, щоб масив залишався розрідженим:

Welcome to Node.js v12.15.0.
Type ".help" for more information.
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 3.07 MB
undefined
> array = []
[]
> array[2**24] = 2**24
16777216
> array
[ <16777216 empty items>, 16777216 ]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 2.8 MB
undefined

Іноді вузол вирішує зробити його щільним (ця поведінка цілком може бути оптимізована в майбутньому):

> otherArray = Array(2**24)
[ <16777216 empty items> ]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 130.57 MB
undefined

Потім знову розріджений:

> yetAnotherArray = Array(2**32-1)
[ <4294967295 empty items> ]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`)
The script is using approximately 130.68 MB
undefined

Тож, можливо, використання щільного масиву, щоб відчути оригінальну помилку ядра AIX, можливо, доведеться змусити застосовувати діапазон :

> denseArray = [...Array(2**24).keys()]
[
   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,
  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
  24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
  36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
  48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
  60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
  72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
  84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
  96, 97, 98, 99,
  ... 16777116 more items
]
> console.log(`The script is using approximately ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 100) / 100} MB`);
The script is using approximately 819.94 MB
undefined

Бо чому б не змусити його впасти?

> tooDenseArray = [...Array(2**32-1).keys()]

<--- Last few GCs --->

[60109:0x1028ca000]   171407 ms: Scavenge 1072.7 (1090.0) -> 1056.7 (1090.0) MB, 0.2 / 0.0 ms  (average mu = 0.968, current mu = 0.832) allocation failure 
[60109:0x1028ca000]   171420 ms: Scavenge 1072.7 (1090.0) -> 1056.7 (1090.0) MB, 0.2 / 0.0 ms  (average mu = 0.968, current mu = 0.832) allocation failure 
[60109:0x1028ca000]   171434 ms: Scavenge 1072.7 (1090.0) -> 1056.7 (1090.0) MB, 0.2 / 0.0 ms  (average mu = 0.968, current mu = 0.832) allocation failure 


<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 0x100931399]
    1: StubFrame [pc: 0x1008ee227]
    2: StubFrame [pc: 0x100996051]
Security context: 0x1043830808a1 <JSObject>
    3: /* anonymous */ [0x1043830b6919] [repl:1] [bytecode=0x1043830b6841 offset=28](this=0x104306fc2261 <JSGlobal Object>)
    4: InternalFrame [pc: 0x1008aefdd]
    5: EntryFrame [pc: 0x1008aedb8]
    6: builtin exit frame: runInThisContext(this=0x104387b8cac1 <ContextifyScript map = 0x1043...

FATAL ERROR: invalid array length Allocation failed - JavaScript heap out of memory

Writing Node.js report to file: report.20200220.220620.60109.0.001.json
Node.js report completed
 1: 0x10007f4b9 node::Abort() [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 2: 0x10007f63d node::OnFatalError(char const*, char const*) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 3: 0x100176a27 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 4: 0x1001769c3 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 5: 0x1002fab75 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 6: 0x1005f3e9b v8::internal::Runtime_FatalProcessOutOfMemoryInvalidArrayLength(int, unsigned long*, v8::internal::Isolate*) [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 7: 0x100931399 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
 8: 0x1008ee227 Builtins_IterableToList [/Users/pzrq/.nvm/versions/node/v12.15.0/bin/node]
Abort trap: 6

1
Приємно, і я дивуюсь, моє десятирічне запитання все ще актуальне!
Беррі

1

Вони можуть бути, але не завжди повинні бути, і вони можуть працювати краще, коли цього не роблять.

Ось обговорення того, як перевірити розрідженість індексу в екземплярі масиву: https://benmccormick.org/2018/06/19/code-golf-sparse-arrays/

Цей переможець коду в гольф (найменше символів):

let isSparse = a => !!a.reduce(x=>x-1,a.length)

В основному ходить по масиву для індексованих записів, !!зменшуючи значення довжини і повертаючи загартований логічний результат хибного / хибного числового результату (якщо накопичувач зменшується до нуля, індекс заповнюється повністю і не є розрідженим). Слід також врахувати наведені вище застереження Чарльза Мерріама, і цей код не стосується їх, але вони стосуються хешованих записів рядків, що може трапитися при призначенні елементів, arr[var]= (something)де var не є цілим числом.

З огляду на те, що турбота про розрідженість індексу полягає в його впливі на продуктивність, яка може відрізнятися між механізмами сценаріїв, тут є велика дискусія щодо створення масиву / .initialization тут: Яка різниця між "Array ()" та "[]" при оголошенні JavaScript масив?

Нещодавня відповідь на цю публікацію має посилання на це глибоке занурення в те, як V8 намагається оптимізувати масиви, позначаючи їх, щоб уникнути (повторного) тестування на такі характеристики, як розрідженість: https://v8.dev/blog/elements-kinds . Повідомлення в блозі від вересня 17 року, і матеріал може зазнати певних змін, але розподіл наслідків для повсякденного розвитку є корисним та зрозумілим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.