Mảng so với Danh sách <T>: Khi nào nên sử dụng?


594
MyClass[] array;
List<MyClass> list;

Các kịch bản khi một là tốt hơn so với cái khác là gì? Và tại sao?


9
Mảng khá lỗi thời, như đã thấy trong một cuộc thảo luận phổ biến ở đây. Cũng chỉ ra ở đây , và bởi máy chủ của chúng tôi trong blog .
gimel

9
Nếu tôi không nhầm thì Danh sách <> có một mảng là cấu trúc bên trong. Bất cứ khi nào mảng bên trong được lấp đầy, chỉ cần sao chép nội dung vào một mảng có kích thước gấp đôi (hoặc một số hằng số khác nhân với kích thước hiện tại). vi.wikipedia.org/wiki/Docate_array
Ykok

Ykok: Những gì bạn nói có vẻ đúng, tôi đã tìm thấy mã nguồn của Danh sách <> ở đây .
Carol

19
@gimel Lập luận rằng các mảng đã lỗi thời có lẽ hơi táo bạo
awdz9nld

Câu trả lời:


580

Trên thực tế, rất hiếm khi bạn muốn sử dụng một mảng. Chắc chắn sử dụng List<T>bất cứ lúc nào bạn muốn thêm / xóa dữ liệu, vì thay đổi kích thước mảng là tốn kém. Nếu bạn biết dữ liệu có độ dài cố định và bạn muốn tối ưu hóa vi mô cho một số lý do rất cụ thể (sau khi đo điểm chuẩn), thì một mảng có thể hữu ích.

List<T>cung cấp rất nhiều chức năng hơn một mảng (mặc dù LINQ phát triển nó lên một chút) và hầu như luôn là lựa chọn đúng đắn. Ngoại trừ paramstranh luận, tất nhiên. ;-p

Như một bộ đếm - List<T>là một chiều; trong đó như bạn có các mảng hình chữ nhật (vv) nhưint[,] hoặc string[,,]- nhưng có nhiều cách khác để mô hình hóa dữ liệu đó (nếu bạn cần) trong một mô hình đối tượng.

Xem thêm:

Điều đó nói rằng, tôi sử dụng rất nhiều mảng trong dự án protobuf-net của mình ; hoàn toàn cho hiệu suất:

  • nó làm rất nhiều thay đổi bit, vì vậy một byte[] rất cần thiết cho việc mã hóa;
  • Tôi sử dụng byte[]bộ đệm cuộn cục bộ mà tôi điền trước khi gửi xuống luồng bên dưới (và vv); nhanh hơnBufferedStream vv;
  • bên trong nó sử dụng một mô hình các đối tượng dựa trên mảng ( Foo[]chứ không phải làList<Foo> ), vì kích thước được cố định một khi được xây dựng và cần phải rất nhanh.

Nhưng đây chắc chắn là một ngoại lệ; để xử lý ngành kinh doanh nói chung, List<T>mỗi lần thắng.


8
Đối số về thay đổi kích thước là hoàn toàn hợp lệ. Tuy nhiên mọi người thích Danh sách ngay cả khi không cần thay đổi kích thước. Đối với trường hợp sau này, có một lập luận chặt chẽ, hợp lý hay không có gì khác hơn là "mảng không hợp thời"?
Frederick The Fool

6
"Chắc chắn sử dụng Danh sách <T> bất cứ khi nào bạn muốn thêm / xóa dữ liệu, vì thay đổi kích thước mảng là tốn kém." Danh sách <T> sử dụng một mảng trong nội bộ. Bạn có nghĩ về LinkedList <T> không?
dan-gph

14
Nhiều tính năng hơn == phức tạp hơn == không tốt, trừ khi bạn cần những tính năng đó. Câu trả lời này về cơ bản liệt kê các lý do tại sao mảng tốt hơn, nhưng lại rút ra kết luận ngược lại.
Eamon Nerbonne

12
@EamonNerbonne nếu bạn không sử dụng những tính năng, tôi có thể khá nhiều sự đảm bảo rằng họ sẽ không làm tổn thương bạn ... nhưng: số lượng các bộ sưu tập mà không bao giờ cần đột biến được nhiều nhỏ hơn, trong kinh nghiệm của tôi, so với những người có bị đột biến
Marc Gravell

7
@MarcGravell: điều đó phụ thuộc vào phong cách mã hóa của bạn. Theo kinh nghiệm của tôi hầu như không có bộ sưu tập nào bị đột biến. Đó là; các bộ sưu tập được lấy từ cơ sở dữ liệu hoặc được xây dựng từ một số nguồn, nhưng việc xử lý thêm luôn được thực hiện bằng cách tạo lại bộ sưu tập mới (ví dụ: map / filter, v.v.). Ngay cả khi đột biến khái niệm là cần thiết, nó có xu hướng đơn giản nhất để tạo ra một bộ sưu tập mới. Tôi chỉ từng thay đổi một bộ sưu tập dưới dạng tối ưu hóa hiệu suất và các tối ưu hóa như vậy có xu hướng mang tính cục bộ cao và không phơi bày sự đột biến cho người tiêu dùng API.
Eamon Nerbonne

121

Thực sự chỉ cần trả lời để thêm một liên kết mà tôi ngạc nhiên chưa được đề cập: mục blog của Eric Lippert trên "Mảng được coi là có hại."

Bạn có thể đánh giá từ tiêu đề mà nó gợi ý bằng cách sử dụng các bộ sưu tập ở bất cứ nơi nào thực tế - nhưng như Marc chỉ ra một cách đúng đắn, có rất nhiều nơi mà một mảng thực sự là giải pháp thực tế duy nhất.


2
Cuối cùng cũng phải đọc cái này hơn 3 năm sau haha. Bài viết tốt sau đó, bài viết tốt bây giờ. :)
Spencer Ruport

21

Mặc dù các câu trả lời khác được đề xuất List<T>, bạn sẽ muốn sử dụng mảng khi xử lý:

  • dữ liệu ảnh bitmap
  • cấu trúc dữ liệu cấp thấp khác (tức là giao thức mạng)

1
Tại sao cho các giao thức mạng? Bạn có muốn sử dụng các cấu trúc tùy chỉnh ở đây và cung cấp cho chúng một bộ nối tiếp đặc biệt hoặc bố cục bộ nhớ rõ ràng không? Hơn nữa, điều gì nói chống lại việc sử dụng List<T>ở đây hơn là một mảng byte?
Konrad Rudolph

8
@Konrad - tốt, dành cho người mới bắt đầu, Stream.Read và Stream.Write hoạt động với byte [], cũng như Mã hóa, v.v ...
Marc Gravell

12

Trừ khi bạn thực sự quan tâm đến hiệu suất, và ý tôi là, "Tại sao bạn lại sử dụng .Net thay vì C ++?" bạn nên gắn bó với Danh sách <>. Nó dễ dàng hơn để duy trì và thực hiện tất cả các công việc bẩn thỉu thay đổi kích thước một mảng phía sau hậu trường cho bạn. (Nếu cần, Danh sách <> khá thông minh về việc chọn kích thước mảng nên thường không cần.)


15
"Tại sao bạn sử dụng .Net thay vì C ++?" XNA
Bengt

6

Mảng nên được sử dụng để ưu tiên cho Danh sách khi tính bất biến của chính bộ sưu tập là một phần của hợp đồng giữa mã khách hàng và nhà cung cấp (không nhất thiết là tính bất biến của các mặt hàng trong bộ sưu tập) VÀ khi IEnumerable không phù hợp.

Ví dụ,

var str = "This is a string";
var strChars = str.ToCharArray();  // returns array

Rõ ràng là việc sửa đổi "strChars" sẽ không làm thay đổi đối tượng "str" ​​ban đầu, bất kể kiến ​​thức ở cấp độ triển khai của loại cơ sở của "str".

Nhưng giả sử rằng

var str = "This is a string";
var strChars = str.ToCharList();  // returns List<char>
strChars.Insert(0, 'X');

Trong trường hợp này, không rõ ràng từ đoạn mã đó nếu phương thức chèn sẽ hoặc không làm thay đổi đối tượng "str" ​​ban đầu. Nó đòi hỏi kiến ​​thức về mức độ triển khai của String để đưa ra quyết định đó, phá vỡ thiết kế theo phương pháp Hợp đồng. Trong trường hợp của String, nó không phải là một vấn đề lớn, nhưng nó có thể là một vấn đề lớn trong hầu hết các trường hợp khác. Đặt Danh sách thành chỉ đọc sẽ giúp nhưng dẫn đến lỗi thời gian chạy, không phải thời gian biên dịch.


Tôi còn khá mới với C # nhưng với tôi không rõ tại sao việc trả lại danh sách sẽ gợi ý khả năng biến đổi dữ liệu gốc theo cách trả về một mảng sẽ không. Tôi có thể mặc dù một phương thức có tên bắt đầu Tosẽ tạo ra một đối tượng không có khả năng sửa đổi thể hiện ban đầu, trái ngược với strChars as char[]điều đó nếu hợp lệ sẽ đề nghị bạn có thể sửa đổi đối tượng ban đầu.
Tim MB

@TimMB Có sự bất biến của bộ sưu tập (không thể thêm hoặc các mục từ xa) và tính bất biến của các mục trong bộ sưu tập. Tôi đã đề cập đến cái sau, trong khi bạn có thể kết hợp cả hai. Trả về một mảng đảm bảo máy khách không thể thêm / xóa các mục. Nếu có, nó phân bổ lại mảng và được đảm bảo nó sẽ không ảnh hưởng đến bản gốc. Trả về một danh sách, không có đảm bảo nào được thực hiện và bản gốc có thể bị ảnh hưởng (phụ thuộc vào việc thực hiện). Thay đổi các mục trong bộ sưu tập (cho dù mảng hoặc danh sách) có thể ảnh hưởng đến bản gốc, nếu loại của mục không phải là một cấu trúc.
Herman Schoenfeld

Cảm ơn bạn đã làm rõ. Tôi vẫn còn bối rối (có lẽ vì tôi đến từ thế giới C ++). Nếu strbên trong sử dụng một mảng và ToCharArraytrả về một tham chiếu đến mảng này thì máy khách có thể biến đổi strbằng cách thay đổi các phần tử của mảng đó, ngay cả khi kích thước vẫn cố định. Tuy nhiên, bạn viết 'Rõ ràng rằng việc sửa đổi "strChars" sẽ không làm thay đổi đối tượng "str" ​​ban đầu. Tôi đang thiếu gì ở đây? Từ những gì tôi có thể thấy, trong cả hai trường hợp, khách hàng có thể có quyền truy cập vào biểu diễn bên trong và, bất kể loại nào, điều này sẽ cho phép đột biến một số loại.
Tim MB

3

Nếu tôi biết chính xác có bao nhiêu yếu tố tôi sẽ cần, giả sử tôi cần 5 yếu tố và chỉ bao giờ có 5 yếu tố thì tôi sử dụng một mảng. Nếu không, tôi chỉ sử dụng Danh sách <T>.


1
Tại sao bạn không sử dụng Danh sách <T> trong trường hợp bạn biết số lượng phần tử?
Oliver

3

Hầu hết thời gian, sử dụng một Listsẽ đủ. A Listsử dụng một mảng bên trong để xử lý dữ liệu của nó và tự động thay đổi kích thước mảng khi thêm nhiều phần tử vào Listdung lượng hiện tại, điều này giúp dễ sử dụng hơn một mảng, nơi bạn cần biết trước dung lượng.

Xem http://msdn.microsoft.com/en-us/l Library / ms379570 (v = vs.80) .aspx # docatuctures20_1_topic5 để biết thêm thông tin về Danh sách trong C # hoặc chỉ dịch ngược System.Collections.Generic.List<T>.

Nếu bạn cần dữ liệu đa chiều (ví dụ: sử dụng ma trận hoặc trong lập trình đồ họa), có lẽ bạn sẽ đi với một arraythay thế.

Như mọi khi, nếu bộ nhớ hoặc hiệu suất là một vấn đề, đo lường nó! Nếu không, bạn có thể đưa ra các giả định sai về mã.


1
Xin chào, bạn có thể giải thích tại sao "Thời gian tra cứu của danh sách sẽ là O (n)" là đúng không? Theo như tôi biết Danh sách <T> sử dụng mảng phía sau hậu trường.
chuồn chuồn

1
@dragonfly bạn hoàn toàn đúng. Nguồn . Vào thời điểm đó, tôi cho rằng việc triển khai đã sử dụng các con trỏ, nhưng tôi đã học được cách khác. Từ liên kết trên: 'Lấy giá trị của thuộc tính này là thao tác O (1); thiết lập thuộc tính cũng là một hoạt động O (1). '
Sune Rievers

2

Mảng Vs. Danh sách là một vấn đề duy trì cổ điển so với vấn đề hiệu suất. Nguyên tắc chung mà gần như tất cả các nhà phát triển tuân theo là bạn nên bắn cho cả hai, nhưng khi họ xảy ra xung đột, hãy chọn khả năng duy trì trên hiệu suất. Ngoại lệ cho quy tắc đó là khi hiệu suất đã được chứng minh là một vấn đề. Nếu bạn thực hiện nguyên tắc này trong Mảng Vs. Danh sách, sau đó những gì bạn nhận được là đây:

Sử dụng danh sách gõ mạnh cho đến khi bạn gặp vấn đề về hiệu suất. Nếu bạn gặp phải một vấn đề về hiệu năng, hãy đưa ra quyết định về việc liệu bỏ ra các mảng sẽ có lợi cho giải pháp của bạn với hiệu suất nhiều hơn nó sẽ gây bất lợi cho giải pháp của bạn về mặt bảo trì.


1

Một tình huống khác chưa được đề cập là khi một người sẽ có một số lượng lớn các mục, mỗi mục bao gồm một bó các biến cố định nhưng độc lập liên quan được gắn với nhau (ví dụ: tọa độ của một điểm hoặc các đỉnh của tam giác 3d). Một loạt các cấu trúc trường tiếp xúc sẽ cho phép các phần tử của nó được sửa đổi một cách hiệu quả "tại chỗ" - điều không thể với bất kỳ loại bộ sưu tập nào khác. Do một mảng cấu trúc giữ các phần tử của nó liên tiếp trong RAM, nên việc truy cập tuần tự vào các phần tử mảng có thể rất nhanh. Trong các tình huống mà mã sẽ cần thực hiện nhiều chuỗi tuần tự đi qua một mảng, một mảng các cấu trúc có thể vượt trội hơn một mảng hoặc tập hợp các tham chiếu đối tượng lớp khác theo hệ số 2: 1; thêm nữa,

Mặc dù các mảng không thể thay đổi kích thước, nhưng không khó để mã lưu trữ một tham chiếu mảng cùng với số lượng phần tử đang sử dụng và thay thế mảng bằng một phần tử lớn hơn theo yêu cầu. Ngoài ra, người ta có thể dễ dàng viết mã cho một loại hoạt động giống như một List<T>cửa hàng sao lưu của nó, do đó cho phép một người nói MyPoints.Add(nextPoint);hoặc MyPoints.Items[23].X += 5;. Lưu ý rằng cái sau sẽ không nhất thiết phải đưa ra một ngoại lệ nếu mã cố truy cập ngoài cuối danh sách, nhưng về mặt sử dụng thì về mặt khái niệm khá giống với List<T>.


Những gì bạn mô tả là một Danh sách <>. Có một bộ chỉ mục để bạn có thể truy cập trực tiếp vào mảng bên dưới và Danh sách <> sẽ duy trì kích thước cho bạn.
Carl

@Carl: Đưa ra ví dụ Point[] arr;, mã có thể nói, ví dụ arr[3].x+=q;. Sử dụng ví dụ List<Point> list, nó sẽ là cần thiết để thay thế nói Point temp=list[3]; temp.x+=q; list[3]=temp;. Nó sẽ hữu ích nếu List<T>có một phương pháp Update<TP>(int index, ActionByRefRef<T,TP> proc, ref TP params). và trình biên dịch có thể biến list[3].x+=q;thành {list.Update(3, (ref int value, ref int param)=>value+=param, ref q);nhưng không có tính năng như vậy tồn tại.
supercat

Tin tốt. Nó hoạt động. list[0].X += 3;sẽ thêm 3 vào thuộc tính X của phần tử đầu tiên của danh sách. Và listlà một List<Point>Pointlà một lớp với các thuộc tính X và Y
Carl

1

Danh sách trong .NET là các hàm bao trên các mảng và sử dụng một mảng bên trong. Độ phức tạp thời gian của các thao tác trên danh sách giống như với các mảng, tuy nhiên có thêm một chút chi phí với tất cả các chức năng được thêm vào / dễ sử dụng danh sách (như thay đổi kích thước tự động và các phương thức đi kèm với lớp danh sách). Khá nhiều, tôi sẽ khuyên bạn sử dụng danh sách trong mọi trường hợp, trừ khi có một lý do thuyết phục không làm như vậy, chẳng hạn như nếu bạn cần phải viết mã cực kỳ tối ưu hóa, hoặc đang làm việc với các mã khác được xây dựng xung quanh mảng.


0

Thay vì đi qua so sánh các tính năng của từng loại dữ liệu, tôi nghĩ câu trả lời thực tế nhất là "sự khác biệt có lẽ không quan trọng đối với những gì bạn cần thực hiện, đặc biệt là khi cả hai đều thực hiện IEnumerable, vì vậy hãy tuân theo quy ước phổ biến và sử dụng Listcho đến khi bạn có lý do để không, tại thời điểm đó bạn có thể sẽ có lý do để sử dụng một mảng trên a List. "

Hầu hết thời gian trong mã được quản lý, bạn sẽ muốn ưu tiên các bộ sưu tập dễ làm việc nhất có thể hơn là lo lắng về tối ưu hóa vi mô.


0

Họ có thể không phổ biến, nhưng tôi là một fan hâm mộ của Mảng trong các dự án trò chơi. - Tốc độ lặp có thể quan trọng trong một số trường hợp, foreach trên Array có chi phí thấp hơn đáng kể nếu bạn không làm nhiều việc trên mỗi phần tử - Thêm và xóa không khó lắm với các hàm trợ giúp - Nó chậm hơn, nhưng trong trường hợp bạn chỉ xây dựng nó một lần nó có thể không quan trọng - Trong hầu hết các trường hợp, lãng phí ít bộ nhớ hơn (chỉ thực sự có ý nghĩa với Mảng của các cấu trúc) - Ít rác hơn và con trỏ và đuổi theo con trỏ

Điều đó đang được nói, tôi sử dụng Danh sách thường xuyên hơn Mảng trong thực tế, nhưng mỗi cái đều có vị trí của chúng.

Sẽ thật tuyệt nếu Liệt kê nơi có một kiểu dựng sẵn để họ có thể tối ưu hóa chi phí bao bọc và liệt kê.


0

Dân số một danh sách dễ dàng hơn một mảng. Đối với mảng, bạn cần biết độ dài chính xác của dữ liệu, nhưng đối với danh sách, kích thước dữ liệu có thể là bất kỳ. Và, bạn có thể chuyển đổi một danh sách thành một mảng.

List<URLDTO> urls = new List<URLDTO>();

urls.Add(new URLDTO() {
    key = "wiki",
    url = "https://...",
});

urls.Add(new URLDTO()
{
    key = "url",
    url = "http://...",
});

urls.Add(new URLDTO()
{
    key = "dir",
    url = "https://...",
});

// convert a list into an array: URLDTO[]
return urls.ToArray();

0

Vì không ai nhắc đến: Trong C #, một mảng là một danh sách. MyClass[]List<MyClass>cả hai thực hiện IList<MyClass>. (ví dụ void Foo(IList<int> foo)có thể được gọi như Foo(new[] { 1, 2, 3 })hoặc Foo(new List<int> { 1, 2, 3 }))

Vì vậy, nếu bạn đang viết một phương thức chấp nhận một List<MyClass>đối số, nhưng chỉ sử dụng tập hợp con các tính năng, bạn có thể muốn khai báo IList<MyClass>thay vì để thuận tiện cho người gọi.

Chi tiết:


"Trong C #, một mảng là một danh sách" Điều đó không đúng; một mảng không phải là một List, nó chỉ thực hiện IListgiao diện.
Rufus L

-1

Nó hoàn toàn phụ thuộc vào bối cảnh trong đó cấu trúc dữ liệu là cần thiết. Ví dụ: nếu bạn đang tạo các mục được sử dụng bởi các chức năng hoặc dịch vụ khác bằng Danh sách là cách hoàn hảo để thực hiện mục đó.

Bây giờ nếu bạn có một danh sách các mục và bạn chỉ muốn hiển thị chúng, hãy nói trên một mảng trang web là nơi bạn cần sử dụng.


1
Nếu bạn có một danh sách các mục và bạn chỉ muốn hiển thị chúng, vậy thì có gì sai khi chỉ sử dụng danh sách bạn đã có? Điều gì sẽ cung cấp một mảng ở đây?
Marc Gravell

1
Và để "tạo các mục được sử dụng bởi các chức năng hoặc dịch vụ khác", thực tế, tôi thích một khối lặp hơn IEnumerable<T>- sau đó tôi có thể truyền phát các đối tượng thay vì đệm chúng.
Marc Gravell

-1

Đó là giá trị đề cập về khả năng làm đúc tại chỗ.

interface IWork { }
class Foo : IWork { }

void Test( )
{
    List<Foo> bb = new List<Foo>( );
    // Error: CS0029 Cannot implicitly convert type 'System.Collections.Generic.List<Foo>' to 'System.Collections.Generic.List<IWork>'
    List<IWork> cc = bb; 

    Foo[] bbb = new Foo[4];
    // Fine
    IWork[] ccc = bbb;
}

Vì vậy, Array cung cấp linh hoạt hơn một chút khi được sử dụng trong kiểu trả về hoặc đối số cho các hàm.

IWork[] GetAllWorks( )
{
    List<Foo> fooWorks = new List<Foo>( );
    return fooWorks.ToArray( ); // Fine
}

void ExecuteWorks( IWork[] works ) { } // Also accept Foo[]

Bạn nên lưu ý rằng phương sai của mảng chia .
mcarton
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.