Các mảng Javascript có thưa thớt không?


97

Đó là, nếu tôi sử dụng thời gian hiện tại làm chỉ mục vào mảng:

array[Date.getTime()] = value;

trình thông dịch sẽ khởi tạo tất cả các phần tử từ 0 đến bây giờ chứ? Các trình duyệt khác nhau có làm điều đó khác nhau không?

Tôi nhớ đã từng có một lỗi trong nhân AIX , lỗi này sẽ tạo ra các tty giả theo yêu cầu, nhưng nếu bạn làm vậy, hãy nói, "echo> / dev / pty10000000000", nó sẽ tạo / dev / pty0, / dev / pty1, .... và sau đó ngã lăn ra chết. Ở triển lãm thương mại rất vui, nhưng tôi không muốn điều này xảy ra với khách hàng của mình.


1
Một nhược điểm có thể xảy ra khi làm điều này là khó gỡ lỗi trong Firebug. một câu lệnh nhật ký trên mảng sẽ chỉ liệt kê 1000 phần tử đầu tiên trong mảng, tất cả sẽ là "không xác định". Ngoài ra, array.length sẽ cho bạn biết mảng của bạn có n phần tử trong đó, mặc dù n-1 chỉ là các giá trị không xác định "ma".
Michael Butler

Gỡ lỗi hiện đã OK trong Chrome - đây là ví dụ về đầu ra bảng điều khiển: [rỗng × 9564, Đối tượng, trống × 105, Đối tượng, trống × 10, Đối tượng, trống × 12, Đối tượng, trống × 9, Đối tượng, trống × 21, Đối tượng, trống × 9, Đối tượng]
jsalvata

Câu trả lời:


40

Cách triển khai chính xác của các mảng JavaScript khác nhau giữa các trình duyệt, nhưng chúng thường rơi vào tình trạng triển khai thưa thớt - rất có thể là cùng một mảng được sử dụng để truy cập thuộc tính của các đối tượng thông thường - nếu sử dụng một mảng thực tế sẽ không hiệu quả.

Bạn sẽ phải hỏi ai đó có nhiều kiến ​​thức hơn về các triển khai cụ thể để trả lời điều gì kích thích sự thay đổi từ dày đặc sang thưa thớt, nhưng ví dụ của bạn phải hoàn toàn an toàn. Nếu bạn muốn nhận một mảng dày đặc, bạn nên gọi hàm tạo với đối số độ dài rõ ràng và hy vọng bạn sẽ thực sự nhận được một.

Xem câu trả lời này để được mô tả chi tiết hơn bởi olliej.


1
Tôi không nghĩ rằng bạn thực sự nhận được một mảng dày đặc nếu bạn nói điều gì đó như foo = new Array(10000). Tuy nhiên, đây là nghĩa vụ phải làm việc: foo = Array.apply(null, {length: 10});.
doubleOrt

70

Đúng vậy. Chúng thực sự là bảng băm bên trong, vì vậy bạn không chỉ có thể sử dụng số nguyên lớn mà còn sử dụng chuỗi, phao hoặc các đối tượng khác. Tất cả các khóa được chuyển đổi thành chuỗi toString()trước khi được thêm vào hàm băm. Bạn có thể xác nhận điều này bằng một số mã kiểm tra:

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

Hiển thị:

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

Lưu ý cách tôi sử dụng for...incú pháp, cú pháp chỉ cung cấp cho bạn các chỉ số thực sự được xác định. Nếu bạn sử dụng for (var i = 0; i < array.length; ++i)kiểu lặp phổ biến hơn thì rõ ràng bạn sẽ gặp vấn đề với các chỉ số mảng không chuẩn.


9
hầu hết các triển khai JS lưu trữ các thuộc tính được lập chỉ mục số trong một mảng thực tế nếu có thể; rằng đằng sau hậu trường ma thuật, mặc dù: từ một góc độ ngôn ngữ, mảng là đối tượng thường xuyên với một ma thuật lengthbất động sản
Christoph

7
@John: lengthchỉ ẩn trong for..incác vòng lặp vì nó đã DontEnumđặt cờ; trong ES5, thuộc tính thuộc tính được gọi enumerablevà có thể được đặt rõ ràng quaObject.defineProperty()
Christoph

14
Tất cả các khóa đối tượng trong JavaScript luôn là String; bất kỳ thứ gì khác mà bạn đưa vào chỉ số dưới đều được toString()-ed. Kết hợp điều này với số nguyên không chính xác của Số lớn và nó có nghĩa là nếu bạn đặt a[9999999999999999]=1, a[10000000000000000]sẽ là 1 (và nhiều hành vi đáng ngạc nhiên hơn). Việc sử dụng các số không phải là số nguyên làm khóa là rất không khôn ngoan và các đối tượng tùy ý sẽ bị loại ngay.
bobince

71
Sau đó, bạn sẽ chỉ sử dụng Chuỗi làm khóa đối tượng, không hơn không kém. Chuỗi sẽ là loại bạn sẽ sử dụng và loại khóa sẽ là Chuỗi. Bạn không được sử dụng số nguyên, không được sử dụng các số không phải số nguyên, ngoại trừ việc bạn sẽ tiến hành ép kiểu thành Chuỗi. Vật tùy tiện ra ngay.
Crescent Fresh

8
Chỉ mục mảng phải là số nguyên. array [3.14] = pi hoạt động vì Array truy xuất từ ​​Object. Ví dụ: var x = []; x [.1] = 5; Khi đó x có độ dài bằng 0 nằm yên.
Mike Blandford

10

Bạn có thể tránh vấn đề này bằng cách sử dụng cú pháp javascript được thiết kế cho loại điều này. Bạn có thể coi nó như một cuốn từ điển, nhưng cú pháp "for ... in ..." sẽ cho phép bạn nắm bắt tất cả.

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

7

Các đối tượng Javascript thưa thớt và mảng chỉ là các đối tượng chuyên biệt với thuộc tính độ dài tự động duy trì (thực tế là một lớn hơn chỉ mục lớn nhất, không phải số phần tử được xác định) và một số phương thức bổ sung. Bạn được an toàn theo cách nào đó; sử dụng một mảng nếu bạn cần các tính năng bổ sung của nó và một đối tượng khác.


4
đó là từ quan điểm ngôn ngữ; triển khai thực sự sử dụng mảng thực để lưu trữ các thuộc tính số dày đặc
Christoph

6

Câu trả lời, như thường đúng với JavaScript, là "nó hơi cũ hơn một chút ..."

Việc sử dụng bộ nhớ không được xác định và bất kỳ triển khai nào được phép là ngu ngốc. Về lý thuyết, const a = []; a[1000000]=0;có thể ghi nhiều MB bộ nhớ const a = [];. Trong thực tế, ngay cả Microsoft cũng tránh những triển khai đó.

Justin Love chỉ ra, thuộc tính độ dài là bộ chỉ số cao nhất . NHƯNG nó chỉ được cập nhật nếu chỉ mục là một số nguyên.

Vì vậy, mảng thưa thớt. NHƯNG các hàm tích hợp sẵn như Reduce (), Math.max () và "for ... of" sẽ đi qua toàn bộ phạm vi các chỉ số nguyên có thể có dạng 0 đến độ dài, truy cập nhiều hàm trả về 'không xác định'. NHƯNG các vòng lặp 'for ... in' có thể làm như bạn mong đợi, chỉ truy cập vào các khóa đã xác định.

Đây là một ví dụ sử dụng 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]}`);

cho:

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

Nhưng. Có nhiều trường hợp góc hơn với Mảng chưa được đề cập.


2

Độ thưa thớt (hoặc độ dày đặc) có thể được xác nhận theo kinh nghiệm đối với NodeJS với process.memoryUsage () không chuẩn .

Đôi khi nút đủ thông minh để giữ cho mảng thưa thớt:

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

Đôi khi nút chọn làm cho nó dày đặc (hành vi này cũng có thể được tối ưu hóa trong tương lai):

> 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

Sau đó lại thưa thớt:

> 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

Vì vậy, có lẽ việc sử dụng một mảng dày đặc để có cảm nhận về lỗi hạt nhân AIX ban đầu có thể cần được buộc phải sử dụng một phạm vi giống nhau :

> 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

Vì tại sao không làm cho nó rơi xuống?

> 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
Thật tuyệt, và tôi thực sự ngạc nhiên khi câu hỏi mười tuổi của tôi vẫn còn phù hợp!
Berry

1

Họ có thể như vậy nhưng không phải lúc nào cũng vậy, và họ có thể hoạt động tốt hơn khi không.

Đây là cuộc thảo luận về cách kiểm tra độ thưa thớt của chỉ mục trong một phiên bản mảng: https://benmccormick.org/2018/06/19/code-golf-sparse-arrays/

Người chiến thắng chơi gôn có mã này (ít ký tự nhất) là:

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

Về cơ bản, đi bộ mảng cho các mục được lập chỉ mục trong khi giảm giá trị độ dài và trả về !!boolean cứng của kết quả số sai / thật (nếu bộ tích lũy được giảm toàn bộ về 0, chỉ mục được điền đầy đủ và không thưa thớt). Cảnh báo của Charles Merriam ở trên cũng nên được xem xét và mã này không giải quyết chúng, nhưng chúng áp dụng cho các mục nhập chuỗi băm có thể xảy ra khi gán các phần tử với arr[var]= (something)var không phải là số nguyên.

Về lý do để quan tâm đến độ thưa thớt của chỉ mục là ảnh hưởng của nó đến hiệu suất, có thể khác nhau giữa các công cụ tập lệnh, có một cuộc thảo luận tuyệt vời về tạo mảng / .initialization ở đây: Sự khác biệt giữa "Array ()" và "[]" trong khi khai báo JavaScript mảng?

Một câu trả lời gần đây cho bài đăng đó có liên kết đến phần đi sâu về cách V8 cố gắng tối ưu hóa các mảng bằng cách gắn thẻ chúng để tránh (lại) kiểm tra các đặc điểm như độ thưa thớt: https://v8.dev/blog/elements-kinds . Bài đăng trên blog là từ tháng 9 năm 17 và tài liệu có thể thay đổi một số, nhưng việc phân tích các tác động đối với sự phát triển hàng ngày là hữu ích và rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.