關於文件合並與修改md5值的問題
圖片文件合並
這裡並沒有提及關於如何修改文件的 md5 值的方法,因為合並文件本身就是修改瞭文件的 md5 值,使用本博客的方法,不僅僅可以修改md5值,還可以達到隱藏文件的目的。這裡強調一下:之所以能修改md5值,是因為修改瞭文件的內容,並不是程序可以直接修改文件的md5值,文件的md5值是根據文件本身計算出來的。
想瞭解 md5 的一些基本知識的可參考下面這篇文章:
如何使用Java計算修改文件的MD5值
前幾天,寫Java爬蟲的時候,偶然間發現圖像的數據重合起來瞭,然後圖片就產生瞭問題。(可能也有的沒有問題,那我也看不出來瞭,哈!)
因為圖片重合的原因,我就突發奇想,把很多張圖片數據存入一個文件中,這樣做的話,可以用於隱藏某些文件,然後需要的時候再將它們分隔開來 ,這樣似乎很有趣,有想法就要付諸實踐。所以,就寫瞭一個demo程序,順便寫一個博客,來記錄一下想法。
文本文件合並
我先來介紹一個簡單的文本文件合並代碼,再來看這個圖片文件合並的,這樣感覺可能會好一點,這個代碼是雙十一那天晚上寫的,可以看出來我的代碼風格到現在的區別,也許沒什麼變化吧,哈哈!
運行效果:
運行前:在這個路徑下面有9個文件。
運行後:產生瞭一個 merge.txt 文件
文件內容展示
代碼部分
這部分代碼,功能很簡單就是把一個個的文本文件合並後寫入一個總的 merge.txt 文件夾,當時學會瞭往文件裡追加內容,所以寫瞭這個 demo。
簡單來說就是獲取每一個文件(文本文件,我進行瞭過濾。)得到一個輸入流,然後一個循環內,每次將一個文件的信息寫入合並的文件內,循環結束,文件合並就完成瞭。
package com.filemerge; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; public class FileMerge { //參數為一個文件夾路徑 public static void fileMerge(String path){ File target = new File(path); //待合並文件夾目錄 File output = new File(path+File.separator+"merge.txt"); //合並文件夾位置 String[] names = target.list((dir,name)->name.contains(".txt")); //過濾非文本文件,返回值為一個 String 數組 BufferedReader reader = null; BufferedWriter writer = null; //OutputStreamWriter 不要記錯瞭! try { writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output,true))); for (String name : names) { reader = new BufferedReader(new InputStreamReader(new FileInputStream(target+File.separator+name))); String line = null; while((line = reader.readLine()) != null) { writer.write(line); writer.newLine(); } writer.newLine(); //每個文件進行換行分隔內容! } System.out.println("File merge successfully!"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { reader.close(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } } }
測試代碼:
package com.filemerge; public class Test { public static void main(String[] args) { FileMerge.fileMerge("D:/DB/DreamDragon"); } }
圖片文件合並(重點)
如果看完瞭上面的文本文件合並的話,不妨再多看一點,把下面這個圖片文件的代碼也看瞭吧,如果有什麼錯誤,歡迎指出來。(還有關於圖片的一些知識,不知道誰能指出來一下。)
代碼如下:合並圖片工具類
package dragon; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; public class ImageMerge { //圖片合並路徑,將要合並圖片放入同一個文件夾方便操作 public static final String mergePath = "D:/DragonDataFile/beauty"; public static final String outputPath = "D:/DragonDataFile/merge"; //工具類,就隻是用靜態方法瞭,不要創建對象瞭。 private ImageMerge() {} /**執行合並操作 * * 思路如下:首先獲取文件夾下面的所有圖片文件信息, * 然後使用輸入輸出流依次將文件進行合並操作。 * * 這裡的信息是指的文件大小,最重要的是文件的大小, * 考慮其它因素,不記錄文件名,所以拆分時,會丟失文件名, * 但是不影響圖片的顯示。 */ public static void imageMerge() throws IOException { File mergeFile = new File(ImageMerge.mergePath); File outputFile = new File(ImageMerge.outputPath); if (!initPath(mergeFile, outputFile)) { // 無法創建 mergePath throw new FileNotFoundException("無法創建文件夾: "+ImageMerge.mergePath); } try (//創建輸出文件 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputFile, System.currentTimeMillis()+".jpeg")))){ File[] files = mergeFile.listFiles(); recordImageInfo(files, outputFile); //記錄文件信息,保存於圖片的文件夾下,可能更好點。 for (File file : files) { try ( BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))){ int hasRead = 0; byte[] b = new byte[1024]; while ((hasRead = bis.read(b)) != -1) { bos.write(b, 0, hasRead); } } } } } //初始化路徑,如果 mergePath 不存在 private static boolean initPath(File mergeFile, File outputFile) { boolean mk_mergeFile = false, mk_outputFile = false; if (!mergeFile.exists()) { // mergePath 不存在 mk_mergeFile = mergeFile.mkdirs(); } else { mk_mergeFile = true; } if (!outputFile.exists()) { mk_outputFile = outputFile.mkdirs(); } else { mk_outputFile = true; } return mk_mergeFile && mk_outputFile; } //記錄信息 private static void recordImageInfo(File[] files, File outputFile) throws FileNotFoundException, IOException { try ( BufferedWriter bos = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(outputFile,"mergeImageInfo.txt"), true)))){ for (File file : files) { String record = file.length()+" "; bos.write(record); bos.newLine(); } } } }
圖片分隔工具類
package dragon; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.LinkedList; import java.util.List; import java.util.UUID; public class ImageSeparate { //拆分文件的位置 private static final String separatePath = "D:/DragonDataFile/separate"; private ImageSeparate() {} /** * 合並後文件夾下面有兩個文件(應該每一批合並文件,一個單獨的文件夾): * 合並後文件,合並文件信息(大小)。 * * 思路:首先讀取合並文件信息,然後依據大小依次從文件中取出 * 對應大小的字節數,寫入一個文件中。 * @throws IOException * */ public static void imageSeparate() throws IOException { File separateFile = new File(ImageSeparate.separatePath); if (initPath(separateFile)) { //無法創建文件夾 throw new FileNotFoundException("無法創建文件夾: "+ImageSeparate.separatePath); } File outputFile = new File(ImageMerge.outputPath); //下面獲取的都是 String 數組,但是正常情況下應該都是隻有一個 String 的字符串 //獲取圖片文件信息文件 File[] infoFile = outputFile.listFiles(f->f.getName().contains(".txt")); //獲取合並圖片文件 File[] mergeFile = outputFile.listFiles(f->!f.getName().contains(".txt")); // 獲取信息文件信息(圖片的長度) List<Long> fileInfo = getFileInfo(infoFile[0]); try ( BufferedInputStream bis = new BufferedInputStream(new FileInputStream(mergeFile[0]))){ fileInfo.stream().forEach(len->{ String filename = System.currentTimeMillis()+UUID.randomUUID().toString()+".jpeg"; System.out.println(filename); try ( BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(separateFile, filename)))){ long record = 0; int hasRead = 0; byte[] b = new byte[1024]; /** * 這裡處理比較麻煩,我說明一下: * 一次性去讀 len 長度的數據,考慮到有時候文件會非常大,這個數據對內存的壓力很大, * 所以舍棄瞭,如果文件很小,倒也是一個很好的方式(簡便)。 * * 這裡采用逐次讀取的方式:(一般圖片都會大於 1024 字節的,這個不考慮) * 當讀取一次後,判斷剩餘的字節數是否小於 1024,如果小於的話,就直接 * 一次性讀取這些字節數,並寫入文件中,然後跳出循環,本次文件讀取完成。 * */ while ((hasRead = bis.read(b)) != -1) { bos.write(b,0,hasRead); //先判斷,再讀取數據,否則會出錯。 record += (long)hasRead; if (len-record < 1024) { long tail = len-record; bis.read(new byte[(int)tail]); bos.write(b, 0, (int)tail); break; } } } catch (IOException e) { e.printStackTrace(); } }); } } //獲取信息文件信息(圖片的長度) private static List<Long> getFileInfo(File file) throws NumberFormatException, IOException{ List<Long> fileInfo = new LinkedList<>(); try ( BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))){ String line = null; while ((line = br.readLine()) != null) { //將數據轉換為 long 再存入集合,或許使用 DataInputStream 更好吧 //註意,如果這個文件裡面被修改瞭,可能會引發 RuntimeException String[] str = line.split(" "); fileInfo.add(Long.parseLong(str[0])); System.out.println(line); } } return fileInfo; } //初始化 拆分文件位置 private static boolean initPath(File file) { return file.mkdirs(); } }
測試類
package dragon; import java.io.IOException; public class Client { public static void main(String[] args) throws IOException, NumberFormatException, ClassNotFoundException { //如果需要合並圖片,就使用第一條語句,註釋第二條, //如果需要拆分圖片,就使用第二條語句,註釋第一條 ImageMerge.imageMerge(); // ImageSeparate.imageSeparate(); } }
說明:
每一個類都含有很多註釋 ,應該還是能表達清楚意思的,有幾點需要說明一下。
運行效果:
測試準備圖片: 註意觀察文件夾的路徑和第一張圖片。
測試準備圖片信息: 註意觀察文件的大小和占用空間信息。
合並效果: 註意觀察合並後的圖片和合並文件的路徑。
合並後的文件會產生一個單獨的文本文件,這裡面存儲的是圖片的大小信息,因為恢復圖片,是需要這些信息的,否則圖片可能就回不來瞭。
註意:我當時看到這個結果,感覺很奇妙,雖然合並瞭37張圖片,但是它居然還可以正常顯示第一張圖片的信息,這個可能和圖片本身的存儲形式有關(我沒有這方面的知識)。
文本文件信息截圖:
註:我是以行為單位進行存儲的,每行一個數據,讀取也是這樣的,這樣感覺比較方便。千萬不能修改這個文件的任何信息,否則就無法恢復圖片的信息瞭。
恢復圖片: 註意觀察右下角那張圖片,因為我沒有保留文件名,所以生成圖片的文件名是重寫生成的,還有註意到瞭我的文件名比較長,這個可以參考我開頭的那個博客鏈接,這裡是使用當前日期的毫秒數+UUID來生成的圖片名,確實是比較長瞭,但是不會重復,這是我需要的。
控制臺輸入信息:
我會把讀取的圖片信息(每張圖片的大小數據)和恢復圖片時生成的圖片文件名打印出來,這樣調試比較方便,看起來也很好看,哈!
一些細節
圖片的合並就是單純的文件合並,隻是獲取每一個文件的輸入流,然後將其依次寫入一個輸出流中。(這裡使用的是字節流,圖片可不能使用字符流!)
for (File file : files) { try ( BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))){ int hasRead = 0; byte[] b = new byte[1024]; while ((hasRead = bis.read(b)) != -1) { bos.write(b, 0, hasRead); } } }
這裡比較難得是如何從一個整的合並圖片中恢復所有圖片的信息,因為圖片的特殊存儲格式,如果在圖片的頭部產生錯誤,就無法識別瞭(我隻知道圖片頭部含有一個魔數,用於標識圖片,其他的不是很清楚,我沒有這圖像方面的知識,如果有人知道,可以在下面評論。),一個字節也不行!
我來說一說我的想法:
舉個例子,幹巴巴的說著估計很難講的明白。
先看下面這張圖片,假定這是(合並後圖片中)某個圖片 的信息,我們需要在一個完整的輸入流中,完整的取出來這一部分,不能多也不能少! 註意是順序讀取數據。再強調一下,這是中間某一張圖片,也就是這個圖表示某一個圖片的數據,但是不是整個文件的數據,也就是說,這個圖片下面還有數據,最下面那個小於 1024 byte,隻是表示這張圖片還剩下少於 1024 byte得數據。
所以下面這種讀取方式是錯誤的,無法正確的恢復圖片。
byte[] b = new byte[1024]; while ((hasRead = bis.read(b)) != -1) { bos.write(b, 0, hasRead); }
其實有一種很簡單的方式,就是下面註釋中的方式,每次直接將整個圖片的數據讀取出來,寫入一個輸出流,就是一張完整得圖片瞭,簡單粗暴,但是我考慮到,有時候圖片太大,對於內存是一個很大的消耗,沒有采用這種方式。
仍然采用逐次讀取的方式:
說明:
設置一個字節計數器,在每次讀取(1024byte)之後,下一次讀取之前,判斷當前圖片的大小和當前讀入的字節數的差值是否大於 1024 字節,即是否滿足一次完整的讀取,如果滿足的條件,就繼續讀取寫入操作,如果不足 1024字節,說明不能再進行讀取寫入瞭(因為當前圖片下面還有其它圖片數據,所以仍然是可以讀取 1024 字節的,隻是屬於當前圖片的字節數,不足 1024 字節瞭,即不能進行一次完整的讀取瞭。)所以,如果不足以進行一次完整的讀取,那就隻讀當前還需要的字節,隻需要讀取一次就行瞭,讀取之後將數據寫入輸出流,退出當前循環,進行下一張圖片的讀取。 可以畫圖觀察一下,就會理解瞭。
try ( BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(separateFile, filename)))){ long record = 0; int hasRead = 0; byte[] b = new byte[1024]; /** * 這裡處理比較麻煩,我說明一下: * 一次性去讀 len 長度的數據,考慮到有時候文件會非常大,這個數據對內存的壓力很大, * 所以舍棄瞭,如果文件很小,倒也是一個很好的方式(簡便)。 * * 這裡采用逐次讀取的方式:(一般圖片都會大於 1024 字節的,這個不考慮) * 當讀取一次後,判斷剩餘的字節數是否小於 1024,如果小於的話,就直接 * 一次性讀取這些字節數,並寫入文件中,然後跳出循環,本次文件讀取完成。 * */ while ((hasRead = bis.read(b)) != -1) { bos.write(b,0,hasRead); //先判斷,再讀取數據,否則會出錯。 record += (long)hasRead; if (len-record < 1024) { long tail = len-record; bis.read(new byte[(int)tail]); bos.write(b, 0, (int)tail); break; } } } catch (IOException e) { e.printStackTrace(); }
不足之處
如果仔細閱讀瞭我的代碼,應該可以看出來瞭,有一些地方寫的不好。
主要有以下幾點:
沒有保存圖片的類型,恢復圖片時,隻能強行指定文件的後綴名為 jpeg,這樣做不是很好的做法。
恢復圖片時,直接指定為jpeg,不太合適。
String filename = System.currentTimeMillis()+UUID.randomUUID().toString()+".jpeg";
這個是創建合並文件時,指定第一張圖片的後綴名,這樣做也不是很好。
new File(outputFile, System.currentTimeMillis()+".jpeg")
所以我對上面代碼進行瞭改進,在保存圖片的大小信息的同時,保存圖片的後綴名信息(一般都是有的,但是如果沒有的話,我就指定一個 “.none” 作為後綴名瞭)。一開始我是準備還是直接按照如下形式存儲:
圖片大小 [空格分隔] 圖片後綴名
但是實際處理過程中,這樣感覺還是比較麻煩的,因為存儲的數據都是字符信息瞭,Java是沒有辦法直接使用的,顯示轉換太麻煩瞭,所以我決定不使用這種方式瞭,轉而使用Java的對象序列化。因為同時需要大小和後綴名兩個屬性,而且兩個屬性之間也是具有很強關系的(一對一),幹脆封裝一下,做成一個Java類,這樣使用起來很方便,而且兩個屬性之間也建立瞭聯系,序列化恢復也比較方便。而且對象序列化還帶來一個好處,Java的對象序列化是二進制序列化,區別於 json 這種字符序列化,二進制是機器讀取的,我們就算打開瞭也是亂碼,所以,可以避免這個文件被別人給修改瞭。(一般是不會去修改二進制文件的吧,哈!)
圖片對象模型
package dragon; import java.io.Serializable; /** * 文件信息模型類: * 記錄文件的大小和後綴名,因為總是 * 需要使用這個,就把它封裝起來使用吧。 * */ public class FileInfo implements Serializable{ /** * 序列化 id */ private static final long serialVersionUID = 1L; private long len; private String suffix; public FileInfo(long len, String suffix) { this.len = len; this.suffix = suffix; } public long getLen() { return this.len; } public String getSuffix() { return this.suffix; } //重寫 toString 方法,方便打印調試代碼 @Override public String toString() { return "FileInfo [len=" + len + ", suffix=" + suffix + "]"; } }
對於原來的圖片合並和分隔方法,都進行瞭一點改進,所以命名規則上都在原來的類前面加瞭一個 Enhance (增強、改進)。
改進的圖片合並類:EnhanceImageMerge
package dragon; 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.io.ObjectOutputStream; import java.util.LinkedList; import java.util.List; public class EnhanceImageMerge { //圖片合並路徑,將要合並圖片放入同一個文件夾方便操作 public static final String mergePath = "D:/DragonDataFile/beauty"; public static final String outputPath = "D:/DragonDataFile/merge"; //工具類,就隻是用靜態方法瞭,不要創建對象瞭。 private EnhanceImageMerge() {} /**執行合並操作 * * 思路如下:首先獲取文件夾下面的所有圖片文件信息, * 然後使用輸入輸出流依次將文件進行合並操作。 * * 這裡的信息是指的文件大小,最重要的是文件的大小, * 考慮其它因素,不記錄文件名,所以拆分時,會丟失文件名, * 但是不影響圖片的顯示。 */ public static void imageMerge() throws IOException { File mergeFile = new File(EnhanceImageMerge.mergePath); File outputFile = new File(EnhanceImageMerge.outputPath); if (!initPath(mergeFile, outputFile)) { // 無法創建 mergePath throw new FileNotFoundException("無法創建文件夾: "+EnhanceImageMerge.mergePath); } File[] files = mergeFile.listFiles(); String suffix = recordImageInfo(files, outputFile); //記錄文件信息,保存於圖片的文件夾下,可能更好點。 try (//創建輸出文件 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputFile, System.currentTimeMillis()+suffix)))){ for (File file : files) { try ( BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))){ int hasRead = 0; byte[] b = new byte[1024]; while ((hasRead = bis.read(b)) != -1) { bos.write(b, 0, hasRead); } } } } } //初始化路徑,如果 mergePath 不存在 private static boolean initPath(File mergeFile, File outputFile) { boolean mk_mergeFile = false, mk_outputFile = false; if (!mergeFile.exists()) { // mergePath 不存在 mk_mergeFile = mergeFile.mkdirs(); } else { mk_mergeFile = true; } if (!outputFile.exists()) { mk_outputFile = outputFile.mkdirs(); } else { mk_outputFile = true; } return mk_mergeFile && mk_outputFile; } 使用對象序列化進行數據的存儲,方便快捷。 private static String recordImageInfo(File[] files, File outputFile) throws FileNotFoundException, IOException { try ( //二進制保存的數據,無法直接閱讀,不加擴展名瞭 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(outputFile, "fileinfo"), true))){ List<FileInfo> fileInfos = new LinkedList<>(); for (File file : files) { String filename = file.getName(); //記錄文件的大小和擴展名信息 如果沒有的話,默認設置為 none。 long len = file.length(); String suffix = filename.lastIndexOf(".") != -1 ? filename.substring(filename.lastIndexOf(".")) : ".none"; FileInfo fileInfo = new FileInfo(len, suffix); System.out.println(fileInfo.toString()); fileInfos.add(fileInfo); } oos.writeObject(fileInfos); //直接將集合序列化,序列化單個對象,讀取的時候太麻煩瞭 } String firstFileName = files[0].getName(); //返回第一個文件的後綴名。 return firstFileName.lastIndexOf(".") != -1 ? firstFileName.substring(firstFileName.lastIndexOf(".")) : ".none"; } }
註意:對象序列化的時候,如果每次序列化一個對象的話,那麼讀取的時候,就無法判斷怎麼結束瞭,因為程序不知道該讀取多少次才結束,而且似乎不能使用讀取結果為 null 來判斷,那樣會引發一個 EOFException。
我去查閱資料,有人推薦瞭,在序列化的最後,添加一個 null 對象,這確實是一個很好的方法,但是感覺還是不好。
另一種方式就是直接序列化一個List 集合,這樣確實是方便多瞭,存入一個集合,讀取回來瞭還是一個集合,可以直接操作瞭,還省去將對象再組裝成集合的時間。(對象序列化,我隻是瞭解,用過那麼一兩次,不是很熟。)
對象序列化部分
使用對象序列化進行數據的存儲,方便快捷。 private static String recordImageInfo(File[] files, File outputFile) throws FileNotFoundException, IOException { try ( //二進制保存的數據,無法直接閱讀,不加擴展名瞭 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(outputFile, "fileinfo"), true))){ List<FileInfo> fileInfos = new LinkedList<>(); for (File file : files) { String filename = file.getName(); //記錄文件的大小和擴展名信息 如果沒有的話,默認設置為 none。 long len = file.length(); String suffix = filename.lastIndexOf(".") != -1 ? filename.substring(filename.lastIndexOf(".")) : ".none"; FileInfo fileInfo = new FileInfo(len, suffix); System.out.println(fileInfo.toString()); fileInfos.add(fileInfo); } oos.writeObject(fileInfos); //直接將集合序列化,序列化單個對象,讀取的時候太麻煩瞭 } String firstFileName = files[0].getName(); //返回第一個文件的後綴名。 return firstFileName.lastIndexOf(".") != -1 ? firstFileName.substring(firstFileName.lastIndexOf(".")) : ".none"; }
改進的圖片分隔類:EnhanceImageSeparate
package dragon; 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.io.ObjectInputStream; import java.util.List; import java.util.UUID; public class EnhanceImageSeparate { //拆分文件的位置 private static final String separatePath = "D:/DragonDataFile/separate"; private EnhanceImageSeparate() {} /** * 合並後文件夾下面有兩個文件(應該每一批合並文件,一個單獨的文件夾): * 合並後文件,合並文件信息(大小)。 * * 思路:首先讀取合並文件信息,然後依據大小依次從文件中取出 * 對應大小的字節數,寫入一個文件中。 * * @throws IOException * @throws ClassNotFoundException * @throws NumberFormatException * */ public static void imageSeparate() throws IOException, NumberFormatException, ClassNotFoundException { File separateFile = new File(EnhanceImageSeparate.separatePath); if (initPath(separateFile)) { //無法創建文件夾 throw new FileNotFoundException("無法創建文件夾: "+EnhanceImageSeparate.separatePath); } File outputFile = new File(ImageMerge.outputPath); //下面獲取的都是 String 數組,但是正常情況下應該都是隻有一個 String 的字符串 //獲取圖片文件信息文件 File[] infoFile = outputFile.listFiles(f->!f.getName().contains(".")); //序列化文件是沒有後綴名的 //獲取合並圖片文件 File[] mergeFile = outputFile.listFiles(f->f.getName().contains(".")); //圖片文件都是有後綴名的 // 獲取信息文件信息(圖片的長度) System.out.println(infoFile[0]); List<FileInfo> fileInfos = getFileInfo(infoFile[0]); mergeOperation(fileInfos, mergeFile[0], separateFile); } /** * 執行文件合並操作 * @param fileInfos 文件信息集合 * @param 需要合並文件的文件夾 * @param separateFile 合並操作後的文件夾 * * @throws IOException * @throws FileNotFoundException * */ private static void mergeOperation(List<FileInfo> fileInfos, File mergeFile, File separateFile) throws FileNotFoundException, IOException { try ( BufferedInputStream bis = new BufferedInputStream(new FileInputStream(mergeFile))){ fileInfos.stream().forEach(fileInfo->{ long len = fileInfo.getLen(); String filename = System.currentTimeMillis()+UUID.randomUUID().toString()+fileInfo.getSuffix(); System.out.println(filename); try ( BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(separateFile, filename)))){ long record = 0; int hasRead = 0; byte[] b = new byte[1024]; /** * 這裡處理比較麻煩,我說明一下: * 一次性去讀 len 長度的數據,考慮到有時候文件會非常大,這個數據對內存的壓力很大, * 所以舍棄瞭,如果文件很小,倒也是一個很好的方式(簡便)。 * * 這裡采用逐次讀取的方式:(一般圖片都會大於 1024 字節的,這個不考慮) * 當讀取一次後,判斷剩餘的字節數是否小於 1024,如果小於的話,就直接 * 一次性讀取這些字節數,並寫入文件中,然後跳出循環,本次文件讀取完成。 * */ while ((hasRead = bis.read(b)) != -1) { bos.write(b,0,hasRead); //先判斷,再讀取數據,否則會出錯。 record += (long)hasRead; if (len-record < 1024) { long tail = len-record; bis.read(new byte[(int)tail]); bos.write(b, 0, (int)tail); break; } } } catch (IOException e) { e.printStackTrace(); } }); } } //獲取信息文件信息(圖片的長度) //抑制一下 unchecked 警告 @SuppressWarnings("unchecked") private static List<FileInfo> getFileInfo(File file) throws NumberFormatException, IOException, ClassNotFoundException{ try ( ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){ return (List<FileInfo>) ois.readObject(); //強制類型轉換一下,讀取出來的數據都是 Object 類型 } } //初始化 拆分文件位置 private static boolean initPath(File file) { return file.mkdirs(); } }
註意: 分隔還原圖片時,圖片的後綴名部分代碼為:
使用Java封裝屬性後,使用很方便瞭。
String filename = System.currentTimeMillis()+UUID.randomUUID().toString()+fileInfo.getSuffix();
反序列化讀取集合:
這裡我抑制瞭一個強制類型轉換的警告。
通過序列化,可以發現代碼量大大減少瞭,直接就是集合,使用非常方便。
//抑制一下 unchecked 警告 @SuppressWarnings("unchecked") private static List<FileInfo> getFileInfo(File file) throws NumberFormatException, IOException, ClassNotFoundException{ try ( ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))){ return (List<FileInfo>) ois.readObject(); //強制類型轉換一下,讀取出來的數據都是 Object 類型 } }
測試代碼
package dragon; import java.io.IOException; public class Client { public static void main(String[] args) throws IOException, NumberFormatException, ClassNotFoundException { //如果需要合並圖片,就使用第一條語句,註釋第二條, //如果需要拆分圖片,就使用第二條語句,註釋第一條 EnhanceImageMerge.imageMerge(); // EnhanceImageSeparate.imageSeparate(); } }
改進後代碼運行結果 執行合並方法時,打印的圖片對象模型的信息
合並後的效果
註意觀察右邊的 fileinfo 文件,因為是二進制數據,我就沒有給它加上文件後綴名,加上瞭也是無法直接閱讀的,裡面存儲的是圖片對象模型集合的序列化信息。
執行分隔操作後的效果
控制臺輸出圖片信息,可以看到每個圖片的後綴名都恢復瞭,註意看最後一個,有一個文本文件!哈哈!這個圖片後面似乎可以添加任何數據,也許視頻也是可以的,隻是我沒有測試,這個應該和圖片的存儲格式、顯示方式有關。
註意,下面恢復的時候,確實是有一個文本文件,並且是完好的,可以閱讀的。
合並後被分隔出的文本文件的信息
總結
因為一個代碼的bug,而產生的一個奇妙想法,並且付諸實踐瞭,收獲瞭一個挺有趣的代碼,發現這個還是挺有趣的,這也和我最近在看Java的IO流相關的部分有關,如果我沒有前期的積累,估計想法隻能是想法瞭(沒有能力實現自己的各種想法,哈哈!)。寫這個代碼的時候,用到瞭很多以前的學過的知識,因為現在沒什麼接觸工作的機會,很多知識不用,逐漸都生疏瞭,偶爾還是要練練手。新學的知識 ,那就忘得更快瞭,很多時候也不是很理解,並且親自實踐一下才行。
紙上得來終覺淺,絕知此事要躬行。 哈哈。
主要還是記錄自己寫代碼的過程,也算是記錄成長吧。
到此這篇關於關於文件合並與修改md5值的問題的文章就介紹到這瞭,更多相關文件合並與修改md5值內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java實現文件的分割與合並
- java基礎知識之FileInputStream流的使用
- Java中IO流解析及代碼實例詳解
- Java進階核心之InputStream流深入講解
- java基礎檢查和未檢查異常處理詳解