JAVA将多文件合并成一个资源包并读取

  • 设计思路
  • 文件协议
  • 具体实现
  • 详细代码
  • 合并文件主体代码
  • 操作工具类
  • 头部文件实体类
  • 读取文件api类
  • 读取文件流类(该对象是为了支持api可以同时读取多个资源包的内容)


设计思路

设计好需要存储文件的协议,根据文件协议写入个个文件,然后根据写入的顺序,读取需要的文件二进制数组

文件协议

这里将合并后的文件设计成三部分

  1. 文件头,存储文件相对路径、文件大小、文件写入文件体的起始位置
  2. 文件头长度,将文件头转化为二进制数据后的长度,是long类型,将此长度转化为定长数组
  3. 根据文件头写入的顺序依次写入文件内容
  4. 如图所示

具体实现

  1. 文件写入实现:
    通过文件夹路径,得到文件夹下所有文件集合,集合用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();
  1. 文件读取实现:这里用到 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;
   }
}