質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

4回答

5387閲覧

C#で動的な1次元配列を確保する際のロジックについて

siksmtt

総合スコア20

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2018/09/06 02:45

編集2018/09/07 02:38

C#でのメモリ管理について質問です。(もしかしたらメモリ関係ないかもしれませんが...)

今制作しているプログラムで、
while(true)のように永続的なループにおいて、各ループ毎に異なるサイズ(要素数)の1次元配列を確保(生成)する
というロジックがあります。
その際いくつかロジックとして候補がでたのですが、メモリ管理の観点からどれがいいのか悩んでいます。

具体的には以下のようなコードです。恐らくコード上ではしていることは変わらないと思います。

C#

1//パターン1 2 Random random = new Random(); 3 byte[] byteArray = null; 4 5 while (true) 6 { 7 int index = random.Next(1024); 8 9 byteArray = new byte[index]; 10 for (int count = 0; count < index; count++) 11 { 12 byteArray[count] = 0x01; 13 } 14 //byteArrayを使って関数を実行 15 16 if (index == 0) 17 { 18 break; 19 } 20 }

C#

1//パターン2 2 Random random = new Random(); 3 byte[] byteArray2 = new byte[1024]; 4 5 while (true) 6 { 7 int index = random.Next(1024); 8 9 Array.Resize(ref byteArray2, index); 10 for (int count = 0; count < index; count++) 11 { 12 byteArray2[count] = 0x01; 13 } 14 15 //byteArray2を使って関数を実行 16 17 Array.Clear(byteArray2, 0, byteArray2.Length); 18 19 if (index == 0) 20 { 21 break; 22 } 23 }

C#

1//パターン3 2 Random random = new Random(); 3 List<byte> list = new List<byte>(); 4 5 while (true) 6 { 7 int index = random.Next(1024); 8 9 for (int count = 0; count < index; count++) 10 { 11 list.Add(0x01); 12 } 13 14 //list.ToArray()として関数を実行 15 16 list.Clear(); 17 18 if (index == 0) 19 { 20 break; 21 } 22 }

上記の3パターンはこの質問用に組んだコードですが、実際のロジックと変わらない点としては

・配列は1024サイズより上回ることはないが、各ループでサイズは異なる
・コメントアウトで実行する関数に引数として配列が渡されるが、その際初期値の0x00は除かなければならない(※)
・配列はbyteの1次元配列に限定する(※)
・ループを何回繰り返すかわからない上、上記1・2点の理由から、while(true)前に予め配列を確保することはできない

上記の4点です。

このような場合、パターン1~3の中ではメモリ管理の観点としてどれが優れているのでしょうか?

実行速度などもできれば伺いたいのですがまだ自分で試しておらず、自分で確認すべきだと思っていますので、
他の方法も含めてもしご助言いただけますと幸いです。
(C#およびプログラミングを始めて半年と少ししか経っていない初学者ですので、至らぬ点がありましたら申し訳ありません。)

追記(実際にやりたいこと)

実際に制作しているプログラムのロジック(ほぼそのまま)を掲載します。
要件としては

・1024バイト毎にファイルの中身を読みとる
・改行コードが見つかったら、もしくはファイルの最後まで読んだら外部関数を実行
・改行コードがあるまでは外部関数は実行しないが、改行コードまでが1MB以上だった場合はエラーとする(未記載)

以上になります。
上記3点および前述の(※)については要件として変えられないので、「1024バイトごとではなく一気にファイルを読めばいい」「ReadLineを使って」等は実装として不可能です。

C#

1 string filePath = "任意のテキストファイルのフルパス"; 2 byte[] readdata = new byte[1024]; 3 byte[] buffer = new byte[1048576]; //1MB 4 int index = 0; 5 6 FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); 7 8 while (true) 9 { 10 int readByte = fileStream.Read(readdata, 0, 1024); 11 12 if (readByte == 0) 13 { 14 Array.Resize(ref buffer, index + 1); 15 //外部関数 0x00を除いたbyte配列を引数に指定しなければならない 16 break; //ファイルを全て読んだことになるのでwhile(true)から抜ける 17 } 18 19 foreach (byte byte1 in readdata) 20 { 21 if (byte1 == 0x00) 22 { 23 break; 24 } 25 26 if (byte1 == 0x0a) 27 { 28 buffer[index] = byte1; 29 30 if (buffer[index - 1] == 0x0d) 31 { 32 Array.Resize(ref buffer, index + 1); 33 //外部関数 0x00を除いたbyte配列を引数に指定しなければならない 34 index = 0; 35 Array.Clear(buffer, 0, buffer.Length); 36 Array.Resize(ref buffer, 1048576); 37 continue; 38 } 39 40 index++; 41 } 42 43 buffer[index++] = byte1; 44 } 45 }

「Array関数を多用している箇所はインスタンス生成やListでどうにかならないかな?その場合どちらを使う方がいいんだろう?」と思い質問させていただいた次第であります。
ご助言いただけますと幸いです...!

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

papinianus

2018/09/06 03:23

どれも気にするほどかわらないと思うのですが、本当は配列に入れるデータは固定ではないのですよね?それってどういう型のデータでどういうところから取得できるのですか?
siksmtt

2018/09/06 04:16

papinianus 様 閲覧いただきありがとうございます。本来は固定ではなく、バイト型のデータを通信パケット等から取得するプログラムになります。
Zuishin

2018/09/06 23:23

そもそも新しい配列を確保しなくてはなりませんか? 配列を初期化するのに使っているデータをどうやって取得しているかによって、もしかしたらもっと効率のいいやり方があるかもしれません。
siksmtt

2018/09/07 01:10

Zuishin 様 閲覧いただきありがとうございます。配列は確保しないとどうにもなりません。というのも任意のファイルの中身を1024バイト毎に読み、改行コードがある度に文字列検索を行うプログラムを作っております。要件や仕様などを言葉で説明すると長くなるのでお昼頃までにロジックを質問文の方に載せたいと思います。
Zuishin

2018/09/07 05:07

1024 と 1MB は hihijiji さんの仰る通り固定でいいと思います。呼び出し用の引数はその都度確保しなくてはならないかもしれませんが、もし呼び出されるメソッドを書き換えていいのであれば、これもサイズを引数として加えればバッファを使いまわせると思います。非同期でないので。
siksmtt

2018/09/07 06:09

ZUishin 様 ご助言いただきありがとうございます。やはり引数に指定できることが最善になりますよね。その点についても検討しようと思います...。
guest

回答4

0

問題なのは最大1MBになる「外部関数」とやらに食わせる配列の側,ということでしょうか.

外部関数の側の引数が
ExternalFunc( byte[] Data )
みたいな形になっていて,Data内の有効な区間を指定する引数のようなものが存在せず,そのことは変えられない,と.
であれば,外部関数を呼び出す毎にその引数に渡すための配列を新たに生成することは避けられないように思われます.

ファイルから1度に何byte読もうが,そんなこととは無関係に結局最大1MBになるかもしれないのですから,

  1. 1MBサイズのバッファAを用意してファイルから1024byte読み出した内容を追加していく(FileStream.Readには格納箇所を指定するoffset引数があるからそれを使えばよいかと)
  2. 条件を満たした時点で,外部関数に渡すための配列Bを必要サイズで新規に生成し,Aから必要なサイズ分のデータをコピーして,外部関数に渡す

という形でしょうか.

投稿2018/09/07 04:14

fana

総合スコア11656

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

siksmtt

2018/09/07 06:05

実際のロジックでは外部関数に食わせる配列の方の問題になります。 やはりそのようになってしまいますよね。 「新規に生成し、Aから必要なサイズ文のデータをコピーして」というコメントについてですが、new byte[] で適したサイズの配列を生成してArray.Copyを使う、という認識で合っていますでしょうか?
fana

2018/09/07 07:06

そういうことになると思います. Listを使ったところで,「配列」を要求する外部関数の仕様がある以上,最後はToArray()で配列メモリ確保と内容コピーをすることは変わらないでしょうから,特別利点があるとは思えませんし.
siksmtt

2018/09/07 07:59

やはりToArrayは内容をコピーするような動きになるのですね...。 非常に参考になりました、ありがとうございます!
guest

0

ベストアンサー

パターン2のArray.Resizeメソッドは新たにヒープ領域を確保したうえで、元の配列から要素がコピーされますから問題外です。
ワークエリアとして使う場合は、領域確保は1回だけにするのが最も効率がいいです。

C#

1 //パターン4 2 Random random = new Random(); 3 byte[] byteArray = new byte[1024]; 4 5 while (true) 6 { 7 int index = random.Next(1024); 8 9 for (int count = 0; count < index; count++) 10 { 11 byteArray[count] = 0x01; 12 } 13 //byteArrayを使って関数を実行 14 15 if (index == 0) 16 { 17 break; 18 } 19 }

投稿2018/09/06 03:40

hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

siksmtt

2018/09/06 04:25

hihijiji 様 ご回答ありがとうございます。 なるほど、Resizeに関してはコピーなどを使うのかなと薄々考えていたのですが、ヒープ領域を確保するのですね。 hihijiji様にご提示いただいた案ですが、もし仮にindexが1024以下だった場合、初期値の0x00が1024-index分残っていると思います。 コメントアウト時の関数で0x00は除いた形にしたいのですが、そうなると領域確保の回数を考慮してListにしたほうがいいのでしょうか?
hihijiji

2018/09/06 04:49

List<T>も中身は配列と同じでシーケンシャルなヒープ領域が必要なのです。 ですからAddメソッドの中では領域確保に勤しんでいると予想されます。 外部関数の引数で位置指定が出来ないなら、パターン1ですね。 PS.縮めるだけならResizeでもうまい事してくれるかもしれません。
hihijiji

2018/09/06 05:09

パターン1の場合、byteArrayはループの中で宣言と同時に初期化してください。
siksmtt

2018/09/06 06:15

hihijiji 様 ご回答、ご助言ありがとうございます。 残念ながら外部関数の仕様(引数の型や引数の数など)がすでにほぼ決まっており、それに沿う必要がありますので、パターン1で組んでみたいと思います。(不思議な組み方と思われるのは承知しておりますが...泣) 参考になりました、先日に引き続きありがとうございます! ベストアンサーについてはまだ他の方の回答を待ちたいと思います。
guest

0

hihijijiさんとのやりとりをみていると、
ListのCapacityを指定すればいいんじゃない?とか思った。

https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,aa9d469618cd43b0,references

List<T>のソース読むと、初期値は4で、倍々+1で増えていく。
1024までには、領域確保は数回発生する。

予め、量がわかっているなら、もうちょっと大きい数でいいはず。

投稿2018/09/06 13:39

kiichi54321

総合スコア1984

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

siksmtt

2018/09/07 00:25

kiichi54321 様 ご回答ありがとうございます。 Listのcapacityは初めて目にしたので調べてのですが、 つまり最初に1024を指定したあと、外部関数の前でTrimExcessを使えばいい、ということでよろしいでしょうか? そういうことでしたら使えるかもしれないです、後で試してみようと思います!
kiichi54321

2018/09/07 03:05

1024じゃなくても、512、256あたりで指定しておけば、領域拡張の回数が抑制される。ということです。 そうすれば、過剰に確保するという問題は解決される。
siksmtt

2018/09/07 06:01

なるほど、ご指摘ありがとうございます。 検討してみようと思います!
guest

0

ご回答、ご助言いただいた皆様ありがとうございました。
どのお方のコメントも知らないことばかりで非常に参考になりました!

ベストアンサーについてはとても悩みましたが1番始めにご回答いただいたhihijiji様にさせていただきました。ありがとうございました。

投稿2018/09/07 08:02

siksmtt

総合スコア20

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問