Java GZip 在压缩文件和再次解压时有细微差别
Java GZip makes small differences when compressing file and decompressing it again
经过一周的努力,我设计了一个二进制文件格式,并为它做了一个Java reader。这只是一个实验,效果很好,除非我使用 GZip 压缩功能。
我把我的二进制类型称为 MBDF(最小二进制数据库格式),它可以存储 8 种不同的类型:
- 整数(没有像字节、短、长或类似的东西,因为它存储在灵活的 space 中(更大的数字需要更多 space))
- Float-32(32 位浮点格式,如 java 的
float
类型)
- Float-64(64 位浮点格式,如 java 的
double
类型)
- 字符串(UTF-16 格式的字符串)
- 布尔值
- Null(只指定一个空值)
- 数组(类似于 java 的
ArrayList<Object>
)
- 化合物(A
String
- Object
地图)
我用这个数据作为测试数据:
COMPOUND {
float1: FLOAT_32 3.3
bool2: BOOLEAN true
float2: FLOAT_64 3.3
int1: INTEGER 3
compound1: COMPOUND {
xml: STRING "two length compound"
int: INTEGER 23
}
string1: STRING "Hello world!"
string2: STRING "3"
arr1: ARRAY [
STRING "Hello world!"
INTEGER 3
STRING "3"
FLOAT_32 3.29
FLOAT_64 249.2992
BOOLEAN true
COMPOUND {
str: STRING "one length compound"
}
BOOLEAN false
NULL null
]
bool1: BOOLEAN false
null1: NULL null
}
化合物中的 xml
键很重要!!
我用这个 java 代码从中创建了一个文件:
MBDFFile.writeMBDFToFile(
"/Users/<anonymous>/Documents/Java/MBDF/resources/file.mbdf",
b.makeMBDF(false)
);
这里,变量b
是一个MBDFBinary
对象,包含上面给出的所有数据。它使用 makeMBDF
函数生成 ISO 8859-1 编码的字符串,如果给定的布尔值是 true
,它会使用 GZip 压缩字符串。然后,写入时,在文件的开头添加一个额外的信息字符,包含有关如何读回它的信息。
然后,写入文件后,我将其读回java并解析它
MBDF mbdf = MBDFFile.readMBDFFromFile("/Users/<anonymous>/Documents/Java/MBDF/resources/file.mbdf");
System.out.println(mbdf.getBinaryObject().parse());
这会打印出上面提到的信息。
然后我尝试使用压缩:
MBDFFile.writeMBDFToFile(
"/Users/<anonymous>/Documents/Java/MBDF/resources/file.mbdf",
b.makeMBDF(true)
);
我读回它的方式与读回未压缩文件的方式完全相同,应该可以。它打印此信息:
COMPOUND {
float1: FLOAT_32 3.3
bool2: BOOLEAN true
float2: FLOAT_64 3.3
int1: INTEGER 3
compound1: COMPOUND {
xUT: STRING 'two length compound'
int: INTEGER 23
}
string1: STRING 'Hello world!'
string2: STRING '3'
arr1: ARRAY [
STRING 'Hello world!'
INTEGER 3
STRING '3'
FLOAT_32 3.29
FLOAT_64 249.2992
BOOLEAN true
COMPOUND {
str: STRING 'one length compound'
}
BOOLEAN false
NULL null
]
bool1: BOOLEAN false
null1: NULL null
}
对比初始信息,名字xml
不知为何变成了xUT
...
经过一些研究,我发现压缩前后的二进制数据差别不大。 110011
等模式变为101010
.
当我将名称 xml
加长时,例如 xmldm
,出于某种原因它只是被解析为 xmldm
。
我目前看到问题只发生在三个字符的名称上。
直接压缩和解压生成的字符串(不保存到文件并读取)确实有效,所以可能是文件编码导致的错误。
据我所知,字符串输出为 ISO 8859-1 格式,但我无法获得正确的文件编码。读一个文件的时候,就按该读的来读,所有的字符都读成ISO 8859-1字符。
我有一些可能是原因,我实际上不知道如何测试它们:
- GZip 输出的编码与未压缩的编码不同,因此在存储为文件时会产生细微差别。
- 文件存储为UTF-8格式,只是忽略顺序为ISO 8859-1编码(不知道怎么解释:))
- java GZip 库中有一个小错误。
但是哪一个是正确的,如果其中 none 个是正确的,那么这个错误的真正原因是什么?
我现在想不通。
MBDF文件class,读取和存储文件:
/* MBDFFile.java */
package com.redgalaxy.mbdf;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MBDFFile {
public static MBDF readMBDFFromFile(String filename) throws IOException {
// FileInputStream is = new FileInputStream(filename);
// InputStreamReader isr = new InputStreamReader(is, "ISO-8859-1");
// BufferedReader br = new BufferedReader(isr);
//
// StringBuilder builder = new StringBuilder();
//
// String currentLine;
//
// while ((currentLine = br.readLine()) != null) {
// builder.append(currentLine);
// builder.append("\n");
// }
//
// builder.deleteCharAt(builder.length() - 1);
//
//
// br.close();
Path path = Paths.get(filename);
byte[] data = Files.readAllBytes(path);
return new MBDF(new String(data, "ISO-8859-1"));
}
private static void writeToFile(String filename, byte[] txt) throws IOException {
// BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
//// FileWriter writer = new FileWriter(filename);
// writer.write(txt.getBytes("ISO-8859-1"));
// writer.close();
// PrintWriter pw = new PrintWriter(filename, "ISO-8859-1");
FileOutputStream stream = new FileOutputStream(filename);
stream.write(txt);
stream.close();
}
public static void writeMBDFToFile(String filename, MBDF info) throws IOException {
writeToFile(filename, info.pack().getBytes("ISO-8859-1"));
}
}
pack
函数以 ISO 8859-1 格式生成文件的最终字符串。
对于所有其他代码,请参阅我的 MBDF Github repository。
我评论了我尝试过的代码,试图展示我的尝试。
我的作品space:
- Macbook Air '11(High Sierra)
- IntellIJ 社区 2017.3
- JDK 1.8
我希望这是足够的信息,这实际上是弄清楚我在做什么以及到底什么不起作用的唯一方法。
编辑:MBDF.java
/* MBDF.java */
package com.redgalaxy.mbdf;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class MBDF {
private String data;
private InfoTag tag;
public MBDF(String data) {
this.tag = new InfoTag((byte) data.charAt(0));
this.data = data.substring(1);
}
public MBDF(String data, InfoTag tag) {
this.tag = tag;
this.data = data;
}
public MBDFBinary getBinaryObject() throws IOException {
String uncompressed = data;
if (tag.isCompressed) {
uncompressed = GZipUtils.decompress(data);
}
Binary binary = getBinaryFrom8Bit(uncompressed);
return new MBDFBinary(binary.subBit(0, binary.getLen() - tag.trailing));
}
public static Binary getBinaryFrom8Bit(String s8bit) {
try {
byte[] bytes = s8bit.getBytes("ISO-8859-1");
return new Binary(bytes, bytes.length * 8);
} catch( UnsupportedEncodingException ignored ) {
// This is not gonna happen because encoding 'ISO-8859-1' is always supported.
return new Binary(new byte[0], 0);
}
}
public static String get8BitFromBinary(Binary binary) {
try {
return new String(binary.getByteArray(), "ISO-8859-1");
} catch( UnsupportedEncodingException ignored ) {
// This is not gonna happen because encoding 'ISO-8859-1' is always supported.
return "";
}
}
/*
* Adds leading zeroes to the binary string, so that the final amount of bits is 16
*/
private static String addLeadingZeroes(String bin, boolean is16) {
int len = bin.length();
long amount = (long) (is16 ? 16 : 8) - len;
// Create zeroes and append binary string
StringBuilder zeroes = new StringBuilder();
for( int i = 0; i < amount; i ++ ) {
zeroes.append(0);
}
zeroes.append(bin);
return zeroes.toString();
}
public String pack(){
return tag.getFilePrefixChar() + data;
}
public String getData() {
return data;
}
public InfoTag getTag() {
return tag;
}
}
此 class 包含 pack()
方法。 data
已在此处压缩(如果应该的话)。
对于其他 classes,请查看 Github 存储库,我不想让我的问题太长。
自己解决了!
好像是读写系统。当我导出文件时,我使用 ISO-8859-1 table 制作了一个字符串,将字节转换为字符。我将该字符串写入一个文本文件,该文件是 UTF-8。最大的问题是我使用 FileWriter
个实例来编写它,这些实例用于文本文件。
阅读使用了逆系统。完整的文件作为字符串读入内存(内存消耗!!),然后被解码。
我不知道文件是二进制数据,它们的特定格式形成文本数据。 ISO-8859-1 和 UTF-8 是其中的一些格式。我在使用 UTF-8 时遇到了问题,因为它将一些字符拆分为两个字节,我无法处理...
我的解决方案是使用流。 Java中存在FileInputStream
和FileOutputStream
,可以用来读写二进制文件。我没有使用流,因为我认为没有太大的区别("files are text, so what's the problem?"),但是...我实现了这个(通过编写一个新的类似库) 并且我现在能够将每个输入流传递给解码器,并将每个输出流传递给编码器。要制作未压缩的文件,您需要传递一个FileOutputStream
。 GZip 文件可以使用 GZipOutputStream
s,依赖于 FileOutputStream
。如果有人想要一个包含二进制数据的字符串,可以使用 ByteArrayOutputStream
。相同的规则适用于阅读,其中应使用上述流的 InputStream
变体。
不再有 UTF-8 或 ISO-8859-1 问题,它似乎可以工作,即使使用 GZip!
经过一周的努力,我设计了一个二进制文件格式,并为它做了一个Java reader。这只是一个实验,效果很好,除非我使用 GZip 压缩功能。
我把我的二进制类型称为 MBDF(最小二进制数据库格式),它可以存储 8 种不同的类型:
- 整数(没有像字节、短、长或类似的东西,因为它存储在灵活的 space 中(更大的数字需要更多 space))
- Float-32(32 位浮点格式,如 java 的
float
类型) - Float-64(64 位浮点格式,如 java 的
double
类型) - 字符串(UTF-16 格式的字符串)
- 布尔值
- Null(只指定一个空值)
- 数组(类似于 java 的
ArrayList<Object>
) - 化合物(A
String
-Object
地图)
我用这个数据作为测试数据:
COMPOUND {
float1: FLOAT_32 3.3
bool2: BOOLEAN true
float2: FLOAT_64 3.3
int1: INTEGER 3
compound1: COMPOUND {
xml: STRING "two length compound"
int: INTEGER 23
}
string1: STRING "Hello world!"
string2: STRING "3"
arr1: ARRAY [
STRING "Hello world!"
INTEGER 3
STRING "3"
FLOAT_32 3.29
FLOAT_64 249.2992
BOOLEAN true
COMPOUND {
str: STRING "one length compound"
}
BOOLEAN false
NULL null
]
bool1: BOOLEAN false
null1: NULL null
}
化合物中的 xml
键很重要!!
我用这个 java 代码从中创建了一个文件:
MBDFFile.writeMBDFToFile(
"/Users/<anonymous>/Documents/Java/MBDF/resources/file.mbdf",
b.makeMBDF(false)
);
这里,变量b
是一个MBDFBinary
对象,包含上面给出的所有数据。它使用 makeMBDF
函数生成 ISO 8859-1 编码的字符串,如果给定的布尔值是 true
,它会使用 GZip 压缩字符串。然后,写入时,在文件的开头添加一个额外的信息字符,包含有关如何读回它的信息。
然后,写入文件后,我将其读回java并解析它
MBDF mbdf = MBDFFile.readMBDFFromFile("/Users/<anonymous>/Documents/Java/MBDF/resources/file.mbdf");
System.out.println(mbdf.getBinaryObject().parse());
这会打印出上面提到的信息。
然后我尝试使用压缩:
MBDFFile.writeMBDFToFile(
"/Users/<anonymous>/Documents/Java/MBDF/resources/file.mbdf",
b.makeMBDF(true)
);
我读回它的方式与读回未压缩文件的方式完全相同,应该可以。它打印此信息:
COMPOUND {
float1: FLOAT_32 3.3
bool2: BOOLEAN true
float2: FLOAT_64 3.3
int1: INTEGER 3
compound1: COMPOUND {
xUT: STRING 'two length compound'
int: INTEGER 23
}
string1: STRING 'Hello world!'
string2: STRING '3'
arr1: ARRAY [
STRING 'Hello world!'
INTEGER 3
STRING '3'
FLOAT_32 3.29
FLOAT_64 249.2992
BOOLEAN true
COMPOUND {
str: STRING 'one length compound'
}
BOOLEAN false
NULL null
]
bool1: BOOLEAN false
null1: NULL null
}
对比初始信息,名字xml
不知为何变成了xUT
...
经过一些研究,我发现压缩前后的二进制数据差别不大。 110011
等模式变为101010
.
当我将名称 xml
加长时,例如 xmldm
,出于某种原因它只是被解析为 xmldm
。
我目前看到问题只发生在三个字符的名称上。
直接压缩和解压生成的字符串(不保存到文件并读取)确实有效,所以可能是文件编码导致的错误。
据我所知,字符串输出为 ISO 8859-1 格式,但我无法获得正确的文件编码。读一个文件的时候,就按该读的来读,所有的字符都读成ISO 8859-1字符。
我有一些可能是原因,我实际上不知道如何测试它们:
- GZip 输出的编码与未压缩的编码不同,因此在存储为文件时会产生细微差别。
- 文件存储为UTF-8格式,只是忽略顺序为ISO 8859-1编码(不知道怎么解释:))
- java GZip 库中有一个小错误。
但是哪一个是正确的,如果其中 none 个是正确的,那么这个错误的真正原因是什么?
我现在想不通。
MBDF文件class,读取和存储文件:
/* MBDFFile.java */
package com.redgalaxy.mbdf;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MBDFFile {
public static MBDF readMBDFFromFile(String filename) throws IOException {
// FileInputStream is = new FileInputStream(filename);
// InputStreamReader isr = new InputStreamReader(is, "ISO-8859-1");
// BufferedReader br = new BufferedReader(isr);
//
// StringBuilder builder = new StringBuilder();
//
// String currentLine;
//
// while ((currentLine = br.readLine()) != null) {
// builder.append(currentLine);
// builder.append("\n");
// }
//
// builder.deleteCharAt(builder.length() - 1);
//
//
// br.close();
Path path = Paths.get(filename);
byte[] data = Files.readAllBytes(path);
return new MBDF(new String(data, "ISO-8859-1"));
}
private static void writeToFile(String filename, byte[] txt) throws IOException {
// BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
//// FileWriter writer = new FileWriter(filename);
// writer.write(txt.getBytes("ISO-8859-1"));
// writer.close();
// PrintWriter pw = new PrintWriter(filename, "ISO-8859-1");
FileOutputStream stream = new FileOutputStream(filename);
stream.write(txt);
stream.close();
}
public static void writeMBDFToFile(String filename, MBDF info) throws IOException {
writeToFile(filename, info.pack().getBytes("ISO-8859-1"));
}
}
pack
函数以 ISO 8859-1 格式生成文件的最终字符串。
对于所有其他代码,请参阅我的 MBDF Github repository。
我评论了我尝试过的代码,试图展示我的尝试。
我的作品space: - Macbook Air '11(High Sierra) - IntellIJ 社区 2017.3 - JDK 1.8
我希望这是足够的信息,这实际上是弄清楚我在做什么以及到底什么不起作用的唯一方法。
编辑:MBDF.java
/* MBDF.java */
package com.redgalaxy.mbdf;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class MBDF {
private String data;
private InfoTag tag;
public MBDF(String data) {
this.tag = new InfoTag((byte) data.charAt(0));
this.data = data.substring(1);
}
public MBDF(String data, InfoTag tag) {
this.tag = tag;
this.data = data;
}
public MBDFBinary getBinaryObject() throws IOException {
String uncompressed = data;
if (tag.isCompressed) {
uncompressed = GZipUtils.decompress(data);
}
Binary binary = getBinaryFrom8Bit(uncompressed);
return new MBDFBinary(binary.subBit(0, binary.getLen() - tag.trailing));
}
public static Binary getBinaryFrom8Bit(String s8bit) {
try {
byte[] bytes = s8bit.getBytes("ISO-8859-1");
return new Binary(bytes, bytes.length * 8);
} catch( UnsupportedEncodingException ignored ) {
// This is not gonna happen because encoding 'ISO-8859-1' is always supported.
return new Binary(new byte[0], 0);
}
}
public static String get8BitFromBinary(Binary binary) {
try {
return new String(binary.getByteArray(), "ISO-8859-1");
} catch( UnsupportedEncodingException ignored ) {
// This is not gonna happen because encoding 'ISO-8859-1' is always supported.
return "";
}
}
/*
* Adds leading zeroes to the binary string, so that the final amount of bits is 16
*/
private static String addLeadingZeroes(String bin, boolean is16) {
int len = bin.length();
long amount = (long) (is16 ? 16 : 8) - len;
// Create zeroes and append binary string
StringBuilder zeroes = new StringBuilder();
for( int i = 0; i < amount; i ++ ) {
zeroes.append(0);
}
zeroes.append(bin);
return zeroes.toString();
}
public String pack(){
return tag.getFilePrefixChar() + data;
}
public String getData() {
return data;
}
public InfoTag getTag() {
return tag;
}
}
此 class 包含 pack()
方法。 data
已在此处压缩(如果应该的话)。
对于其他 classes,请查看 Github 存储库,我不想让我的问题太长。
自己解决了!
好像是读写系统。当我导出文件时,我使用 ISO-8859-1 table 制作了一个字符串,将字节转换为字符。我将该字符串写入一个文本文件,该文件是 UTF-8。最大的问题是我使用 FileWriter
个实例来编写它,这些实例用于文本文件。
阅读使用了逆系统。完整的文件作为字符串读入内存(内存消耗!!),然后被解码。
我不知道文件是二进制数据,它们的特定格式形成文本数据。 ISO-8859-1 和 UTF-8 是其中的一些格式。我在使用 UTF-8 时遇到了问题,因为它将一些字符拆分为两个字节,我无法处理...
我的解决方案是使用流。 Java中存在FileInputStream
和FileOutputStream
,可以用来读写二进制文件。我没有使用流,因为我认为没有太大的区别("files are text, so what's the problem?"),但是...我实现了这个(通过编写一个新的类似库) 并且我现在能够将每个输入流传递给解码器,并将每个输出流传递给编码器。要制作未压缩的文件,您需要传递一个FileOutputStream
。 GZip 文件可以使用 GZipOutputStream
s,依赖于 FileOutputStream
。如果有人想要一个包含二进制数据的字符串,可以使用 ByteArrayOutputStream
。相同的规则适用于阅读,其中应使用上述流的 InputStream
变体。
不再有 UTF-8 或 ISO-8859-1 问题,它似乎可以工作,即使使用 GZip!