C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

圧縮・解凍をメモリ上で行う方法

[トピック内 28 記事 (1 - 20 表示)]  << 0 | 1 >>

■94584 / inTopicNo.1)  圧縮・解凍をメモリ上で行う方法
  
□投稿者/ gegege (1回)-(2020/04/23(Thu) 11:07:41)

分類:[.NET 全般] 

VB.NETに関する質問です。


バイナリファイルを出力する時に、バイナリの一部をZipやLZH、RARなどの圧縮形式に変換して保存したいと考えています。

VSに標準で付いているZipFileクラスやZipArchiveクラスだと
ファイルを介した方法をサポートしていないと思います。

もし、これを使うのだとすると、
圧縮したいバイナリの一部だけを一旦ファイルとして保存してから
Zip圧縮し、その圧縮ファイルをバイナリとして読み込んで
先ほどのバイナリの一部に埋め込むというような行程が必要となります。

これをHDD/SSDを使わずにメモリー上で行いたいのですが、
メモリー上で圧縮・解凍をサポートしているDLLはございますでしょうか?

引用返信 編集キー/
■94586 / inTopicNo.2)  Re[1]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ とっちゃん (672回)-(2020/04/23(Thu) 11:27:00)
No94584 (gegege さん) に返信

> バイナリファイルを出力する時に、バイナリの一部をZipやLZH、RARなどの圧縮形式に変換して保存したいと考えています。
>
> VSに標準で付いているZipFileクラスやZipArchiveクラスだと
> ファイルを介した方法をサポートしていないと思います。
>
> もし、これを使うのだとすると、
> 圧縮したいバイナリの一部だけを一旦ファイルとして保存してから
> Zip圧縮し、その圧縮ファイルをバイナリとして読み込んで
> 先ほどのバイナリの一部に埋め込むというような行程が必要となります。
>
> これをHDD/SSDを使わずにメモリー上で行いたいのですが、
> メモリー上で圧縮・解凍をサポートしているDLLはございますでしょうか?
>

zip 形式だけですが、ZipArchive は Stream を対象として処理するので
渡すストリームを MemoryStream にすれば、ファイルに保存せずに圧縮・展開できます。

その代わり、ZipFile にあるように、一括展開・圧縮という形での提供手段はないので
その部分は自分で頑張って回すことになります。

また、LZH/RAR など zip 形式以外のファイル形式には対応していないので、その場合も別のものを探すことになります。

引用返信 編集キー/
■94587 / inTopicNo.3)  Re[2]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (2回)-(2020/04/23(Thu) 11:44:50)
ZipArchiveでもできるとは気づきませんでした。

自分でも書いてみたのですが
以下のところまではできたのですが、
最後にファイルをどのようにして出力して良いか分かりません
どのようにすれば良いですか?

        Using stream As MemoryStream = New MemoryStream

            Using writer As New BinaryWriter(stream)

                writer.Write(123)       

            End Using

            Using aaa As New ZipArchive(stream)

            End Using

        End Using

引用返信 編集キー/
■94588 / inTopicNo.4)  Re[3]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ Hongliang (1008回)-(2020/04/23(Thu) 12:00:05)
エントリとかが不要で単純にバイナリを圧縮したいだけなら、ZipArchiveではなくてDeflateStreamで十分かもしれません。

ZipArchiveでzipバイナリを生成するサンプルはここの最初の例にあります。
https://docs.microsoft.com/ja-jp/dotnet/api/system.io.compression.ziparchive?redirectedfrom=MSDN&view=netframework-4.8
サンプルではStreamWriterを使っていますが、バイナリを書き出す場合、OpenしたStreamに直接Writeするなり、BinaryWriterを使うなりします。
一通りアーカイブし終わった後、MemoryStreamを.ToArray()でバイト配列にするとかします。

バイナリファイルを出力する際に一部だけzipバイナリを混ぜたいのなら、MemoryStreamをつくるまでもなく、FileStreamを元にZipArchiveクラスを作ってやれば十分ですが。
引用返信 編集キー/
■94589 / inTopicNo.5)  Re[4]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (3回)-(2020/04/23(Thu) 12:13:10)
> バイナリファイルを出力する際に一部だけzipバイナリを混ぜたいのなら、MemoryStreamをつくるまでもなく、FileStreamを元にZipArchiveクラスを作ってやれば十分ですが。

これはどのようにすれば良いですか?
以下のようにしてみましたがうまくいきませんでした

        Using stream As Stream = New FileStream("D:\aaa.zip", FileMode.Create)

            Using archive As New ZipArchive(stream, ZipArchiveMode.Update)

                Using writer As New BinaryWriter(stream)


                    writer.Write(1)


                End Using

            End Using

        End Using

引用返信 編集キー/
■94590 / inTopicNo.6)  Re[5]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ Hongliang (1009回)-(2020/04/23(Thu) 12:19:25)
> Using archive As New ZipArchive(stream, ZipArchiveMode.Update)
zipバイナリを新しく作るんだからZipArchiveMode.Createです。
あとは、ZipArchive::CreateEntryでエントリ(zipファイルに格納されている個々のファイルを表します)を作って、ZipArchiveEntry::Openでエントリのストリームを開き、そこに目的のバイナリを書き込みます。

なおZipArchiveをnewする際の第3引数にTrueを指定しない限り、ZipArchiveをDisposeしたときに元のFileStreamもDisposeされて読み書きできなくなるのでご注意。
引用返信 編集キー/
■94592 / inTopicNo.7)  Re[6]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (5回)-(2020/04/23(Thu) 19:08:25)
すいません、誤解があったかも知れませんが
こちらがしたいこととしては
バイナリデータからZIP圧縮されたバイナリデータ(ファイルではない)を生成することです。

ご提示くださった方法は
バイナリファイルが格納されたZIPファイルのバイナリデータを得る方法でしょうか?

引用返信 編集キー/
■94593 / inTopicNo.8)  Re[7]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (7回)-(2020/04/23(Thu) 19:20:56)
例えば、以下のようにすると、バイナリデータを格納したzipファイルを生成することには成功しました。


        Using stream As Stream = New FileStream("D:\aaa.zip", FileMode.Create)

            Using archive As New ZipArchive(stream, ZipArchiveMode.Create)

                Dim readmeEntry As ZipArchiveEntry = archive.CreateEntry("sss.txt")

                Using writer As New BinaryWriter(readmeEntry.Open)

                    writer.Write(1)

                End Using

            End Using

        End Using

一方で、バイナリデータの一部にこのzipデータを埋め込みたい場合にはどうしたら良いですか?

以下のようにしてもエラーが出てうまくいきませんでした。


        Using stream As Stream = New FileStream("D:\aaa.zip", FileMode.Create)


            Using writer As New BinaryWriter(stream)

                writer.Write(1)

            End Using


            Using archive As New ZipArchive(stream, ZipArchiveMode.Create)

                Dim readmeEntry As ZipArchiveEntry = archive.CreateEntry("sss.txt")

                Using writer As New BinaryWriter(readmeEntry.Open)

                    writer.Write(1)

                End Using

            End Using




            Using writer As New BinaryWriter(stream)

                writer.Write(1)

            End Using

        End Using


引用返信 編集キー/
■94595 / inTopicNo.9)  Re[8]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ 魔界の仮面弁士 (2703回)-(2020/04/23(Thu) 19:33:09)
No94592 (gegege さん) に返信
> こちらがしたいこととしては
> バイナリデータからZIP圧縮されたバイナリデータ(ファイルではない)を生成することです。

あれ、冒頭に書かれた記述では
>> VSに標準で付いているZipFileクラスやZipArchiveクラスだと
>> ファイルを介した方法をサポートしていないと思います。
とあったので、質問内容は
「ファイルが必要なのだけど、ファイルを介した方法がサポートされていないように思える」
ではなかったのでしょうか。


> ご提示くださった方法は
> バイナリファイルが格納されたZIPファイルのバイナリデータを得る方法でしょうか?

引数として引き渡すのは、「System.IO.Stream を継承したクラス」のインスタンスです。

先の回答にもあるように、「System.IO.MemoryStream クラス」のインスタンスを渡せば
オンメモリで処理を行え、「System.IO.FileStream クラス」のインスタンスを渡せば
ファイルに対する処理となります。

すなわち、どちらにも流用できるものである…と言えるかと。


No94593 (gegege さん) に返信
> 一方で、バイナリデータの一部にこのzipデータを埋め込みたい場合にはどうしたら良いですか?

zip バイナリとして得たいなら、ZipArchiveMode.Create する Stream を
FileStream ではなく、MemoryStream にして書き込みます。
書き込み終わったら、Writer が Flush した後で、MemoryStream の ToArray() メソッドから
バイナリが得られます。

えられた『zipデータ』のバイナリをどう扱うかは、そちらで決めて頂く必要があります。

単に『バイナリデータの一部』に埋め込むといっても、埋め込む先のデータ構造が
どういう物であるのか分からないので、こちらでは答えられません。

既存のバイナリデータの先頭にくっつけておきたいのか、
あるいは末尾に繋げるようにしたいのか、
あるいはバイナリデータの特定の領域を上書きあるいは挿入したいのか。

そして、単に埋め込んだだけではなく、それを取り出せるような仕組みが
元のバイナリデータの構造としてそもそも存在しているのか。
引用返信 編集キー/
■94596 / inTopicNo.10)  Re[8]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ Hongliang (1010回)-(2020/04/23(Thu) 19:46:46)
ん? 最終的には
> バイナリファイルを出力する時に、バイナリの一部をZipやLZH、RARなどの
> 圧縮形式に変換して保存したいと考えています。
ですよね?
ZipArchiveは途中まで書き込んだFileStreamに対してもちゃんとその途中から書き込んでくれるはずですよ。

Dim fs As New FileStream(...)
fs.Write(data, 0, data.Length)
Dim bw As New BinaryWRiter(fs)
bw.Write(...)
bw.Flush()
Using zip As New ZipArchive(fs, ZipArchiveMode.Create, True)
    ' CreateEntryしてOpenしてBinaryWriter作って
    writer.Write(...)
End Using
fs.Dispsoe()

まあバイト配列が欲しいのなら、FileStreamの代わりにMemoryStreamを使って
一通り出力が終わった後で.ToArray()すればいいですが。

引用返信 編集キー/
■94597 / inTopicNo.11)  Re[9]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (9回)-(2020/04/23(Thu) 20:24:02)
ありがとうございます。

ファイルの圧縮の方はうまくいきました。

> 「ファイルが必要なのだけど、ファイルを介した方法がサポートされていないように思える」
> ではなかったのでしょうか。


ただ、この方法だと、バイナリデータの圧縮されたデータだけでなく、
Zipのヘッダー情報など不必要な情報も含んでしまいます。

また、バイナリは一つしかないのに
zipファイル内に格納されたファイル名も指定する必要があるため
更に不必要な情報が増えてしまいます。

こちらが必要としているのは
バイナリの圧縮データそのものだけで、
ヘッダーなどがないZipのデータ部分だけを取得できればと思いました。

つまり、バイナリデータのサイズをできるだけ下げたいので不要な情報を削りたいという意図です。

ただ、ヘッダー有りでもHDDを介するよりかは良いのでこれはこれで良いのですが。




あと、ファイルの読み込みにかんしてですが、
以下のようにしても、
Do While reader.BaseStream.Position < reader.BaseStream.Length
でエラーが出てしまいます。

どうすれば、ファイルの最後に到達したかを判定することができますか?

        Using stream As Stream = New FileStream("D:\aaa3.zip", FileMode.Open)

            Using a As New ZipArchive(stream, ZipArchiveMode.Read)

                For Each e1 As ZipArchiveEntry In a.Entries

                    Using reader As New BinaryReader(e1.Open)

                        Do While reader.BaseStream.Position < reader.BaseStream.Length

                            Dim ggg2 = reader.ReadInt32

                        Loop

                    End Using

                Next e1

            End Using

        End Using

引用返信 編集キー/
■94598 / inTopicNo.12)  Re[10]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ Hongliang (1011回)-(2020/04/23(Thu) 20:29:37)
> こちらが必要としているのは
> バイナリの圧縮データそのものだけで、
> ヘッダーなどがないZipのデータ部分だけを取得できればと思いました。
>
> つまり、バイナリデータのサイズをできるだけ下げたいので不要な情報を削りたいという意図です。

それをするのが、先にご紹介したDeflateStreamです。
引用返信 編集キー/
■94599 / inTopicNo.13)  Re[11]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (11回)-(2020/04/23(Thu) 21:33:55)
そういうことでしたか
DeflateStreamはかなり使えそうですね
ありがとうございます。

あと、読み込みの方はどのようにすれば良いでしょうか?
                        Do While reader.BaseStream.Position < reader.BaseStream.Length

はどのように修正すれば良いですか?

        Using stream As Stream = New FileStream("D:\aaa3.zip", FileMode.Open)

            Using a As New ZipArchive(stream, ZipArchiveMode.Read)

                For Each e1 As ZipArchiveEntry In a.Entries

                    Using reader As New BinaryReader(e1.Open)

                        Do While reader.BaseStream.Position < reader.BaseStream.Length

                            Dim ggg2 = reader.ReadInt32

                        Loop

                    End Using

                Next e1

            End Using

        End Using

引用返信 編集キー/
■94600 / inTopicNo.14)  Re[12]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ Hongliang (1012回)-(2020/04/23(Thu) 22:41:40)
2020/04/23(Thu) 22:45:30 編集(投稿者)

BinaryReaderは個人的には使い勝手の悪さが目立つため全然使っていないので、的を外してるかもしれませんが。
不定個数のInt32データを読み取るのに、
・最善なのは、冒頭にまず何個データがあるかを出力し、それから実データを出力しておくことでしょう。
 こうすれば、読み取る際はまず1つ値を読み取り、その後その回数ループすればいいことになります。
・一旦ストリーム全体をMemoryStreamにCopyToしてやれば、Lengthを取得できるようになります。
・開き直って、Try-CatchでOutOfStreamExceptionをキャッチするまでループするという手もあります。

私ならまず個数なり出力するようにしますが、どうしても不定個数読み取らねばなないのなら。
Streamから直接Readで4バイトずつ読み取って、BitConverter.ToInt32で変換していきますかね。
Readの返値が4未満になれば終了です。
引用返信 編集キー/
■94601 / inTopicNo.15)  Re[13]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (12回)-(2020/04/23(Thu) 22:55:23)
それもそうですね
バイナリデータの場合にはファイルフォーマットが決まっているので
ファイルの最後を検出する必要はありませんでしたね。

> BinaryReaderは個人的には使い勝手の悪さが目立つため全然使っていないので、

とのことですが、他に何を使ってらっしゃるのでしょうか?

引用返信 編集キー/
■94602 / inTopicNo.16)  Re[14]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ Hongliang (1013回)-(2020/04/23(Thu) 23:09:22)
Streamをそのまま使います。
必要なバイト数を一旦Readして、バイト配列から数値への変換が必要ならBitConverterあるいは自作の変換メソッドを。
自分が扱ったことがあるのはエンディアンとかなんやかんやあったりもしたので。
引用返信 編集キー/
■94606 / inTopicNo.17)  Re[15]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (13回)-(2020/04/24(Fri) 11:23:53)
No94602 (Hongliang さん) に返信
> Streamをそのまま使います。

そういうことでしたか
どうもありがとうございます。


解決済み
引用返信 編集キー/
■94607 / inTopicNo.18)  Re[16]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ gegege (14回)-(2020/04/24(Fri) 13:04:44)
2020/04/24(Fri) 13:05:14 編集(投稿者)

うまくいったと思ったのですが、
GZipStreamの読み込みで問題が発生しました。

ファイル全体がGZipStreamで圧縮されたデータの場合にはうまくいったのですが
例えば、1MByteのファイルがあり、
その1000byte〜2000byte領域にGZipStreamで圧縮されたデータが格納されている場合がうまくいきません。


Dim stream As Stream = File.OpenRead(FilePath)

Dim reader0 As New BinaryReader(Stream)

reader0.BaseStream.Seek(1000, SeekOrigin.Begin)


で最初の1000バイトを読み飛ばし、
そこから


Dim gzipStrm As New System.IO.Compression.GZipStream(Stream, System.IO.Compression.CompressionMode.Decompress)
Dim reader As BinaryReader= New BinaryReader(gzipStrm)

Dim aaa = reader.ReadBoolean()

とやってみたのですが、
ReadBooleanでエラーが出ます。


予め、圧縮した領域だけバイト配列にコピーしておいて処理する必要があると思うのですが
System.IO.Compression.GZipStreamは引数としてストリームしか受け取らないようです
また、positionの開始値や終了値も引数として受け取らないようです

どうすれば、一部データの場合に読み込むことができますか?

引用返信 編集キー/
■94611 / inTopicNo.19)  Re[17]: 圧縮・解凍をメモリ上で行う方法
□投稿者/ Hongliang (1014回)-(2020/04/24(Fri) 14:51:58)
> Dim reader0 As New BinaryReader(Stream)
> reader0.BaseStream.Seek(1000, SeekOrigin.Begin)
いちいちBinaryReaderを作る意味があるのでしょうか…。

> とやってみたのですが、
> ReadBooleanでエラーが出ます。
何のエラーでしょうか? 具体的なエラーメッセージは?
本当にStreamの1001バイト目から圧縮データが存在しているのでしょうか?

データ圧縮する場合はサイズが可変長になるのですから、バイナリに埋め込む際は注意して設計する必要があります。
末尾に配置するならそんなに考える必要もないかもしれませんが、途中に配置するのであれば、圧縮データが何バイト存在しているかを圧縮データの前に置いておくなどする必要があるでしょう。
例えば一旦0を書き込み、現在地を記憶しておいて、圧縮データを書き込み、書き込んだ後の現在地を元に何バイトの圧縮データになったか計算して、0を書き込んだところに戻って圧縮データのサイズを上書きする、とか。
引用返信 編集キー/
■94612 / inTopicNo.20)  Re[18]: 圧縮・解凍をメモリ上で行う方法
 
□投稿者/ gegege (15回)-(2020/04/24(Fri) 15:14:08)
エラーの出る原因が分かりました。

出力側が
GZipStreamなのに
入力側が
DeflateStream
になっているが原因でした。
修正するとうまく読み込むことができました。

> データ圧縮する場合はサイズが可変長になるのですから、バイナリに埋め込む際は注意して設計する必要があります。
> 末尾に配置するならそんなに考える必要もないかもしれませんが、途中に配置するのであれば、圧縮データが何バイト存在しているかを圧縮データの前に置いておくなどする必要があるでしょう。

> いちいちBinaryReaderを作る意味があるのでしょうか…。

省略しましたが、圧縮したデータのバイナリサイズを圧縮データの前に保存してあり
それを読み込むために、
BinaryReaderを生成しています。

今回、圧縮データがファイルの末尾にある場合には
読み込みに成功しましたが、
ファイルの途中にある場合にはどのようにして読み込めば良いですか?

ファイルに書き込んである
圧縮データのサイズをどのようにして使えばいいですか?

引用返信 編集キー/

このトピックをツリーで一括表示

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -