Tôi nên sử dụng một danh sách hoặc một mảng?


22

Tôi đang làm việc trên một biểu mẫu cửa sổ để tính UPC cho số mục.

Tôi tạo thành công một cái sẽ xử lý một số mục / UPC tại một thời điểm, bây giờ tôi muốn mở rộng và thực hiện nó cho nhiều số mục / UPC.

Tôi đã bắt đầu và thử sử dụng một danh sách, nhưng tôi cứ bị kẹt. Tôi đã tạo một lớp trợ giúp:

public class Codes
{
    private string incrementedNumber;
    private string checkDigit;
    private string wholeNumber;
    private string wholeCodeNumber;
    private string itemNumber;

    public Codes(string itemNumber, string incrementedNumber, string checkDigit, string wholeNumber, string wholeCodeNumber)
    {
        this.incrementedNumber = incrementedNumber;
        this.checkDigit = checkDigit;
        this.wholeNumber = wholeNumber;
        this.wholeCodeNumber = wholeCodeNumber;
        this.itemNumber = itemNumber;
    }

    public string ItemNumber
    {
        get { return itemNumber; }
        set { itemNumber = value; }
    }

    public string IncrementedNumber
    { 
        get { return incrementedNumber; }
        set { incrementedNumber = value; } 
    }

    public string CheckDigit
    {
        get { return checkDigit; }
        set { checkDigit = value; }
    }

    public string WholeNumber
    {
        get { return wholeNumber; }
        set { wholeNumber = value; }
    }

    public string WholeCodeNumber
    {
        get { return wholeCodeNumber; }
        set { wholeCodeNumber = value; }
    }

}

Sau đó, tôi đã bắt đầu sử dụng mã của mình, nhưng vấn đề là quá trình này tăng dần, nghĩa là tôi lấy số thứ tự từ một khung nhìn lưới thông qua các hộp kiểm và đưa chúng vào danh sách. Sau đó, tôi nhận được UPC cuối cùng từ cơ sở dữ liệu, tách mã kiểm tra, sau đó tăng số lượng lên một và đưa nó vào danh sách. Sau đó, tôi tính toán số kiểm tra cho số mới và đưa nó vào danh sách. Và ở đây tôi đã có được Ngoại lệ bộ nhớ. Đây là mã tôi có cho đến nay:

List<Codes> ItemNumberList = new List<Codes>();


    private void buttonSearch2_Click(object sender, EventArgs e)
    {
        //Fill the datasets
        this.immasterTableAdapter.FillByWildcard(this.alereDataSet.immaster, (textBox5.Text));
        this.upccodeTableAdapter.FillByWildcard(this.hangtagDataSet.upccode, (textBox5.Text));
        this.uPCTableAdapter.Fill(this.uPCDataSet.UPC);
        string searchFor = textBox5.Text;
        int results = 0;
        DataRow[] returnedRows;
        returnedRows = uPCDataSet.Tables["UPC"].Select("ItemNumber = '" + searchFor + "2'");
        results = returnedRows.Length;
        if (results > 0)
        {
            MessageBox.Show("This item number already exists!");
            textBox5.Clear();
            //clearGrids();
        }
        else
        {
            //textBox4.Text = dataGridView1.Rows[0].Cells[1].Value.ToString();
            MessageBox.Show("Item number is unique.");
        }
    }

    public void checkMarks()
    {

        for (int i = 0; i < dataGridView7.Rows.Count; i++)
        {
            if ((bool)dataGridView7.Rows[i].Cells[3].FormattedValue)
            {
                {
                    ItemNumberList.Add(new Codes(dataGridView7.Rows[i].Cells[0].Value.ToString(), "", "", "", ""));
                }
            }
        }
    }

    public void multiValue1()
    {
        _value = uPCDataSet.UPC.Rows[uPCDataSet.UPC.Rows.Count - 1]["UPCNumber"].ToString();//get last UPC from database
        _UPCNumber = _value.Substring(0, 11);//strip out the check-digit
        _UPCNumberInc = Convert.ToInt64(_UPCNumber);//convert the value to a number

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            _UPCNumberInc = _UPCNumberInc + 1;
            _UPCNumberIncrement = Convert.ToString(_UPCNumberInc);//assign the incremented value to a new variable
            ItemNumberList.Add(new Codes("", _UPCNumberIncrement, "", "", ""));//**here I get the OutOfMemoreyException**
        }

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            long chkDigitOdd;
            long chkDigitEven;
            long chkDigitSubtotal;
            chkDigitOdd = Convert.ToInt64(_UPCNumberIncrement.Substring(0, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(2, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(4, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(6, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(8, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(10, 1));
            chkDigitOdd = (3 * chkDigitOdd);
            chkDigitEven = Convert.ToInt64(_UPCNumberIncrement.Substring(1, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(3, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(5, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(7, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(9, 1));
            chkDigitSubtotal = (300 - (chkDigitEven + chkDigitOdd));
            _chkDigit = chkDigitSubtotal.ToString();
            _chkDigit = _chkDigit.Substring(_chkDigit.Length - 1, 1);
            ItemNumberList.Add(new Codes("", "",_chkDigit, "", ""));
        }

Đây có phải là cách đúng đắn để sử dụng nó, sử dụng một danh sách, hay tôi nên nhìn theo một cách khác?


Sử dụng một danh sách là tốt. Lặp lại danh sách trong khi thêm vào đó là một cách chắc chắn để làm nổ mã của bạn và chỉ ra một vấn đề trong logic của bạn (hoặc viết mã). Ngoài ra, đây là lỗi của bạn và không có khả năng giúp khách truy cập trong tương lai. Bỏ phiếu để đóng.
Telastyn

2
Như một lưu ý phụ, tất cả các trường riêng này (trong Codelớp của bạn ) đều dư thừa và không có gì ngoài tiếng ồn thực sự, { get; private set; }sẽ là đủ.
Konrad Morawski

5
Là tiêu đề câu hỏi cho điều này thực sự chính xác? Điều này thực sự không giống như một danh sách so với câu hỏi mảng , nhiều như là Làm thế nào tôi có thể cải thiện câu hỏi thực hiện của mình . Điều đó đang được nói, nếu bạn thêm hoặc xóa các thành phần, bạn muốn có một danh sách (hoặc cấu trúc dữ liệu linh hoạt khác). Mảng chỉ thực sự tốt khi bạn biết chính xác có bao nhiêu yếu tố bạn cần khi bắt đầu.
KChaloux

@KChaloux, đó là những gì tôi muốn biết. Là một danh sách đúng cách để đi về điều này, hay tôi nên xem xét một cách khác để theo đuổi điều này? Vì vậy, có vẻ như một danh sách là một cách tốt, tôi chỉ cần điều chỉnh logic của tôi.
campagnolo_1

1
@Telastyn, tôi không yêu cầu cải thiện mã của mình để thể hiện những gì tôi đang cố gắng làm.
campagnolo_1

Câu trả lời:


73

Tôi sẽ mở rộng nhận xét của mình:

... Nếu bạn thêm hoặc xóa các thành phần, bạn muốn có một danh sách (hoặc cấu trúc dữ liệu linh hoạt khác). Mảng chỉ thực sự tốt khi bạn biết chính xác có bao nhiêu yếu tố bạn cần khi bắt đầu.

Sự cố nhanh

Mảng là tốt khi bạn có một số yếu tố cố định không có khả năng thay đổi và bạn muốn truy cập nó theo cách không tuần tự.

  • Kích thước cố định
  • Truy cập nhanh - O (1)
  • Thay đổi kích thước chậm - O (n) - cần sao chép mọi phần tử sang một mảng mới!

Danh sách liên kết được tối ưu hóa để bổ sung và xóa nhanh ở hai đầu, nhưng chậm truy cập ở giữa.

  • Kích thước thay đổi
  • Truy cập chậm ở giữa - O (n)
    • Cần phải đi qua từng phần tử bắt đầu từ đầu để đạt được chỉ số mong muốn
  • Truy cập nhanh vào đầu - O (1)
  • Có khả năng truy cập nhanh tại Tail
    • O (1) nếu tham chiếu được lưu trữ ở cuối đuôi (như với danh sách liên kết đôi)
    • O (n) nếu không có tham chiếu nào được lưu trữ (độ phức tạp tương tự như truy cập một nút ở giữa)

Danh sách mảng (chẳng hạn như List<T>trong C #!) Là hỗn hợp của cả hai, với các bổ sung khá nhanh truy cập ngẫu nhiên. List<T> thường sẽ là bộ sưu tập tiếp theo của bạn khi bạn không chắc chắn nên sử dụng cái gì.

  • Sử dụng một mảng như một cấu trúc sao lưu
  • Là thông minh về thay đổi kích thước của nó - phân bổ gấp đôi không gian hiện tại của nó khi nó hết.
    • Điều này dẫn đến thay đổi kích thước O (log n), tốt hơn so với thay đổi kích thước mỗi khi chúng ta thêm / xóa
  • Truy cập nhanh - O (1)

Cách thức hoạt động

Hầu hết các ngôn ngữ mô hình mảng là dữ liệu liền kề trong bộ nhớ, trong đó mỗi phần tử có cùng kích thước. Giả sử chúng ta có một mảng ints (được hiển thị là [địa chỉ: giá trị], sử dụng địa chỉ thập phân vì tôi lười biếng)

[0: 10][32: 20][64: 30][96: 40][128: 50][160: 60]

Mỗi phần tử này là một số nguyên 32 bit, vì vậy chúng tôi biết nó chiếm bao nhiêu dung lượng trong bộ nhớ (32 bit!). Và chúng ta biết địa chỉ bộ nhớ của con trỏ đến phần tử đầu tiên.

Thật dễ dàng để có được giá trị của bất kỳ phần tử nào khác trong mảng đó:

  • Lấy địa chỉ của phần tử đầu tiên
  • Lấy phần của từng phần tử (kích thước của nó trong bộ nhớ)
  • Nhân số bù với chỉ số mong muốn
  • Thêm kết quả của bạn vào địa chỉ của phần tử đầu tiên

Giả sử phần tử đầu tiên của chúng tôi là '0'. Chúng tôi biết phần tử thứ hai của chúng tôi ở mức '32' (0 + (32 * 1)) và phần tử thứ ba của chúng tôi ở mức 64 (0 + (32 * 2)).

Việc chúng ta có thể lưu trữ tất cả các giá trị này cạnh nhau trong bộ nhớ có nghĩa là mảng của chúng ta nhỏ gọn nhất có thể. Điều đó cũng có nghĩa là tất cả các yếu tố của chúng ta cần ở lại với nhau để mọi thứ tiếp tục hoạt động!

Ngay khi chúng tôi thêm hoặc xóa một phần tử, chúng tôi cần chọn mọi thứ khác và sao chép chúng sang một vị trí mới trong bộ nhớ, để đảm bảo không có khoảng cách giữa các phần tử và mọi thứ đều có đủ chỗ. Điều này có thể rất chậm , đặc biệt nếu bạn đang thực hiện nó mỗi khi bạn muốn thêm một yếu tố.

Danh sách liên kết

Không giống như mảng, Danh sách được liên kết không cần tất cả các thành phần của chúng nằm cạnh nhau trong bộ nhớ. Chúng bao gồm các nút, lưu trữ thông tin sau:

Node<T> {
    Value : T      // Value of this element in the list
    Next : Node<T> // Reference to the node that comes next
}

Danh sách này giữ một tham chiếu đến đầuđuôi (các nút đầu tiên và cuối cùng) trong hầu hết các trường hợp, và đôi khi theo dõi kích thước của nó.

Nếu bạn muốn thêm một phần tử vào cuối danh sách, tất cả những gì bạn cần làm là lấy phần đuôi và thay đổi phần tử đó Nextđể tham chiếu phần mới Nodechứa giá trị của bạn. Loại bỏ từ cuối cũng đơn giản không kém - chỉ cần hủy bỏ Nextgiá trị của nút trước đó.

Thật không may, nếu bạn có LinkedList<T>1000 phần tử và bạn muốn phần tử 500, không có cách nào dễ dàng để chuyển ngay sang phần tử thứ 500 giống như có một mảng. Bạn cần bắt đầu từ đầu và tiếp tục đi đến Nextnút, cho đến khi bạn thực hiện xong 500 lần.

Đây là lý do tại sao thêm và xóa từ a LinkedList<T>là nhanh (khi làm việc ở cuối), nhưng truy cập vào giữa là chậm.

Chỉnh sửa : Brian chỉ ra trong các nhận xét rằng Danh sách được liên kết có nguy cơ gây ra lỗi trang, do không được lưu trữ trong bộ nhớ liền kề. Điều này có thể khó đo điểm chuẩn và có thể khiến Danh sách được liên kết chậm hơn một chút so với bạn mong đợi chỉ vì sự phức tạp về thời gian của chúng.

Tốt nhất của cả hai thế giới

List<T>thỏa hiệp cho cả hai T[]LinkedList<T>đưa ra một giải pháp hợp lý nhanh chóng và dễ sử dụng trong hầu hết các tình huống.

Trong nội bộ, List<T>là một mảng! Nó vẫn phải nhảy qua các vòng sao chép các phần tử của nó khi thay đổi kích thước, nhưng nó kéo theo một số thủ thuật gọn gàng.

Đối với người mới bắt đầu, việc thêm một phần tử không thường làm cho mảng sao chép. List<T>đảm bảo luôn có đủ chỗ cho nhiều yếu tố hơn. Khi hết, thay vì phân bổ một mảng nội bộ mới chỉ bằng một phần tử mới, nó sẽ phân bổ một mảng mới với một số phần tử mới (thường gấp đôi số lượng hiện tại!).

Các thao tác sao chép rất tốn kém, vì vậy hãy List<T>cắt giảm chúng càng nhiều càng tốt, trong khi vẫn cho phép truy cập ngẫu nhiên nhanh chóng. Là một tác dụng phụ, nó có thể sẽ lãng phí không gian nhiều hơn một chút so với một mảng thẳng hoặc danh sách được liên kết, nhưng nó thường có giá trị đánh đổi.

TL; DR

Sử dụng List<T>. Đó thường là những gì bạn muốn, và nó có vẻ đúng với bạn trong tình huống này (nơi bạn đang gọi .Add ()). Nếu bạn không chắc chắn về những gì bạn cần, List<T>là một nơi tốt để bắt đầu.

Mảng là tốt cho hiệu suất cao, "Tôi biết tôi cần chính xác các yếu tố X". Ngoài ra, chúng rất hữu ích khi nhanh chóng, một lần "Tôi cần nhóm các X này lại với nhau để tôi có thể lặp lại chúng".

Có một số lớp sưu tập khác. Stack<T>giống như một danh sách liên kết chỉ hoạt động từ đầu. Queue<T>hoạt động như một danh sách nhập trước xuất trước. Dictionary<T, U>là một ánh xạ liên kết, không có thứ tự giữa các khóa và giá trị. Chơi với họ và nhận biết điểm mạnh và điểm yếu của từng người. Họ có thể thực hiện hoặc phá vỡ các thuật toán của bạn.


2
Trong một số trường hợp, có thể có những lợi thế khi sử dụng kết hợp một mảng và intchỉ ra số lượng phần tử có thể sử dụng. Trong số những thứ khác, có thể sao chép hàng loạt nhiều phần tử từ mảng này sang mảng khác, nhưng sao chép giữa các danh sách thường yêu cầu xử lý từng phần tử riêng lẻ. Hơn nữa, các phần tử mảng có thể được chuyển qua refcho những thứ như Interlocked.CompareExchange, trong khi các mục danh sách không thể.
supercat

2
Tôi ước tôi có thể cung cấp nhiều hơn một upvote. Tôi biết sự khác biệt trong trường hợp sử dụng và cách các mảng / danh sách được liên kết hoạt động, nhưng tôi không bao giờ biết hoặc nghĩ về cách List<>làm việc dưới mui xe.
Bobson

1
Thêm một phần tử vào Danh sách <T> được khấu hao O (1); hiệu quả của việc thêm các phần tử thường không đủ để biện minh cho việc sử dụng danh sách được liên kết (và Danh sách tròn cho phép bạn thêm vào phía trước VÀ trở lại trong khấu hao O (1)). Danh sách liên kết có rất nhiều đặc điểm hiệu suất. Ví dụ, không được lưu trữ liên tục trong bộ nhớ có nghĩa là lặp đi lặp lại trên toàn bộ danh sách được liên kết có nhiều khả năng gây ra lỗi trang ... và điều này khó có thể đo điểm chuẩn. Sự biện minh lớn hơn cho việc sử dụng Danh sách được liên kết là khi bạn muốn ghép hai danh sách (có thể được thực hiện trong O (1)) hoặc thêm các yếu tố vào giữa.
Brian

1
Tôi nên làm rõ. Khi tôi nói danh sách vòng tròn, tôi có nghĩa là một danh sách mảng tròn, không phải là một danh sách liên kết tròn. Thuật ngữ chính xác sẽ là deque (hàng đợi hai đầu). Chúng thường được triển khai khá giống với Danh sách (mảng dưới mui xe), với một ngoại lệ: Có một giá trị nguyên bên trong, "đầu tiên" cho biết chỉ số nào của mảng là thành phần đầu tiên. Để thêm một phần tử vào mặt sau, bạn chỉ cần trừ 1 từ "đầu tiên" (bao quanh theo chiều dài của mảng nếu cần thiết). Để truy cập một yếu tố, bạn chỉ cần truy cập (index+first)%length.
Brian

2
Có một vài điều bạn không thể làm với Danh sách mà bạn có thể thực hiện với một mảng đơn giản, ví dụ, chuyển một phần tử chỉ mục làm tham số ref.
Ian Goldby

6

Trong khi câu trả lời của KChaloux là tuyệt vời, tôi muốn chỉ ra một sự cân nhắc khác: List<T>mạnh hơn rất nhiều so với một Array. Các phương thức List<T>rất hữu ích trong nhiều trường hợp - một mảng không có các phương thức này và bạn có thể mất nhiều thời gian để thực hiện các cách giải quyết.

Vì vậy, từ góc độ phát triển tôi gần như luôn luôn sử dụng List<T>bởi vì khi có các yêu cầu bổ sung, chúng thường dễ thực hiện hơn nhiều khi bạn đang sử dụng a List<T>.

Điều này dẫn đến một vấn đề cuối cùng: Mã của tôi (tôi không biết về mã của bạn) chứa 90% List<T>, vì vậy Mảng không thực sự phù hợp. Khi tôi chuyển chúng xung quanh, tôi phải gọi .toList()phương thức của chúng và chuyển đổi chúng thành Danh sách - điều này gây phiền nhiễu và chậm đến mức mọi hiệu suất đạt được từ việc sử dụng Mảng đều bị mất.


Đó là sự thật, đây là một lý do tốt để sử dụng Danh sách <T> - nó cung cấp nhiều chức năng hơn cho bạn được tích hợp trực tiếp vào lớp.
KChaloux

1
Không LINQ cấp trường bằng cách thêm rất nhiều chức năng đó cho bất kỳ IEnumerable nào (bao gồm cả một mảng)? Có bất cứ điều gì còn lại trong C # hiện đại (4+) mà bạn chỉ có thể làm với Danh sách <T> chứ không phải là một mảng không?
Dave

1
@Dave Mở rộng Mảng / Danh sách có vẻ như vậy. Ngoài ra, tôi sẽ nói rằng cú pháp để xây dựng / xử lý một Danh sách đẹp hơn nhiều so với các mảng.
Christian Sauer

2

Không ai đề cập đến phần này mặc dù: "Và ở đây tôi đã nhận được Ngoại lệ bộ nhớ." Điều này hoàn toàn do

for (int i = 0; i < ItemNumberList.Count; i++)
{
    ItemNumberList.Add(new item());
}

Thật đơn giản để xem tại sao. Tôi không biết liệu bạn có ý định thêm vào một danh sách khác hay chỉ nên lưu trữ ItemNumberList.Countdưới dạng một biến trước vòng lặp để có kết quả mong muốn, nhưng điều này chỉ đơn giản là bị hỏng.

Lập trình viên.SE dành cho "... quan tâm đến các câu hỏi khái niệm về phát triển phần mềm ..." và các câu trả lời khác đã xử lý nó như vậy. Thay vào đó, hãy thử http://codereview.stackexchange.com , nơi câu hỏi này sẽ phù hợp. Nhưng ngay cả ở đó thật kinh khủng, vì chúng ta chỉ có thể giả sử mã này bắt đầu tại _Click, không có cuộc gọi đến multiValue1nơi bạn nói lỗi xảy ra.

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.