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


    (零)前言

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

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

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

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

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

    带有⭐️的例子,能够完成“直接读文本行”的功能,其它例子得自己改。

    (一)【ZIP】格式

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

    (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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (1.2)Java ⭐️

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

    pom.xml中添加依赖:

    
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-compressartifactId>
        <version>1.24.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    .java代码文件中:

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    (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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    (1.4)Pascal

    (1.4.1)Lazarus(Free Pascal)

    在Lazarus(FPC)官方WIKI关于paszlib中有接近的方式:

    1. 不设置TUnZipper的OutputPath。
    2. 在事件中创建和读取Stream。
    3. 我们可以用自己的Steam来取得其中内容,再按行读取(虽然曲折,也算间接实现了吧)。

    FPC官方相关的代码如下(稍改就行):

    uses
      Zipper;
    
    ...
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      ExtractFileFromZip(FileNameEdit1.FileName,Edit1.Text);
    end;
    
    procedure TForm1.DoCreateOutZipStream(Sender: TObject; var AStream: TStream;
      AItem: TFullZipFileEntry);
    begin
      AStream:=TMemorystream.Create;
    end;
    
    procedure TForm1.DoDoneOutZipStream(Sender: TObject; var AStream: TStream;
      AItem: TFullZipFileEntry);
    begin
      AStream.Position:=0;
      Memo1.lines.LoadFromStream(Astream);
      Astream.Free;
    end;
    
    procedure TForm1.ExtractFileFromZip(ZipName, FileName: string);
    var
      ZipFile: TUnZipper;
      sl:TStringList;
    begin
      sl:=TStringList.Create;
      sl.Add(FileName);
      ZipFile := TUnZipper.Create;
      try
        ZipFile.FileName := ZipName;
        ZipFile.OnCreateStream := @DoCreateOutZipStream;
        ZipFile.OnDoneStream:=@DoDoneOutZipStream;
        ZipFile.UnZipFiles(sl);
      finally
        ZipFile.Free;
        sl.Free;
      end;
    end;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    (1.4.2)Delphi ⭐️

    新版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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    (1.5)C++

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

    如果是从实际的zip文件来处理,似乎可以这样。
    PS:同样是zlib提供的,gz可以按行读取(见下面gz的部分),而zip只能读数据块。
    ⚠️按行读字符串需要再判断一下\n的位置啥的,自己组成字符串。。

    unzFile zfile = unzOpen64(SourceFile);
    unz_global_info64 globalInfo;
    if (UNZ_OK != unzGoToFirstFile(zfile))
    {
    	return false;
    }
    
    char fileName[512] = { 0 };
    unz_file_info64 fileInfo;
    do
    {
    	if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &fileInfo, fileName, sizeof(fileName), nullptr, 0, nullptr, 0))
    	{
    		return false;
    	}
    	if (fileInfo.external_fa == FILE_ATTRIBUTE_DIRECTORY)	// 文件夹
    	{
    		//如果需要处理,就自己建目录啊。
    	}
    	else	// 普通文件
    	{
    		if (UNZ_OK != unzOpenCurrentFile(zfile))
    		{
    			return false;
    		}
    		int size = 0;
    		while (unzReadCurrentFile(zfile, Buffer, bufferSize) != NULL)
    		{
    			//do what ever with Buffer
    		}
    	}
    } while (unzGoToNextFile(zfile) != UNZ_END_OF_LIST_OF_FILE);
    unzClose(zfile);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    (二)【GZIP】格式

    是Linux/Unix下最常见的压缩格式。
    单个压缩包内只能包含单个文件

    • 可以保存压缩前的原始文件名的信息到meta data里。
    • 也可以不保存(去掉.gz后缀就是原始文件名)。

    (2.1)Python ⭐️

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

    import gzip
    ...
    with gzip.open(SourceFile,'r') as gzMy:
        lines=gzMy.readlines()
        # do what ever with lines.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (2.2)Java ⭐️

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

    
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-compressartifactId>
        <version>1.24.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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.
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (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.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    (2.4)Pascal

    (2.4.1)Lazarus(Free Pascal)

    也是官方的例子,似乎没有读一行的方式。
    不过改改也能用是吧(下面是张图片,代码请参考官方文档)。
    code

    (2.4.2)Delphi ⭐️

    新版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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (2.5)C++ ⭐️

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

    如果是从实际的gz文件来处理,似乎可以这样。
    PS:严谨些需要判断读到的内容长度是否为bufferSize-1(没到换行符呢),自己再处理一下。

    gzFile gzf = gzopen(SourceFile, "r");
    while (gzgets(gzf, Buffer, bufferSize)!=NULL)
    {
    	//do what ever with Buffer as char*
    }
    gzclose(gzf);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但如果来自流,比如下面的InputStream,那么代码就有点长了。

    ⚠️下面的代码没有提供readline这种方便的方式。
    需要自己把解开的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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    (三)【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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (3.2)Java ⭐️

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

    
    <dependency>
        <groupId>org.apache.antgroupId>
        <artifactId>antartifactId>
        <version>1.10.14version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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.
    				}
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (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.
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    (3.4)Pascal

    (3.4.1)Lazarus(Free Pascal)

    请参考官方文档

    (3.4.2)Delphi ⭐️

    新版Delphi可以这样:
    需要LibTar(Tar Library),也许还有其它实现方式,不太清楚。
    同上例子是单独的.tar文件。
    如果是tar.gz则和解gz串起来就好了。

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    (四)【RAR】格式

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

    (4.1)Python

    需要pip install rarfile
    用法和zip基本没区别,实测没成功,需要调用unrar.exe什么的。
    此项暂时保留(参考zipfile的方式真的几乎一样)。

    (4.2)Java

    com.github.junrar不能处理RAR5格式.
    net.sf.sevenzipjbinding可以处理RAR5,但直接用只能read数据块。

    • 用这种方法不仅可以读RAR,还可以解压非常多的格式,包括7Z,ZIP,TAR,GZ,ARJ,CAB,WIM,etc……💡
      如果仅仅需要解压成文件,那么单这一种方式就可以处理常见的所有压缩格式啦。

    • 下面取得文件名那部分和解压RAR无关,主要是gz格式有取不到文件名的情况(no metadata)。

    • 修改pom中的依赖为sevenzipjbinding-all-platforms,支持更多的平台(MAC,ARM……)💡。

    
    <dependency>
        <groupId>net.sf.sevenzipjbindinggroupId>
        <artifactId>sevenzipjbindingartifactId>
        <version>16.02-2.01version>
    dependency>
    
    
    <dependency>
        <groupId>net.sf.sevenzipjbindinggroupId>
        <artifactId>sevenzipjbinding-windows-amd64artifactId>
        <version>16.02-2.01version>
    dependency>
    
    
    <dependency>
        <groupId>net.sf.sevenzipjbindinggroupId>
        <artifactId>sevenzipjbinding-linux-amd64artifactId>
        <version>16.02-2.01version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    代码中的SevenZip.openInArchive(null...为啥用null,是为了自动检测格式。
    顺便吐槽匿名函数,不熟悉看不懂在干嘛。
    实际上是在处理ISequentialOutStream->write(byte[] data)

    import actp.tnu.api.CrossLog;
    import net.sf.sevenzipjbinding.ExtractOperationResult;
    import net.sf.sevenzipjbinding.IInArchive;
    import net.sf.sevenzipjbinding.SevenZip;
    import net.sf.sevenzipjbinding.SevenZipException;
    import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
    import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
    import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.RandomAccessFile;
    ...
    
    public static void Uncompress(File inputFile, String targetFileDir) throws Exception {
        File newdir = new File(targetFileDir);
        if (!newdir.exists() && !newdir.mkdirs()) {
            throw new Exception("Create Dir failed! : " + targetFileDir);
        }
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(inputFile, "r");
             IInArchive inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile))) {
            ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
            for (final ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
                if (!item.isFolder()) {
                    ExtractOperationResult result = item.extractSlow(data -> {
                        try {
                            String fileName = GetNameAndPathOK(inputFile, targetFileDir, item);
                            try (FileOutputStream fos = new FileOutputStream(targetFileDir + File.separator + fileName, true)) {
                                fos.write(data);
                                //change sth here, if you want to read line
                            }
                        } catch (Exception e) {
                            throw new SevenZipException(e.getMessage());
                        }
                        return data.length;
                    });
    
                    if (result != ExtractOperationResult.OK) {
                        //error
                    }
                }
            }
        }
    }
    
    private static String GetNameAndPathOK(File inputFile, String targetFileDir, ISimpleInArchiveItem item) throws Exception {
        String fileName = item.getPath();
        if (fileName == null || fileName.isEmpty()) {
            fileName = inputFile.getName().substring(0, inputFile.getName().lastIndexOf("."));
        }
        if (fileName.indexOf(File.separator) > 0) {
            String path = targetFileDir + File.separator + fileName.substring(0, fileName.lastIndexOf(File.separator));
            File newdir1 = new File(path);
            if (!newdir1.exists() && !newdir1.mkdirs()) {
                throw new Exception("Create Dir failed! : " + path);
            }
        }
        return fileName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    (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.
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    (五)【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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (5.2)Java ⭐️

    org.apache.commons.compress

     
     <dependency>
         <groupId>org.apache.commonsgroupId>
         <artifactId>commons-compressartifactId>
         <version>1.24.0version>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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.
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (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.
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    (六)【Unix Z】格式

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

    • 和gzip不同,Unix Z格式似乎不能保存原始文件名(去掉.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.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (6.2)Java ⭐️

    org.apache.commons.compress

     
     <dependency>
         <groupId>org.apache.commonsgroupId>
         <artifactId>commons-compressartifactId>
         <version>1.24.0version>
     dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    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.
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (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.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    Java8和Java9新特性
    贪心算法之——阶乘之和(nyoj91)
    回顾本轮行情,聊聊我的区块链投资逻辑 2021-04-25
    springboot+vue球员数据统计分析系统java
    媳妇面试了一家公司,期望月薪20K,对方没多问就答应了,只要求3天内到岗,可我总觉得哪里不对劲。
    【mia】3 本地rtc发布/推流:frame分发
    C语言数组在内存中是怎样表示的?
    Echarts-3D柱状图
    如何提高用户粘性,增强活跃度?
    第十节:继承【java】
  • 原文地址:https://blog.csdn.net/ddrfan/article/details/133812076