Array หรือ List ใน Java ไหนเร็วกว่ากัน


351

ฉันต้องเก็บหลายพันสายในหน่วยความจำเพื่อให้สามารถเข้าถึงได้ใน Java ฉันควรเก็บไว้ในอาร์เรย์หรือฉันควรใช้รายการบางประเภท?

เนื่องจากอาร์เรย์เก็บข้อมูลทั้งหมดไว้ในหน่วยความจำที่ต่อเนื่องกัน (ซึ่งแตกต่างจากรายการ) การใช้อาร์เรย์เพื่อเก็บสตริงหลายพันรายการจะทำให้เกิดปัญหาหรือไม่


5
"ตั้งแต่อาร์เรย์เก็บข้อมูลทั้งหมดไว้ในหน่วยความจำที่ต่อเนื่องกัน" คุณมีการอ้างอิงแบบใดในการสำรองข้อมูลนี้สำหรับ Java หรือไม่
4657 b แมตต์

1
ไม่มีด้าน ฉันรู้สิ่งนี้สำหรับ C. ฉันคาดว่า Java จะใช้วิธีการเดียวกัน
euphoria83

ฉันสงสัยว่ามันจะทำให้พวกเขาอยู่ในความทรงจำอันเดียว
Fortyrunner

3
แม้ว่าจะเป็นหน่วยความจำบล็อกเดียว แต่ก็ยังมีค่าประมาณ 1,000 * 4 = 4kb ซึ่งมีหน่วยความจำไม่มาก
CookieOfFortune

3
@attatt นั่นคือความหมายของ 'array' ตลอดทั้ง CS ไม่จำเป็นต้องมีการอ้างอิง การอ้างอิงจำนวนมากในJLSและ [JVM Spec] () กับความยาวของอาร์เรย์นั้นสามารถเข้าใจได้ก็ต่อเมื่ออาร์เรย์นั้นต่อเนื่องกัน
มาร์ควิสแห่ง Lorne

คำตอบ:


358

ฉันแนะนำให้คุณใช้ตัวสร้างโปรไฟล์เพื่อทดสอบว่าเร็วกว่าไหน

ความคิดเห็นส่วนตัวของฉันคือคุณควรใช้รายการ

ผมทำงานใน codebase ขนาดใหญ่และกลุ่มก่อนหน้านี้ของนักพัฒนาอาร์เรย์ใช้ทุกที่ มันทำให้รหัสยืดหยุ่นมาก หลังจากเปลี่ยนชิ้นใหญ่เป็นรายการเราพบว่าความเร็วไม่แตกต่างกัน


2
@Fortyrunner - จากประสบการณ์ของคุณมีตัวเลือกใน Java ระหว่างรูปแบบนามธรรมและข้อมูลดิบที่สร้างความแตกต่างอย่างมีนัยสำคัญในประสิทธิภาพหรือไม่?
euphoria83

4
หนึ่งในปัญหาที่เกี่ยวกับการวัดประสิทธิภาพคือคุณต้องทดสอบซ้ำกับ Java รุ่นใหม่อยู่ตลอดเวลา ฉันกำลังทำงานกับปัญหาในขณะที่มีคนใช้ int สำหรับคีย์ในแผนที่ (เพื่อประหยัดพื้นที่ / เวลา) ตอนนี้เราต้องเปลี่ยนทุกบรรทัดเป็นวัตถุใหม่ - มันเจ็บปวด
Fortyrunner

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

4
โปรดจำไว้ว่าผลลัพธ์ของตัวสร้างโปรไฟล์ใช้ได้กับแพลตฟอร์ม Java ที่คุณใช้งานตัวสร้างโปรไฟล์เท่านั้น ซึ่งอาจแตกต่างจากลูกค้าของคุณ
Mikkel Løkke

4
Java ที่มีประสิทธิภาพแนะนำรายการเพื่อช่วยในการทำงานร่วมกันของ API และยังปลอดภัยยิ่งขึ้นด้วยความปลอดภัยของประเภท
juanmf

164

วิธี Java คือคุณควรพิจารณาสิ่งที่นามธรรมข้อมูลที่เหมาะสมที่สุดกับความต้องการของคุณ โปรดจำไว้ว่าใน Java a List นั้นเป็นนามธรรมไม่ใช่ประเภทข้อมูลที่เป็นรูปธรรม คุณควรประกาศสตริงเป็นรายการจากนั้นเตรียมใช้งานโดยใช้ ArrayList

List<String> strings = new ArrayList<String>();

การแยกประเภทข้อมูลนามธรรมและการใช้งานเฉพาะอย่างนี้เป็นประเด็นสำคัญประการหนึ่งของการเขียนโปรแกรมเชิงวัตถุ

ArrayList ใช้ List Data Data Type โดยใช้อาร์เรย์เป็นการนำไปปฏิบัติ ความเร็วในการเข้าถึงนั้นเหมือนกันกับอาเรย์ด้วยข้อดีเพิ่มเติมของความสามารถในการเพิ่มและลบอิลิเมนต์ในรายการ (แม้ว่านี่จะเป็นการดำเนินการ O (n) ด้วย ArrayList) และถ้าคุณตัดสินใจที่จะเปลี่ยนการใช้งานพื้นฐานในภายหลัง คุณสามารถ. ตัวอย่างเช่นหากคุณรู้ว่าคุณต้องการเข้าถึงแบบซิงโครไนซ์คุณสามารถเปลี่ยนการใช้งานเป็น Vector ได้โดยไม่ต้องเขียนโค้ดทั้งหมดของคุณอีกครั้ง

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

เนื่องจากอาร์เรย์เก็บข้อมูลทั้งหมดไว้ในหน่วยความจำที่ต่อเนื่องกัน (ซึ่งแตกต่างจากรายการ) การใช้อาร์เรย์เพื่อเก็บสตริงหลายพันรายการจะทำให้เกิดปัญหาหรือไม่

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


แน่นอนว่าการเพิ่มอาจเกี่ยวข้องกับการจัดสรรอาเรย์การสำรองอีกครั้งดังนั้นหากประสิทธิภาพมีความสำคัญและขนาดของอาเรย์นั้นเป็นที่ทราบล่วงหน้าผู้ใช้ควรพิจารณาใช้ ArrayList # sureCapacity
JesperE

6
คุณไม่จ่ายค่าใช้จ่ายของการผูกแบบไดนามิกที่นี่หรือ
Uri

2
ฉันเดาว่าการเพิ่มไม่ใช่ O (n) ใน ArrayList ควรมีเอฟเฟกต์ ammortization บางอย่างเมื่อทำการเพิ่มมากกว่าหนึ่งครั้งเช่นความจุเพิ่มขึ้นเป็นสองเท่าแทนที่จะเพิ่มขึ้นเพียง 1
zedoo

@zedoo ฉันคิดว่าพวกเขาหมายถึงการเพิ่มและลบตรงกลาง
MalcolmOcean

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

100

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

มีบางสิ่งที่คุณสามารถทำกับอาร์เรย์:

  • สร้างมัน
  • ตั้งค่ารายการ
  • รับรายการ
  • โคลน / คัดลอก

บทสรุปทั่วไป

แม้ว่าการรับและการตั้งค่าจะค่อนข้างช้ากว่าใน ArrayList (การตอบสนองที่ 1 และ 3 nanosecond ต่อการโทรบนเครื่องของฉัน) มีค่าใช้จ่ายน้อยมากในการใช้ ArrayList กับอาเรย์สำหรับการใช้ที่ไม่ต้องใช้ความรุนแรงใด ๆ อย่างไรก็ตามมีบางสิ่งที่ต้องจำไว้:

  • การปรับขนาดการดำเนินการในรายการ (เมื่อโทรlist.add(...)) มีค่าใช้จ่ายสูงและควรพยายามตั้งค่าความจุเริ่มต้นในระดับที่เพียงพอเมื่อเป็นไปได้ (โปรดทราบว่าปัญหาเดียวกันนี้เกิดขึ้นเมื่อใช้อาร์เรย์)
  • เมื่อต้องรับมือกับลำดับต้น ๆ อาร์เรย์จะเร็วขึ้นอย่างมากเนื่องจากจะช่วยให้ผู้ใช้สามารถหลีกเลี่ยงการแปลงมวย / ไม่แปลงจำนวนมาก
  • แอปพลิเคชันที่รับ / ตั้งค่าใน ArrayList เท่านั้น (ไม่ธรรมดามาก!) จะเห็นประสิทธิภาพที่เพิ่มขึ้นมากกว่า 25% โดยการสลับไปที่อาร์เรย์

รายละเอียดผลลัพธ์

นี่คือผลลัพธ์ที่ฉันวัดสำหรับการดำเนินการทั้งสามนี้โดยใช้ไลบรารีการเปรียบเทียบ jmh (คูณเป็นนาโนวินาที) ด้วย JDK 7 บนเครื่องเดสก์ท็อปมาตรฐาน x86 โปรดทราบว่า ArrayList จะไม่ถูกปรับขนาดในการทดสอบเพื่อให้แน่ใจว่าผลลัพธ์จะได้รับการเปรียบเทียบ รหัสเกณฑ์มาตรฐานอยู่ที่นี่

การสร้าง ArrayList / ArrayList

ฉันทดสอบ 4 ครั้งรันคำสั่งต่อไปนี้:

  • createArray1: Integer[] array = new Integer[1];
  • createList1: List<Integer> list = new ArrayList<> (1);
  • createArray10000: Integer[] array = new Integer[10000];
  • createList10000: List<Integer> list = new ArrayList<> (10000);

ผลลัพธ์ (เป็นนาโนวินาทีต่อการโทร, มั่นใจ 95%):

a.p.g.a.ArrayVsList.CreateArray1         [10.933, 11.097]
a.p.g.a.ArrayVsList.CreateList1          [10.799, 11.046]
a.p.g.a.ArrayVsList.CreateArray10000    [394.899, 404.034]
a.p.g.a.ArrayVsList.CreateList10000     [396.706, 401.266]

สรุป: ไม่มีความแตกต่างที่เห็นได้ชัด

รับการดำเนินงาน

ฉันทดสอบ 2 ครั้งรันคำสั่งต่อไปนี้:

  • getList: return list.get(0);
  • getArray: return array[0];

ผลลัพธ์ (เป็นนาโนวินาทีต่อการโทร, มั่นใจ 95%):

a.p.g.a.ArrayVsList.getArray   [2.958, 2.984]
a.p.g.a.ArrayVsList.getList    [3.841, 3.874]

บทสรุป: การได้รับจากอาเรย์นั้นเร็วกว่าการรับ ArrayList ประมาณ 25%ถึงแม้ว่าความแตกต่างจะอยู่ในคำสั่งของหนึ่งนาโนวินาทีเท่านั้น

ชุดปฏิบัติการ

ฉันทดสอบ 2 ครั้งรันคำสั่งต่อไปนี้:

  • ลิสท์: list.set(0, value);
  • setArray: array[0] = value;

ผลลัพธ์ (เป็นนาโนวินาทีต่อการโทร):

a.p.g.a.ArrayVsList.setArray   [4.201, 4.236]
a.p.g.a.ArrayVsList.setList    [6.783, 6.877]

บทสรุป: การดำเนินการที่กำหนดไว้ในอาร์เรย์นั้นเร็วกว่ารายการประมาณ 40%แต่สำหรับการดำเนินการแต่ละชุดใช้เวลาไม่กี่นาโนวินาทีดังนั้นความแตกต่างในการเข้าถึง 1 วินาทีหนึ่งจะต้องตั้งค่ารายการในรายการ / อาร์เรย์หลายร้อย ของล้านครั้ง!

โคลน / คัดลอก

ArrayList ของผู้ได้รับมอบหมายสำเนาคอนสตรัคไปArrays.copyOfเพื่อประสิทธิภาพเป็นเหมือนสำเนาอาร์เรย์ (คัดลอกอาร์เรย์ผ่านclone,Arrays.copyOfหรือSystem.arrayCopy ทำให้ประสิทธิภาพการทำงานที่ชาญฉลาดไม่แตกต่างกันวัสดุ )


1
การวิเคราะห์ที่ดี อย่างไรก็ตามด้วยความเคารพต่อความคิดเห็นของคุณ "เมื่อต้องรับมือกับระบบดั้งเดิมอาร์เรย์จะเร็วขึ้นอย่างมากเพราะจะช่วยให้ผู้ใช้หลีกเลี่ยงการแปลงชกมวย / unboxing จำนวนมาก" คุณสามารถมีเค้กของคุณและกินมันด้วยรายการสำรองดั้งเดิม การดำเนินการ; เช่น: github.com/scijava/scijava-common/blob/master/src/main/java/org/... ฉันค่อนข้างประหลาดใจจริง ๆ สิ่งนี้ไม่ได้ทำให้มันกลายเป็นแกน Java
ctrueden

2
@ctrueden ใช่ความคิดเห็นที่ใช้กับมาตรฐาน JDK ArrayList trove4j เป็นห้องสมุดที่รู้จักกันดีซึ่งสนับสนุนรายการดั้งเดิม Java 8 นำการปรับปรุงมาใช้กับสตรีมดั้งเดิมหลายตัว
assylias

ฉันไม่ทราบว่ามาตรฐาน jmh ทำงานอย่างไร แต่พวกเขาคำนึงถึงการรวบรวม JIT ที่สามารถเกิดขึ้นได้หรือไม่? ประสิทธิภาพของแอ็พพลิเคชัน Java สามารถเปลี่ยนแปลงได้เมื่อเวลาผ่านไปเนื่องจาก JVM รวบรวมโค้ดของคุณ
Hoffmann

@ ฮอฟมานน์ใช่ - รวมระยะการอุ่นเครื่องซึ่งไม่รวมอยู่ในการวัด
assylias

97

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

แต่เช่นเคยเมื่อปรับให้เหมาะสมคุณควรทำตามขั้นตอนเหล่านี้เสมอ:

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

24

ฉันเดาว่าโปสเตอร์ต้นฉบับมาจากพื้นหลัง C ++ / STL ซึ่งทำให้เกิดความสับสน ใน C ++std::listเป็นรายการที่เชื่อมโยงเป็นสองเท่า

ใน Java [java.util.]Listเป็นอินเทอร์เฟซที่ไม่ต้องติดตั้งใช้งาน (คลาสนามธรรมที่แท้จริงในเงื่อนไข C ++) Listสามารถเชื่อมโยงรายการทวีคูณ - java.util.LinkedListมีให้ อย่างไรก็ตาม 99 ครั้งจาก 100 เมื่อคุณต้องการทำใหม่Listคุณต้องการที่จะใช้java.util.ArrayListแทนซึ่งเป็นเทียบเท่าหยาบของ std::vectorC มีการใช้งานมาตรฐานอื่น ๆ เช่นการกลับมาjava.util.Collections.emptyList()และjava.util.Arrays.asList()และ

จากมุมมองประสิทธิภาพการทำงานมีการตีขนาดเล็กมากจากต้องผ่านอินเทอร์เฟซและวัตถุพิเศษอย่างไรก็ตามอินไลน์ซับไทม์หมายถึงสิ่งนี้ไม่ค่อยมีความสำคัญใด ๆ ยังจำไว้ว่าStringโดยทั่วไปแล้วจะเป็นออบเจ็กต์และอาร์เรย์ ดังนั้นสำหรับแต่ละรายการคุณอาจมีวัตถุสองชิ้น ใน C ++ std::vector<std::string>ถึงแม้ว่าการคัดลอกตามค่าโดยไม่มีตัวชี้เช่นนั้นอาร์เรย์อักขระจะจัดรูปแบบวัตถุสำหรับสตริง (และโดยทั่วไปจะไม่ใช้ร่วมกัน)

ถ้ารหัสเฉพาะนี้มีความอ่อนไหวต่อประสิทธิภาพจริง ๆ คุณสามารถสร้างchar[]อาเรย์เดี่ยว(หรือแม้กระทั่งbyte[]) สำหรับอักขระทั้งหมดของสตริงทั้งหมดและจากนั้นอาเรย์ของออฟเซ็ต IIRC นี่คือวิธีการนำ javac ไปปฏิบัติ


1
ขอบคุณสำหรับคำตอบ แต่ไม่ฉันไม่สับสนรายการ C ++ กับรายการอินเตอร์เฟสของ Java ฉันถามคำถามในลักษณะนี้เพราะฉันต้องการเปรียบเทียบประสิทธิภาพของการใช้งานรายการเช่น ArrayList และ Vector กับอาร์เรย์ดิบ
euphoria83

ทั้ง ArrayList และ Vector "เก็บข้อมูลทั้งหมดไว้ในหน่วยความจำที่ต่อเนื่องกัน"
Tom Hawtin - tackline

13

ฉันยอมรับว่าในกรณีส่วนใหญ่คุณควรเลือกความยืดหยุ่นและความสง่างามของ ArrayLists เหนืออาร์เรย์ - และในกรณีส่วนใหญ่ผลกระทบต่อประสิทธิภาพของโปรแกรมจะไม่สำคัญ

อย่างไรก็ตามถ้าคุณทำอย่างต่อเนื่องการทำซ้ำอย่างหนักด้วยการเปลี่ยนแปลงโครงสร้างเล็กน้อย (ไม่มีการเพิ่มและลบ) สำหรับการพูดการแสดงผลกราฟิกซอฟต์แวร์หรือเครื่องเสมือนที่กำหนดเองการทดสอบการเปรียบเทียบการเข้าถึงของฉันแสดงว่าArrayLists ช้ากว่าอาร์เรย์ของฉัน ระบบ (Java 1.6 บน iMac อายุหนึ่งปีของฉัน)

บางรหัส:

import java.util.*;

public class ArrayVsArrayList {
    static public void main( String[] args ) {

        String[] array = new String[300];
        ArrayList<String> list = new ArrayList<String>(300);

        for (int i=0; i<300; ++i) {
            if (Math.random() > 0.5) {
                array[i] = "abc";
            } else {
                array[i] = "xyz";
            }

            list.add( array[i] );
        }

        int iterations = 100000000;
        long start_ms;
        int sum;

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += array[j].length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (array)" );
        // Prints ~13,500 ms on my system

        start_ms = System.currentTimeMillis();
        sum = 0;

        for (int i=0; i<iterations; ++i) {
          for (int j=0; j<300; ++j) sum += list.get(j).length();
        }

        System.out.println( (System.currentTimeMillis() - start_ms) + " ms (ArrayList)" );
        // Prints ~20,800 ms on my system - about 1.5x slower than direct array access
    }
}

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

4
เกณฑ์มาตรฐานขนาดเล็กนี้มีข้อบกพร่อง (ไม่มีการวอร์มอัพการดำเนินการไม่ได้อยู่ในวิธีที่แยกต่างหากดังนั้นส่วนรายการอาร์เรย์จะไม่ถูกปรับให้เหมาะสมโดย JIT ฯลฯ )
assylias

ฉันเห็นด้วยกับ assylias ผลลัพธ์ของการวัดประสิทธิภาพนี้ไม่ควรเชื่อถือได้
สตีเฟ่นซี

@StephenC ฉันได้เพิ่มเกณฑ์มาตรฐานไมโครที่เหมาะสม (ที่แสดงให้เห็นว่าการดำเนินการรับเทียบเคียง)
assylias

11

ประการแรกมันคุ้มค่าที่จะอธิบายคุณหมายถึง "รายชื่อ" ในความหมายของโครงสร้างข้อมูลแบบคลาสสิก (เช่นรายการที่เชื่อมโยง) หรือคุณหมายถึง java.util.List หรือไม่ หากคุณหมายถึง java.util.List มันเป็นส่วนต่อประสาน หากคุณต้องการใช้อาเรย์เพียงแค่ใช้การใช้ ArrayList และคุณจะได้รับพฤติกรรมและความหมายเหมือนอาเรย์ แก้ไขปัญหา.

หากคุณหมายถึงอาร์เรย์เทียบกับรายการที่เชื่อมโยงมันเป็นอาร์กิวเมนต์ที่แตกต่างกันเล็กน้อยซึ่งเรากลับไปที่ Big O (นี่เป็นคำอธิบายภาษาอังกฤษแบบธรรมดาหากนี่เป็นคำที่ไม่คุ้นเคย

อาร์เรย์;

  • การเข้าถึงแบบสุ่ม: O (1);
  • แทรก: O (n);
  • ลบ: O (n)

รายการที่เชื่อมโยง:

  • การเข้าถึงแบบสุ่ม: O (n);
  • แทรก: O (1);
  • ลบ: O (1)

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

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


ฉันอินเตอร์เฟซ java.util.List เฉลี่ย
euphoria83

1
การเข้าถึงแบบสุ่ม O (n) ในรายการที่เชื่อมโยงดูเหมือนจะเป็นเรื่องใหญ่สำหรับฉัน
Bjorn

11

ฉันเขียนมาตรฐานเล็กน้อยเพื่อเปรียบเทียบ ArrayLists กับอาร์เรย์ บนแล็ปท็อปเก่าของฉันเวลาในการสำรวจผ่านรายการอาร์เรย์ 5000 องค์ประกอบ 1,000 ครั้งนั้นประมาณ 10 มิลลิวินาทีช้ากว่ารหัสอาร์เรย์ที่เทียบเท่ากัน

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

nb ผมไม่ได้แจ้งให้ทราบว่าการใช้for String s: stringsListเป็นประมาณ 50% ช้ากว่าการใช้แบบเก่าวงในการเข้าถึงรายการ ไปรูป ... นี่คือสองฟังก์ชันที่ฉันหมดเวลา อาร์เรย์และรายการถูกเติมด้วยสตริง 5000 แบบสุ่ม (ต่างกัน)

private static void readArray(String[] strings) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < strings.length; i++) {
            totalchars += strings[i].length();

        }
    }
}

private static void readArrayList(List<String> stringsList) {
    long totalchars = 0;
    for (int j = 0; j < ITERATIONS; j++) {
        totalchars = 0;
        for (int i = 0; i < stringsList.size(); i++) {
            totalchars += stringsList.get(i).length();
        }
    }
}

@ Chris May: เยี่ยมมาก! เวลาทำงานจริงของทั้งคู่คืออะไร คุณช่วยบอกขนาดของสายที่คุณใช้ได้ไหม นอกจากนี้เนื่องจากการใช้ 'String s: stringsList' ทำให้ใช้เวลานานขึ้นนี่คือความกลัวหลักของฉันในการใช้ abstractions ที่สูงขึ้นใน Java โดยทั่วไป
euphoria83

มันไม่สำคัญว่าจะใช้สายอักขระนานเท่าใดสำหรับ mcirobenchmark นี้ ไม่มี gc และchar[]ไม่แตะต้อง (นี่ไม่ใช่ C)
Tom Hawtin - tackline

เวลาทั่วไปสำหรับฉันคือ ~ 25ms สำหรับรุ่นอาร์เรย์ ~ 35ms สำหรับรุ่น ArrayList สายยาว 15-20 ตัวอักษร ดังที่ทอมบอกว่าขนาดของสตริงนั้นไม่ได้สร้างความแตกต่างมากนักกับสตริง ~ 100-char การกำหนดเวลาก็เหมือนกัน
Chris พฤษภาคม

3
คุณวัดได้อย่างไร การวัดที่ไร้เดียงสาในการวัดประสิทธิภาพแบบไมโครของ Java มักจะสร้างข้อมูลที่ผิดมากกว่าข้อมูล ระวังข้อความข้างต้น
jmg

6

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


5
รายการยังเก็บการอ้างอิงถึงสตริงเท่านั้น
ปีเตอร์ibrtibraný

6

หากคุณมีเป็นพันลองใช้คู่ชีวิต Trie เป็นโครงสร้างแบบต้นไม้ที่รวมคำนำหน้าร่วมของสตริงที่จัดเก็บไว้

ตัวอย่างเช่นถ้าเป็นสตริง

intern
international
internationalize
internet
internets

ทั้งคู่จะเก็บ:

intern
 -> \0
 international
 -> \0
 -> ize\0
 net
 ->\0
 ->s\0

สตริงต้องการอักขระ 57 ตัว (รวมถึง null terminator, '\ 0') สำหรับการจัดเก็บรวมถึงขนาดของวัตถุ String ที่เก็บไว้ (อันที่จริงแล้วเราน่าจะปัดเศษทุกขนาดจนถึงทวีคูณของ 16 แต่ ... ) เรียกมันว่า 57 + 5 = 62 ไบท์ประมาณ

trie ต้องการ 29 (รวมถึง null terminator, '\ 0') สำหรับจัดเก็บบวกขนาดของ trie nodes ซึ่งเป็นการอ้างอิงถึงอาร์เรย์และรายการโหนดชายน์ trie

สำหรับตัวอย่างนี้ที่อาจออกมาเกี่ยวกับเดียวกัน; เป็นพันอาจน้อยถ้าคุณมีคำนำหน้าทั่วไป

ตอนนี้เมื่อใช้ trie ในโค้ดอื่นคุณจะต้องแปลงเป็น String ซึ่งอาจใช้ StringBuffer เป็นตัวกลาง หากมีการใช้สตริงจำนวนมากพร้อมกันในฐานะ Strings ซึ่งอยู่นอกคู่ชีวิตมันจะสูญเสีย

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

คุณบอกว่าคุณกำลังเข้าถึงพวกเขา "ลำดับ" - ถ้านั่นหมายถึงลำดับตัวอักษรตามลำดับทั้งคู่ก็ให้ลำดับตัวอักษรตามตัวอักษรฟรีถ้าคุณย้ำมันลึกก่อน


1
Trie เป็นเหมือนห้องสมุดหรือฉันจะสร้างมันได้อย่างไร
euphoria83

Trie จะมีประโยชน์เฉพาะในกรณีของสายโทเค็นเท่านั้นไม่ใช่ถ้ามีใครบางคนกำลังเก็บข้อความที่ใช้เป็นสตริง
MN

5

UPDATE:

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

ยังคงแรก 1-2 ผ่านอาร์เรย์ที่เรียบง่ายเป็น 2-3 ครั้งเร็วขึ้น

โพสต์ต้นฉบับ:

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

import java.util.List;
import java.util.Arrays;

public class IterationTest {

    private static final long MAX_ITERATIONS = 1000000000;

    public static void main(String [] args) {

        Integer [] array = {1, 5, 3, 5};
        List<Integer> list = Arrays.asList(array);

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i) {
//            for (int e : array) {
            for (int e : list) {
                test_sum += e;
            }
        }
        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }
}

และนี่คือคำตอบ:

ขึ้นอยู่กับอาร์เรย์ (บรรทัดที่ 16 ใช้งานอยู่):

Time: 7064

ตามรายการ (บรรทัดที่ 17 ใช้งานอยู่):

Time: 20950

มีความคิดเห็นเพิ่มเติมเกี่ยวกับ 'เร็ว' อีกไหม เรื่องนี้ค่อนข้างเข้าใจ คำถามคือเมื่อคุณเร็วกว่าประมาณ 3 ครั้งจะดีกว่าสำหรับความยืดหยุ่นของรายการ แต่นี่เป็นคำถามอื่น ArrayListโดยวิธีการที่ผมตรวจสอบตามนี้เกินไปในการสร้างด้วยตนเอง ผลเกือบเหมือนกัน


2
3เวลาจริงเร็วกว่า แต่ไม่มีนัยสำคัญดังนั้น 14msไม่นาน
0x6C38

1
เกณฑ์มาตรฐานไม่ได้พิจารณา JVM การอุ่นเครื่อง เปลี่ยน main () เป็น test () และ call test จาก main ซ้ำ ๆ จากการทดสอบครั้งที่ 3 หรือครั้งที่ 4 มันจะทำงานเร็วขึ้นหลายเท่า ณ จุดนี้ฉันเห็นว่าอาเรย์นั้นเร็วกว่าอาเรย์ประมาณ 9 เท่า
Mike

5

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

นี่เป็นการตรวจสอบประสิทธิภาพอย่างง่าย ๆ จริง ๆ
ดังนั้นผลลัพธ์จะขึ้นอยู่กับประสิทธิภาพของเครื่อง

ซอร์สโค้ดที่ใช้สำหรับสิ่งนี้อยู่ด้านล่าง:

import java.util.Iterator;
import java.util.LinkedList;

public class Array_vs_LinkedList {

    private final static int MAX_SIZE = 40000000;

    public static void main(String[] args) {

        LinkedList lList = new LinkedList(); 

        /* insertion performance check */

        long startTime = System.currentTimeMillis();

        for (int i=0; i<MAX_SIZE; i++) {
            lList.add(i);
        }

        long stopTime = System.currentTimeMillis();
        long elapsedTime = stopTime - startTime;
        System.out.println("[Insert]LinkedList insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");

        int[] arr = new int[MAX_SIZE];

        startTime = System.currentTimeMillis();
        for(int i=0; i<MAX_SIZE; i++){
            arr[i] = i; 
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Insert]Array Insert operation with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        /* iteration performance check */

        startTime = System.currentTimeMillis();

        Iterator itr = lList.iterator();

        while(itr.hasNext()) {
            itr.next();
            // System.out.println("Linked list running : " + itr.next());
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]LinkedList iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");


        startTime = System.currentTimeMillis();

        int t = 0;
        for (int i=0; i < MAX_SIZE; i++) {
            t = arr[i];
            // System.out.println("array running : " + i);
        }

        stopTime = System.currentTimeMillis();
        elapsedTime = stopTime - startTime;
        System.out.println("[Loop]Array iteration with " + MAX_SIZE + " number of integer elapsed time is " + elapsedTime + " millisecond.");
    }
}

ผลการดำเนินงานด้านล่าง:

ป้อนคำอธิบายรูปภาพที่นี่


4

รายการจะช้ากว่าอาร์เรย์ถ้าคุณต้องการประสิทธิภาพการใช้อาร์เรย์ถ้าคุณต้องการความยืดหยุ่นในการใช้รายการ


4

โปรดจำไว้ว่า ArrayList จะใส่อาเรย์ไว้ดังนั้นจึงมีความแตกต่างเล็กน้อยเมื่อเทียบกับการใช้อาเรย์ดั้งเดิม

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


4

ตัวเลือก Array vs. List ไม่สำคัญมาก (เมื่อพิจารณาถึงประสิทธิภาพ) ในกรณีที่เก็บวัตถุสตริง เนื่องจากทั้งอาร์เรย์และรายการจะเก็บการอ้างอิงวัตถุสตริงไม่ใช่วัตถุจริง

  1. หากจำนวนของสตริงเกือบคงที่ให้ใช้อาร์เรย์ (หรือ ArrayList) แต่ถ้าจำนวนแตกต่างกันมากเกินไปคุณควรใช้ LinkedList
  2. หากมี (หรือจะ) ความต้องการในการเพิ่มหรือลบองค์ประกอบตรงกลางคุณต้องใช้ LinkedList อย่างแน่นอน

4

ฉันมาที่นี่เพื่อรับความรู้สึกที่ดีขึ้นสำหรับผลกระทบด้านประสิทธิภาพของการใช้รายการผ่านอาร์เรย์ ฉันต้องปรับโค้ดที่นี่สำหรับสถานการณ์ของฉัน: array / list ของ ~ 1000 ints โดยใช้ getters ส่วนใหญ่หมายถึง array [j] vs. list.get (j)

สละสิ่งที่ดีที่สุดจาก 7 ให้เป็นวิทยาศาสตร์ตามหลักวิทยาศาสตร์

array Integer[] best 643ms iterator
ArrayList<Integer> best 1014ms iterator

array Integer[] best 635ms getter
ArrayList<Integer> best 891ms getter (strange though)

- ดังนั้นเร็วขึ้นประมาณ 30% เมื่อใช้อาเรย์

เหตุผลที่สองสำหรับการโพสต์ในขณะนี้คือไม่มีใครพูดถึงผลกระทบถ้าคุณทำคณิตศาสตร์ / เมทริกซ์ / จำลอง / รหัสการเพิ่มประสิทธิภาพด้วยลูปซ้อนกัน

สมมติว่าคุณมีสามระดับซ้อนกันและวงด้านในเป็นสองเท่าในขณะที่คุณกำลังดูที่การตี 8 ครั้ง บางสิ่งบางอย่างที่จะทำงานในหนึ่งวันจะใช้เวลาหนึ่งสัปดาห์

* แก้ไขค่อนข้างตกใจที่นี่สำหรับการเตะฉันพยายามประกาศ int [1000] แทนที่จะเป็น Integer [1000]

array int[] best 299ms iterator
array int[] best 296ms getter

การใช้ Integer [] vs. int [] หมายถึงประสิทธิภาพที่เพิ่มขึ้นเป็นสองเท่า ListArray ที่มีตัววนซ้ำจะช้ากว่า int [3] จริง ๆ แล้วคิดว่าการใช้งานรายการของ Java คล้ายกับอาร์เรย์พื้นเมือง ...

รหัสสำหรับการอ้างอิง (โทรหลายครั้ง):

    public static void testArray()
    {
        final long MAX_ITERATIONS = 1000000;
        final int MAX_LENGTH = 1000;

        Random r = new Random();

        //Integer[] array = new Integer[MAX_LENGTH];
        int[] array = new int[MAX_LENGTH];

        List<Integer> list = new ArrayList<Integer>()
        {{
            for (int i = 0; i < MAX_LENGTH; ++i)
            {
                int val = r.nextInt();
                add(val);
                array[i] = val;
            }
        }};

        long start = System.currentTimeMillis();
        int test_sum = 0;
        for (int i = 0; i < MAX_ITERATIONS; ++i)
        {
//          for (int e : array)
//          for (int e : list)          
            for (int j = 0; j < MAX_LENGTH; ++j)
            {
                int e = array[j];
//              int e = list.get(j);
                test_sum += e;
            }
        }

        long stop = System.currentTimeMillis();

        long ms = (stop - start);
        System.out.println("Time: " + ms);
    }

3

ถ้าคุณรู้ล่วงหน้าว่าข้อมูลมีขนาดใหญ่เท่าใดอาเรย์จะเร็วขึ้น

รายการมีความยืดหยุ่นมากขึ้น คุณสามารถใช้ ArrayList ซึ่งได้รับการสนับสนุนจากอาร์เรย์


ArrayList มีเมธอด sureCapacity () ซึ่งทำการจัดสรรอาร์เรย์สำรองให้เป็นขนาดที่ระบุไว้ล่วงหน้า
JesperE

หรือคุณสามารถระบุขนาดในเวลาก่อสร้าง นอกจากนี้ยังมี "เร็วขึ้น" นี่หมายความว่า "กี่ microseconds การจัดสรรสองพื้นที่หน่วยความจำแทนหนึ่ง"
แอรอน Digulla

3

คุณสามารถใช้งานขนาดที่กำหนดได้อาร์เรย์จะเร็วขึ้นและต้องการหน่วยความจำน้อยลง

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

คุณอาจต้องการดูhttp://java.dzone.com/articles/gaplist-%E2%80%93-lightning-fast-listซึ่งแนะนำ GapList การใช้งานรายการใหม่นี้รวมจุดแข็งของ ArrayList และ LinkedList เข้าด้วยกันทำให้เกิดประสิทธิภาพที่ดีมากสำหรับการทำงานเกือบทั้งหมด


2

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

แน่นอนว่าเป็นไปได้ที่การดำเนินการ jvm จะมีกรณีพิเศษสำหรับสถานการณ์นี้ซึ่งในกรณีนี้ประสิทธิภาพจะเหมือนกัน


2

รายการเป็นวิธีที่ต้องการใน java 1.5 ขึ้นไปเนื่องจากสามารถใช้ชื่อสามัญได้ อาร์เรย์ไม่สามารถมีชื่อสามัญได้ นอกจากนี้อาร์เรย์ยังมีความยาวที่กำหนดไว้ล่วงหน้าซึ่งไม่สามารถเติบโตได้แบบไดนามิก การเริ่มต้นอาร์เรย์ที่มีขนาดใหญ่ไม่ใช่ความคิดที่ดี ArrayList เป็นวิธีการประกาศอาร์เรย์ที่มีชื่อสามัญและสามารถเติบโตแบบไดนามิก แต่ถ้าใช้การลบและแทรกบ่อยครั้งกว่ารายการที่ลิงก์จะเป็นโครงสร้างข้อมูลที่เร็วที่สุดที่จะใช้


2

แนะนำอาร์เรย์ทุกที่ที่คุณสามารถใช้แทนรายการโดยเฉพาะในกรณีที่คุณรู้ว่าจำนวนและขนาดของรายการจะไม่เปลี่ยนแปลง

ดูแนวทางปฏิบัติที่ดีที่สุดของ Oracle Java: http://docs.oracle.com/cd/A97688_16/generic.903/bp/java.htm#1007056

แน่นอนถ้าคุณต้องการเพิ่มและลบวัตถุออกจากการรวบรวมรายการใช้งานง่ายหลายครั้ง


เอกสารที่คุณเชื่อมโยงกับมีอายุมากกว่า 10 ปีเช่นใช้กับจาวา 1.3 การปรับปรุงประสิทธิภาพที่สำคัญได้รับการทำตั้งแต่นั้นมา ...
assylias

@assylias ดูคำตอบข้างต้นพวกเขามีการทดสอบประสิทธิภาพที่บอกว่าอาร์เรย์ได้เร็วขึ้น
Nik

3
ฉันรู้ว่าฉันเขียนหนึ่งในนั้น แต่ฉันไม่คิดว่า " แนะนำให้ใช้อาร์เรย์ทุกที่ที่คุณสามารถใช้แทนรายการได้ " เป็นคำแนะนำที่ดี ArrayList ควรเป็นตัวเลือกเริ่มต้นในสถานการณ์ส่วนใหญ่ยกเว้นว่าคุณกำลังติดต่อกับ primitives และโค้ดของคุณนั้นไวต่อประสิทธิภาพ
assylias

2

ไม่มีคำตอบใดที่มีข้อมูลที่ฉันสนใจ - การสแกนซ้ำหลายครั้งหลายครั้ง ต้องสร้างการทดสอบ JMH สำหรับสิ่งนี้

ผลลัพธ์ (Java 1.8.0_66 x32, การทำซ้ำอาร์เรย์ธรรมดาอย่างน้อย 5 ครั้งเร็วกว่า ArrayList):

Benchmark                    Mode  Cnt   Score   Error  Units
MyBenchmark.testArrayForGet  avgt   10   8.121 ? 0.233  ms/op
MyBenchmark.testListForGet   avgt   10  37.416 ? 0.094  ms/op
MyBenchmark.testListForEach  avgt   10  75.674 ? 1.897  ms/op

ทดสอบ

package my.jmh.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

@State(Scope.Benchmark)
@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {

    public final static int ARR_SIZE = 100;
    public final static int ITER_COUNT = 100000;

    String arr[] = new String[ARR_SIZE];
    List<String> list = new ArrayList<>(ARR_SIZE);

    public MyBenchmark() {
        for( int i = 0; i < ARR_SIZE; i++ ) {
            list.add(null);
        }
    }

    @Benchmark
    public void testListForEach() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( String str : list ) {
                if( str != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testListForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( list.get(j) != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

    @Benchmark
    public void testArrayForGet() {
        int count = 0;
        for( int i = 0; i < ITER_COUNT; i++ ) {
            for( int j = 0; j < ARR_SIZE; j++ ) {
                if( arr[j] != null )
                    count++;
            }
        }
        if( count > 0 )
            System.out.print(count);
    }

}

2

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


8 ไบต์ในการใช้งานแบบ 64 บิตส่วนใหญ่
Tom Hawtin - tackline

มีหลักฐานว่าสิ่งนี้เร็วกว่า java.util.LinkedList หรือไม่? 'ในหน่วยความจำ' ด้วยเช่นกัน นอกจากนี้ยังสามารถทำให้ไม่เปลี่ยนรูปเหมือนว่าจะสร้างความแตกต่างใด ๆ
มาร์ควิสแห่ง Lorne

1

อย่าเข้าไปในกับดักของการเพิ่มประสิทธิภาพหากไม่มีการเปรียบเทียบที่เหมาะสม ตามที่คนอื่นแนะนำให้ใช้ตัวสร้างโปรไฟล์ก่อนทำการสันนิษฐาน

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

เกี่ยวกับการใช้หน่วยความจำ คุณดูเหมือนจะผสมบางสิ่ง อาร์เรย์จะให้หน่วยความจำต่อเนื่องสำหรับประเภทข้อมูลที่คุณมีเท่านั้น อย่าลืมว่า java มีชนิดข้อมูลคงที่: บูลีน, ถ่าน, int, long, float และ Object (ซึ่งรวมถึงวัตถุทั้งหมดแม้กระทั่งอาร์เรย์คือวัตถุ) หมายความว่าถ้าคุณประกาศอาร์เรย์ของสตริงสตริง [1000] หรือ MyObject myObjects [1000] คุณจะได้รับกล่องหน่วยความจำ 1,000 กล่องใหญ่พอที่จะเก็บตำแหน่ง (การอ้างอิงหรือตัวชี้) ของวัตถุ คุณไม่ได้รับหน่วยความจำ 1,000 กล่องใหญ่พอที่จะพอดีกับขนาดของวัตถุ อย่าลืมว่าวัตถุของคุณถูกสร้างขึ้นครั้งแรกด้วย "ใหม่" นี่คือเมื่อการจัดสรรหน่วยความจำเสร็จสิ้นและการอ้างอิงภายหลัง (ที่อยู่หน่วยความจำ) จะถูกเก็บไว้ในอาร์เรย์ วัตถุไม่ได้ถูกคัดลอกไปยังอาเรย์เท่านั้น แต่เป็นการอ้างอิง


1

ฉันไม่คิดว่ามันจะสร้างความแตกต่างให้กับ Strings อย่างแท้จริง สิ่งที่อยู่ติดกันในอาเรย์ของสตริงคือการอ้างอิงถึงสตริงสตริงนั้นจะถูกเก็บไว้ที่สุ่มในหน่วยความจำ

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



1

microbenchmarks จำนวนมากที่ระบุในที่นี้ได้พบว่ามีจำนวนไม่กี่นาโนวินาทีสำหรับสิ่งต่างๆเช่นอาเรย์ / ArrayList อ่าน นี่ค่อนข้างสมเหตุสมผลถ้าทุกอย่างอยู่ในแคช L1 ของคุณ

แคชระดับสูงขึ้นหรือการเข้าถึงหน่วยความจำหลักอาจมีลำดับเวลาเท่ากับ 10nS-100nS เทียบกับ 1nS สำหรับแคช L1 การเข้าถึง ArrayList มีการอ้อมหน่วยความจำเพิ่มเติมและในแอปพลิเคชันจริงคุณสามารถชำระค่าใช้จ่ายนี้ได้เกือบทุกอย่างไม่ขึ้นอยู่กับว่ารหัสของคุณทำอะไรระหว่างการเข้าถึง และแน่นอนถ้าคุณมี ArrayLists ขนาดเล็กจำนวนมากสิ่งนี้อาจเพิ่มการใช้หน่วยความจำของคุณและทำให้มีโอกาสมากขึ้นที่คุณจะพลาดแคช

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

อย่างไรก็ตาม Java Strings นั้นสิ้นเปลืองอย่างน่าตกใจโดยเฉพาะอย่างยิ่งถ้าคุณเก็บของชิ้นเล็ก ๆ (ดูที่ตัววิเคราะห์หน่วยความจำดูเหมือนว่า> 60 ไบต์สำหรับสตริงของอักขระบางตัว) อาเรย์ของสตริงมีทิศทางไปยังวัตถุ String และอีกอันจากอ็อบเจกต์ String ไปยังอักขระ [] ซึ่งมีสตริงตัวเอง ถ้ามีอะไรจะระเบิดแคช L1 ของคุณมันคือสิ่งนี้รวมกับสายพันหรือหมื่น ดังนั้นหากคุณจริงจัง - จริงจังมาก - เกี่ยวกับการขูดขีดประสิทธิภาพให้มากที่สุดเท่าที่จะเป็นไปได้ คุณสามารถพูดได้ถือสองอาร์เรย์ตัวอักขระ [] กับสตริงทั้งหมดในนั้นหนึ่งหลังจากนั้นอีกและ int [] กับ offsets เพื่อเริ่มต้น นี่จะเป็น PITA ที่จะทำทุกอย่างด้วยและคุณแทบจะไม่ต้องการมัน และถ้าคุณทำคุณก็จะได้


0

ขึ้นอยู่กับว่าคุณจะเข้าถึงมันอย่างไร

หลังจากการจัดเก็บถ้าคุณต้องการทำการค้นหาเป็นหลักโดยมีการแทรกหรือลบเพียงเล็กน้อยหรือน้อยไปจากนั้นไปหา Array (เนื่องจากการค้นหาทำใน O (1) ในอาร์เรย์ในขณะที่การเพิ่ม / ลบอาจต้องเรียงลำดับองค์ประกอบใหม่) .

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


0

ArrayList ภายในใช้วัตถุอาร์เรย์เพื่อเพิ่ม (หรือเก็บ) องค์ประกอบ กล่าวอีกนัยหนึ่ง ArrayList ได้รับการสนับสนุนโดย Array data -structure อาร์เรย์ของ ArrayList สามารถปรับขนาดได้ (หรือไดนามิก)

Array นั้นเร็วกว่า Arrayเนื่องจาก ArrayList ใช้อาร์เรย์ภายใน หากเราสามารถเพิ่มองค์ประกอบโดยตรงใน Array และเพิ่มองค์ประกอบทางอ้อมใน Array ผ่าน ArrayList กลไกโดยตรงจะเร็วกว่ากลไกทางอ้อมโดยตรง

มีสองวิธีการเพิ่ม () overloaded ในชั้น ArrayList:
1. add(Object) : เพิ่มวัตถุในตอนท้ายของรายการ
2. add(int index , Object ) : แทรกวัตถุที่ระบุที่ตำแหน่งที่ระบุในรายการ

ขนาดของ ArrayList เติบโตแบบไดนามิกได้อย่างไร

public boolean add(E e)        
{       
     ensureCapacity(size+1);
     elementData[size++] = e;         
     return true;
}

จุดสำคัญที่ควรทราบจากโค้ดด้านบนคือเรากำลังตรวจสอบความสามารถของ ArrayList ก่อนที่จะเพิ่มองค์ประกอบ sureCapacity () กำหนดขนาดปัจจุบันขององค์ประกอบที่ถูกครอบครองและขนาดสูงสุดของอาร์เรย์คืออะไร ถ้าขนาดขององค์ประกอบที่เติม (รวมถึงองค์ประกอบใหม่ที่จะเพิ่มในคลาส ArrayList) มีขนาดใหญ่กว่าขนาดสูงสุดของอาร์เรย์จากนั้นเพิ่มขนาดของอาร์เรย์ แต่ขนาดของอาเรย์ไม่สามารถเพิ่มได้แบบไดนามิก ดังนั้นสิ่งที่เกิดขึ้นภายในคือ Array ใหม่ที่ถูกสร้างขึ้นด้วยความสามารถ

จนถึง Java 6

int newCapacity = (oldCapacity * 3)/2 + 1;

(อัพเดต) จาก Java 7

 int newCapacity = oldCapacity + (oldCapacity >> 1);

นอกจากนี้ข้อมูลจากอาเรย์เก่าจะถูกคัดลอกไปยังอาเรย์ใหม่

มีวิธีการค่าใช้จ่ายใน ArrayList ArrayListที่ว่าทำไมอาร์เรย์จะเร็วกว่า


0

อาร์เรย์ - มันจะดีกว่าเสมอเมื่อเราต้องได้ผลลัพธ์ที่รวดเร็วกว่า

รายการ - ดำเนินการผลลัพธ์เกี่ยวกับการแทรกและการลบเนื่องจากสามารถทำได้ใน O (1) และยังมีวิธีการเพิ่มดึงและลบข้อมูลได้อย่างง่ายดาย ใช้ง่ายกว่ามาก

แต่โปรดจำไว้เสมอว่าการดึงข้อมูลจะเร็วเมื่อทราบตำแหน่งดัชนีในอาเรย์ที่จัดเก็บข้อมูล - เป็นที่รู้จักกัน

สิ่งนี้สามารถทำได้โดยการจัดเรียงอาร์เรย์ ดังนั้นสิ่งนี้จะเพิ่มเวลาในการดึงข้อมูล (เช่นการเก็บข้อมูล + การเรียงลำดับข้อมูล + ค้นหาตำแหน่งที่พบข้อมูล) ดังนั้นวิธีนี้จะเพิ่มเวลาแฝงเพิ่มเติมในการดึงข้อมูลจากอาเรย์ถึงแม้ว่ามันจะสามารถดึงข้อมูลได้เร็วขึ้น

ดังนั้นสิ่งนี้สามารถแก้ไขได้ด้วยโครงสร้างข้อมูล trie หรือโครงสร้างข้อมูลแบบไตรภาค ดังที่ได้กล่าวไว้ข้างต้นโครงสร้างข้อมูลคู่กรณีจะมีประสิทธิภาพมากในการค้นหาข้อมูลการค้นหาคำเฉพาะสามารถทำได้ในขนาด O (1) เมื่อเวลามีความสำคัญเช่น; หากคุณต้องค้นหาและดึงข้อมูลอย่างรวดเร็วคุณอาจไปกับโครงสร้างข้อมูลของ Trie

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

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