文件归档就是将多个小文件归档形成一个大的文件,然后在进行解归档的算法进行还原。
直接看源代码:
核心归档文件类:
package com.wang.archiver;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
/**
这里是具体算法:
* 将一个文件夹下的所有文件归档成为一个文件 如果用户有多个文件,可以将其放在一个文夹下,进行归档 格式如下: 前4字节,为该文件夹名长度
* 接下来的若干字节为文件夹名 然后4个字节为该文件夹下文件个数(不包括文件夹) 接着的四个字节为第一个文件名的长度, 接下来若干字节为文件名
* 接着四个字节为文件长度,因此这个只能归档不超过4G(2的32次方)的单个文件 然后是文件夹的个数 接着的四个字节为第一个文件夹名的长度,
* 接下来若干字节为文件夹名 接着四个字节为文件夹下文件个数,从而形成递归
*
* @author root
*
*/
public class Archiver {
private static final Logger LOG = Logger.getLogger(Archiver.class);
/**
* 默认的缓冲区大小
*/
private static final int DEFALUT_SIZE = 10 * 1024;
/**
* 归档文件后缀
*/
private static final String POSTFIX = ".yar";
/**
* 文件夹分隔符
*/
private static final String separator = "/";
/**
* 文件输出流
*/
private BufferedOutputStream out;
/**
* 归档文件输入流
*/
private BufferedInputStream in;
/**
* 根据文件夹的前缀获取归档文件的路径
*
* @param srcPath
* 目标文件绝对路径
*/
public void createNewArchiver(String srcPath) {
String destPath = Utils.getPrefix(srcPath);
createNewArchiver(srcPath, destPath);
}
/**
* 创建归档文件
*
* @param srcPath
* 目标文件绝对路径
* @param destPath
* 归档文件存放目录
*/
public void createNewArchiver(String srcPath, String destPath) {
File src = new File(srcPath);
if (!destPath.contains(POSTFIX)) {
String name = src.getName();
destPath = destPath + separator + name + POSTFIX;
}
try {
out = new BufferedOutputStream(new FileOutputStream(destPath));
} catch (FileNotFoundException e) {
LOG.error("destPath is exists! ", e);
}
setFileNameHead(src);
if (!src.exists()) {
LOG.warn("this file isn't exists!");
} else if (src.isDirectory()) {
handleDir(src);
} else if (src.isFile()) {
LOG.warn("file don't need archiver!");
}
// 归档完成,关闭输出流
Utils.close(out);
}
/**
* 设置单个文件的文件头
*
* @param src
* 文件
*/
private void setFileNameHead(File src) {
String fileName = src.getName();
byte[] nameBytes = fileName.getBytes();
byte[] lens = Utils.intToBytes(nameBytes.length);
Utils.write(out, lens, nameBytes);
}
/**
* 处理归档文件夹
*
* @param src
*/
private void handleDir(File src) {
List<File> files = new ArrayList<>();
List<File> dirs = new ArrayList<>();
for (File file : src.listFiles()) {
if (file.isDirectory()) {
dirs.add(file);
} else if (file.isFile()) {
files.add(file);
}
}
handlerFiles(files);
handlerDirs(dirs);
}
/**
* 处理各个文件夹
*
* @param dirs
* 文件夹
*/
private void handlerDirs(List<File> dirs) {
setContentHead(dirs.size());
for (File dir : dirs) {
setFileNameHead(dir);
handleDir(dir);
}
}
/**
* 处理各个文件
*
* @param files
* 文件
*/
private void handlerFiles(List<File> files) {
setContentHead(files.size());
for (File file : files) {
writeOneFile(file);
}
}
/**
* 写出单个文件
*
* @param file
* 文件
*/
private void writeOneFile(File file) {
setFileOneHead(file);
BufferedInputStream in = null;
try {
in = new BufferedInputStream(new FileInputStream(file));
byte[] buf = new byte[DEFALUT_SIZE];
int len = -1;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.flush();
} catch (IOException e) {
LOG.error("read file error", e);
} finally {
Utils.close(in);
}
}
/**
* 设置单个文件的文件头
*
* @param file
*/
private void setFileOneHead(File file) {
setFileNameHead(file);
// 文件内容最大为Integer.MAX_VALUE
setContentHead((int) file.length());
}
/**
* 设置文件的头
*
* @param size
*/
private void setContentHead(int size) {
byte[] fileNums = Utils.intToBytes(size);
Utils.write(out, fileNums);
}
/**
* 判断传入的文件是否存在
*
* @param src
* @return
*/
private boolean isExists(File src) {
if (!src.exists()) {
LOG.warn("file isn't exists");
return false;
} else {
return true;
}
}
public void unArchiver(String srcPath) {
File src = new File(srcPath);
String destPath = src.getParent();
unArchiver(src, destPath);
}
public void unArchiver(String srcPath, String destPath) {
unArchiver(new File(srcPath), destPath);
}
/**
* 解归档文件
*
* @param src
* @param destPath
*/
public void unArchiver(File src, String destPath) {
if (!isExists(src)) {
return;
}
/*String fullPath = destPath;
String fileName = Utils.getPrefix(src.getName());
if(!destPath.endsWith(fileName)){
fullPath += separator + fileName;
}*/
File dest = new File(destPath);
dest.mkdirs();
try {
in = new BufferedInputStream(new FileInputStream(src));
String dirName = readName();
splitDir(destPath + separator + dirName);
} catch (IOException e) {
LOG.error("file not found !", e);
} finally {
// 解归档完成,关闭输入流
Utils.close(in);
}
}
/**
* 获取一个归档文件的绝对路径
*
* @param dirPath
*/
private void splitDir(String dirPath) {
new File(dirPath).mkdirs();
int filelen = Utils.read(in);
for (int i = 0; i < filelen; i++) {
String fileName = readName();
splitFile(dirPath + separator + fileName);
}
int dirlen = Utils.read(in);
for (int i = 0; i < dirlen; i++) {
String dirName = readName();
splitDir(dirPath + separator + dirName);
}
}
/**
* 解压一个归档文件
*
* @param dest
*/
private void splitFile(String dest) {
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(dest));
int len = Utils.read(in);
int bufsize = 0;
int count = 1;
if (len < DEFALUT_SIZE) {
bufsize = len;
} else {
count = len / DEFALUT_SIZE + 1;
bufsize = DEFALUT_SIZE;
}
byte[] buf = new byte[bufsize];
while (count > 0) {
// 如果是最后一个改变缓冲区大小
if (count == 1) {
bufsize = len % DEFALUT_SIZE;
buf = new byte[bufsize];
}
in.read(buf);
out.write(buf);
count--;
}
out.flush();
} catch (Exception e) {
LOG.error(e);
} finally {
Utils.close(out);
}
}
/**
* 从输出流中读取字符串
*
* @return
*/
private String readName() {
int len = Utils.read(in);
byte[] buf = new byte[len];
try {
in.read(buf);
} catch (IOException e) {
LOG.error("file not found !", e);
}
return new String(buf, 0, len);
}
}
工具类:
package com.wang.archiver;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.log4j.Logger;
/**
* @author root 归档类使用的工具类
*/
public class Utils {
private static final Logger LOG = Logger.getLogger(Utils.class);
/**
* 关闭流
*
* @param io
*/
public static void close(Closeable... io) {
for (Closeable temp : io) {
try {
if (temp != null) {
temp.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] shortToBytes(short s) {
byte[] bytes = new byte[2];
bytes[0] = (byte) (s);
bytes[1] = (byte) (s >> 8);
return bytes;
}
public static short bytesToShort(byte[] bytes) {
int s0 = bytes[0] & 0xFF;
int s1 = (bytes[1] & 0xFF) << 8;
return (short) (s0 + s1);
}
/**
* 将一个字节数组转为int
*
* @param bytes
* @return
*/
public static int bytesToInt(byte[] bytes) {
int i0 = bytes[0] & 0xFF;
int i1 = (bytes[1] & 0xFF) << 8;
int i2 = (bytes[2] & 0xFF) << 16;
int i3 = (bytes[3] & 0xFF) << 24;
return i0 + i1 + i2 + i3;
}
/**
* 将一个int转为字节数组
*
* @param i
* @return
*/
public static byte[] intToBytes(int i) {
byte[] bytes = new byte[4];
bytes[0] = (byte) i;
bytes[1] = (byte) (i >> 8);
bytes[2] = (byte) (i >> 16);
bytes[3] = (byte) (i >> 24);
return bytes;
}
/**
* 获取文件路径的前缀
*
* @param filePath
* @return
*/
public static String getPrefix(String filePath) {
int index = filePath.lastIndexOf('.');
return filePath.substring(0, index);
}
/**
* 向一个输出流中写出byte[]
*
* @param out
* @param data
*/
public static void write(OutputStream out, byte[]... data) {
try {
for (byte[] bs : data) {
out.write(bs);
}
} catch (IOException e) {
LOG.error("output stream write error ! ", e);
}
}
/**
* 从一个输入流中读取一个int数字
*
* @param in
* @return
*/
public static int read(InputStream in) {
byte[] buf = new byte[4];
try {
in.read(buf);
} catch (IOException e) {
LOG.error("input stream read error ! ", e);
}
return bytesToInt(buf);
}
}
测试类:
package com.wang.archiver;
public class App {
public static void main(String[] args) {
Archiver archiver = new Archiver();
if(args.length != 2){
new Exception("参数长度是2,第一个参数是归档文件夹,第二个是归档文件夹存放目录!");
return;
}
archiver.createNewArchiver(args[0],args[1]);
}
}
测试类2:
package com.wang.archiver;
public class App2 {
public static void main(String[] args) {
Archiver archiver = new Archiver();
if(args.length != 2){
new Exception("参数长度是2,第一个参数是归档文件,第二个是解归档文件夹存放目录!");
return;
}
archiver.unArchiver(args[0],args[1]);
}
}