不解压的情况下从各种压缩包中直接读取文本行内容

文章目录

  • (零)前言
  • (一)【ZIP】格式
    • (1.1)Python
    • (1.2)Java
    • (1.3)Golang
    • (1.4)Pascal
  • (二)【GZIP】格式
    • (2.1)Python
    • (2.2)Java
    • (2.3)Golang
    • (2.4)Pascal
    • (2.5)C++
  • (三)【TAR】格式
    • (3.1)Python
    • (3.2)Java
    • (3.3)Golang
    • (3.4)Pascal
  • (四)【RAR】格式
    • (4.1)Python
    • (4.2)Java
    • (4.3)Golang
  • (五)【7Zip】格式
    • (5.1)Python
    • (5.2)Java
    • (5.3)Golang
  • (六)【Unix Z】格式
    • (6.1)Python
    • (6.2)Java
    • (6.3)Golang

(零)前言

通常的情况下遇到压缩包,都是解开后再进行文件操作。
比如把A.zip 解压缩成-> a.txt,再用程序打开a.txt正常读取。
上网查各种例子也是文件解开到文件。

很多年前我们也是这么干的,直到发现了这些情况:

  1. 似乎是4G时代吧,VoLTE的时代,各个厂商都用极其冗长的格式来存储并传输数据。
    导致压缩比非常大比如大到100:1。也就是1GB的压缩包,解开后是100GB的数据。
    这些无谓的磁盘开销改为直接读取压缩包就可以完全避免。

  2. 服务器虚拟化后,有些磁盘读写(尤其是写)特别慢。
    比如200MB的数据如果解压成4GB再进行处理,单个数据节点需要4小时,不解压只要2分钟左右。
    时间的差距实在是巨大。

所以不得已,只能直接读写压缩文件了。
不能调用外部的命令,还得兼顾跨平台,各种语言实现的方式都不太一样。
能读通常就能写,加上读文本比读二进制块更“高级”,所以就用读文本做例子吧。

(一)【ZIP】格式

是Windows下最常见的格式。
单个压缩包内可以包含多个文件
虽然压缩率不及RAR和7Z,读取速度不及GZIP,但是在Windows下兼容性是最好的。

(1.1)Python

我完全不懂python,不过挺好用呢。
对zip的支持是自带的。
参考:Python的数据压缩与存档。

import zipfile
...
if not zipfile.is_zipfile(SourceFile):
	raise Exception(SourceFile+" is not in ZIP format!")
with zipfile.ZipFile(SourceFile) as zipMy:
	for oneItem in zipMy.infolist():
	    with zipMy.open(oneItem) as filein:
	        lines = filein.readlines()
	        # do what ever with lines.

(1.2)Java

看上去Java是流式处理。
如果某文件被zip重复压缩了几次,用Java也可以一次读出最内层的数据(理论如此,没试过)。
org.apache.commons.compress


<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-compressartifactId>
    <version>1.24.0version>
dependency>
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
...

try(ZipFile zipFile =  new ZipFile(SourceFile, "GBK")){
	Enumeration<?> enums = zipFile.getEntries();
	while(enums.hasMoreElements()){
		ZipArchiveEntry entry = (ZipArchiveEntry) enums.nextElement();
		if(!entry.isDirectory()){
			try(BufferedReader br = new BufferedReader(new InputStreamReader(zipFile.getInputStream(entry), Charset.forName("GBK"))))
			{
				String aLine;
				while((aLine=br.readLine()) != null) {
					// do what ever with every Line.
				}
			}
		}
	}
}

(1.3)Golang

同上,发现Go的Reader也能流式处理。
本身代码没几行,但是error处理占了不少代码。
还有defer close,显然没有Python的with简练,也比不过Java的try with resource,哎这也是Go的一大特点。

import (
	...
	"archive/zip"
	...
)
...

zipReader, err := zip.OpenReader(SourceFile)
if err != nil {
	panic(err)
}
defer func(zipReader *zip.ReadCloser) {
	err := zipReader.Close()
	if err != nil {
		panic(err)
	}
}(zipReader)
for _, f := range zipReader.File {
	if !f.FileInfo().IsDir() {
		inFile, err := f.Open()
		if err != nil {
			panic(err)
		}
		OneReader := bufio.NewReader(inFile)
		for {
			line, _, err := OneReader.ReadLine()
			if err == io.EOF {
				MyEof = true
				break
			}
			if err != nil {
				panic(err)
			}
			// do what ever with every Line.
		}
	}
}

(1.4)Pascal

抱歉Lazarus(FPC)我不知道怎么写,以后再学。
假如用新版Delphi可以如下这样:

uses
	System.Zip,
	...
var
	line:String;
	aLH:TZipHeader
	...
begin	
	LZip:=TZipFile.Create;
	LZip.Open(SourceFile,zmRead);
	LZip.Encoding:=TEncoding.GetEncoding(936);
	for i:=0 to LZip.FileCount-1 do
	begin
	  LOutput := TMemoryStream.Create();
	  LZip.Read(i,LOutput,aLH);
	  var asr:=TStreamReader.Create(LOutput);
	  while not asr.EndOfStream do
	  begin
	    line:String;
	    line:=asr.ReadLine;
	    // do what ever with every Line.
	  end;
	  FreeAndNil(asr);
      FreeAndNil(LOutput);
    end;
    FreeAndNil(LZip);
end;

(二)【GZIP】格式

是Linux/Unix下最常见的压缩格式。
单个压缩包内只能包含单个文件
压缩包可以保存压缩前的原始文件名的信息到Meta data,也可以不保存,呵呵。

(2.1)Python

对gz的支持是自带,不用额外安装包。
根本和打开文件文件没区别。
似乎读不到Meda data中的原始文件名。

import gzip
...
with gzip.open(SourceFile,'r') as gzMy:
    lines=gzMy.readlines()
    # do what ever with lines.

(2.2)Java

可以用GzipCompressorInputStream.getMetaData().getFilename()读取原始文件名(没存就为空)。


<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-compressartifactId>
    <version>1.24.0version>
dependency>
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
...

try(BufferedReader br = new BufferedReader(new InputStreamReader(new GzipCompressorInputStream(Files.newInputStream(Paths.get(SourceFile))), Charset.forName("GBK"))))
{
	String aLine;
	while((aLine=br.readLine()) != null) {
		// do what ever with every Line.
	}
}

(2.3)Golang

可以从*gzip.Reader.Name中读取原始文件名(没存就为空)。

import (
	...
	"compress/gzip"
	...
)
...

fz, err := gzip.NewReader(SourceFile)
if err != nil {
	panic(err)
}
defer func(fz *gzip.Reader) {
	err := fz.Close()
	if err != nil {
		panic(err)
	}
}(fz)
OneReader := bufio.NewReader(fz)
for {
	line, _, err := OneReader.ReadLine()
	if err == io.EOF {
		MyEof = true
		break
	}
	if err != nil {
		panic(err)
	}
	// do what ever with every Line.
}

(2.4)Pascal

抱歉Lazarus(FPC)我不知道怎么写,以后再学。
假如用新版Delphi可以如下这样:

uses
	System.ZLib,
	...
var
	line:String;
	...
begin	
	LInput := TFileStream.Create(SourceFile, fmOpenRead);
	LUnGZ := TZDecompressionStream.Create(LInput,15 + 16);
	var asr:=TStreamReader.Create(LUnGZ);
	while not asr.EndOfStream do
	begin
	  line:String;
	  line:=asr.ReadLine;
	  // do what ever with every Line.
	end;
	FreeAndNil(asr);
    FreeAndNil(LUnGZ);
    FreeAndNil(LInput);
end;

(2.5)C++

使用zlib。下面的例子来自zlib1.3(August 18, 2023)

⚠️下面的代码没有提供readline这种方便的方式,毕竟C++嘛(改掉Stream就是C语言了)。
需要自己把解开的outBuffer复制到自己的缓存中,再判断一下\n的位置啥的,自己组成字符串。
如果还需要自由的fseek移动位置,那么还是全部写入内存流当中再操作比较好。

供参考(改改才能用):

z_stream zlibStream;
memset(&zlibStream, 0, sizeof(zlibStream));
zlibStream.next_in = nullptr;
zlibStream.avail_in = 0;
zlibStream.next_out = nullptr;
zlibStream.avail_out = 0;
zlibStream.zalloc = Z_NULL;
zlibStream.zfree = Z_NULL;
zlibStream.opaque = Z_NULL;

if (inflateInit2(&zlibStream, 16 + MAX_WBITS) != Z_OK) {
	// show error
	if (error_list_.end() == error_list_.find(path))
	{
		error_list_[path] = 0x00;
	}
	error_list_[path] |= 0x04;
	continue;
}

char* inBuffer = new char[bufferSize];
char* outBuffer = new char[bufferSize];

int zlibResult;
do {
	InputStream.read(inBuffer, bufferSize);
	zlibStream.next_in = reinterpret_cast<Bytef*>(inBuffer);
	zlibStream.avail_in = (uInt)InputStream.gcount();
	do {
		zlibStream.next_out = reinterpret_cast<Bytef*>(outBuffer);
		zlibStream.avail_out = bufferSize;

		zlibResult = inflate(&zlibStream, Z_NO_FLUSH);
		if (zlibResult == Z_STREAM_ERROR) {
			// show error
			inflateEnd(&zlibStream);
			if (error_list_.end() == error_list_.find(path))
			{
				error_list_[path] = 0x00;
			}
			error_list_[path] |= 0x04;
			continue;
		}
		//Do something with decompressed data, write to some file? 
		//OutputStream.write(outBuffer, bufferSize - zlibStream.avail_out);
	} while (zlibStream.avail_out == 0);
} while (InputStream.good() || zlibResult == Z_OK);

delete[] inBuffer;
delete[] outBuffer;

inflateEnd(&zlibStream);
OriginalFile.flush();
OriginalFile.close();

(三)【TAR】格式

是Linux/Unix下最常见的打包格式。
单个压缩包内可以包含多个文件
打包通常和压缩同时使用,比如:.tar.gz.tgz.tar.xz

(3.1)Python

对tar的支持是自带,不用额外安装包。
下面例子是.tar.gz.tgz的,其它的格式类似。
Python不用依次串起来解gz和解tar。只需要指定open时的参数。
可以从TarInfo.name得到包中每个文件的名字。

import tarfile
...
with tarfile.open(SourceFile,'r:gz') as tarMy:
    for oneItem in tarMy.getmembers():
        with tarMy.extractfile(oneItem) as filein:
            lines = filein.readlines()
    		# do what ever with lines.

(3.2)Java

同上例子是单独的.tar
可以从org.apache.tools.tar.TarEntry.getName()得到包中每个文件的名字。


<dependency>
    <groupId>org.apache.antgroupId>
    <artifactId>antartifactId>
    <version>1.10.14version>
dependency>
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
...

try (TarInputStream in = new TarInputStream(Files.newInputStream(new File(SourceFile).toPath()),"UTF8")) {
	TarEntry entry;
	while ((entry = in.getNextEntry()) != null) {
		if (entry.isFile()) {
			try(BufferedReader br = new BufferedReader(new InputStreamReader(in, Charset.forName("GBK"))))
			{
				String aLine;
				while((aLine=br.readLine()) != null) {
					// do what ever with every Line.
				}
			}
		}
	}
}

(3.3)Golang

同上例子是单独的.tar文件。
如果是tar.gz则和解gz串起来就好了。
类似:OneTAR := tar.NewReader(GZReader)这样。
可以从*tar.Header.FileInfo().Name()得到包中每个文件的名字。

import (
	...
	"archive/tar"
	...
)
...

OneTAR := tar.NewReader(SourceFile)
for {
	h, err := OneTAR.Next()
	if err == io.EOF {
		break
	} else if err != nil {
		panic(err)
	}
	if !h.FileInfo().IsDir() {
		OneReader := bufio.NewReader(OneTAR)
		for {
			line, _, err := OneReader.ReadLine()
			if err == io.EOF {
				MyEof = true
				break
			}
			if err != nil {
				panic(err)
			}
			// do what ever with every Line.
		}
	}
}

(3.4)Pascal

同上例子是单独的.tar文件。
如果是tar.gz则和解gz串起来就好了。
抱歉Lazarus(FPC)我不知道怎么写,以后再学。
假如用新版Delphi可以如下这样:
需要LibTar(Tar Library),也许还有其它实现方式,不太清楚。

uses
	LibTar,
	...
var
	line:String;
	DirRec:TTarDirRec;
	...
begin
	LInput := TFileStream.Create(SourceFile, fmOpenRead);
    TarStream:= TTarArchive.Create(LInput);
    TarStream.Reset;
    while TarStream.FindNext(DirRec) do
    begin
		LOutput := TMemoryStream.Create();
		TarStream.ReadFile(LOutput);
		var asr:=TStreamReader.Create(LOutput);
		while not asr.EndOfStream do
		begin
		  line:String;
		  line:=asr.ReadLine;
		  // do what ever with every Line.
		end;
		FreeAndNil(asr);
		FreeAndNil(LOutput);
	end;
	FreeAndNil(TarStream);
	FreeAndNil(LInput);
end;

(四)【RAR】格式

是Windows下主流的压缩格式。
单个压缩包内可以包含多个文件
压缩比较高,最新的是RAR5格式。
常见的软件:WinRAR。

(4.1)Python

需要pip install rarfile
用法和zip基本没区别,实测没成功,有说需要unrar.exe什么的(跨平台咋办)。
此项暂时保留。

(4.2)Java

com.github.junrar不能处理RAR5格式.
net.sf.sevenzipjbinding可以处理RAR5,但是我还不清楚如何直接读文本,只能read数据块。。
此项暂时保留。

(4.3)Golang

使用github.com/nwaples/rardecode
也有其它的方式处理RAR,比如archiver.NewRar().Unarchive(Source,Dest)是从文件解压到文件,但不能直接读压缩包的内容。

import (
	...
	"github.com/nwaples/rardecode"
	...
)
...

RARReader, err := rardecode.OpenReader(SourceFile, "")
if err != nil {
	panic(err)
}
defer func(RARReader *rardecode.ReadCloser) {
	err := RARReader.Close()
	if err != nil {
		panic(err)
	}
}(RARReader)

for {
	f, err := RARReader.Next()
	if err != nil {
		break
	}

	if !f.IsDir {
		OneReader := bufio.NewReader(RARReader)
		for {
			line, _, err := OneReader.ReadLine()
			if err == io.EOF {
				MyEof = true
				break
			}
			if err != nil {
				panic(err)
			}
			// do what ever with every Line.
		}
	}
}

(五)【7Zip】格式

是Windows下新晋的压缩格式,扩展名.7z
单个压缩包内可以包含多个文件
压缩比较高,处理速度较快。
常用的软件:7Zip。不过我比较喜欢基于它的:NanaZip。
Nana = なな = 七。

(5.1)Python

需要安装包:pip install py7zr

import py7zr
...
if not py7zr.is_7zfile(SourceFile):
    raise Exception(SourceFile+" is not in 7Z format!")
with py7zr.SevenZipFile(SourceFile) as sevenMy:
    for oneItem in sevenMy.files:
        filein = sevenMy.read([oneItem.filename])
        lines = filein[oneItem.filename].readlines()
    	# do what ever with lines.

(5.2)Java

org.apache.commons.compress

 
 <dependency>
     <groupId>org.apache.commonsgroupId>
     <artifactId>commons-compressartifactId>
     <version>1.24.0version>
 dependency>
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
...

try (SevenZFile z7In = new SevenZFile(SourceFile)){
    SevenZArchiveEntry entry;
    while ((entry = z7In.getNextEntry()) != null) {
        if (!entry.isDirectory()) {
            try(BufferedReader br = new BufferedReader(new InputStreamReader(z7In.getInputStream(entry), Charset.forName("GBK"))))
            {
                String aLine;
                while((aLine=br.readLine()) != null) {
                    // do what ever with every Line.
                }
            }
        }
    }
}

(5.3)Golang

使用github.com/bodgit/sevenzip

import (
	...
	"github.com/bodgit/sevenzip"
	...
)
...

sevenReader, err := sevenzip.OpenReader(SourceFile)
if err != nil {
	panic(err)
}
defer func(sevenReader *sevenzip.ReadCloser) {
	err := sevenReader.Close()
	if err != nil {
		panic(err)
	}
}(sevenReader)
for _, f := range sevenReader.File {
	if !f.FileInfo().IsDir() {
		inFile, err := f.Open()
		if err != nil {
			panic(err)
		}
		OneReader := bufio.NewReader(inFile)
		for {
			line, _, err := OneReader.ReadLine()
			if err == io.EOF {
				MyEof = true
				break
			}
			if err != nil {
				panic(err)
			}
			// do what ever with every Line.
		}
	}
}

(六)【Unix Z】格式

是Linux/Unix下古老的压缩格式,扩展名.z
单个压缩包内只能包含单个文件。

(6.1)Python

需要pip install unlzw3

import unlzw3
...
    unCompress = BytesIO(unlzw3.unlzw(Path(fileNameFull).read_bytes()))
    lines=unCompress.readlines()
    # do what ever with lines.

(6.2)Java

org.apache.commons.compress

 
 <dependency>
     <groupId>org.apache.commonsgroupId>
     <artifactId>commons-compressartifactId>
     <version>1.24.0version>
 dependency>
import org.apache.commons.compress.compressors.z.ZCompressorInputStream;
...

try(BufferedReader br = new BufferedReader(new InputStreamReader(new ZCompressorInputStream(Files.newInputStream(Paths.get(SourceFile))), Charset.forName("GBK"))))
{
    String aLine;
    while((aLine=br.readLine()) != null) {
        // do what ever with every Line.
    }
}

(6.3)Golang

github.com/hotei/dcompress

import (
	...
	"github.com/hotei/dcompress"
	...
)
...
fi, err := os.Open(filepath.Join(SourcePath, SourceFile))
if err != nil {
	panic(err)
}
defer fi.Close()
dcompress.Verbose = true
fz, err := dcompress.NewReader(OneIn)
if err != nil {
	panic(err)
}
OneReader := bufio.NewReader(fz)
for {
	line, _, err := OneReader.ReadLine()
	if err == io.EOF {
		MyEof = true
		break
	}
	if err != nil {
		panic(err)
	}
	// do what ever with every Line.
}

你可能感兴趣的:(Windows,Linux,General,Coding,ZIP,GZ,RAR,7Z,TAR)