我应该使用列表还是数组?


22

我正在Windows窗体上计算项目编号的UPC。

我成功创建了一个可一次处理一个物料编号/ UPC的物料,现在我想扩展并针对多个物料编号/ UPC进行处理。

我已经开始尝试使用列表,但是我一直陷于困境。我创建了一个帮助器类:

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; }
    }

}

然后我开始编写代码,但问题是该过程是渐进式的,这意味着我可以通过复选框从gridview获取项目编号并将其放入列表中。然后,我从数据库中获取最后一个UPC,剥离校验位,然后将数字递增1并将其放入列表中。然后,我计算新号码的校验位并将其放在列表中。在这里,我已经收到内存不足异常。这是我到目前为止的代码:

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, "", ""));
        }

这是使用列表的正确方法吗?还是应该以其他方式查看?


使用列表很好。在添加列表时对其进行迭代是确定代码的可靠方法,它表明逻辑(或代码编写)有问题。另外,这是您的错误,不太可能对将来的访问者有所帮助。投票关闭。
Telastyn 2013年

2
顺便提一句,所有这些私有字段(在您的Code班级中)都是多余的,只有杂音确实{ get; private set; }足够。
Konrad Morawski 2013年

5
问题标题真的准确吗?这似乎并不是一个列表与数组的问题,而是一个如何改善实现问题的问题。话虽如此,如果要添加删除元素,则需要一个列表(或其他灵活的数据结构)。仅当您确切地知道开始时需要多少个元素时,数组才是真正好的。
KChaloux 2013年

@KChaloux,这几乎是我想知道的。列表是解决此问题的正确方法,还是我应该考虑采用另一种方法来解决此问题?因此,列表似乎是个好方法,我只需要调整逻辑即可。
campagnolo_1 2013年

1
@Telastyn,我并没有要求改善我的代码以显示我要执行的操作。
campagnolo_1 2013年

Answers:


73

我将扩大我的评论:

...如果要添加删除元素,则需要一个列表(或其他灵活的数据结构)。仅当您确切地知道开始时需要多少个元素时,数组才是真正好的。

快速故障

当您有固定数量的,不太可能更改的元素,并且希望以非顺序方式访问它时,数组是很好的选择。

  • 固定尺寸
  • 快速访问-O(1)
  • 慢速调整大小-O(n)-需要将每个元素复制到新数组中!

链接列表经过优化,可在两端进行快速添加和删除,但在中间访问速度较慢。

  • 可变大小
  • 中间缓慢进入-O(n)
    • 需要从头开始遍历每个元素,以达到所需的索引
  • 头部快速进入-O(1)
  • 可能快速访问尾巴
    • O(1)如果引用存储在末尾(与双向链接列表一样)
    • 如果没有存储参考,则为O(n)(与访问中间节点的复杂度相同)

数组列表(例如List<T>C#!)是两者的混合,具有相当快的添加随机访问。List<T> 当您不确定使用什么时,通常会成为您的首选收藏。

  • 使用数组作为支持结构
  • 调整大小很聪明-在用完时分配其当前空间的两倍。
    • 这导致O(log n)调整大小,这比每次添加/删除时调整大小更好
  • 快速访问-O(1)

阵列如何运作

大多数语言将数组建模为内存中的连续数据,其中每个元素的大小相同。假设我们有一个ints 数组(显示为[address:value],使用十进制地址,因为我很懒)

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

这些元素中的每一个都是32位整数,因此我们知道它在内存中占用了多少空间(32位!)。并且我们知道了指向第一个元素的指针的内存地址。

获得该数组中任何其他元素的值非常容易:

  • 取第一个元素的地址
  • 取每个元素的偏移量(其在内存中的大小)
  • 偏移量乘以所需的索引
  • 将结果添加到第一个元素的地址

假设我们的第一个元素为“ 0”。我们知道我们的第二个元素是'32'(0 +(32 * 1)),而我们的第三个元素是64(0 +(32 * 2))。

我们可以将所有这些值彼此相邻地存储在内存中,这意味着我们的数组尽可能紧凑。这也意味着我们所有的元素都需要保持在一起,以便事物继续工作!

添加或删除元素后,我们需要拾取其他所有内容并将其复制到内存中的某个新位置,以确保元素之间没有间隙,并且所有内容都有足够的空间。这可能非常慢特别是如果每次要添加单个元素时都要这样做。

链表

与数组不同,链接列表不需要它们的所有元素在内存中彼此相邻。它们由存储以下信息的节点组成:

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

在大多数情况下,列表本身会引用(第一个和最后一个节点),有时还会跟踪其大小。

如果要将元素添加到列表的末尾,则只需获取tail,然后将其更改Next为引用Node包含您的值的新元素即可。从末尾删除同样简单-只需取消引用Next前一个节点的值即可。

不幸的是,如果您有一个LinkedList<T>包含1000个元素的元素,而您想要500个元素,那么像数组那样,没有简单的方法可以直接跳到第500个元素。您需要在开始的头,和继续下去的Next节点,直到你已经做了500次。

这就是为什么在a中添加和删除LinkedList<T>速度很快(在末端工作时),而访问中间位置却很慢的原因。

编辑:Brian在注释中指出,由于未存储在连续内存中,因此链表有导致页面错误的风险。这可能很难进行基准测试,并且可能会使链接列表的速度比您预期的要慢一些,因为它们的时间很复杂。

两全其美

List<T>两者兼顾T[]LinkedList<T>并提出了在大多数情况下相当快速且易于使用的解决方案。

在内部,List<T>是一个数组!调整大小时,它仍然必须跳过复制其元素的过程,但是却带来了一些巧妙的技巧。

对于初学者,添加单个元素通常不会导致数组复制。List<T>确保始终有足够的空间容纳更多元素。当它用完时,它不会分配带有一个新元素的新内部数组,而是会分配一个带有几个新元素的新数组(通常是当前数量的两倍!)。

复制操作很昂贵,因此List<T>尽可能减少复制操作,同时仍然允许快速随机访问。副作用是,它可能最终会浪费掉比直接排列数组或链接列表更多的空间,但是通常值得进行权衡。

TL; DR

使用List<T>。通常这就是您想要的,并且在这种情况下(您正在调用.Add())对您来说似乎是正确的。如果不确定自己的需求,那么这List<T>是一个不错的起点。

数组对高性能很有帮助,“我知道我需要X元素”。另外,它们对于快速的一次性“我需要将已经定义的X项分组在一起,以便可以遍历它们”结构很有用。

还有许多其他收集类。Stack<T>就像一个仅从顶部开始操作的链表。Queue<T>用作先进先出列表。Dictionary<T, U>是键和值之间的无序关联映射。与他们一起玩耍,并了解他们各自的优缺点。他们可以制定或破坏您的算法。


2
在某些情况下,使用数组和int指示可用元素数量的组合可能会有好处。除其他外,可以将多个元素从一个数组批量复制到另一个数组,但是在列表之间进行复制通常需要单独处理元素。此外,数组元素可以传递ref给类似的东西Interlocked.CompareExchange,而列表项则不能。
2013年

2
我希望我可以提出不止一项投票。我知道用例的区别,以及数组/链接列表的工作方式,但是我从不知道或从未想到过如何List<>工作。
Bobson

1
将单个元素添加到List <T>会摊销O(1); 通常,添加元素的效率不足以使用链接列表来证明其正确性(而循环列表允许您在摊销的O(1)中前后添加)。链接列表具有很多性能特质。例如,不连续存储在内存中意味着遍历整个链表更可能触发页面错误……这很难进行基准测试。使用链接列表的更大理由是当您要串联两个列表(可以在O(1)中完成)或将元素添加到中间时。
布莱恩

1
我应该澄清。当我说循环列表时,我指的是循环数组列表,而不是循环链接列表。正确的术语是双端队列(双端队列)。它们的实现方式通常与List(幕后的数组)几乎相同,但有一个例外:内部整数值“ first”指示数组的哪个索引是第一个元素。要将元素添加到后面,只需从“ first”中减去1(必要时可换成数组的长度)。要访问元素,只需访问(index+first)%length
布莱恩

2
使用List不能完成某些操作,而使用普通数组则可以完成一些操作,例如,将index元素作为ref参数传递。
伊恩·戈德比2015年

6

尽管KChaloux的答案很不错,但我想指出另一个考虑因素:List<T>它比数组强大得多。的方法List<T>在很多情况下非常有用-数组没有这些方法,您可能会花费很多时间来实现变通方法。

因此,从开发角度来看,我几乎总是使用它,List<T>因为当有其他需求时,使用时,它们通常更容易实现List<T>

这导致了最后一个问题:我的代码(我不知道您的代码)包含90%List<T>,因此数组并不是真正适合的数组。当我传递它们时,我必须调用它们的.toList()方法并将其转换为列表-这令人烦恼,而且速度太慢,以至于使用阵列失去了任何性能提升。


没错,这是使用List <T>的另一个很好的理由-它为您直接内置到类中提供了更多功能。
KChaloux

1
LINQ不会通过为任何IEnumerable(包括数组)添加很多功能来平整该字段吗?现代C#(4+)中还有什么只能用List <T>而不是数组来做的吗?
戴夫

1
@Dave扩展数组/列表似乎是这样。另外,我想说构造/处理List的语法比数组要好得多。
Christian Sauer 2014年

2

不过,没有人提到这部分:“并且这里我已经收到内存不足异常”。这完全是由于

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

明白为什么。我不知道您是要添加到其他列表中,还是应该ItemNumberList.Count在循环之前将其存储为变量以获取所需的结果,但是这简直是被破坏了。

Programmers.SE用于“ ...对软件开发的概念性问题感兴趣...”,而其他答案也是如此。请改用http://codereview.stackexchange.com,以解决此问题。但是即使是这样也很可怕,因为我们只能假设此代码从开始于_Click,而没有调用multiValue1您所说的错误发生的地方。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.