配列をコピーするには、for/foreachループを使う方法もあるが、ArrayクラスのCopyメソッドを使うのが一番簡単で速度の面でも有利である。
配列をコピーするのにforループやforeachループを書くのが面倒だと思ったことはないだろうか? Arrayクラス(System名前空間)の機能を使えば、それがたった1行で書けるのである。
List<T>クラスなどの便利なコレクションを使わずにわざわざ配列を使うのは、速度を重視している場合に多いだろう。そこで本稿では、配列をコピーする方法を解説するとともに、コピー時間を計測するコードの例も紹介する。
なお、配列は.NET Frameworkの最初からあるものだが、本稿はそれ以降の内容も含んでいる。サンプルコードをそのまま試すには、Visual Studio 2015(またはそれ以降)が必要である。
ArrayクラスのCopyメソッドを使うと、1行でコピーできる。
特に多次元配列ではforループ/foreachループでコピーするコードを書くのは面倒なものだ。2次元配列をforループ/foreachループ/ArrayクラスのCopyメソッドを使ってそれぞれコピーする例を次のコードに示す(コンソールアプリ)。
なお、1次元配列の場合には、その配列のCopyToメソッドも利用できる。
using System;
using System.Linq;
using static System.Console;
class Program
{
static void Main(string[] args)
{
// コピー元の2次元配列
int[,] src = { { 1, 2, 3 }, { 4, 5, 6 }, };
WriteLine($"コピー元の配列:{string.Join(", ", src.Cast<int>())}");
// 出力: 1, 2, 3, 4, 5, 6
// (以降の出力例は省略)
// forループでコピー
int[,] dest1 = new int[src.GetLength(0), src.GetLength(1)];
for (int i = 0; i < src.GetLength(0); i++)
for (int j = 0; j < src.GetLength(1); j++)
dest1[i,j] = src[i,j];
WriteLine($"forループでコピー:{string.Join(", ", dest1.Cast<int>())}");
// foreachループでコピー
int[,] dest2 = new int[src.GetLength(0), src.GetLength(1)];
int index0 = 0, index1 = 0;
foreach (var n in src)
{
dest2[index0, index1++] = n;
if (index1 >= src.GetLength(1))
{
index0++; index1 = 0;
}
}
WriteLine($"foreachループでコピー:{string.Join(", ", dest2.Cast<int>())}");
// Array.Copyでコピー
int[,] dest3 = new int[src.GetLength(0), src.GetLength(1)];
Array.Copy(src, dest3, src.Length);
WriteLine($"Array.Copyでコピー:{string.Join(", ", dest3.Cast<int>())}");
ReadKey();
}
}
Imports System.Console
Module Module1
Sub Main()
' コピー元の2次元配列
Dim src As Integer(,) = {{1, 2, 3}, {4, 5, 6}}
WriteLine($"コピー元の配列:{String.Join(", ", src.Cast(Of Integer)())}")
' 出力: 1, 2, 3, 4, 5, 6
' (以降の出力例は省略)
' Forループでコピー
Dim dest1(src.GetLength(0) - 1, src.GetLength(1) - 1) As Integer
For i As Integer = 0 To (src.GetLength(0) - 1)
For j As Integer = 0 To (src.GetLength(1) - 1)
dest1(i, j) = src(i, j)
Next
Next
WriteLine($"Forループでコピー:{String.Join(", ", dest1.Cast(Of Integer)())}")
' For Eachループでコピー
Dim dest2(src.GetLength(0) - 1, src.GetLength(1) - 1) As Integer
Dim Index0 As Integer = 0, index1 As Integer = 0
For Each n In src
dest2(Index0, index1) = n
index1 += 1
If (index1 >= src.GetLength(1)) Then
Index0 += 1 : index1 = 0
End If
Next
WriteLine($"For Eachループでコピー:{String.Join(", ",
dest2.Cast(Of Integer)())}")
' Array.Copyでコピー
Dim dest3(src.GetLength(0) - 1, src.GetLength(1) - 1) As Integer
Array.Copy(src, dest3, src.Length)
WriteLine($"Array.Copyでコピー:{String.Join(", ",
dest3.Cast(Of Integer)())}")
ReadKey()
End Sub
End Module
ArrayクラスのCopyメソッドは、1行で書けるだけでなく、forループ/foreachループよりも少し速いという特徴がある(Releaseビルドの場合、筆者の実測では、1次元配列で2割くらい速かった)。
List<T>クラスなどの便利なジェネリックコレクションを使わずに配列を使う理由は、主に処理速度を気にしているからだろう。そこで、先に示した3通りの配列コピー方法(ただし1次元配列)の速度を試してみるためのサンプルコードを次に示す。少々長いので、コードはC#のもののみとさせていただく。ポイントは、最初の数回は安定しないので、それを除外して処理時間の平均を求めることだ(この例では実行中のメモリ消費量は安定しているため、強制的なガベージコレクションはしていない)。
using System;
using System.Linq;
using static System.Console;
class Program
{
static void Main(string[] args)
{
const int Length = 50000000; // テストに使う配列の長さ
int[] a = new int[Length]; // コピー元の配列
for (int i = 0; i < Length; i++)
a[i] = i;
int[] b = new int[Length]; // コピー先の配列
// テスト用の定数と実行時間格納用の配列
const int LoopCount = 23;
long[] result1 = new long[LoopCount];
long[] result2 = new long[LoopCount];
long[] result3 = new long[LoopCount];
// 経過時間計測に使うストップウォッチ
var sw = new System.Diagnostics.Stopwatch();
// テスト実施
for (int i=0; i< LoopCount; i++)
{
WriteLine($"{i+1}回目");
result1[i] = ArrayCopyTest1(a, b, sw);
result2[i] = ArrayCopyTest2(a, b, sw);
result3[i] = ArrayCopyTest3(a, b, sw);
WriteLine();
}
// 結果を算出(最初の3回は除外して平均値を求める)
double ave1 = result1.Skip(3).Average();
double ave2 = result2.Skip(3).Average();
double ave3 = result3.Skip(3).Average();
WriteLine($"forループでコピー(平均):{ave1:#,##0.000}ミリ秒");
WriteLine($"foreachループでコピー(平均):{ave2:#,##0.000}ミリ秒");
WriteLine($"Array.Copyでコピー(平均):{ave3:#,##0.000}ミリ秒");
// コンソールが閉じてしまうのを防ぐ
ReadKey();
}
static long ArrayCopyTest1(int[] a, int[] b, System.Diagnostics.Stopwatch sw)
{
sw.Restart();
for (int i = 0; i < a.Length; i++)
b[i] = a[i];
sw.Stop();
WriteLine($"forループでコピー:{sw.ElapsedMilliseconds:#,##0}ミリ秒");
Array.Clear(b, 0, b.Length);
return sw.ElapsedMilliseconds;
}
static long ArrayCopyTest2(int[] a, int[] b, System.Diagnostics.Stopwatch sw)
{
sw.Restart();
int index = 0;
foreach (var n in a)
b[index++] = n;
sw.Stop();
WriteLine($"foreachループでコピー:{sw.ElapsedMilliseconds:#,##0}ミリ秒");
Array.Clear(b, 0, b.Length);
return sw.ElapsedMilliseconds;
}
static long ArrayCopyTest3(int[] a, int[] b, System.Diagnostics.Stopwatch sw)
{
sw.Restart();
Array.Copy(a, b, a.Length);
sw.Stop();
WriteLine($"Array.Copyでコピー:{sw.ElapsedMilliseconds:#,##0}ミリ秒");
Array.Clear(b, 0, b.Length);
return sw.ElapsedMilliseconds;
}
}
ArrayクラスのCopyメソッドを使えば(1次元配列に限定するなら配列のCopyToメソッドでも)、配列のコピーが1行で書ける。速度的にも有利なので、コピーしながら何か他の処理もするというのでなければ、Copyメソッドを使おう。
利用可能バージョン:.NET Framework 1.0以降(サンプルコードにはそれ以降の機能/構文も含む)
カテゴリ:クラス・ライブラリ 処理対象:配列
使用ライブラリ:Arrayクラス(System名前空間)
関連TIPS:C#で配列を宣言するには?
関連TIPS:VB.NETで配列を宣言するには?
関連TIPS:配列のサイズを変更するには?(Resize編)[2.0のみ、C#、VB]
関連TIPS:配列を独自の順序でソート(並べ替え)するには?
関連TIPS:自作クラスによる配列をソート(並べ替え)するには?(LINQ版)[3.5、C#、VB]
関連TIPS:文字列配列内の文字列を連結するには?
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
Copyright© Digital Advantage Corp. All Rights Reserved.