อาร์เรย์ Javascript กระจัดกระจายหรือไม่


99

นั่นคือถ้าฉันใช้เวลาปัจจุบันเป็นดัชนีในอาร์เรย์:

array[Date.getTime()] = value;

ล่ามจะสร้างอินสแตนซ์องค์ประกอบทั้งหมดจาก 0 ถึงตอนนี้หรือไม่ เบราว์เซอร์ต่างๆทำแตกต่างกันหรือไม่?

ฉันจำได้ว่าเคยมีบั๊กในเคอร์เนลAIXซึ่งจะสร้าง pseudo-ttys ตามคำขอ แต่ถ้าคุณทำให้พูดว่า "echo> / dev / pty10000000000" มันจะสร้าง / dev / pty0, / dev / pty1, .... แล้วล้มทับตาย. มันสนุกมากที่งานแสดงสินค้า แต่ฉันไม่อยากให้สิ่งนี้เกิดขึ้นกับลูกค้าของฉัน


1
ข้อเสียที่เป็นไปได้ในการทำเช่นนี้คือความยากลำบากในการดีบักใน Firebug คำสั่งบันทึกบนอาร์เรย์จะแสดงรายการองค์ประกอบ 1,000 รายการแรกในอาร์เรย์เท่านั้นซึ่งทั้งหมดจะเป็น "ไม่ได้กำหนด" นอกจากนี้ array.length จะบอกคุณว่าอาร์เรย์ของคุณมีองค์ประกอบ n อยู่แม้ว่า n-1 จะเป็นเพียงค่าที่ไม่ได้กำหนด "โกสต์" ก็ตาม
Michael Butler

ตอนนี้การดีบักทำได้ดีใน Chrome - นี่คือตัวอย่างของเอาต์พุตคอนโซล: [ว่าง× 9564, อ็อบเจกต์, ว่าง× 105, อ็อบเจกต์, ว่าง× 10, อ็อบเจกต์, ว่าง× 12, อ็อบเจกต์, ว่าง× 9, อ็อบเจกต์, ว่าง× 21, Object, empty × 9, Object]
jsalvata

คำตอบ:


40

วิธีการใช้งานอาร์เรย์ JavaScript นั้นแตกต่างจากเบราว์เซอร์ไปยังเบราว์เซอร์อย่างไร แต่โดยทั่วไปแล้วจะกลับไปใช้งานแบบกระจัดกระจายซึ่งส่วนใหญ่เป็นแบบเดียวกับที่ใช้สำหรับการเข้าถึงคุณสมบัติของวัตถุทั่วไป - หากใช้อาร์เรย์จริงจะไม่มีประสิทธิภาพ

คุณจะต้องถามคนที่มีความรู้เพิ่มเติมเกี่ยวกับการใช้งานเฉพาะเพื่อตอบว่าอะไรที่กระตุ้นการเปลี่ยนแปลงจากหนาแน่นเป็นเบาบาง แต่ตัวอย่างของคุณควรปลอดภัยอย่างสมบูรณ์ ถ้าคุณต้องการรับอาร์เรย์หนาแน่นคุณควรเรียกตัวสร้างด้วยอาร์กิวเมนต์ที่มีความยาวชัดเจนและหวังว่าคุณจะได้รับจริง

ดูคำตอบนี้สำหรับคำอธิบายโดยละเอียดเพิ่มเติมโดย olliej


1
foo = new Array(10000)ผมไม่คิดว่าคุณได้รับจริงอาร์เรย์หนาแน่นถ้าคุณบอกว่าสิ่งที่ต้องการ foo = Array.apply(null, {length: 10});แต่นี้ควรจะทำงาน:
doubleOrt

71

ใช่พวกเขาเป็น จริงๆแล้วมันเป็นตารางแฮชภายในดังนั้นคุณจึงสามารถใช้ไม่เพียง แต่จำนวนเต็มขนาดใหญ่เท่านั้น แต่ยังรวมถึงสตริงการลอยหรือวัตถุอื่น ๆ คีย์ทั้งหมดจะถูกแปลงเป็นสตริง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
@ จอห์น: lengthมองไม่เห็นเฉพาะในfor..inลูปเพราะมีการDontEnumตั้งค่าสถานะ ใน ES5 แอตทริบิวต์คุณสมบัติถูกเรียกenumerableและสามารถตั้งค่าได้อย่างชัดเจนผ่านObject.defineProperty()
Christoph

14
ทุกปุ่มวัตถุใน JavaScript อยู่เสมอString; สิ่งอื่นใดที่คุณใส่ในตัวห้อยจะได้รับtoString()-ed รวมนี้กับความไม่แน่ชัดจำนวนเต็มของจำนวนที่มีขนาดใหญ่และมันหมายความว่าถ้าคุณตั้งค่าa[9999999999999999]=1, a[10000000000000000]จะเป็น 1 (และพฤติกรรมที่น่าแปลกใจอื่น ๆ อีกมากมาย) การใช้ที่ไม่ใช่จำนวนเต็มเป็นคีย์นั้นไม่ฉลาดมากและอ็อบเจ็กต์ที่กำหนดเองจะถูกใช้
bobince

73
จากนั้นคุณจะใช้สตริงเป็นปุ่มวัตถุเท่านั้นไม่มากไม่น้อยกว่า String จะเป็นประเภทที่คุณจะใช้และประเภทของคีย์ต้องเป็น String คุณจะไม่ใช้จำนวนเต็มและห้ามใช้ที่ไม่ใช่จำนวนเต็มยกเว้นว่าคุณจะส่งไปยัง String วัตถุโดยพลการถูกออก
Crescent Fresh

8
ดัชนีอาร์เรย์ต้องเป็นจำนวนเต็ม array [3.14] = pi ทำงานได้เนื่องจาก Array มาจาก Object ตัวอย่าง: var x = []; x [.1] = 5; จากนั้น x มีความยาวเป็น 0 นิ่ง
Mike Blandford

10

คุณสามารถหลีกเลี่ยงปัญหาได้โดยใช้ไวยากรณ์จาวาสคริปต์ที่ออกแบบมาสำหรับสิ่งนี้ คุณสามารถใช้เป็นพจนานุกรมได้ แต่ไวยากรณ์ "for ... in ... " จะช่วยให้คุณสามารถจับมันได้ทั้งหมด

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

7

วัตถุ Javascript นั้นกระจัดกระจายและอาร์เรย์เป็นเพียงออบเจ็กต์พิเศษที่มีคุณสมบัติความยาวที่คงไว้โดยอัตโนมัติ (ซึ่งจริงๆแล้วมีขนาดใหญ่กว่าดัชนีที่ใหญ่ที่สุดไม่ใช่จำนวนองค์ประกอบที่กำหนดไว้) และวิธีการเพิ่มเติมบางอย่าง คุณปลอดภัยไม่ว่าจะด้วยวิธีใดก็ตาม ใช้อาร์เรย์หากคุณต้องการคุณสมบัติพิเศษและวัตถุอย่างอื่น


4
นั่นมาจากมุมมองทางภาษา การนำไปใช้จริงใช้อาร์เรย์จริงเพื่อจัดเก็บคุณสมบัติตัวเลขที่หนาแน่น
Christoph

6

คำตอบที่มักจะเป็นจริงกับ JavaScript คือ "มันค่อนข้างแย่กว่า .... "

ไม่ได้กำหนดการใช้งานหน่วยความจำและการใช้งานใด ๆ ได้รับอนุญาตให้โง่ ในทางทฤษฎีสามารถเขียนเมกะไบต์หน่วยความจำเป็นที่จะทำได้const a = []; a[1000000]=0; const a = [];ในทางปฏิบัติแม้แต่ Microsoft ก็หลีกเลี่ยงการใช้งานเหล่านั้น

Justin Loveชี้ให้เห็นว่าแอตทริบิวต์ความยาวเป็นดัชนีที่ตั้งไว้สูงสุด แต่จะอัปเดตเฉพาะในกรณีที่ดัชนีเป็นจำนวนเต็ม

ดังนั้นอาร์เรย์จึงเบาบาง แต่ฟังก์ชั่นในตัวเช่น reduce (), Math.max () และ "for ... of" จะเดินผ่านช่วงของดัชนีจำนวนเต็มที่เป็นไปได้ทั้งหมดในรูปแบบ 0 ไปจนถึงความยาวโดยไปที่ค่าต่างๆที่ส่งคืน 'ไม่ได้กำหนด' แต่ลูป '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

แต่. ยังมีกรณีมุมอีกมากมายที่ยังไม่ได้กล่าวถึงอาร์เรย์


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 ดั้งเดิมอาจจำเป็นต้องบังคับด้วยrange-เหมือนกัน :

> 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
ดีและฉันรู้สึกประหลาดใจที่คำถามอายุสิบขวบของฉันยังคงเกี่ยวข้องอยู่!
Berry

1

พวกเขาสามารถเป็นได้ แต่ไม่จำเป็นต้องเป็นเสมอไปและสามารถทำงานได้ดีขึ้นเมื่อไม่ได้เป็นเช่นนั้น

นี่คือการอภิปรายเกี่ยวกับวิธีทดสอบความเบาบางของดัชนีในอินสแตนซ์อาร์เรย์: https://benmccormick.org/2018/06/19/code-golf-sparse-arrays/

ผู้ชนะรหัสกอล์ฟ (อักขระน้อยที่สุด) คือ:

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

โดยพื้นฐานแล้วการเดินอาร์เรย์สำหรับรายการที่จัดทำดัชนีในขณะที่ลดค่าความยาวและส่งคืน!!บูลีนที่แข็งตัวของผลลัพธ์ที่เป็นตัวเลขเท็จ / จริง (ถ้าตัวสะสมลดลงจนสุดเป็นศูนย์ดัชนีจะถูกเติมเต็มและไม่กระจัดกระจาย) คำเตือนของCharles Merriamข้างต้นควรได้รับการพิจารณาเช่นกันและรหัสนี้ไม่ได้ระบุถึงพวกเขา แต่จะใช้กับรายการสตริงที่แฮชซึ่งอาจเกิดขึ้นได้เมื่อกำหนดองค์ประกอบโดยarr[var]= (something)ที่ var ไม่ใช่จำนวนเต็ม

เหตุผลที่ต้องดูแลเกี่ยวกับความเบาบางของดัชนีคือผลกระทบต่อประสิทธิภาพซึ่งอาจแตกต่างกันระหว่างเอ็นจิ้นสคริปต์มีการอภิปรายที่ดีเกี่ยวกับการสร้างอาร์เรย์ / .initialization ที่นี่: อะไรคือความแตกต่างระหว่าง "Array ()" และ "[]" ในขณะที่ประกาศ JavaScript อาร์เรย์?

คำตอบล่าสุดสำหรับโพสต์ดังกล่าวมีลิงก์ไปยังข้อมูลเชิงลึกเกี่ยวกับวิธีที่ V8 พยายามเพิ่มประสิทธิภาพอาร์เรย์โดยการแท็กเพื่อหลีกเลี่ยง ( ซ้ำ) การทดสอบลักษณะเช่นความเบาบาง: https://v8.dev/blog/elements-kinds บล็อกโพสต์มาจากเดือนกันยายน '17 และเนื้อหาอาจมีการเปลี่ยนแปลงบางอย่าง แต่การแจกแจงผลกระทบต่อการพัฒนาในแต่ละวันนั้นมีประโยชน์และชัดเจน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.