设计思路
设计好需要存储文件的协议,根据文件协议写入个个文件,然后根据写入的顺序,读取需要的文件二进制数组
文件协议
这里将合并后的文件设计成三部分
- 文件头,存储文件相对路径、文件大小、文件写入文件体的起始位置
- 文件头长度,将文件头转化为二进制数据后的长度,是long类型,将此长度转化为定长数组
- 根据文件头写入的顺序依次写入文件内容
- 如图所示
具体实现
- 文件写入实现:
通过文件夹路径,得到文件夹下所有文件集合,集合用LinkedList 保存,保持集合的有序性
/**
* 得到源文件路径下的所有文件
* @param dirFile 源文件路径
* */
public static List<File> getAllFile(File dirFile){
List<File> fileList=new ArrayList<>();
File[] files= dirFile.listFiles();
for(File file:files){//文件
if(file.isFile()){
fileList.add(file);
//System.out.println("add file:"+file.getPath());
}else {//目录
if(file.listFiles().length!=0){//非空目录
fileList.addAll(getAllFile(file));//把递归文件加到fileList中
}else {//空目录
fileList.add(file);
//System.out.println("add empty dir:"+file.getPath());
}
}
}
return fileList;
}
设计文件头存储对象
/**
* 文件相对路径
*/
private String relativePath;
/**
* 文件开始位置
*/
private long start;
/**
* 文件大小
*/
private long size;
遍历集合,得到文件头信息,根据文件头集合,将文件头集合转成byte[]
/**
* 将Object数组转化为二进制数据
* @param heads
* @return
*/
public static byte[] getEntryByte(List<Head> heads){
try {
ByteArrayOutputStream byt=new ByteArrayOutputStream();
ObjectOutputStream obj=new ObjectOutputStream(byt);
obj.writeObject(heads);
byte[] bytes=byt.toByteArray();
return bytes;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
然后将文件头长度,转化成bety[],下面方法就是实现long类型和byte[]的互相转换
/**
* long 类型转化bytes数组
* @param l
* @return
*/
public static byte[] getByteByLong(long l){
byte[] bytes = new byte[8];
for (int i = 0; i < bytes.length; i++)
bytes[i] = (byte)(long)(((l) >> i * 8) & 0xff); // 移位和清零
return bytes;
}
/**
* byte数组转化为long
* @param bytes
* @return
*/
public static long getLongByBytes(byte[] bytes){
long longa = 0;
for (int i = 0; i < bytes.length; i++)
longa += (long)((bytes[i] & 0xff) << i * 8); // 移位和清零
return longa;
}
最后将 文件头长度、文件头、文件内容依次写入一个新的文件(资源包)中
List<File> files = FileUtils.getAllFile(new File(filePath));
byte[] head = getHeadBytes(files,filePath);
long headLength = head.length;
byte[] headLengthByte = new byte[8];
headLengthByte=FileUtils.getByteByLong(headLength);
//存储文件流的位置
File res = new File(targetFile);
BufferedOutputStream stream = null;
//写入文件头
FileOutputStream fstream = new FileOutputStream(res);
stream = new BufferedOutputStream(fstream);
stream.write(headLengthByte);
stream.write(head);
//写入文件
InputStream fis=null;
for (File f:files){
fis= new FileInputStream(f);
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = fis.read(buffer))) {
stream.write(buffer, 0, n);
}
System.out.println("写入文件:"+f.getPath());
}
stream.flush();
stream.close();
- 文件读取实现:这里用到 RandomAccessFile 类,该类实现了从文件指定位置读取指定长度的字节;具体步骤,先读取8个字节。这部分为文件头长度部分,然后根据此长度得到文件头信息;然后根据文件相对路径名找出文件读取的起始位置和文件大小,然后输出
/**
* 读取指定位置,指定长度的字节
* @param rf
* @param start 从哪里读取
* @param size 读取多少
* @return
*/
public static byte[] getBytesFromRf(RandomAccessFile rf,long start,long size){
byte[] data = new byte[(int)size];
try {
rf.seek(start);
rf.read(data);
return data;
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
详细代码
合并文件主体代码
package com.verification.main;
import com.verification.entry.Head;
import com.verification.utils.FileUtils;
import java.io.*;
import java.util.LinkedList;
import java.util.List;
public class PackFile {
/**
* @param filePath
* @param targetFile
*/
public static void packFile(String filePath,String targetFile){
try {
File target = new File(targetFile);
String targetPath = target.getParent();
File targetFolder = new File(targetPath);
if ( ! targetFolder.exists() || !targetFolder.isDirectory()){
if (!targetFolder.mkdir()){
System.out.println("打包后存放文件的路径输入不合法");
return;
}
}
List<File> files = FileUtils.getAllFile(new File(filePath));
byte[] head = getHeadBytes(files,filePath);
long headLength = head.length;
byte[] headLengthByte = new byte[8];
headLengthByte=FileUtils.getByteByLong(headLength);
//存储文件流的位置
File res = new File(targetFile);
BufferedOutputStream stream = null;
//写入文件头
FileOutputStream fstream = new FileOutputStream(res);
stream = new BufferedOutputStream(fstream);
stream.write(headLengthByte);
stream.write(head);
//写入文件
InputStream fis=null;
for (File f:files){
fis= new FileInputStream(f);
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = fis.read(buffer))) {
stream.write(buffer, 0, n);
}
System.out.println("写入文件:"+f.getPath());
}
stream.flush();
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成文件头部信息
* @param files
* @param filePath
* @return
*/
public static byte[] getHeadBytes(List<File> files,String filePath){
List<Head> heads = new LinkedList<Head>();
long cursor=0;
for(File f:files){
Head head = new Head();
String relativePath = f.getPath().replace(filePath,"");
head.setRelativePath(relativePath);
long fileLength=f.length();
head.setSize(fileLength);
head.setStart(cursor);
heads.add(head);
cursor=cursor+fileLength;
}
return FileUtils.getEntryByte(heads);
}
/**
* 根据路径判断是否为相对路径,如果是相对路径则转成其绝对路径
* @param filePath
* @return
*/
public static String getFilePath(String filePath){
File f = new File(filePath);
if (f.isAbsolute()){
return filePath;
}else{
String rootPath = System.getProperty("user.dir");
if (filePath.startsWith("../")){
//上层目录
File f1 = new File(rootPath);
return f1.getParent()+File.separator+filePath.replace("../","");
} else if (filePath.startsWith("./")){
//当前目录
return rootPath+File.separator+filePath.replace("./","");
}else{
// 不是已 ./ ../开头的,当作当前目录
return rootPath+File.separator+filePath.replace("./","");
}
}
}
/**
* 主函数
* @param args
*/
public static void main(String[] args){
//getFilePath("."); java -jar PackFile.jar /Users/hjd/Desktop/test1 /Users/hjd/Desktop/test/result3/result.entry
//System.out.println(getNewName("../out?222&"));
//System.out.println(getFilePath("../out"));
String filePath="/Users/hjd/Desktop/test1";
String targetfile="/Users/hjd/Desktop/test/result3/ssss.entry";
//System.out.println(new File("/Users/hjd/Desktop/test/result3/ssss.entry").getParent());
if (args.length<1){
System.out.println("参数输入不正确,请至少输入需要打包的文件夹路径");
return;
}else if (args.length==1){
System.out.println("未输入打包后文件路径,这里默认生成 文件 packFile.entry 在打包目录下");
filePath=args[0];
targetfile =filePath+File.separator+"packFile.entry";
}else{
filePath=args[0];
targetfile=args[1];
}
filePath = getFilePath(filePath);
targetfile = getFilePath(targetfile);
packFile(filePath,targetfile);
}
}
操作工具类
package com.verification.utils;
import com.verification.entry.Head;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 文件操作工具类
*/
public class FileUtils {
/**
* 得到源文件路径下的所有文件
* @param dirFile 源文件路径
* */
public static List<File> getAllFile(File dirFile){
List<File> fileList=new ArrayList<>();
File[] files= dirFile.listFiles();
for(File file:files){//文件
if(file.isFile()){
fileList.add(file);
//System.out.println("add file:"+file.getPath());
}else {//目录
if(file.listFiles().length!=0){//非空目录
fileList.addAll(getAllFile(file));//把递归文件加到fileList中
}else {//空目录
fileList.add(file);
//System.out.println("add empty dir:"+file.getPath());
}
}
}
return fileList;
}
/**
* 将Object数组转化为二进制数据
* @param heads
* @return
*/
public static byte[] getEntryByte(List<Head> heads){
try {
ByteArrayOutputStream byt=new ByteArrayOutputStream();
ObjectOutputStream obj=new ObjectOutputStream(byt);
obj.writeObject(heads);
byte[] bytes=byt.toByteArray();
return bytes;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 将文件流转化成二进制数组
* @param input
* @return
* @throws IOException
*/
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}
/**
* 将文件流转化成二进制数组
* @param rf
* @return
* @throws IOException
*/
public static byte[] toByteArrayByRandomAccess(RandomAccessFile rf,long length) throws IOException {
byte[] data = new byte[(int)length];
Lock lock = new ReentrantLock();
lock.lock();
try {
//rf.seek(0);
rf.read(data);
return data;
} catch (IOException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return data;
}
/**
* 把字节数组保存为一个文件
*
* @param b
* @param outputFile
* @return
*/
public static File getFileFromBytes(byte[] b, String outputFile) {
File ret = null;
BufferedOutputStream stream = null;
try {
ret = new File(outputFile);
FileOutputStream fstream = new FileOutputStream(ret);
stream = new BufferedOutputStream(fstream);
stream.write(b);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return ret;
}
/**
* 将二进制数据转化存储的Object数组
* @param datas
* @return
*/
public static List<Head> getHeadList(byte[] datas){
try {
ByteArrayInputStream byteInt=new ByteArrayInputStream(datas);
ObjectInputStream objInt=new ObjectInputStream(byteInt);
List<Head> heads = (List<Head>)objInt.readObject();
return heads;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* long 类型转化bytes数组
* @param l
* @return
*/
public static byte[] getByteByLong(long l){
byte[] bytes = new byte[8];
for (int i = 0; i < bytes.length; i++)
bytes[i] = (byte)(long)(((l) >> i * 8) & 0xff); // 移位和清零
return bytes;
}
/**
* byte数组转化为long
* @param bytes
* @return
*/
public static long getLongByBytes(byte[] bytes){
long longa = 0;
for (int i = 0; i < bytes.length; i++)
longa += (long)((bytes[i] & 0xff) << i * 8); // 移位和清零
return longa;
}
/**
* 读取指定位置,指定长度的字节
* @param rf
* @param start
* @param size
* @return
*/
public static byte[] getBytesFromRf(RandomAccessFile rf,long start,long size){
byte[] data = new byte[(int)size];
try {
rf.seek(start);
rf.read(data);
return data;
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
public static void main(String[] args){
byte[] bytes= getByteByLong(344);System.out.println(bytes);
System.out.println(bytes);
long l = getLongByBytes(bytes);
System.out.println(l);
}
}
头部文件实体类
package com.verification.entry;
import java.io.Serializable;
public class Head implements Serializable {
/**
* 文件相对路径
*/
private String relativePath;
/**
* 文件开始位置
*/
private long start;
/**
* 文件大小
*/
private long size;
public String getRelativePath() {
return relativePath;
}
public void setRelativePath(String relativePath) {
this.relativePath = relativePath;
}
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}
读取文件api类
package com.verification.main;
import com.verification.entry.Head;
import com.verification.entry.ReadHead;
import com.verification.utils.FileUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
*/
public class ReadFileApi {
private static Map<String, ReadHead> dataMap = new HashMap<>();
/**
* 根据文件相对路径查找文件头部信息
* @param heads
* @param relativePath
* @return
*/
private static Head getStaHeadByName(List<Head> heads,String relativePath){
for (Head h:heads){
if (h.getRelativePath().equals(relativePath)){
return h;
}
}
return null;
}
/**
* 根据头部信息获取文件字节
* @param head
* @return
*/
private static byte[] getFileData(RandomAccessFile rf,Head head,long headLength){
long start = 8+head.getStart()+headLength;
long size =head.getSize();
return FileUtils.getBytesFromRf(rf,start,size);
}
/**
* 根据资源包获取文件列表
* @param filePath
* @return
*/
private static List<String> getFileName(String filePath){
ReadHead heads = dataMap.get(filePath);
if (null!=heads){
List<Head> hs = heads.getHeads();
List<String> res = new ArrayList<>();
for (Head h:hs){
res.add(h.getRelativePath());
}
return res;
}else {
return null;
}
}
/**
* 判断文件存不存在
* @param fileRelationName
* @return
*/
public static boolean isHave(String fileRelationName){
ReadHead readHead = getReadHead(fileRelationName);
if (null==readHead){
return false;
}else {
List<Head> heads = readHead.getHeads();
Head head = getStaHeadByName(heads,fileRelationName);
if (null==head){
return false;
}else {
return true;
}
}
}
/**
* 对外提供的api调用方法
* @param fileRelationName 文件相对路径
* @return
*/
public static byte[] getFile(String fileRelationName){
//根据文件名找到资源包
ReadHead readHead = getReadHead(fileRelationName);
if (null==readHead){
return null;
}else {
List<Head> heads = readHead.getHeads();
Head head = getStaHeadByName(heads,fileRelationName);
if (null!=head){
return getFileData(readHead.getRandomAccessFile(),head,readHead.getHeadLenth());
}else {
System.out.println("文件不存在!");
}
return null;
}
}
/**
*
* @param fileRelationName
* @return
*/
private static ReadHead getReadHead(String fileRelationName){
for(ReadHead h:dataMap.values()){
List<Head> heads = h.getHeads();
for (Head head:heads){
if (head.getRelativePath().equals(fileRelationName)){
return h;
}
}
}
return null;
}
/**
* 对外提供,将资源包加载到类中
* @param file
*/
public static void reloadFile(String file){
try {
if (null==dataMap ){
dataMap=new HashMap<String, ReadHead>();
}
if (null == dataMap.get(file)) {
ReadHead rh = new ReadHead();
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
byte[] headLengthBytes = new byte[8];
randomAccessFile.read(headLengthBytes);
long headLength = FileUtils.getLongByBytes(headLengthBytes);
byte[] headDataBytes = FileUtils.getBytesFromRf(randomAccessFile, 8, headLength);
List<Head> heads = FileUtils.getHeadList(headDataBytes);
rh.setRandomAccessFile(randomAccessFile);
rh.setHeadLenth(headLength);
rh.setHeads(heads);
dataMap.put(file,rh);
}
}catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
String filePath = "/Users/hjd/Desktop/test/result/88818.entry";
//ReadApi api1 = new ReadApi(filePath);
byte[] datas = getFile("111.docx");
FileUtils.getFileFromBytes(datas,"/Users/hjd/Desktop/test/result/"+"111.docx");
}
}
读取文件流类(该对象是为了支持api可以同时读取多个资源包的内容)
package com.verification.entry;
import java.io.RandomAccessFile;
import java.util.List;
public class ReadHead {
/**
* 文件头集合
*/
private List<Head> heads;
/**
* 读取文件数据类
*/
private RandomAccessFile randomAccessFile;
/**
* 文件头长度
*/
private long headLenth;
public ReadHead(){
}
public List<Head> getHeads() {
return heads;
}
public void setHeads(List<Head> heads) {
this.heads = heads;
}
public RandomAccessFile getRandomAccessFile() {
return randomAccessFile;
}
public void setRandomAccessFile(RandomAccessFile randomAccessFile) {
this.randomAccessFile = randomAccessFile;
}
public long getHeadLenth() {
return headLenth;
}
public void setHeadLenth(long headLenth) {
this.headLenth = headLenth;
}
}