ใน Java 8 เหตุใดความจุเริ่มต้นของ ArrayList จึงเป็นศูนย์


94

อย่างที่ฉันจำได้ก่อน Java 8 ความจุเริ่มต้นArrayListคือ 10

น่าแปลกที่ความคิดเห็นเกี่ยวกับตัวสร้างเริ่มต้น (โมฆะ) ยังคงระบุว่า: Constructs an empty list with an initial capacity of ten.

จากArrayList.java:

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

...

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

คำตอบ:


107

ในทางเทคนิคแล้วมัน10ไม่ใช่ศูนย์หากคุณยอมรับว่ามีการเริ่มต้นอาร์เรย์สำรองที่ขี้เกียจ ดู:

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

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

ที่ไหน

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

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

เป็นที่ยอมรับว่าสัญญาผู้สร้างไม่ถูกต้องทั้งหมด บางทีนี่อาจเป็นที่มาของความสับสนที่นี่

พื้นหลัง

นี่คืออีเมลของ Mike Duigou

ฉันได้โพสต์แพตช์ ArrayList และ HashMap เวอร์ชันที่อัปเดตแล้ว

http://cr.openjdk.java.net/~mduigou/JDK-7143928/1/weopsis/

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

สำหรับ HashMap การใช้ครีเอทีฟจะสร้างขึ้นจากช่องเกณฑ์เพื่อติดตามขนาดเริ่มต้นที่ร้องขอจนกว่าจะต้องใช้อาร์เรย์ที่เก็บข้อมูล ในด้านการอ่านจะทดสอบกรณีแผนที่ว่างด้วย isEmpty () ในขนาดการเขียนจะใช้การเปรียบเทียบ (table == EMPTY_TABLE) เพื่อตรวจจับความต้องการที่จะขยายอาร์เรย์ที่เก็บข้อมูล ใน readObject มีงานอีกเล็กน้อยที่จะพยายามเลือกความจุเริ่มต้นที่มีประสิทธิภาพ

จาก: http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-April/015585.html


4
อ้างอิงจากbugs.java.com/bugdatabase/view_bug.do?bug_id=7143928จะนำไปสู่การลดการใช้ฮีปและปรับปรุงเวลาตอบสนอง (ตัวเลขสำหรับแอปสองแอปจะแสดง)
Thomas Kläger

3
@khelwood: ArrayList ไม่ได้ "รายงาน" ความจุของมันจริงๆนอกจากผ่าน Javadoc นี้: ไม่มีgetCapacity()วิธีการหรืออะไรแบบนั้น (ที่กล่าวว่ามีบางอย่างเช่นensureCapacity(7)no-op สำหรับ ArrayList ที่เริ่มต้นโดยปริยายดังนั้นฉันเดาว่าเราควรจะทำราวกับว่าความจุเริ่มต้นคือ 10 อย่างแท้จริง)
ruakh

11
การขุดที่ดี ความจุเริ่มต้นที่เป็นค่าเริ่มต้นไม่ใช่ศูนย์ แต่เป็น 10 โดยกรณีที่เป็นค่าเริ่มต้นจะถูกจัดสรรอย่างเฉื่อยชาเป็นกรณีพิเศษ คุณสามารถสังเกตสิ่งนี้ได้หากคุณเพิ่มองค์ประกอบซ้ำ ๆ ในสิ่งที่ArrayListสร้างขึ้นด้วยตัวสร้าง no-arg เทียบกับการส่งศูนย์ไปยังตัวintสร้างและถ้าคุณดูขนาดอาร์เรย์ภายในแบบสะท้อนหรือในดีบักเกอร์ ในกรณีเริ่มต้นอาร์เรย์จะกระโดดจากความยาว 0 ถึง 10 จากนั้นเป็น 15, 22 ตามอัตราการเติบโต 1.5x การส่งผ่านศูนย์เนื่องจากกำลังการผลิตเริ่มต้นส่งผลให้เติบโตจาก 0 เป็น 1, 2, 3, 4, 6, 9, 13, 19 ....
Stuart Marks

14
ฉันชื่อ Mike Duigou ผู้เขียนการเปลี่ยนแปลงและอีเมลที่ยกมาและฉันอนุมัติข้อความนี้ 🙂ดังที่ Stuart กล่าวว่าแรงจูงใจส่วนใหญ่เกี่ยวกับการประหยัดพื้นที่มากกว่าประสิทธิภาพแม้ว่าจะมีประโยชน์ด้านประสิทธิภาพเล็กน้อยเนื่องจากมักหลีกเลี่ยงการสร้างอาร์เรย์สำรอง
Mike Duigou

4
@assylias:; ^) ไม่มันยังคงมีสถานที่เนื่องจากซิงเกิลตันemptyList()ยังใช้หน่วยความจำน้อยกว่าArrayListอินสแตนซ์ที่ว่างเปล่าหลายๆ ตอนนี้มีความสำคัญน้อยกว่าและไม่จำเป็นต้องใช้ในทุกสถานที่โดยเฉพาะอย่างยิ่งไม่ใช่ในสถานที่ที่มีความเป็นไปได้สูงกว่าในการเพิ่มองค์ประกอบในภายหลัง นอกจากนี้โปรดทราบว่าบางครั้งคุณต้องการรายการว่างเปล่าที่ไม่เปลี่ยนรูปแล้วemptyList()เป็นวิธีที่จะไป
Holger

24

ใน java 8 ความจุเริ่มต้นของ ArrayList คือ 0 จนกว่าเราจะเพิ่มวัตถุอย่างน้อยหนึ่งชิ้นในวัตถุ ArrayList (คุณสามารถเรียกมันว่าการเริ่มต้นแบบขี้เกียจ)

คำถามคือเหตุใดจึงมีการเปลี่ยนแปลงนี้ใน JAVA 8?

คำตอบคือเพื่อประหยัดการใช้หน่วยความจำ วัตถุรายการอาร์เรย์หลายล้านรายการถูกสร้างขึ้นในแอปพลิเคชัน Java แบบเรียลไทม์ ขนาดเริ่มต้นของวัตถุ 10 ชิ้นหมายความว่าเราจัดสรร 10 พอยน์เตอร์ (40 หรือ 80 ไบต์) สำหรับอาร์เรย์พื้นฐานในการสร้างและกรอกข้อมูลด้วย nulls อาร์เรย์ว่าง (เต็มไปด้วย null) ใช้หน่วยความจำจำนวนมาก

การเริ่มต้นอย่างขี้เกียจจะเลื่อนการใช้หน่วยความจำนี้ออกไปจนกว่าคุณจะใช้รายการอาร์เรย์จริงๆ

โปรดดูรหัสด้านล่างเพื่อขอความช่วยเหลือ

ArrayList al = new ArrayList();          //Size:  0, Capacity:  0
ArrayList al = new ArrayList(5);         //Size:  0, Capacity:  5
ArrayList al = new ArrayList(new ArrayList(5)); //Size:  0, Capacity:  0
al.add( "shailesh" );                    //Size:  1, Capacity: 10

public static void main( String[] args )
        throws Exception
    {
        ArrayList al = new ArrayList();
        getCapacity( al );
        al.add( "shailesh" );
        getCapacity( al );
    }

    static void getCapacity( ArrayList<?> l )
        throws Exception
    {
        Field dataField = ArrayList.class.getDeclaredField( "elementData" );
        dataField.setAccessible( true );
        System.out.format( "Size: %2d, Capacity: %2d%n", l.size(), ( (Object[]) dataField.get( l ) ).length );
}

Response: - 
Size:  0, Capacity:  0
Size:  1, Capacity: 10

บทความความจุเริ่มต้นของ ArrayList ใน Java 8อธิบายรายละเอียดไว้


7

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

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


addAll()จุดดีมากเกี่ยวกับ นั่นเป็นอีกหนึ่งโอกาสในการปรับปรุงประสิทธิภาพของ malloc แรก
kevinarpe

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

3

คำถามคือ 'ทำไม?'

การตรวจสอบโปรไฟล์หน่วยความจำ (ตัวอย่างเช่น ( https://www.yourkit.com/docs/java/help/inspections_mem.jsp#sparse_arrays ) แสดงให้เห็นว่าอาร์เรย์ว่างเปล่า (เต็มไปด้วยค่า null) ใช้หน่วยความจำจำนวนมาก

ขนาดเริ่มต้นของวัตถุ 10 ชิ้นหมายความว่าเราจัดสรร 10 พอยน์เตอร์ (40 หรือ 80 ไบต์) สำหรับอาร์เรย์พื้นฐานในการสร้างและกรอกข้อมูลด้วย nulls แอปพลิเคชัน java จริงสร้างรายการอาร์เรย์หลายล้านรายการ

การปรับเปลี่ยนที่แนะนำจะลบ ^ W เลื่อนการใช้หน่วยความจำนี้ไปจนถึงช่วงเวลาที่คุณจะใช้รายการอาร์เรย์จริงๆ


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


0

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

คุณสามารถปรับการเปลี่ยนแปลงนี้ได้โดยดูที่ภาพหน้าจอด้านล่าง ในนั้นคุณจะเห็นว่าขนาด ArrayList ถูกระบุเป็น 10 ใน Object [10] แต่จำนวนขององค์ประกอบที่แสดงมีเพียง 7 องค์ประกอบ Rest null value จะไม่แสดงที่นี่ ใน JAVA 7 ด้านล่างภาพหน้าจอจะเหมือนกันโดยมีการเปลี่ยนแปลงเพียงครั้งเดียวซึ่งก็คือองค์ประกอบค่าว่างจะถูกแสดงด้วยซึ่งผู้เขียนโค้ดต้องเขียนโค้ดสำหรับจัดการค่า null หากเขากำลังทำซ้ำรายการอาร์เรย์ทั้งหมดในขณะที่อยู่ใน JAVA 8 ภาระนี้จะถูกลบออกจาก หัวหน้าผู้เข้ารหัส / ผู้พัฒนา

ลิงค์ภาพหน้าจอ

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