データが実際にはバイナリであっても、文字列を他の文字列に圧縮したいと思います。最適な圧縮アルゴリズムが何であるかはわかりませんが(データによって異なります)、入力テキストをビットに変換し、これらを圧縮してから、base-64エンコーディングを使用して圧縮バイトを文字列に再度変換できます。これにより、文字列から文字列に移動しながら、選択した圧縮アルゴリズムを適用できます。
.NET Frameworkは、DeflateStream
バイトのストリームを圧縮できるようにするクラスを提供します。Stream
最初のステップは、テキスト形式の読み取りと書き込みを可能にするカスタムを作成することです。より良い名前がないため、私はそれに名前を付けましたTextStream
。問題を単純化するために\n
、(の代わりに)行末として使用することに注意してください\r\n
。
class TextStream : Stream {
readonly String text;
readonly Int32 bitsPerLine;
readonly StringBuilder buffer;
Int32 textPosition;
// Initialize a readable stream.
public TextStream(String text) {
if (text == null)
throw new ArgumentNullException("text");
this.text = text;
}
// Initialize a writeable stream.
public TextStream(Int32 bitsPerLine) {
if (bitsPerLine <= 0)
throw new ArgumentException();
this.bitsPerLine = bitsPerLine;
this.buffer = new StringBuilder();
}
public override Boolean CanRead { get { return this.text != null; } }
public override Boolean CanWrite { get { return this.buffer != null; } }
public override Boolean CanSeek { get { return false; } }
public override Int64 Length { get { throw new InvalidOperationException(); } }
public override Int64 Position {
get { throw new InvalidOperationException(); }
set { throw new InvalidOperationException(); }
}
public override void Flush() {
}
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count) {
// TODO: Validate buffer, offset and count.
if (!CanRead)
throw new InvalidOperationException();
var byteCount = 0;
Byte currentByte = 0;
var bitCount = 0;
for (; byteCount < count && this.textPosition < this.text.Length; this.textPosition += 1) {
if (text[this.textPosition] != '0' && text[this.textPosition] != '1')
continue;
currentByte = (Byte) ((currentByte << 1) | (this.text[this.textPosition] == '0' ? 0 : 1));
bitCount += 1;
if (bitCount == 8) {
buffer[offset + byteCount] = currentByte;
byteCount += 1;
currentByte = 0;
bitCount = 0;
}
}
if (bitCount > 0) {
buffer[offset + byteCount] = currentByte;
byteCount += 1;
}
return byteCount;
}
public override void Write(Byte[] buffer, Int32 offset, Int32 count) {
// TODO: Validate buffer, offset and count.
if (!CanWrite)
throw new InvalidOperationException();
for (var i = 0; i < count; ++i) {
var currentByte = buffer[offset + i];
for (var mask = 0x80; mask > 0; mask /= 2) {
if (this.buffer.Length > 0) {
if ((this.buffer.Length + 1)%(2*this.bitsPerLine) == 0)
this.buffer.Append('\n');
else
this.buffer.Append(',');
}
this.buffer.Append((currentByte & mask) == 0 ? '0' : '1');
}
}
}
public override String ToString() {
if (this.text != null)
return this.text;
else
return this.buffer.ToString();
}
public override Int64 Seek(Int64 offset, SeekOrigin origin) {
throw new InvalidOperationException();
}
public override void SetLength(Int64 length) {
throw new InvalidOperationException();
}
}
次に、を使用して圧縮および解凍するためのメソッドを記述できますDeflateStream
。非圧縮入力は質問で提供したような文字列であり、圧縮出力はbase-64でエンコードされた文字列であることに注意してください。
String Compress(String text) {
using (var inputStream = new TextStream(text))
using (var outputStream = new MemoryStream()) {
using (var compressedStream = new DeflateStream(outputStream, CompressionMode.Compress))
inputStream.CopyTo(compressedStream);
return Convert.ToBase64String(outputStream.ToArray());
}
}
String Decompress(String compressedText, Int32 bitsPerLine) {
var bytes = Convert.FromBase64String(compressedText);
using (var inputStream = new MemoryStream(bytes))
using (var outputStream = new TextStream(bitsPerLine)) {
using (var compressedStream = new DeflateStream(inputStream, CompressionMode.Decompress))
compressedStream.CopyTo(outputStream);
return outputStream.ToString();
}
}
それをテストするために、私はランダムな文字列を作成する方法を使用しました(固定シードを使用して常に同じ文字列を作成します):
String CreateRandomString(Int32 width, Int32 height) {
var random = new Random(0);
var stringBuilder = new StringBuilder();
for (var i = 0; i < width; ++i) {
for (var j = 0; j < height; ++j) {
if (i > 0 && j == 0)
stringBuilder.Append('\n');
else if (j > 0)
stringBuilder.Append(',');
stringBuilder.Append(random.Next(2) == 0 ? '0' : '1');
}
}
return stringBuilder.ToString();
}
ランダムな4,096x4,096文字列を作成すると、非圧縮サイズは33,554,431文字になります。これは2,797,056文字に圧縮され、元のサイズの約8%に縮小されます。
base-64エンコーディングをスキップすると、圧縮率がさらに向上しますが、出力は文字列ではなくバイナリになります。入力をバイナリと見なすと、確率が0と1のランダムデータに対して実際に次の結果が得られます。
入力バイト:4,096 x 4,096 / 8 = 2,097,152
出力バイト:2,097,792
圧縮後のサイズ:100%
単純にバイトに変換する方が、デフレートを使用して変換するよりも優れています。ただし、ランダム入力を使用しますが、25%0と75%1を使用すると、次の結果が得られます。
入力バイト:4,096 x 4,096 / 8 = 2,097,152
出力バイト:1,757,846
圧縮後のサイズ:84%
どの程度の収縮がデータを圧縮するかは、実際にはデータの性質によって異なります。それが完全にランダムである場合、テキストからバイトに変換した後、多くの圧縮を得ることができません。