原文链接: http://snowolf.iteye.com/blog/648652
在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实现方式基本上没有差别! 先说归档,依旧需要考虑待归档的对象是文件还是目录:
Java代码 /** * 归档 * * @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 = "/";)。 如果是带有子文件的目录,则需要对其迭代归档: Java代码 /** * 目录归档 * * @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); } } 最后,来看归档操作: Java代码 /** * 数据归档 * * @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解压相似,一样要遍历获得归档项: Java代码 /** * 文件 解归档 * * @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); } } } 最后,进行解归档: Java代码 /** * 文件解归档 * * @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(); } 文件探针用于构建父目录: Java代码 /** * 文件探针 * * <pre> * 当父目录不存在时,创建目录! * </pre> * * @param dirFile */ private static void fileProber(File dirFile) { File parentFile = dirFile.getParentFile(); if (!parentFile.exists()) { // 递归寻找上级目录 fileProber(parentFile); parentFile.mkdir(); } } 给出完整实现: Java代码 /** * 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(); } } } 最后给出测试用例: Java代码 /** * 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,请朋友们参照前几篇内容!