關(guān)于文件合并與修改md5值的問(wèn)題
圖片文件合并
這里并沒(méi)有提及關(guān)于如何修改文件的 md5 值的方法,因?yàn)楹喜⑽募旧砭褪切薷牧宋募?md5 值,使用本博客的方法,不僅僅可以修改md5值,還可以達(dá)到隱藏文件的目的。這里強(qiáng)調(diào)一下:之所以能修改md5值,是因?yàn)樾薷牧宋募膬?nèi)容,并不是程序可以直接修改文件的md5值,文件的md5值是根據(jù)文件本身計(jì)算出來(lái)的。
想了解 md5 的一些基本知識(shí)的可參考下面這篇文章:
前幾天,寫(xiě)Java爬蟲(chóng)的時(shí)候,偶然間發(fā)現(xiàn)圖像的數(shù)據(jù)重合起來(lái)了,然后圖片就產(chǎn)生了問(wèn)題。(可能也有的沒(méi)有問(wèn)題,那我也看不出來(lái)了,哈?。?/p>
因?yàn)閳D片重合的原因,我就突發(fā)奇想,把很多張圖片數(shù)據(jù)存入一個(gè)文件中,這樣做的話,可以用于隱藏某些文件,然后需要的時(shí)候再將它們分隔開(kāi)來(lái) ,這樣似乎很有趣,有想法就要付諸實(shí)踐。所以,就寫(xiě)了一個(gè)demo程序,順便寫(xiě)一個(gè)博客,來(lái)記錄一下想法。
文本文件合并
我先來(lái)介紹一個(gè)簡(jiǎn)單的文本文件合并代碼,再來(lái)看這個(gè)圖片文件合并的,這樣感覺(jué)可能會(huì)好一點(diǎn),這個(gè)代碼是雙十一那天晚上寫(xiě)的,可以看出來(lái)我的代碼風(fēng)格到現(xiàn)在的區(qū)別,也許沒(méi)什么變化吧,哈哈!
運(yùn)行效果:
運(yùn)行前:在這個(gè)路徑下面有9個(gè)文件。
運(yùn)行后:產(chǎn)生了一個(gè) merge.txt 文件
文件內(nèi)容展示
代碼部分
這部分代碼,功能很簡(jiǎn)單就是把一個(gè)個(gè)的文本文件合并后寫(xiě)入一個(gè)總的 merge.txt 文件夾,當(dāng)時(shí)學(xué)會(huì)了往文件里追加內(nèi)容,所以寫(xiě)了這個(gè) demo。
簡(jiǎn)單來(lái)說(shuō)就是獲取每一個(gè)文件(文本文件,我進(jìn)行了過(guò)濾。)得到一個(gè)輸入流,然后一個(gè)循環(huán)內(nèi),每次將一個(gè)文件的信息寫(xiě)入合并的文件內(nèi),循環(huán)結(jié)束,文件合并就完成了。
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 { //參數(shù)為一個(gè)文件夾路徑 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")); //過(guò)濾非文本文件,返回值為一個(gè) String 數(shù)組 BufferedReader reader = null; BufferedWriter writer = null; //OutputStreamWriter 不要記錯(cuò)了! 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(); //每個(gè)文件進(jìn)行換行分隔內(nèi)容! } 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(); } } } }
測(cè)試代碼:
package com.filemerge; public class Test { public static void main(String[] args) { FileMerge.fileMerge("D:/DB/DreamDragon"); } }
圖片文件合并(重點(diǎn))
如果看完了上面的文本文件合并的話,不妨再多看一點(diǎn),把下面這個(gè)圖片文件的代碼也看了吧,如果有什么錯(cuò)誤,歡迎指出來(lái)。(還有關(guān)于圖片的一些知識(shí),不知道誰(shuí)能指出來(lái)一下。)
代碼如下:合并圖片工具類
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 { //圖片合并路徑,將要合并圖片放入同一個(gè)文件夾方便操作 public static final String mergePath = "D:/DragonDataFile/beauty"; public static final String outputPath = "D:/DragonDataFile/merge"; //工具類,就只是用靜態(tài)方法了,不要?jiǎng)?chuàng)建對(duì)象了。 private ImageMerge() {} /**執(zhí)行合并操作 * * 思路如下:首先獲取文件夾下面的所有圖片文件信息, * 然后使用輸入輸出流依次將文件進(jìn)行合并操作。 * * 這里的信息是指的文件大小,最重要的是文件的大小, * 考慮其它因素,不記錄文件名,所以拆分時(shí),會(huì)丟失文件名, * 但是不影響圖片的顯示。 */ public static void imageMerge() throws IOException { File mergeFile = new File(ImageMerge.mergePath); File outputFile = new File(ImageMerge.outputPath); if (!initPath(mergeFile, outputFile)) { // 無(wú)法創(chuàng)建 mergePath throw new FileNotFoundException("無(wú)法創(chuàng)建文件夾: "+ImageMerge.mergePath); } try (//創(chuàng)建輸出文件 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputFile, System.currentTimeMillis()+".jpeg")))){ File[] files = mergeFile.listFiles(); recordImageInfo(files, outputFile); //記錄文件信息,保存于圖片的文件夾下,可能更好點(diǎn)。 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() {} /** * 合并后文件夾下面有兩個(gè)文件(應(yīng)該每一批合并文件,一個(gè)單獨(dú)的文件夾): * 合并后文件,合并文件信息(大?。?。 * * 思路:首先讀取合并文件信息,然后依據(jù)大小依次從文件中取出 * 對(duì)應(yīng)大小的字節(jié)數(shù),寫(xiě)入一個(gè)文件中。 * @throws IOException * */ public static void imageSeparate() throws IOException { File separateFile = new File(ImageSeparate.separatePath); if (initPath(separateFile)) { //無(wú)法創(chuàng)建文件夾 throw new FileNotFoundException("無(wú)法創(chuàng)建文件夾: "+ImageSeparate.separatePath); } File outputFile = new File(ImageMerge.outputPath); //下面獲取的都是 String 數(shù)組,但是正常情況下應(yīng)該都是只有一個(gè) String 的字符串 //獲取圖片文件信息文件 File[] infoFile = outputFile.listFiles(f->f.getName().contains(".txt")); //獲取合并圖片文件 File[] mergeFile = outputFile.listFiles(f->!f.getName().contains(".txt")); // 獲取信息文件信息(圖片的長(zhǎng)度) 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]; /** * 這里處理比較麻煩,我說(shuō)明一下: * 一次性去讀 len 長(zhǎng)度的數(shù)據(jù),考慮到有時(shí)候文件會(huì)非常大,這個(gè)數(shù)據(jù)對(duì)內(nèi)存的壓力很大, * 所以舍棄了,如果文件很小,倒也是一個(gè)很好的方式(簡(jiǎn)便)。 * * 這里采用逐次讀取的方式:(一般圖片都會(huì)大于 1024 字節(jié)的,這個(gè)不考慮) * 當(dāng)讀取一次后,判斷剩余的字節(jié)數(shù)是否小于 1024,如果小于的話,就直接 * 一次性讀取這些字節(jié)數(shù),并寫(xiě)入文件中,然后跳出循環(huán),本次文件讀取完成。 * */ while ((hasRead = bis.read(b)) != -1) { bos.write(b,0,hasRead); //先判斷,再讀取數(shù)據(jù),否則會(huì)出錯(cuò)。 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(); } }); } } //獲取信息文件信息(圖片的長(zhǎng)度) 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) { //將數(shù)據(jù)轉(zhuǎn)換為 long 再存入集合,或許使用 DataInputStream 更好吧 //注意,如果這個(gè)文件里面被修改了,可能會(huì)引發(fā) 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(); } }
測(cè)試類
package dragon; import java.io.IOException; public class Client { public static void main(String[] args) throws IOException, NumberFormatException, ClassNotFoundException { //如果需要合并圖片,就使用第一條語(yǔ)句,注釋第二條, //如果需要拆分圖片,就使用第二條語(yǔ)句,注釋第一條 ImageMerge.imageMerge(); // ImageSeparate.imageSeparate(); } }
說(shuō)明:
每一個(gè)類都含有很多注釋 ,應(yīng)該還是能表達(dá)清楚意思的,有幾點(diǎn)需要說(shuō)明一下。
運(yùn)行效果:
測(cè)試準(zhǔn)備圖片: 注意觀察文件夾的路徑和第一張圖片。
測(cè)試準(zhǔn)備圖片信息: 注意觀察文件的大小和占用空間信息。
合并效果: 注意觀察合并后的圖片和合并文件的路徑。
合并后的文件會(huì)產(chǎn)生一個(gè)單獨(dú)的文本文件,這里面存儲(chǔ)的是圖片的大小信息,因?yàn)榛謴?fù)圖片,是需要這些信息的,否則圖片可能就回不來(lái)了。
注意:我當(dāng)時(shí)看到這個(gè)結(jié)果,感覺(jué)很奇妙,雖然合并了37張圖片,但是它居然還可以正常顯示第一張圖片的信息,這個(gè)可能和圖片本身的存儲(chǔ)形式有關(guān)(我沒(méi)有這方面的知識(shí))。
文本文件信息截圖:
注:我是以行為單位進(jìn)行存儲(chǔ)的,每行一個(gè)數(shù)據(jù),讀取也是這樣的,這樣感覺(jué)比較方便。千萬(wàn)不能修改這個(gè)文件的任何信息,否則就無(wú)法恢復(fù)圖片的信息了。
恢復(fù)圖片: 注意觀察右下角那張圖片,因?yàn)槲覜](méi)有保留文件名,所以生成圖片的文件名是重寫(xiě)生成的,還有注意到了我的文件名比較長(zhǎng),這個(gè)可以參考我開(kāi)頭的那個(gè)博客鏈接,這里是使用當(dāng)前日期的毫秒數(shù)+UUID來(lái)生成的圖片名,確實(shí)是比較長(zhǎng)了,但是不會(huì)重復(fù),這是我需要的。
控制臺(tái)輸入信息:
我會(huì)把讀取的圖片信息(每張圖片的大小數(shù)據(jù))和恢復(fù)圖片時(shí)生成的圖片文件名打印出來(lái),這樣調(diào)試比較方便,看起來(lái)也很好看,哈!
一些細(xì)節(jié)
圖片的合并就是單純的文件合并,只是獲取每一個(gè)文件的輸入流,然后將其依次寫(xiě)入一個(gè)輸出流中。(這里使用的是字節(jié)流,圖片可不能使用字符流!)
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); } } }
這里比較難得是如何從一個(gè)整的合并圖片中恢復(fù)所有圖片的信息,因?yàn)閳D片的特殊存儲(chǔ)格式,如果在圖片的頭部產(chǎn)生錯(cuò)誤,就無(wú)法識(shí)別了(我只知道圖片頭部含有一個(gè)魔數(shù),用于標(biāo)識(shí)圖片,其他的不是很清楚,我沒(méi)有這圖像方面的知識(shí),如果有人知道,可以在下面評(píng)論。),一個(gè)字節(jié)也不行!
我來(lái)說(shuō)一說(shuō)我的想法:
舉個(gè)例子,干巴巴的說(shuō)著估計(jì)很難講的明白。
先看下面這張圖片,假定這是(合并后圖片中)某個(gè)圖片 的信息,我們需要在一個(gè)完整的輸入流中,完整的取出來(lái)這一部分,不能多也不能少! 注意是順序讀取數(shù)據(jù)。再?gòu)?qiáng)調(diào)一下,這是中間某一張圖片,也就是這個(gè)圖表示某一個(gè)圖片的數(shù)據(jù),但是不是整個(gè)文件的數(shù)據(jù),也就是說(shuō),這個(gè)圖片下面還有數(shù)據(jù),最下面那個(gè)小于 1024 byte,只是表示這張圖片還剩下少于 1024 byte得數(shù)據(jù)。
所以下面這種讀取方式是錯(cuò)誤的,無(wú)法正確的恢復(fù)圖片。
byte[] b = new byte[1024]; while ((hasRead = bis.read(b)) != -1) { bos.write(b, 0, hasRead); }
其實(shí)有一種很簡(jiǎn)單的方式,就是下面注釋中的方式,每次直接將整個(gè)圖片的數(shù)據(jù)讀取出來(lái),寫(xiě)入一個(gè)輸出流,就是一張完整得圖片了,簡(jiǎn)單粗暴,但是我考慮到,有時(shí)候圖片太大,對(duì)于內(nèi)存是一個(gè)很大的消耗,沒(méi)有采用這種方式。
仍然采用逐次讀取的方式:
說(shuō)明:
設(shè)置一個(gè)字節(jié)計(jì)數(shù)器,在每次讀取(1024byte)之后,下一次讀取之前,判斷當(dāng)前圖片的大小和當(dāng)前讀入的字節(jié)數(shù)的差值是否大于 1024 字節(jié),即是否滿足一次完整的讀取,如果滿足的條件,就繼續(xù)讀取寫(xiě)入操作,如果不足 1024字節(jié),說(shuō)明不能再進(jìn)行讀取寫(xiě)入了(因?yàn)楫?dāng)前圖片下面還有其它圖片數(shù)據(jù),所以仍然是可以讀取 1024 字節(jié)的,只是屬于當(dāng)前圖片的字節(jié)數(shù),不足 1024 字節(jié)了,即不能進(jìn)行一次完整的讀取了。)所以,如果不足以進(jìn)行一次完整的讀取,那就只讀當(dāng)前還需要的字節(jié),只需要讀取一次就行了,讀取之后將數(shù)據(jù)寫(xiě)入輸出流,退出當(dāng)前循環(huán),進(jìn)行下一張圖片的讀取。 可以畫(huà)圖觀察一下,就會(huì)理解了。
try ( BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(separateFile, filename)))){ long record = 0; int hasRead = 0; byte[] b = new byte[1024]; /** * 這里處理比較麻煩,我說(shuō)明一下: * 一次性去讀 len 長(zhǎng)度的數(shù)據(jù),考慮到有時(shí)候文件會(huì)非常大,這個(gè)數(shù)據(jù)對(duì)內(nèi)存的壓力很大, * 所以舍棄了,如果文件很小,倒也是一個(gè)很好的方式(簡(jiǎn)便)。 * * 這里采用逐次讀取的方式:(一般圖片都會(huì)大于 1024 字節(jié)的,這個(gè)不考慮) * 當(dāng)讀取一次后,判斷剩余的字節(jié)數(shù)是否小于 1024,如果小于的話,就直接 * 一次性讀取這些字節(jié)數(shù),并寫(xiě)入文件中,然后跳出循環(huán),本次文件讀取完成。 * */ while ((hasRead = bis.read(b)) != -1) { bos.write(b,0,hasRead); //先判斷,再讀取數(shù)據(jù),否則會(huì)出錯(cuò)。 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(); }
不足之處
如果仔細(xì)閱讀了我的代碼,應(yīng)該可以看出來(lái)了,有一些地方寫(xiě)的不好。
主要有以下幾點(diǎn):
沒(méi)有保存圖片的類型,恢復(fù)圖片時(shí),只能強(qiáng)行指定文件的后綴名為 jpeg,這樣做不是很好的做法。
恢復(fù)圖片時(shí),直接指定為jpeg,不太合適。
String filename = System.currentTimeMillis()+UUID.randomUUID().toString()+".jpeg";
這個(gè)是創(chuàng)建合并文件時(shí),指定第一張圖片的后綴名,這樣做也不是很好。
new File(outputFile, System.currentTimeMillis()+".jpeg")
所以我對(duì)上面代碼進(jìn)行了改進(jìn),在保存圖片的大小信息的同時(shí),保存圖片的后綴名信息(一般都是有的,但是如果沒(méi)有的話,我就指定一個(gè) “.none” 作為后綴名了)。一開(kāi)始我是準(zhǔn)備還是直接按照如下形式存儲(chǔ):
圖片大小 [空格分隔] 圖片后綴名
但是實(shí)際處理過(guò)程中,這樣感覺(jué)還是比較麻煩的,因?yàn)榇鎯?chǔ)的數(shù)據(jù)都是字符信息了,Java是沒(méi)有辦法直接使用的,顯示轉(zhuǎn)換太麻煩了,所以我決定不使用這種方式了,轉(zhuǎn)而使用Java的對(duì)象序列化。因?yàn)橥瑫r(shí)需要大小和后綴名兩個(gè)屬性,而且兩個(gè)屬性之間也是具有很強(qiáng)關(guān)系的(一對(duì)一),干脆封裝一下,做成一個(gè)Java類,這樣使用起來(lái)很方便,而且兩個(gè)屬性之間也建立了聯(lián)系,序列化恢復(fù)也比較方便。而且對(duì)象序列化還帶來(lái)一個(gè)好處,Java的對(duì)象序列化是二進(jìn)制序列化,區(qū)別于 json 這種字符序列化,二進(jìn)制是機(jī)器讀取的,我們就算打開(kāi)了也是亂碼,所以,可以避免這個(gè)文件被別人給修改了。(一般是不會(huì)去修改二進(jìn)制文件的吧,哈?。?/p>
圖片對(duì)象模型
package dragon; import java.io.Serializable; /** * 文件信息模型類: * 記錄文件的大小和后綴名,因?yàn)榭偸? * 需要使用這個(gè),就把它封裝起來(lái)使用吧。 * */ 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; } //重寫(xiě) toString 方法,方便打印調(diào)試代碼 @Override public String toString() { return "FileInfo [len=" + len + ", suffix=" + suffix + "]"; } }
對(duì)于原來(lái)的圖片合并和分隔方法,都進(jìn)行了一點(diǎn)改進(jìn),所以命名規(guī)則上都在原來(lái)的類前面加了一個(gè) Enhance (增強(qiáng)、改進(jìn))。
改進(jìn)的圖片合并類: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 { //圖片合并路徑,將要合并圖片放入同一個(gè)文件夾方便操作 public static final String mergePath = "D:/DragonDataFile/beauty"; public static final String outputPath = "D:/DragonDataFile/merge"; //工具類,就只是用靜態(tài)方法了,不要?jiǎng)?chuàng)建對(duì)象了。 private EnhanceImageMerge() {} /**執(zhí)行合并操作 * * 思路如下:首先獲取文件夾下面的所有圖片文件信息, * 然后使用輸入輸出流依次將文件進(jìn)行合并操作。 * * 這里的信息是指的文件大小,最重要的是文件的大小, * 考慮其它因素,不記錄文件名,所以拆分時(shí),會(huì)丟失文件名, * 但是不影響圖片的顯示。 */ public static void imageMerge() throws IOException { File mergeFile = new File(EnhanceImageMerge.mergePath); File outputFile = new File(EnhanceImageMerge.outputPath); if (!initPath(mergeFile, outputFile)) { // 無(wú)法創(chuàng)建 mergePath throw new FileNotFoundException("無(wú)法創(chuàng)建文件夾: "+EnhanceImageMerge.mergePath); } File[] files = mergeFile.listFiles(); String suffix = recordImageInfo(files, outputFile); //記錄文件信息,保存于圖片的文件夾下,可能更好點(diǎn)。 try (//創(chuàng)建輸出文件 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; } 使用對(duì)象序列化進(jìn)行數(shù)據(jù)的存儲(chǔ),方便快捷。 private static String recordImageInfo(File[] files, File outputFile) throws FileNotFoundException, IOException { try ( //二進(jìn)制保存的數(shù)據(jù),無(wú)法直接閱讀,不加擴(kuò)展名了 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(outputFile, "fileinfo"), true))){ List<FileInfo> fileInfos = new LinkedList<>(); for (File file : files) { String filename = file.getName(); //記錄文件的大小和擴(kuò)展名信息 如果沒(méi)有的話,默認(rèn)設(shè)置為 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); //直接將集合序列化,序列化單個(gè)對(duì)象,讀取的時(shí)候太麻煩了 } String firstFileName = files[0].getName(); //返回第一個(gè)文件的后綴名。 return firstFileName.lastIndexOf(".") != -1 ? firstFileName.substring(firstFileName.lastIndexOf(".")) : ".none"; } }
注意:對(duì)象序列化的時(shí)候,如果每次序列化一個(gè)對(duì)象的話,那么讀取的時(shí)候,就無(wú)法判斷怎么結(jié)束了,因?yàn)槌绦虿恢涝撟x取多少次才結(jié)束,而且似乎不能使用讀取結(jié)果為 null 來(lái)判斷,那樣會(huì)引發(fā)一個(gè) EOFException。
我去查閱資料,有人推薦了,在序列化的最后,添加一個(gè) null 對(duì)象,這確實(shí)是一個(gè)很好的方法,但是感覺(jué)還是不好。
另一種方式就是直接序列化一個(gè)List 集合,這樣確實(shí)是方便多了,存入一個(gè)集合,讀取回來(lái)了還是一個(gè)集合,可以直接操作了,還省去將對(duì)象再組裝成集合的時(shí)間。(對(duì)象序列化,我只是了解,用過(guò)那么一兩次,不是很熟。)
對(duì)象序列化部分
使用對(duì)象序列化進(jìn)行數(shù)據(jù)的存儲(chǔ),方便快捷。 private static String recordImageInfo(File[] files, File outputFile) throws FileNotFoundException, IOException { try ( //二進(jìn)制保存的數(shù)據(jù),無(wú)法直接閱讀,不加擴(kuò)展名了 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File(outputFile, "fileinfo"), true))){ List<FileInfo> fileInfos = new LinkedList<>(); for (File file : files) { String filename = file.getName(); //記錄文件的大小和擴(kuò)展名信息 如果沒(méi)有的話,默認(rèn)設(shè)置為 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); //直接將集合序列化,序列化單個(gè)對(duì)象,讀取的時(shí)候太麻煩了 } String firstFileName = files[0].getName(); //返回第一個(gè)文件的后綴名。 return firstFileName.lastIndexOf(".") != -1 ? firstFileName.substring(firstFileName.lastIndexOf(".")) : ".none"; }
改進(jìn)的圖片分隔類: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() {} /** * 合并后文件夾下面有兩個(gè)文件(應(yīng)該每一批合并文件,一個(gè)單獨(dú)的文件夾): * 合并后文件,合并文件信息(大?。?。 * * 思路:首先讀取合并文件信息,然后依據(jù)大小依次從文件中取出 * 對(duì)應(yīng)大小的字節(jié)數(shù),寫(xiě)入一個(gè)文件中。 * * @throws IOException * @throws ClassNotFoundException * @throws NumberFormatException * */ public static void imageSeparate() throws IOException, NumberFormatException, ClassNotFoundException { File separateFile = new File(EnhanceImageSeparate.separatePath); if (initPath(separateFile)) { //無(wú)法創(chuàng)建文件夾 throw new FileNotFoundException("無(wú)法創(chuàng)建文件夾: "+EnhanceImageSeparate.separatePath); } File outputFile = new File(ImageMerge.outputPath); //下面獲取的都是 String 數(shù)組,但是正常情況下應(yīng)該都是只有一個(gè) String 的字符串 //獲取圖片文件信息文件 File[] infoFile = outputFile.listFiles(f->!f.getName().contains(".")); //序列化文件是沒(méi)有后綴名的 //獲取合并圖片文件 File[] mergeFile = outputFile.listFiles(f->f.getName().contains(".")); //圖片文件都是有后綴名的 // 獲取信息文件信息(圖片的長(zhǎng)度) System.out.println(infoFile[0]); List<FileInfo> fileInfos = getFileInfo(infoFile[0]); mergeOperation(fileInfos, mergeFile[0], separateFile); } /** * 執(zhí)行文件合并操作 * @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]; /** * 這里處理比較麻煩,我說(shuō)明一下: * 一次性去讀 len 長(zhǎng)度的數(shù)據(jù),考慮到有時(shí)候文件會(huì)非常大,這個(gè)數(shù)據(jù)對(duì)內(nèi)存的壓力很大, * 所以舍棄了,如果文件很小,倒也是一個(gè)很好的方式(簡(jiǎn)便)。 * * 這里采用逐次讀取的方式:(一般圖片都會(huì)大于 1024 字節(jié)的,這個(gè)不考慮) * 當(dāng)讀取一次后,判斷剩余的字節(jié)數(shù)是否小于 1024,如果小于的話,就直接 * 一次性讀取這些字節(jié)數(shù),并寫(xiě)入文件中,然后跳出循環(huán),本次文件讀取完成。 * */ while ((hasRead = bis.read(b)) != -1) { bos.write(b,0,hasRead); //先判斷,再讀取數(shù)據(jù),否則會(huì)出錯(cuò)。 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(); } }); } } //獲取信息文件信息(圖片的長(zhǎng)度) //抑制一下 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(); //強(qiáng)制類型轉(zhuǎn)換一下,讀取出來(lái)的數(shù)據(jù)都是 Object 類型 } } //初始化 拆分文件位置 private static boolean initPath(File file) { return file.mkdirs(); } }
注意: 分隔還原圖片時(shí),圖片的后綴名部分代碼為:
使用Java封裝屬性后,使用很方便了。
String filename = System.currentTimeMillis()+UUID.randomUUID().toString()+fileInfo.getSuffix();
反序列化讀取集合:
這里我抑制了一個(gè)強(qiáng)制類型轉(zhuǎn)換的警告。
通過(guò)序列化,可以發(fā)現(xiàn)代碼量大大減少了,直接就是集合,使用非常方便。
//抑制一下 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(); //強(qiáng)制類型轉(zhuǎn)換一下,讀取出來(lái)的數(shù)據(jù)都是 Object 類型 } }
測(cè)試代碼
package dragon; import java.io.IOException; public class Client { public static void main(String[] args) throws IOException, NumberFormatException, ClassNotFoundException { //如果需要合并圖片,就使用第一條語(yǔ)句,注釋第二條, //如果需要拆分圖片,就使用第二條語(yǔ)句,注釋第一條 EnhanceImageMerge.imageMerge(); // EnhanceImageSeparate.imageSeparate(); } }
改進(jìn)后代碼運(yùn)行結(jié)果 執(zhí)行合并方法時(shí),打印的圖片對(duì)象模型的信息
合并后的效果
注意觀察右邊的 fileinfo 文件,因?yàn)槭嵌M(jìn)制數(shù)據(jù),我就沒(méi)有給它加上文件后綴名,加上了也是無(wú)法直接閱讀的,里面存儲(chǔ)的是圖片對(duì)象模型集合的序列化信息。
執(zhí)行分隔操作后的效果
控制臺(tái)輸出圖片信息,可以看到每個(gè)圖片的后綴名都恢復(fù)了,注意看最后一個(gè),有一個(gè)文本文件!哈哈!這個(gè)圖片后面似乎可以添加任何數(shù)據(jù),也許視頻也是可以的,只是我沒(méi)有測(cè)試,這個(gè)應(yīng)該和圖片的存儲(chǔ)格式、顯示方式有關(guān)。
注意,下面恢復(fù)的時(shí)候,確實(shí)是有一個(gè)文本文件,并且是完好的,可以閱讀的。
合并后被分隔出的文本文件的信息
總結(jié)
因?yàn)橐粋€(gè)代碼的bug,而產(chǎn)生的一個(gè)奇妙想法,并且付諸實(shí)踐了,收獲了一個(gè)挺有趣的代碼,發(fā)現(xiàn)這個(gè)還是挺有趣的,這也和我最近在看Java的IO流相關(guān)的部分有關(guān),如果我沒(méi)有前期的積累,估計(jì)想法只能是想法了(沒(méi)有能力實(shí)現(xiàn)自己的各種想法,哈哈!)。寫(xiě)這個(gè)代碼的時(shí)候,用到了很多以前的學(xué)過(guò)的知識(shí),因?yàn)楝F(xiàn)在沒(méi)什么接觸工作的機(jī)會(huì),很多知識(shí)不用,逐漸都生疏了,偶爾還是要練練手。新學(xué)的知識(shí) ,那就忘得更快了,很多時(shí)候也不是很理解,并且親自實(shí)踐一下才行。
紙上得來(lái)終覺(jué)淺,絕知此事要躬行。 哈哈。
主要還是記錄自己寫(xiě)代碼的過(guò)程,也算是記錄成長(zhǎng)吧。
到此這篇關(guān)于關(guān)于文件合并與修改md5值的問(wèn)題的文章就介紹到這了,更多相關(guān)文件合并與修改md5值內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring使用RedisTemplate的操作類訪問(wèn)Redis
本篇文章主要介紹了spring使用RedisTemplate的操作類訪問(wèn)Redis,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05java實(shí)現(xiàn)客戶信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)客戶信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-02-02Socket結(jié)合線程池使用實(shí)現(xiàn)客戶端和服務(wù)端通信demo
這篇文章主要為大家介紹了Socket結(jié)合線程池的使用來(lái)實(shí)現(xiàn)客戶端和服務(wù)端通信實(shí)戰(zhàn)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03Java?Web實(shí)現(xiàn)簡(jiǎn)易圖書(shū)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java?Web實(shí)現(xiàn)簡(jiǎn)易圖書(shū)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09Java Lambda List轉(zhuǎn)Map代碼實(shí)例
這篇文章主要介紹了Java Lambda List轉(zhuǎn)Map代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Java的Struts框架中<results>標(biāo)簽的使用方法
這篇文章主要介紹了Java的Struts框架中<results>標(biāo)簽的使用方法,Struts框架是Java的SSH三大web開(kāi)發(fā)框架之一,需要的朋友可以參考下2015-11-11springboot+EHcache 實(shí)現(xiàn)文章瀏覽量的緩存和超時(shí)更新
這篇文章主要介紹了springboot+EHcache 實(shí)現(xiàn)文章瀏覽量的緩存和超時(shí)更新,問(wèn)題描述和解決思路給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-04-04java全角與半角標(biāo)點(diǎn)符號(hào)相互轉(zhuǎn)換詳解
這篇文章主要為大家介紹了java全角與半角標(biāo)點(diǎn)符號(hào)相互轉(zhuǎn)換詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03