`

Java压缩技术(七) TAR——Commons实现

阅读更多
在linux下,tar是一个归档命令。当然,如果配合gzip、bzip2就可以达到归档+压缩的效果!
我们通过tar获得归档压缩文件其实恰恰包含了归档压缩两个操作,并且其操作次序也是先做归档操作,再做压缩操作! 通常我们忽略了归档的概念,将归档压缩文件简称为压缩文件!~

相关链接:
Java压缩技术(一) ZLib
Java压缩技术(二) ZIP压缩——Java原生实现
Java压缩技术(三) ZIP解压缩——Java原生实现
Java压缩技术(四) GZIP——Java原生实现
Java压缩技术(五) GZIP相关——浏览器解析
Java压缩技术(六) BZIP2——Commons实现
Java压缩技术(七) TAR——Commons实现

顺便复习一遍linux命令:
tar cf <file.tar> <file>将由文件<file>创建名为<file.tar>归档文件,同时保留原文件。
tar xf <file.tar>将由归档文件<file.tar>创建名为<file>的文件/目录,同时保留原文件。

对于归档压缩,需分为gzip和bzip2,相应的linux为:
gzip
tar czf <file.tar.gz> <file>将由文件<file>创建名为<file.tar.gz>归档压缩文件,同时保留原文件。
tar xzf <file.tar.gz>将由归档压缩文件<file.tar.gz>创建名为<file>的文件/目录,同时保留原文件。

bzip2
tar cjf <file.tar.bz2> <file>将由文件<file>创建名为<file.tar.bz2>归档压缩文件,同时保留原文件。
tar xjf <file.tar.bz2>将由归档压缩文件<file.tar.bz2>创建名为<file>的文件/目录,同时保留原文件。

今天的主角是Apache Commons Compress下用于Tar操作的三元干将
TarArchiveEntry 类似于Java 原生的ZipEntry,可以理解为Tar归档添加项。
TarArchiveOutputStream Tar归档输出流,用于归档。
TarArchiveInputStream Tar归档输入流,用于解归档。

至于jar,其实现方式与tar非常接近,我就不在这里废话了!
JarArchiveEntry 类似于Java 原生的ZipEntry,可以理解为Jar归档添加项。
JarArchiveOutputStream Jar归档输出流,用于归档。
JarArchiveInputStream Jar归档输入流,用于解归档。

读过Java压缩技术(二)Java压缩技术(三)会发现,其实Tar的实现与Java原生的Zip实现方式基本上没有差别!
先说归档,依旧需要考虑待归档的对象是文件还是目录:
	/**
	 * 归档
	 * 
	 * @param srcFile
	 *            源路径
	 * @param taos
	 *            TarArchiveOutputStream
	 * @param basePath
	 *            归档包内相对路径
	 * @throws Exception
	 */
	private static void archive(File srcFile, TarArchiveOutputStream taos,
			String basePath) throws Exception {
		if (srcFile.isDirectory()) {
			archiveDir(srcFile, taos, basePath);
		} else {
			archiveFile(srcFile, taos, basePath);
		}
	}

对于目录,需要区分空目录和包含文件的目录。
如果是空目录,只要简单追加一个归档项(TarArchiveEntry)即可,但注意其名字的结尾需要使用"/"作为区分目录的标识符(String PATH = "/";)。
如果是带有子文件的目录,则需要对其迭代归档:
	/**
	 * 目录归档
	 * 
	 * @param dir
	 * @param taos
	 *            TarArchiveOutputStream
	 * @param basePath
	 * @throws Exception
	 */
	private static void archiveDir(File dir, TarArchiveOutputStream taos,
			String basePath) throws Exception {

		File[] files = dir.listFiles();

		if (files.length < 1) {
			TarArchiveEntry entry = new TarArchiveEntry(basePath
					+ dir.getName() + PATH);

			taos.putArchiveEntry(entry);
			taos.closeArchiveEntry();
		}

		for (File file : files) {

			// 递归归档
			archive(file, taos, basePath + dir.getName() + PATH);

		}
	}

最后,来看归档操作:
	/**
	 * 数据归档
	 * 
	 * @param data
	 *            待归档数据
	 * @param path
	 *            归档数据的当前路径
	 * @param name
	 *            归档文件名
	 * @param taos
	 *            TarArchiveOutputStream
	 * @throws Exception
	 */
	private static void archiveFile(File file, TarArchiveOutputStream taos,
			String dir) throws Exception {

		TarArchiveEntry entry = new TarArchiveEntry(dir + file.getName());

		entry.setSize(file.length());

		taos.putArchiveEntry(entry);

		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
				file));
		int count;
		byte data[] = new byte[BUFFER];
		while ((count = bis.read(data, 0, BUFFER)) != -1) {
			taos.write(data, 0, count);
		}

		bis.close();

		taos.closeArchiveEntry();
	}

注意执行归档操作后,执行closeArchiveEntry()方法。
Tar解归档,与Zip解压相似,一样要遍历获得归档项:
	/**
	 * 文件 解归档
	 * 
	 * @param destFile
	 *            目标文件
	 * @param tais
	 *            ZipInputStream
	 * @throws Exception
	 */
	private static void dearchive(File destFile, TarArchiveInputStream tais)
			throws Exception {

		TarArchiveEntry entry = null;
		while ((entry = tais.getNextTarEntry()) != null) {

			// 文件
			String dir = destFile.getPath() + File.separator + entry.getName();

			File dirFile = new File(dir);

			// 文件检查
			fileProber(dirFile);

			if (entry.isDirectory()) {
				dirFile.mkdirs();
			} else {
				dearchiveFile(dirFile, tais);
			}

		}
	}

最后,进行解归档:
	/**
	 * 文件解归档
	 * 
	 * @param destFile
	 *            目标文件
	 * @param tais
	 *            TarArchiveInputStream
	 * @throws Exception
	 */
	private static void dearchiveFile(File destFile, TarArchiveInputStream tais)
			throws Exception {

		BufferedOutputStream bos = new BufferedOutputStream(
				new FileOutputStream(destFile));

		int count;
		byte data[] = new byte[BUFFER];
		while ((count = tais.read(data, 0, BUFFER)) != -1) {
			bos.write(data, 0, count);
		}

		bos.close();
	}

文件探针用于构建父目录:
	/**
	 * 文件探针
	 * 
	 * <pre>
	 * 当父目录不存在时,创建目录!
	 * </pre>
	 * 
	 * @param dirFile
	 */
	private static void fileProber(File dirFile) {

		File parentFile = dirFile.getParentFile();
		if (!parentFile.exists()) {

			// 递归寻找上级目录
			fileProber(parentFile);

			parentFile.mkdir();
		}

	}

给出完整实现:
/**
 * 2010-4-20
 */
package org.zlex.commons.compress;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;

/**
 * TAR工具
 * 
 * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a>
 * @since 1.0
 */
public abstract class TarUtils {

	private static final String BASE_DIR = "";

	// 符号"/"用来作为目录标识判断符
	private static final String PATH = "/";
	private static final int BUFFER = 1024;

	private static final String EXT = ".tar";

	/**
	 * 归档
	 * 
	 * @param srcPath
	 * @param destPath
	 * @throws Exception
	 */
	public static void archive(String srcPath, String destPath)
			throws Exception {

		File srcFile = new File(srcPath);

		archive(srcFile, destPath);

	}

	/**
	 * 归档
	 * 
	 * @param srcFile
	 *            源路径
	 * @param destPath
	 *            目标路径
	 * @throws Exception
	 */
	public static void archive(File srcFile, File destFile) throws Exception {

		TarArchiveOutputStream taos = new TarArchiveOutputStream(
				new FileOutputStream(destFile));

		archive(srcFile, taos, BASE_DIR);

		taos.flush();
		taos.close();
	}

	/**
	 * 归档
	 * 
	 * @param srcFile
	 * @throws Exception
	 */
	public static void archive(File srcFile) throws Exception {
		String name = srcFile.getName();
		String basePath = srcFile.getParent();
		String destPath = basePath + name + EXT;
		archive(srcFile, destPath);
	}

	/**
	 * 归档文件
	 * 
	 * @param srcFile
	 * @param destPath
	 * @throws Exception
	 */
	public static void archive(File srcFile, String destPath) throws Exception {
		archive(srcFile, new File(destPath));
	}

	/**
	 * 归档
	 * 
	 * @param srcPath
	 * @throws Exception
	 */
	public static void archive(String srcPath) throws Exception {
		File srcFile = new File(srcPath);

		archive(srcFile);
	}

	/**
	 * 归档
	 * 
	 * @param srcFile
	 *            源路径
	 * @param taos
	 *            TarArchiveOutputStream
	 * @param basePath
	 *            归档包内相对路径
	 * @throws Exception
	 */
	private static void archive(File srcFile, TarArchiveOutputStream taos,
			String basePath) throws Exception {
		if (srcFile.isDirectory()) {
			archiveDir(srcFile, taos, basePath);
		} else {
			archiveFile(srcFile, taos, basePath);
		}
	}

	/**
	 * 目录归档
	 * 
	 * @param dir
	 * @param taos
	 *            TarArchiveOutputStream
	 * @param basePath
	 * @throws Exception
	 */
	private static void archiveDir(File dir, TarArchiveOutputStream taos,
			String basePath) throws Exception {

		File[] files = dir.listFiles();

		if (files.length < 1) {
			TarArchiveEntry entry = new TarArchiveEntry(basePath
					+ dir.getName() + PATH);

			taos.putArchiveEntry(entry);
			taos.closeArchiveEntry();
		}

		for (File file : files) {

			// 递归归档
			archive(file, taos, basePath + dir.getName() + PATH);

		}
	}

	/**
	 * 数据归档
	 * 
	 * @param data
	 *            待归档数据
	 * @param path
	 *            归档数据的当前路径
	 * @param name
	 *            归档文件名
	 * @param taos
	 *            TarArchiveOutputStream
	 * @throws Exception
	 */
	private static void archiveFile(File file, TarArchiveOutputStream taos,
			String dir) throws Exception {

		/**
		 * 归档内文件名定义
		 * 
		 * <pre>
		 * 如果有多级目录,那么这里就需要给出包含目录的文件名
		 * 如果用WinRAR打开归档包,中文名将显示为乱码
		 * </pre>
		 */
		TarArchiveEntry entry = new TarArchiveEntry(dir + file.getName());

		entry.setSize(file.length());

		taos.putArchiveEntry(entry);

		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
				file));
		int count;
		byte data[] = new byte[BUFFER];
		while ((count = bis.read(data, 0, BUFFER)) != -1) {
			taos.write(data, 0, count);
		}

		bis.close();

		taos.closeArchiveEntry();
	}

	/**
	 * 解归档
	 * 
	 * @param srcFile
	 * @throws Exception
	 */
	public static void dearchive(File srcFile) throws Exception {
		String basePath = srcFile.getParent();
		dearchive(srcFile, basePath);
	}

	/**
	 * 解归档
	 * 
	 * @param srcFile
	 * @param destFile
	 * @throws Exception
	 */
	public static void dearchive(File srcFile, File destFile) throws Exception {

		TarArchiveInputStream tais = new TarArchiveInputStream(
				new FileInputStream(srcFile));
		dearchive(destFile, tais);

		tais.close();

	}

	/**
	 * 解归档
	 * 
	 * @param srcFile
	 * @param destPath
	 * @throws Exception
	 */
	public static void dearchive(File srcFile, String destPath)
			throws Exception {
		dearchive(srcFile, new File(destPath));

	}

	/**
	 * 文件 解归档
	 * 
	 * @param destFile
	 *            目标文件
	 * @param tais
	 *            ZipInputStream
	 * @throws Exception
	 */
	private static void dearchive(File destFile, TarArchiveInputStream tais)
			throws Exception {

		TarArchiveEntry entry = null;
		while ((entry = tais.getNextTarEntry()) != null) {

			// 文件
			String dir = destFile.getPath() + File.separator + entry.getName();

			File dirFile = new File(dir);

			// 文件检查
			fileProber(dirFile);

			if (entry.isDirectory()) {
				dirFile.mkdirs();
			} else {
				dearchiveFile(dirFile, tais);
			}

		}
	}

	/**
	 * 文件 解归档
	 * 
	 * @param srcPath
	 *            源文件路径
	 * 
	 * @throws Exception
	 */
	public static void dearchive(String srcPath) throws Exception {
		File srcFile = new File(srcPath);

		dearchive(srcFile);
	}

	/**
	 * 文件 解归档
	 * 
	 * @param srcPath
	 *            源文件路径
	 * @param destPath
	 *            目标文件路径
	 * @throws Exception
	 */
	public static void dearchive(String srcPath, String destPath)
			throws Exception {

		File srcFile = new File(srcPath);
		dearchive(srcFile, destPath);
	}

	/**
	 * 文件解归档
	 * 
	 * @param destFile
	 *            目标文件
	 * @param tais
	 *            TarArchiveInputStream
	 * @throws Exception
	 */
	private static void dearchiveFile(File destFile, TarArchiveInputStream tais)
			throws Exception {

		BufferedOutputStream bos = new BufferedOutputStream(
				new FileOutputStream(destFile));

		int count;
		byte data[] = new byte[BUFFER];
		while ((count = tais.read(data, 0, BUFFER)) != -1) {
			bos.write(data, 0, count);
		}

		bos.close();
	}

	/**
	 * 文件探针
	 * 
	 * <pre>
	 * 当父目录不存在时,创建目录!
	 * </pre>
	 * 
	 * @param dirFile
	 */
	private static void fileProber(File dirFile) {

		File parentFile = dirFile.getParentFile();
		if (!parentFile.exists()) {

			// 递归寻找上级目录
			fileProber(parentFile);

			parentFile.mkdir();
		}

	}

}


最后给出测试用例:
/**
 * 2010-4-20
 */
package org.zlex.commons.compress;

import static org.junit.Assert.*;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.junit.Before;
import org.junit.Test;

/**
 * Tar测试
 * 
 * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a>
 * @since 1.0
 */
public class TarUtilsTest {
	private String inputStr;
	private String name = "data.xml";

	@Before
	public void before() {
		StringBuilder sb = new StringBuilder();
		sb.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
		sb.append("\r\n");
		sb.append("<dataGroup>");
		sb.append("\r\n\t");
		sb.append("<dataItem>");
		sb.append("\r\n\t\t");
		sb.append("<data>");
		sb.append("Test");
		sb.append("</data>");
		sb.append("\r\n\t");
		sb.append("<dataItem>");
		sb.append("\r\n");
		sb.append("</dataGroup>");

		inputStr = sb.toString();
	}

	@Test
	public void testArchiveFile() throws Exception {

		byte[] contentOfEntry = inputStr.getBytes();

		String path = "d:/" + name;

		FileOutputStream fos = new FileOutputStream(path);

		fos.write(contentOfEntry);
		fos.flush();
		fos.close();

		TarUtils.archive(path);

		TarUtils.dearchive(path + ".tar");

		File file = new File(path);

		FileInputStream fis = new FileInputStream(file);

		DataInputStream dis = new DataInputStream(fis);

		byte[] data = new byte[(int) file.length()];

		dis.readFully(data);

		fis.close();

		String outputStr = new String(data);
		assertEquals(inputStr, outputStr);

	}

	@Test
	public void testArchiveDir() throws Exception {
		String path = "d:/fd";
		TarUtils.archive(path);

		TarUtils.dearchive(path + ".tar", "d:/fds");
	}

}

执行代码,来看下效果:

这是原始文件。

这是归档后的文件。
注意红框,这里没有经过任何压缩!
除了tar、zip,其实还有很多归档算法,如ar、jar、cpio。其实现方式,与上述内容较为接近。
至于压缩成*.tar.gz、*.tar.bz2,请朋友们参照前几篇内容!

完整实现见附件!

相关链接:
Java压缩技术(一) ZLib
Java压缩技术(二) ZIP压缩——Java原生实现
Java压缩技术(三) ZIP解压缩——Java原生实现
Java压缩技术(四) GZIP——Java原生实现
Java压缩技术(五) GZIP相关——浏览器解析
Java压缩技术(六) BZIP2——Commons实现
Java压缩技术(七) TAR——Commons实现

  • 大小: 33.9 KB
  • 大小: 73.1 KB
7
2
分享到:
评论
5 楼 yangyanglixiao 2017-09-19  
ywskin 写道
commons compress 生成tar包,长文件名怎么处理啊?在windows下taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)倒可以,但是主机上,解开有问题。怎么解决啊?


在你的输出流中添加前缀:
TarArchiveOutputStream stream = new TarArchiveOutputStream(...)
stream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX)
参考:https://stackoverflow.com/questions/32528799/when-i-tar-a-file-its-throw-exception-as-is-too-long-100-bytes-tararchiveo
和http://commons.apache.org/proper/commons-compress/tar.html#Long_File_Names
4 楼 yanmie 2015-01-11  
您的被系列文章已被本站收录(已注明出处)
地址:http://www.airmyth.com/forum.php?mod=viewthread&tid=812
3 楼 ywskin 2011-11-24  
commons compress 生成tar包,长文件名怎么处理啊?在windows下taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)倒可以,但是主机上,解开有问题。怎么解决啊?
2 楼 snowolf 2010-04-24  
spiritfrog 写道
如果目录深的话,归档会有文件名过长的问题。请问该如何解决?

好问题!
commons compress目前只支持160个字符的文件名长度~
1 楼 spiritfrog 2010-04-24  
如果目录深的话,归档会有文件名过长的问题。请问该如何解决?

相关推荐

Global site tag (gtag.js) - Google Analytics