.toArray (new MyClass [0]) hoặc .toArray (new MyClass [myList.size ()])?


176

Giả sử tôi có một ArrayList

ArrayList<MyClass> myList;

Và tôi muốn gọi tớiArray, có lý do hiệu suất để sử dụng

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

kết thúc

MyClass[] arr = myList.toArray(new MyClass[0]);

?

Tôi thích kiểu thứ hai, vì nó ít dài dòng hơn và tôi cho rằng trình biên dịch sẽ đảm bảo mảng trống không thực sự được tạo, nhưng tôi đã tự hỏi liệu điều đó có đúng không.

Tất nhiên, trong 99% trường hợp, nó không tạo ra sự khác biệt theo cách này hay cách khác, nhưng tôi muốn giữ một phong cách nhất quán giữa mã thông thường của tôi và các vòng lặp bên trong được tối ưu hóa của tôi ...


6
Có vẻ như câu hỏi hiện đã được giải quyết trong một bài đăng trên blog mới của Aleksey Shipilёv, Mảng về trí tuệ của người xưa !
chiếu

6
Từ bài đăng trên blog: "Dòng dưới cùng: toArray (T [0]) mới có vẻ nhanh hơn, an toàn hơn và sạch hơn theo hợp đồng, và do đó nên là lựa chọn mặc định ngay bây giờ."
DavidS

Câu trả lời:


109

Ngược lại, phiên bản nhanh nhất, trên Hotspot 8, là:

MyClass[] arr = myList.toArray(new MyClass[0]);

Tôi đã chạy một điểm chuẩn vi mô bằng cách sử dụng jmh các kết quả và mã bên dưới, cho thấy phiên bản có mảng trống luôn vượt trội so với phiên bản có mảng được chỉ định. Lưu ý rằng nếu bạn có thể sử dụng lại một mảng hiện có kích thước chính xác, kết quả có thể khác.

Kết quả điểm chuẩn (điểm tính bằng micrô giây, nhỏ hơn = tốt hơn):

Benchmark                      (n)  Mode  Samples    Score   Error  Units
c.a.p.SO29378922.preSize         1  avgt       30    0.025  0.001  us/op
c.a.p.SO29378922.preSize       100  avgt       30    0.155  0.004  us/op
c.a.p.SO29378922.preSize      1000  avgt       30    1.512  0.031  us/op
c.a.p.SO29378922.preSize      5000  avgt       30    6.884  0.130  us/op
c.a.p.SO29378922.preSize     10000  avgt       30   13.147  0.199  us/op
c.a.p.SO29378922.preSize    100000  avgt       30  159.977  5.292  us/op
c.a.p.SO29378922.resize          1  avgt       30    0.019  0.000  us/op
c.a.p.SO29378922.resize        100  avgt       30    0.133  0.003  us/op
c.a.p.SO29378922.resize       1000  avgt       30    1.075  0.022  us/op
c.a.p.SO29378922.resize       5000  avgt       30    5.318  0.121  us/op
c.a.p.SO29378922.resize      10000  avgt       30   10.652  0.227  us/op
c.a.p.SO29378922.resize     100000  avgt       30  139.692  8.957  us/op

Để tham khảo, mã:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
  @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
  private final List<Integer> list = new ArrayList<>();
  @Setup public void populateList() {
    for (int i = 0; i < n; i++) list.add(0);
  }
  @Benchmark public Integer[] preSize() {
    return list.toArray(new Integer[n]);
  }
  @Benchmark public Integer[] resize() {
    return list.toArray(new Integer[0]);
  }
}

Bạn có thể tìm thấy kết quả tương tự, phân tích đầy đủ và thảo luận trong bài đăng trên blog Mảng về trí tuệ của người xưa . Tóm lại: trình biên dịch JVM và JIT chứa một số tối ưu hóa cho phép nó tạo và khởi tạo một mảng có kích thước chính xác mới và không thể sử dụng các tối ưu hóa đó nếu bạn tự tạo mảng.


2
Nhận xét rất thú vị. Tôi ngạc nhiên không ai bình luận về điều này. Tôi đoán đó là vì nó mâu thuẫn với các câu trả lời khác ở đây, về tốc độ. Cũng thú vị để lưu ý, danh tiếng của người này gần như cao hơn tất cả các câu trả lời khác (ers) cộng lại.
Pimp Trizkit 04/12/2015

Tôi lạc đề. Tôi cũng muốn xem điểm chuẩn cho MyClass[] arr = myList.stream().toArray(MyClass[]::new);.. mà tôi đoán sẽ chậm hơn. Ngoài ra, tôi muốn xem điểm chuẩn cho sự khác biệt với khai báo mảng. Như trong sự khác biệt giữa: MyClass[] arr = new MyClass[myList.size()]; arr = myList.toArray(arr);MyClass[] arr = myList.toArray(new MyClass[myList.size()]);... hoặc không nên có sự khác biệt nào? Tôi đoán hai điều này là một vấn đề nằm ngoài các toArraychức năng xảy ra. Nhưng này! Tôi không nghĩ rằng tôi sẽ tìm hiểu về những khác biệt phức tạp khác.
Pimp Trizkit 04/12/2015

1
@PimpTrizkit Chỉ cần kiểm tra: sử dụng một biến phụ sẽ không có sự khác biệt như mong đợi, Sử dụng một luồng mất từ ​​60% đến 100% thời gian hơn khi gọi toArraytrực tiếp (kích thước càng nhỏ, chi phí tương đối càng lớn)
assylias

Wow, đó là một phản ứng nhanh! Cảm ơn! Vâng, tôi đã nghi ngờ điều đó. Chuyển đổi thành một luồng không hiệu quả. Nhưng bạn không bao giờ biết!
Pimp Trizkit

2
Kết luận tương tự này đã được tìm thấy ở đây: shipilev.net/blog/2016/arrays-wisdom-ancents
user167019

122

Kể từ ArrayList trong Java 5 , mảng sẽ được điền nếu nó có kích thước phù hợp (hoặc lớn hơn). hậu quả là

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

sẽ tạo một đối tượng mảng, điền vào nó và trả về "mảng". Mặt khác

MyClass[] arr = myList.toArray(new MyClass[0]);

sẽ tạo ra hai mảng. Cái thứ hai là một mảng MyClass có chiều dài 0. Vì vậy, có một tạo đối tượng cho một đối tượng sẽ bị ném đi ngay lập tức. Theo như mã nguồn cho thấy trình biên dịch / JIT không thể tối ưu hóa cái này để nó không được tạo. Ngoài ra, sử dụng đối tượng có độ dài bằng không dẫn đến việc truyền (s) trong phương thức toArray () -.

Xem nguồn của ArrayList.toArray ():

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Sử dụng phương pháp đầu tiên để chỉ có một đối tượng được tạo và tránh các vật đúc (ngầm nhưng vẫn đắt tiền).


1
Hai nhận xét, có thể được ai đó quan tâm: 1) LinkedList.toArray (T [] a) thậm chí còn chậm hơn (sử dụng sự phản chiếu: Array.newInstance) và phức tạp hơn; 2) Mặt khác, trong bản phát hành JDK7, tôi đã rất ngạc nhiên khi phát hiện ra rằng Array.newInstance thường hoạt động chậm gần như nhanh như tạo mảng thông thường!
java.is.for.desktop

1
Kích thước @ktaria là một thành viên riêng của ArrayList, chỉ định **** bất ngờ **** kích thước. Xem Mã nguồn ArrayList
MyPasswordIsLasercats

3
Đoán hiệu suất mà không có điểm chuẩn chỉ hoạt động trong các trường hợp tầm thường. Trên thực tế, new Myclass[0]nhanh hơn: shipilev.net/blog/2016/arrays-wisdom-ancents
Karol S

Đây không còn là câu trả lời hợp lệ như của JDK6 +
Антон Антонов

28

Từ JetBrains Intellij Idea kiểm tra:

Có hai kiểu để chuyển đổi một bộ sưu tập thành một mảng: sử dụng một mảng có kích thước sẵn (như c.toArray (Chuỗi mới [c.size ()]) ) hoặc sử dụng một mảng trống (như c.toArray (Chuỗi mới [ 0]) .

Trong các phiên bản Java cũ hơn sử dụng mảng có kích thước trước được khuyến nghị, vì lệnh gọi phản chiếu cần thiết để tạo một mảng có kích thước phù hợp khá chậm. Tuy nhiên, do các bản cập nhật muộn của OpenJDK 6, cuộc gọi này đã được thực hiện, làm cho hiệu suất của phiên bản mảng trống giống nhau và đôi khi còn tốt hơn so với phiên bản có kích thước trước. Ngoài ra, việc truyền mảng có kích thước trước rất nguy hiểm cho bộ sưu tập đồng thời hoặc đồng bộ vì cuộc đua dữ liệu có thể xảy ra giữa cuộc gọi kích thước và cuộc gọi toArray có thể dẫn đến vô hiệu hóa thêm ở cuối mảng, nếu bộ sưu tập bị thu hẹp đồng thời trong quá trình hoạt động.

Việc kiểm tra này cho phép tuân theo kiểu thống nhất: sử dụng một mảng trống (được khuyến nghị trong Java hiện đại) hoặc sử dụng một mảng có kích thước sẵn (có thể nhanh hơn trong các phiên bản Java cũ hơn hoặc các JVM không dựa trên HotSpot).


Nếu tất cả những điều này được sao chép / trích dẫn văn bản, chúng ta có thể định dạng nó phù hợp và cũng cung cấp một liên kết đến nguồn không? Tôi thực sự đã đến đây vì sự kiểm tra của IntelliJ và tôi rất quan tâm đến liên kết để tra cứu tất cả các cuộc kiểm tra của họ và lý do đằng sau chúng.
Tim Büthe

3
Ở đây bạn có thể kiểm tra việc kiểm tra văn bản: github.com/JetBrains/intellij-community/tree/master/plugins/...
Антон Антонов


17

Các JVM hiện đại tối ưu hóa việc xây dựng mảng phản chiếu trong trường hợp này, do đó sự khác biệt về hiệu năng là rất nhỏ. Đặt tên cho bộ sưu tập hai lần trong mã soạn sẵn như vậy không phải là một ý tưởng hay, vì vậy tôi nên tránh phương pháp đầu tiên. Một ưu điểm khác của thứ hai là nó hoạt động với các bộ sưu tập đồng bộ và đồng thời. Nếu bạn muốn thực hiện tối ưu hóa, hãy sử dụng lại mảng trống (mảng trống là bất biến và có thể được chia sẻ) hoặc sử dụng trình lược tả (!).


2
Nâng cao 'tái sử dụng mảng trống', bởi vì đó là sự thỏa hiệp giữa khả năng đọc và hiệu suất tiềm năng đáng để xem xét. Truyền một đối số được khai báo private static final MyClass[] EMPTY_MY_CLASS_ARRAY = new MyClass[0]không ngăn mảng trả về được xây dựng bằng phản xạ, nhưng nó ngăn không cho một mảng bổ sung được tạo mỗi lần.
Michael Scheper

Machael đã đúng, nếu bạn sử dụng mảngđộ dài bằng 0 thì không có cách nào khác: (T []) java.lang.reflect.Array.newInstance (a.getClass (). GetComponentType (), size); sẽ là thừa nếu kích thước sẽ là> = factSize (JDK7)
Alex

Nếu bạn có thể đưa ra một trích dẫn cho "các JVM hiện đại tối ưu hóa việc xây dựng mảng phản chiếu trong trường hợp này", tôi sẽ vui lòng đưa ra câu trả lời này.
Tom Panning

Tôi đang học ở đây. Nếu thay vào đó tôi sử dụng: MyClass[] arr = myList.stream().toArray(MyClass[]::new);Nó sẽ giúp hoặc làm tổn thương với các bộ sưu tập đồng bộ và đồng thời. Và tại sao? Xin vui lòng.
Pimp Trizkit 04/12/2015

3

toArray kiểm tra rằng mảng được truyền có kích thước phù hợp (nghĩa là đủ lớn để phù hợp với các yếu tố trong danh sách của bạn) và nếu vậy, sử dụng nó. Do đó, nếu kích thước của mảng cung cấp nhỏ hơn yêu cầu, một mảng mới sẽ được tạo theo phản xạ.

Trong trường hợp của bạn, một mảng có kích thước bằng 0, là bất biến, do đó có thể được nâng lên một biến cuối cùng một cách an toàn, điều này có thể làm cho mã của bạn sạch hơn một chút, tránh tạo ra mảng trên mỗi lệnh gọi. Một mảng mới sẽ được tạo bên trong phương thức, vì vậy đó là một tối ưu hóa dễ đọc.

Có thể cho rằng phiên bản nhanh hơn là vượt qua mảng có kích thước chính xác, nhưng trừ khi bạn có thể chứng minh mã này là nút cổ chai hiệu năng, hãy ưu tiên khả năng đọc cho hiệu năng thời gian chạy cho đến khi được chứng minh khác đi.


2

Trường hợp đầu tiên là hiệu quả hơn.

Đó là bởi vì trong trường hợp thứ hai:

MyClass[] arr = myList.toArray(new MyClass[0]);

thời gian chạy thực sự tạo ra một mảng trống (với kích thước bằng 0) và sau đó bên trong phương thức toArray tạo ra một mảng khác để phù hợp với dữ liệu thực tế. Việc tạo này được thực hiện bằng cách sử dụng sự phản chiếu bằng mã sau đây (lấy từ jdk1.5.0_10):

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.
    newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Bằng cách sử dụng biểu mẫu đầu tiên, bạn tránh được việc tạo mảng thứ hai và cũng tránh được mã phản chiếu.


toArray () không sử dụng sự phản chiếu. Ít nhất là miễn là bạn không tính "truyền" đến phản xạ, dù sao đi nữa ;-).
Georgi

toArray (T []) nào. Nó cần phải tạo ra một mảng của loại thích hợp. Các JVM hiện đại tối ưu hóa loại phản xạ đó có cùng tốc độ với phiên bản không phản chiếu.
Tom Hawtin - tackline

Tôi nghĩ rằng nó không sử dụng sự phản ánh. JDK 1.5.0_10 chắc chắn và phản xạ là cách duy nhất tôi biết để tạo một mảng kiểu mà bạn không biết tại thời điểm biên dịch.
Panagiotis Korros

Sau đó, một trong những ví dụ về mã nguồn của cô ấy (cái ở trên hoặc của tôi) đã lỗi thời. Đáng buồn thay, tôi đã không tìm thấy một số phiên bản phụ chính xác cho tôi.
Georgi

1
Georgi, mã của bạn là từ JDK 1.6 và nếu bạn thấy việc triển khai phương thức Arrays.copyTo, bạn sẽ thấy việc triển khai sử dụng sự phản chiếu.
Panagiotis Korros

-1

Cái thứ hai không thể đọc được, nhưng có rất ít cải tiến đến mức không đáng. Phương pháp đầu tiên nhanh hơn, không có nhược điểm khi chạy, vì vậy đó là những gì tôi sử dụng. Nhưng tôi viết nó theo cách thứ hai, bởi vì nó nhanh hơn để gõ. Sau đó, IDE của tôi đánh dấu nó như một cảnh báo và đề nghị sửa nó. Với một lần nhấn phím, nó chuyển đổi mã từ loại thứ hai sang loại thứ nhất.


-2

Sử dụng 'toArray' với mảng có kích thước chính xác sẽ hoạt động tốt hơn vì thay thế sẽ tạo ra mảng có kích thước bằng 0 sau đó là mảng có kích thước chính xác. Tuy nhiên, như bạn nói sự khác biệt có thể là không đáng kể.

Ngoài ra, lưu ý rằng trình biên dịch javac không thực hiện bất kỳ tối ưu hóa nào. Ngày nay, tất cả các tối ưu hóa được thực hiện bởi trình biên dịch JIT / HotSpot khi chạy. Tôi không biết về bất kỳ tối ưu hóa nào xung quanh 'toArray' trong bất kỳ JVM nào.

Sau đó, câu trả lời cho câu hỏi của bạn chủ yếu là vấn đề về phong cách nhưng vì tính nhất quán nên tạo thành một phần của bất kỳ tiêu chuẩn mã hóa nào bạn tuân thủ (cho dù là tài liệu hay cách khác).


OTOH, nếu tiêu chuẩn là sử dụng một mảng có độ dài bằng không, thì các trường hợp sai lệch ngụ ý rằng hiệu suất là một mối quan tâm.
Michael Scheper

-5

mã mẫu cho số nguyên:

Integer[] arr = myList.toArray(new integer[0]);
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.