Подготовка к массиву С#
Учитывая заполненный byte[] values
в С#, я хочу добавить значение (byte)0x00
в массив. Я предполагаю, что для этого потребуется создать новый массив и добавить содержимое старого массива. Скорость - важный аспект моего приложения. Каков наилучший способ сделать это?
- EDIT -
byte[]
используется для хранения параметров DSA (Digital Signature Algorithm). Операция должна выполняться только один раз для каждого массива, но скорость важна, потому что я потенциально выполняю эту операцию для разных byte[]
s.
Ответы
Ответ 1
Если вы собираетесь выполнять эту операцию только один раз, выбор невозможен. Код, предоставленный Monroe answer, должен делать только хорошо.
byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00; // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values
Если, однако, вы будете выполнять эту операцию несколько раз, у вас есть еще несколько вариантов. Существует фундаментальная проблема, заключающаяся в том, что добавление данных в массив не является эффективной операцией, поэтому вы можете использовать альтернативную структуру данных.
A LinkedList
может эффективно добавлять данные, но в большинстве случаев он менее эффективен для большинства задач, поскольку он включает в себя гораздо больше распределения/освобождения памяти, а также теряет локальность памяти, поэтому может быть не чистой победой.
Двухсторонняя очередь (известная как deque) была бы фантастической структурой данных для вас. Вы можете эффективно добавлять в начало или конец и эффективно получать доступ к данным в любой точке структуры (но вы не можете эффективно вставлять где-то, кроме начала или конца). Основная проблема здесь заключается в том, что .NET не обеспечивает реализацию deque. Вам нужно будет найти стороннюю библиотеку с реализацией.
Вы также можете сэкономить много при копировании, отслеживая "данные, которые мне нужно добавить" (используя список/очередь/и т.д.), а затем ожидая фактического добавления данных как можно дольше, чтобы вы максимально свести к минимуму создание новых массивов, а также ограничить количество копий существующих элементов.
Можно также рассмотреть вопрос о том, можно ли настроить структуру так, чтобы вы добавляли ее до конца, а не в начало (даже если вы знаете, что вам нужно будет отменить ее позже). Если вы добавляете много за короткий промежуток времени, возможно, стоит хранить данные в List
(который может эффективно добавить в конец) и добавить в конец. В зависимости от ваших потребностей, возможно, даже стоит сделать класс, который является оберткой для списка, и это скрывает тот факт, что оно отменено. Вы можете сделать указатель, который отображает i
в Count-i
и т.д., Чтобы он отображался снаружи, как будто ваши данные хранятся в обычном режиме, даже если внутренний List
фактически сохраняет данные назад.
Ответ 2
Как вы догадались, самый быстрый способ сделать это - создать новый массив длиной + 1 и скопировать все старые значения.
Если вы собираетесь делать это много раз, я предлагаю использовать List<byte>
вместо byte[]
, поскольку стоимость перераспределения и копирования при росте базового хранилища амортизируется более эффективно; в обычном случае базовый вектор в List
выращивается в два раза каждый раз, когда добавляется или добавляется элемент List
, который превышает его текущую емкость.
...
byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00; // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values
Ответ 3
Хорошо, ребята, давайте взглянем на вопрос о производительности в этом вопросе.
Это не ответ, а просто микрозапуск, чтобы узнать, какой вариант более эффективен.
Итак, зададим сценарий:
- Байт-массив из 1 000 000 элементов, случайно заполненный
- Нам нужно добавить элемент 0x00
У нас есть 3 варианта:
- Вручное создание и заполнение нового массива
- Вручную создаем новый массив и используя Array.Copy(@Monroe)
- Создание списка, загрузка массива, вставка элемента и преобразование списка в массив
Здесь код:
byte[] byteArray = new byte[1000000];
for (int i = 0; i < byteArray.Length; i++)
{
byteArray[i] = Convert.ToByte(DateTime.Now.Second);
}
Stopwatch stopWatch = new Stopwatch();
//#1 Manually creating and populating a new array;
stopWatch.Start();
byte[] extendedByteArray1 = new byte[byteArray.Length + 1];
extendedByteArray1[0] = 0x00;
for (int i = 0; i < byteArray.Length; i++)
{
extendedByteArray1[i + 1] = byteArray[i];
}
stopWatch.Stop();
Console.WriteLine(string.Format("#1: {0} ms", stopWatch.ElapsedMilliseconds));
stopWatch.Reset();
//#2 Using a new array and Array.Copy
stopWatch.Start();
byte[] extendedByteArray2 = new byte[byteArray.Length + 1];
extendedByteArray2[0] = 0x00;
Array.Copy(byteArray, 0, extendedByteArray2, 1, byteArray.Length);
stopWatch.Stop();
Console.WriteLine(string.Format("#2: {0} ms", stopWatch.ElapsedMilliseconds));
stopWatch.Reset();
//#3 Using a List
stopWatch.Start();
List<byte> byteList = new List<byte>();
byteList.AddRange(byteArray);
byteList.Insert(0, 0x00);
byte[] extendedByteArray3 = byteList.ToArray();
stopWatch.Stop();
Console.WriteLine(string.Format("#3: {0} ms", stopWatch.ElapsedMilliseconds));
stopWatch.Reset();
Console.ReadLine();
И результаты:
#1: 9 ms
#2: 1 ms
#3: 6 ms
Я запускаю его несколько раз, и у меня разные номера, но пропорция всегда одна: # 2 всегда самый эффективный выбор.
Мой вывод: массивы более эффективны, чем списки (хотя они обеспечивают меньшую функциональность), и как-то Array.Copy
действительно optmized (хотелось бы это понять, хотя).
Любая обратная связь будет оценена.
С уважением.
PS: это не бойцовый пост, мы находимся на Q & A сайте, чтобы учиться и учить. И узнать.
Ответ 4
Когда мне нужно часто добавлять данные, но также хочу, чтобы O (1) произвольный доступ к отдельным элементам, я буду использовать массив, который превышает выделенную часть заполнения для быстрого добавления новых значений. Это означает, что вам нужно сохранить фактическую длину содержимого в другой переменной, так как array.length укажет длину + дополнение. Новое значение добавляется при использовании одного слота прокладки, не требуется выделение и копирование, пока вы не закончите заполнение. Фактически, распределение амортизируется в течение нескольких операций добавления. Есть компромиссы в области скоростного пространства, как если бы у вас было много таких структур данных, вы могли бы иметь достаточное количество дополнений, используемых в любой момент в программе.
Эта же методика может использоваться при добавлении. Как и при добавлении, вы можете ввести интерфейс или абстракцию между пользователями и реализацией: у вас может быть несколько слотов отступов, чтобы иногда требовалось новое распределение памяти. Как было предложено выше, вы также можете реализовать предварительный интерфейс с добавочной структурой данных, которая меняет индексы.
Я бы упаковал структуру данных как реализацию некоторого универсального интерфейса коллекции, так что интерфейс выглядит довольно нормально для пользователя (например, список массивов или что-то еще).
(Кроме того, если удаление поддерживается, возможно, полезно очистить элементы, как только они будут удалены, чтобы уменьшить нагрузку gc.)
Главное - рассмотреть реализацию и интерфейс по отдельности, поскольку их развязка дает вам гибкость в выборе разнообразных реализаций или для скрытия деталей реализации с использованием минимального интерфейса.
Есть много других структур данных, которые вы могли бы использовать в зависимости от применимости к вашему домену. Веревки или буфер разрыва; см. Какая лучшая структура данных подходит для реализации редактора как блокнот?; Trie делать некоторые полезные вещи, тоже.
Ответ 5
Лучший выбор зависит от того, что вы собираетесь делать с этой коллекцией позже в очереди. Если это единственное редактируемое изменение длины, которое когда-либо будет сделано, то лучше всего создать новый массив с одним дополнительным слотом и использовать Array.Copy()
, чтобы сделать все остальное. Нет необходимости инициализировать первое значение, так как новые массивы С# всегда обнуляются:
byte[] PrependWithZero(byte[] input)
{
var newArray = new byte[input.Length + 1];
Array.Copy(input, 0, newArray, 1, input.Length);
return newArray;
}
Если будут другие изменения, изменяющие длину, которые могут произойти, наиболее эффективным вариантом может быть использование List<byte>
все время, пока дополнения не всегда к началу. (Если в этом случае даже связанный список может не быть вариантом, который вы можете уволить из рук.):
var list = new List<byte>(input);
list.Insert(0, 0);
Ответ 6
Я знаю, что это ОЧЕНЬ старый пост, но мне действительно нравится использовать лямбду. Конечно, мой код НЕ МОЖЕТ быть самым эффективным способом, но его можно прочитать и в одной строке. Я использую комбинацию .Concat и ArraySegment.
string[] originalStringArray = new string[] { "1", "2", "3", "5", "6" };
int firstElementZero = 0;
int insertAtPositionZeroBased = 3;
string stringToPrepend = "0";
string stringToInsert = "FOUR"; // Deliberate !!!
originalStringArray = new string[] { stringToPrepend }
.Concat(originalStringArray).ToArray();
insertAtPositionZeroBased += 1; // BECAUSE we prepended !!
originalStringArray = new ArraySegment<string>(originalStringArray, firstElementZero, insertAtPositionZeroBased)
.Concat(new string[] { stringToInsert })
.Concat(new ArraySegment<string>(originalStringArray, insertAtPositionZeroBased, originalStringArray.Length - insertAtPositionZeroBased)).ToArray();
Ответ 7
Я знаю, что это более 4-летняя принятая запись, но для тех, кто может быть релевантным Buffer.BlockCopy, будет быстрее.