Чи коли-небудь є причина використовувати масив, коли списки доступні? [зачинено]


30

Схоже, що List<T>в C # можна зробити все, що може зробити масив, і більше, і здається також настільки ж ефективним у пам'яті та продуктивності, як масив.

То чому б мені хотілося використовувати масив?

Я, очевидно, не запитую про випадки, коли API або інше зовнішнє обмеження (тобто Основна функція) вимагає від мене використовувати масив ... Я лише запитую про створення нових структур даних у власному коді.


56
Для здійсненняList<T>
храповик урод

43
is also just as efficient in memory and performance as an array- гм. Звідки ви взяли це поняття?
Одід

12
Кілька розмірів на зразок var test = new string[5,5];)
Кнерд

7
Існує також часто використовуваний масив byte [].
SBoss

11
Зокрема, для C # Ерік Ліпперт писав про це у blogs.msdn.com/b/ericlippert/archive/2008/09/22/… .
Мефі

Відповіді:


26

З тієї ж причини, коли я не їжджу на вантажівці, коли їду на роботу. Я не використовую те, що не буду використовувати функції.

Перш за все масив - це примітивна конструкція, тому масив швидше і ефективніше, ніж List <> напевно, тому ваш аргумент не відповідає дійсності. Масив також доступний скрізь і відомий розробникам, що використовують різні мови та платформи.

Найголовніша причина, що я використовую масив замість списку <>, полягає в тому, що я маю на увазі, що дані мають фіксовану довжину . Якщо я не додаю або видаляю будь-які елементи із цього збору даних, я хочу переконатися, що тип відображає це.

Інша справа, скажімо, ви впроваджуєте нову структуру даних, і ви прочитали деякі документи про неї. Тепер, впроваджуючи конкретні алгоритми, ви не завжди можете розраховувати на чужу реалізацію типу, що має загальне призначення. Він змінюється від .NET на Mono і навіть між різними версіями фреймворку.

І іноді простіше перенести фрагмент коду, який використовує масив замість типу, що залежить від фрейму.


4
Як масив "швидший", ніж список, враховуючи, що List<T>реалізований за допомогою масиву? Якщо ви заздалегідь знаєте кількість елементів (що ви повинні знати, використовуючи масив), ви можете використовувати ці знання і при ініціалізації списку.
Теодорос Чатзіґянакікіс

1
@TheodorosChatzigiannakis При використанні масиву ми виділяємо пам'ять і просто робимо завдання, звичайний malloc (), без накладних витрат. Коли ви використовуєте Список <>, ви будете "Додавати" елементи, а при додаванні елементів Список <> здійснює деяку перевірку кордонів і викликає метод EnsureCapacity, який скопіює вміст в інший щойно виділений масив, якщо поточна ємність недостатня. Це означає, що якщо вам не потрібно динамічно розподіляти нову пам'ять, продуктивність списку не настільки хороша, як масив.
Мерт Аккакая

4
Те , що ви хочете сказати , що це правильно, але це передбачає , що або ми заздалегідь не знаємо кількість елементів (в цьому випадку, масиви не є корисними в будь-якому випадку) , або що ми робимо знати кількість елементів заздалегідь , але ми навмисно не використовувати це у випадку зі списком. Якщо ми робимо використовувати кількість елементів , щоб форматувати список з необхідною потужністю, ми отримуємо тільки один виділення масиву (поки ви не досягнете цього потенціалу), таким чином , ми платимо ту ж витрату продуктивності. Усі інші загальні операції рівні (пошук, пошук за індексом, присвоєння за індексом), за винятком подальших додавань (яких масивів взагалі немає).
Теодорос Чатзіґянакікіс

3
@TheodorosChatzigiannakis: Масиви є швидше , ніж списки. Для перевірки просто запустіть простий орієнтир.
Мехрдад

1
@TheodorosChatzigiannakis, Так, але все ж є граничні перевірки.
Мерт Аккакая

18

Звичайно, вам потрібні масиви для управління вашою колекцією змінних конструкцій, і що б ми без них зробили.

struct EvilMutableStruct { public double X; } // don't do this

EvilMutableStruct[] myArray = new EvilMutableStruct[1];
myArray[0] = new EvilMutableStruct()
myArray[0].X = 1; // works, this modifies the original struct

List<EvilMutableStruct> myList = new List<EvilMutableStruct>();
myList.Add(new EvilMutableStruct());
myList[0].X = 1; // does not work, the List will return a *copy* of the struct

(зауважте, що можуть бути випадки, коли масив змінної структури бажаний, але зазвичай така різнича поведінка змінних структур у масивах порівняно з іншими колекціями є джерелом помилок, яких слід уникати)


Більш серйозно, вам потрібен масив, якщо ви хочете передати елемент за посиланням . тобто

Interlocked.Increment(ref myArray[i]);  // works
Interlocked.Increment(ref myList[i]);   // does not work, you can't pass a property by reference

Це може бути корисно для безпечного коду, що захищений від потоку.


Вам потрібен масив, якщо ви швидко та ефективно хочете ініціалізувати свою колекцію фіксованого розміру зі значенням за замовчуванням .

double[] myArray = new double[1000]; // contains 1000 '0' values
                                     // without further initialisation

List<double> myList = new List<double>(1000) // internally contains 1000 '0' values, 
                                             // since List uses an array as backing storage, 
                                             // but you cannot access those
for (int i =0; i<1000; i++) myList.Add(0);   // slow and inelegant

(зауважте, що можна було б реалізувати конструктор для List, який робить те саме, це просто те, що c # не пропонує цю функцію)


вам потрібен масив, якщо ви хочете ефективно копіювати частини колекції

Array.Copy(array1, index1, array2, index2, length) // can't get any faster than this

double[,] array2d = new double[10,100];
double[] arraySerialized = new double[10*100];
Array.Copy(array2d, 0, arraySerialized, 0, arraySerialized.Length);
// even works for different dimensions

(знову ж, це може бути реалізовано і для List, але ця функція не існує в c #)


Якщо тип повинен представляти групу споріднених, але незалежних змінних, склеєних разом із стрічкою каналів (наприклад, координати точки), структура найкращого зображення є найкращим представленням. Якщо змінна повинна містити купу таких груп, найкраще представлення є масив цього типу структури. Не слід використовувати змінні структури в місцях, де хочеться об'єкт, але це не означає, що слід використовувати об'єкти або речі, які видають себе за об'єкти в місцях, де потрібно купу змінних, склеєних разом з клейкою стрічкою.
supercat

Є деякі ситуації, коли вам може знадобитися змінна структура, наприклад, коли вам потрібен останній біт продуктивності і ви не можете дозволити купувати розподіли та посилання, і в такому випадку масив структур - найкраща ставка. Хороший твір тут . Але я не погоджуюся з тим, що клас не повинен представляти "купу змінних, що злилися". Концептуальні структури та класи здебільшого однакові, відмінності пов'язані насамперед із деталями реалізації.
HugoRune

Якщо хочеться використовувати колекцію посилань на змінний об'єкт як сукупність незалежних груп незалежних змінних, потрібно встановити та підтримувати інваріант, щоб кожен елемент ідентифікував об’єкт, на який не існує ніде інших ефемерних посилань ніде у Всесвіті. Це, безумовно, можна зробити (і часто є), але немає нічого в мові чи рамках, які б допомогли програмісту підтримувати цю інваріантність. На противагу цьому, масив довжиною-100 типу структури з чотирма полями примірника автоматично та постійно інкапсулює 400 незалежних змінних.
supercat

Я думаю, що ці коментарі - це неправильне місце для подальшого обговорення цього питання, і коли ви додали власну відповідь , схоже, не потрібно дублювати цю інформацію тут. Досить сказати, масиви можна використовувати для управління змінними конструкціями. Чи потрібно і коли використовувати поведінку цих структур для забезпечення певних обмежень даних, дійсно виходить за рамки моєї відповіді.
HugoRune

15

То чому б мені хотілося використовувати масив?

Рідко у вас буде сценарій, коли ви знаєте, що вам потрібна фіксована кількість елементів. З точки зору дизайну цього слід уникати. Якщо вам потрібні 3 речі, характер бізнесу означає, що вам дуже часто знадобляться 4 у наступному випуску.

Тим не менш, коли цей рідкісний сценарій насправді трапляється, використання масиву для закріплення інваріанта фіксованого розміру є корисним. Він надає сигнал іншим програмістам, що це фіксований розмір, і допомагає запобігти неправильному використанню, коли хтось додає або видаляє елемент - порушує очікування в іншому місці коду.


23
Розмір масивів не встановлюється в камені під час написання коду. Це може, і часто є змінною часу виконання. Він просто ніколи не змінюється після створення масиву .

11
Незвично мати справу з фіксованою кількістю елементів. Якщо ви працюєте з 3D-точками, ви, ймовірно, не будете працювати з 5D-точками в будь-якій точці в майбутньому. Більші проблеми з масивами полягають у тому, що вони змінюються та не мають зручних міток, прикріплених до їх елементів.
Довал

3
@JanDvorak Списки можуть скорочуватися або зростати і, таким чином, не мають сенсу в будь-якому контексті, що включає фіксовану кількість елементів.
Довал

9
Рідко? Не всі працюють над надскладними ультрафігурувальними жахливими ситуаціями на підприємстві.
whatsisname

3
Дуже звичайно мати фіксовану кількість елементів. Просто переконайтеся, що довжина визначається постійною, тому при наступному випуску збільшуйте кількість елементів з 3 до 4, ви просто змінюєте константу і все, на що покладається, адаптується.
Ганс

9

На ваше запитання фактично вже відповіли раніше .

і здається також настільки ж ефективним у пам’яті та продуктивності, як масив.

Це не так. З питання, яке я пов'язав:

List/foreach:  3054ms (589725196)
Array/foreach: 1860ms (589725196)

Масиви вдвічі швидші у певних важливих випадках. Я впевнений, що використання пам'яті також відрізняється нетривіально.

Оскільки головна передумова вашого питання таким чином переможена, я припускаю, що це відповідає на ваше запитання. На додаток до цього, іноді масиви на вас примушують API Win32, або шейдер вашого графічного процесора чи інша бібліотека, яка не є DotNet.

Навіть у DotNet деякі методи споживають та / або повертають масиви (наприклад, String.Split). А це значить , або ви повинні тепер з'їсти вартість виклику ToListі ToArrayвесь час, або ви повинні відповідати і використовувати масив, можливо , продовжуючи цикл, поширюючи це бідні нижчестоящих користувачів вашого коду.

Більше запитань та відповідей щодо переповнення стека на цю тему:


Цікаво, що відповідь вказує, що якщо ви використовуєте старомодні індексовані петлі ( для замість Еогеасп ) різниця в продуктивності мінімальна.
user949300

3

Окрім причин, перелічених у інших відповідях, літерал масиву вимагає менше символів для оголошення:

var array = new [] { "A", "B", "C" };
var list = new List<string>() { "A", "B", "C" };

Використання масиву замість того, що Listробить код трохи коротшим і просто трохи читабельнішим у випадках, коли (1) вам потрібно передати будь-який IEnumerable<T>буквальний текст, або (2), коли інша функціональність Listне має значення і вам потрібно скористатися деяким подібним до списку буквальний.

Я робив це час від часу в одиничних тестах.


1
Так, просто додати. Він також працює для вас, щоб пройти IList <T>
Есбен Сков Педерсен

+1, часто в мене є деяка операція для пари об'єктів одного типу, які не містяться в колекції. Це легко, коротко і зрозуміло, щоб писати foreach( var x in new []{ a, b, c ) ) DoStuff( x )або new []{ a, b, c ).Select( ... )інше
stijn

3

Це суто з точки зору ОО.

Хоча я не можу придумати причину пропускати просто масив навколо, я, безумовно, бачу ситуації, коли внутрішнє представлення масиву для класу, мабуть, найкращий вибір.

Хоча є й інші параметри, що дають подібні характеристики, жоден не здається настільки інтуїтивним, як масив для проблем із переробкою перестановок, вкладених для циклів, матричного представлення, растрових зображень та алгоритмів переплетення даних.

Існує значна кількість наукових галузей, які багато в чому покладаються на математику. (наприклад, обробка зображень, виправлення помилок даних, обробка цифрових сигналів, розробка прикладних задач з математики). Більшість алгоритмів у цих полях написані в термінах використання багатовимірних масивів / матриць. Тому було б більш природно реалізовувати алгоритми так, як вони визначені, а не робити їх більш "програмними" дружніми за рахунок втрати прямих зв'язків з документами, на яких алгоритми ґрунтуються.

Як я вже говорив, у цих випадках ви, ймовірно, можете піти з використанням списків, але це додає ще один складний рівень поверх того, що вже є складними алгоритмами.


3

Це насправді стосується й інших мов, які мають списки (наприклад, Java або Visual Basic). Бувають випадки, коли вам потрібно використовувати масив, оскільки метод повертає масив замість списку.

В реальній програмі я не думаю, що масив буде використовуватися дуже часто, але іноді ви знаєте, що дані будуть фіксованого розміру і вам подобається невеликий приріст продуктивності, який ви отримуєте за допомогою масиву. Мікрооптимізація була б вагомою причиною, як метод повернення списку, чи потреба у багатовимірній структурі даних.


1
Багато мов навіть не мають окремих колекцій списків та масивів. З іншого боку, з допомогою list<T>якої vector<T>буде робота катастрофічно погана ідея в C / C ++.
Gort the Robot

1
"Тому що метод повертає масив" - це неправильне використання Java, змушує програмістів ламати код з "Arrays.asList (x)". T [] повинен принаймні реалізувати Iterable <T>
kevin cline

4
@StevenBurnap - це, звичайно, катастрофічно погано в C, тому що немає можливості скласти цей код.
Піт Бекер


Вибачте, я мав на увазі C, прокатаний еквівалентом list<T>. В основному, я бачив безліч проблем із продуктивністю, викликаних розробниками, які використовували списки за замовчуванням, коли масив був кращим вибором.
Gort the Robot

1

Ну, я знайшов застосування для масивів у грі, про яку я писав. Я використовував її для створення системи інвентаризації з фіксованою кількістю слотів. Це мало ряд переваг:

  1. Я точно знав, скільки відкритих слотів для інвентаря мав об’єкт гри, переглянувши та побачивши, які слоти є недійсними.
  2. Я точно знав, в якому індексі кожен елемент.
  3. Це все ще був "набраний" масив (Item [] Inventory), тому я міг додати / видалити об'єкти типу "Item".

Я подумав, що якщо мені колись знадобиться «збільшити» розмір інвентарю, я можу це зробити, перенісши старі елементи в новий масив, але оскільки інвентар був зафіксований простором екрану, і мені не потрібно було динамічно збільшувати його / менший, він добре працював для тієї мети, якою я його використовував.


1

Якщо ви перетинаєте всі елементи списку, то ні, масив не потрібен, "наступний" або довільний "вибір без заміни" буде чудовим.

Але якщо ваш алгоритм потребує випадкового доступу до елементів колекції, то, так, масив необхідний.

Це дещо аналогічно "чи треба?". У розумній сучасній мові вона зовсім не потрібна. Але якщо ви зняти абстракції, в якийсь момент це все, що є фактично доступним для вас, тобто єдиний спосіб здійснити ці абстракції - це функція "непотріб". (Звичайно, аналогія не є досконалою, я не думаю, що хтось каже, що масиви є поганою практикою програмування; їх легко зрозуміти і подумати).


Список <T> також пропонує випадковий доступ. Звичайно, він реалізований з масивом, але це не повинно турбувати вас, користувача. Тож у кращому випадку ви запропонували єдину причину використовувати масиви: Для реалізації List<T>.

@delnan Я думаю, що це моя думка щодо абстракцій. Іноді краще думати з точки зору масиву, а не лише з міркувань ефективності. (звичайно, іноді краще придумати пов’язаний список, і набагато краще, ніж придумати список (не піклуючись про посилання чи іншим чином, як він реалізований), а краще - просто колекцію.
Mitch

0

Спадкова сумісність.

Усі форми особистого досвіду:

Спадкові програмісти - мій колега використовує масиви скрізь, робив це за 30+ років, удача передумав ваші нові потворні ідеї.

Спадковий код - foo (рядок масиву []), що ви можете використовувати функцію toarray зі списком / вектором / колекцією, але якщо ви не використовуєте жодних додаткових функцій, простіше використовувати масив для початку, часто більш читабельний без перемикання типу.

Legacy boss - мій начальник був хорошим програмістом до того, як вона перейшла в управління багато років тому і досі вважає, що вона в курсі, «використовували масиви» може закінчити зустріч, пояснивши, що колекція може коштувати обіду для всіх.


3
колеги, які 20 років за кривою, і начальник, який хоче знати, які типи даних ви використовуєте ... Мені подобається цей сайт, щоб нагадати собі, наскільки гіршою могла бути моя робота
JoelFan

1
приблизно 40% того, що ми робимо, - це вбудований дизайн, де дискусії проводяться в КБ, він любить мені говорити, що він не застарів, він розказав лол
Skeith

0

1) Немає багатовимірної версії Список. Якщо ваші дані мають більше одного виміру, використовувати списки буде неефективно.

2) Якщо ви маєте справу з великою кількістю малих типів даних (скажімо, карта, де у вас є один байт для типу місцевості), можливі значні відмінності в продуктивності через кешування. Версія масиву завантажує кілька елементів на прочитану пам'ять, версія списку завантажує лише один. Крім того, версія масиву містить кілька разів більше комірок у кеші, ніж версія списку - якщо ви неодноразово обробляєте дані, це може призвести до великої різниці, якщо версія масиву вписується в кеш, але версія списку - ні.

Для крайнього випадку розглянемо Minecraft. (Так, це не написано на C #. Ця ж причина застосовується.)



0

100-елементний масив деякого типу T інкапсулює 100 незалежних змінних типу T. Якщо T трапляється типом значення, яке має змінене публічне поле типу Q та одне типу R, то кожен елемент масиву буде інкапсулювати незалежні змінні типів Q і R. Масив в цілому таким чином буде інкапсулювати 100 незалежних змінних типу Q і 100 незалежних змінних типу R; будь-яка з цих змінних може бути доступна індивідуально, не впливаючи на будь-яку іншу. Жоден тип колекції, крім масивів, не може дозволяти полям структур використовуватись як незалежні змінні.

Якщо замість T трапився тип класу із відкритими змінними полями типу Q і R, кожен елемент масиву містить єдине посилання в будь-якій точці Всесвіту на екземпляр використання масиву або колекції змінних типів класу створює можливість що змінні, ідентифіковані елементами масиву, можуть не бути незалежними .T , і якщо жоден з елементів масиву ніколи не буде бути модифікованим для ідентифікації об'єкта, на який існує будь-яка зовнішня посилання, тоді масив ефективно інкапсулює 100 незалежних змінних типу Q та 100 незалежних змінних типу R. Інші типи колекції можуть імітувати поведінку такого масиву, але якщо єдина мета масиву полягає в інкапсуляції 100 змінних типу Q і 100 типу R, інкапсуляція кожної пари змінних у власний об'єкт класу - це дорогий спосіб зробити це. Далі,

Якщо тип повинен поводитись як якийсь об’єкт, то це повинен бути або тип класу, або структура приватного поля, яка не забезпечує інших способів мутації, крім заміни. Якщо, однак, тип повинен поводитись як купа пов'язаних, але незалежних змінних, скріплених з клейкою стрічкою, тоді слід використовувати тип, який є купою змінних, склеєних разом із клейкою стрічкою - структурою відкритого поля . Масиви таких типів дуже ефективні для роботи та мають дуже чисту семантику. Використання будь-якого іншого типу призведе до заплутаної семантики, низької продуктивності або обох.


0

Одне важливе значення - розподіл пам'яті. Наприклад, проходження пов’язаного списку може призвести до безлічі пропусків кешу та повільної продуктивності, тоді як масив являє собою суцільний шматок пам'яті, що містить декілька екземплярів певного типу даних, а проходження його через це, швидше за все, потрапить у процесор. кеш.

Звичайно, масив посилань на об’єкти може не отримати користі від звернень до кешу, оскільки перенаправлення все ще може перенести вас в будь-яку точку пам'яті.

Потім є реалізації списку, такі як ArrayList, які реалізують список за допомогою масиву. Вони є корисними примітивами.


2
Питання конкретно задає тип .Net List<T>, який реалізується не за допомогою зв'язаного списку, а за допомогою масиву (він по суті еквівалентний ArrayList<T>Java).
svick

-2

Ось декілька вказівок, якими ви можете скористатися, коли вибрати Arrayта коли вибрати List.

  • Використовувати Arrayпри поверненні з методу.
  • Використовуйте Listяк змінну, коли ви будуєте повернене значення (всередині методу). Потім використовувати .ToArray()при поверненні з методу.

Як правило, використовуйте те, Arrayколи споживач не має наміру додавати товари до колекції. Використовуйте, Listколи споживач має намір додавати предмети до колекції.

Arrayпризначений для роботи зі "статичними" колекціями, тоді Listяк призначений для роботи з "динамічними" колекціями.


2
Запропоноване вами правило є довільним і може призвести до неефективного коду, який виконує непотрібне копіювання. Принаймні вам потрібна якась форма обґрунтування для цього.
Жуль

@Jules Я вважав, що досвід розробника є набагато важливішим, ніж мікрооптимізація. Я ніколи не маю проблем з роботою на цьому рівні.
Даніель Ліддрьом

Однак я не бачу, як це правило покращується завдяки розробникам. Це вимагає від вас здогадатися, які клієнти вашого коду хочуть робити із поверненими значеннями, і робить ваш код менш зручним для використання, коли ви помиляєтесь, і все ж я не бачу жодної переваги перед ним. Продуктивність може бути вторинною проблемою, але я б не жертвував цим, щоб слідувати правилу, яке нічого не отримує.
Жуль

@Jules Я думаю, це зводиться до ваших власних уподобань тоді. Я вважаю за краще повертати значення як константи, як такі я вважаю за краще використовуватись Arrayзамість List. Приємно почути ваші думки, хоча!
Даніель Лідстрем

Чи обдумали ви використовувати UmmodifiableLists?
Жуль
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.