Java使用ByteBuffer進(jìn)行多文件合并和拆分的代碼實(shí)現(xiàn)
1.背景
因?yàn)轵?yàn)證證書的需要,需要把證書文件和公鑰給到客戶,考慮到多個(gè)文件交互的不便性,所以決定將2個(gè)文件合并成一個(gè)文件交互給客戶。剛開始采用字符串拼接2個(gè)文件內(nèi)容,但是由于是加密文件,采用字符串形式合并后,拆分后文件不可用,最后采用基于二進(jìn)制流拆分和合并文件,效果不錯(cuò)!
2.代碼工程
實(shí)驗(yàn)?zāi)康?/h3>
對(duì)文件進(jìn)行二進(jìn)制流合并和拆分
對(duì)文件進(jìn)行二進(jìn)制流合并和拆分
合并代碼
ByteBuffer.allocate(4).putInt(publicCertsContent.length).array()
的作用是將 publicCertsContent
(公鑰證書內(nèi)容)的長(zhǎng)度轉(zhuǎn)換為 4 個(gè)字節(jié)的整數(shù),并寫入到字節(jié)緩沖區(qū)中。具體作用如下:
ByteBuffer.allocate(4)
: 創(chuàng)建一個(gè)大小為 4 字節(jié)的ByteBuffer
,因?yàn)?Java 中的整數(shù)(int
)是 4 個(gè)字節(jié)。putInt(publicCertsContent.length)
: 將publicCertsContent
的字節(jié)數(shù)組長(zhǎng)度(即證書文件的字節(jié)長(zhǎng)度)存入ByteBuffer
。這樣就把證書內(nèi)容的長(zhǎng)度存儲(chǔ)為一個(gè) 4 字節(jié)的整數(shù)。array()
: 將ByteBuffer
轉(zhuǎn)換為字節(jié)數(shù)組。這個(gè)數(shù)組包含了公鑰證書內(nèi)容長(zhǎng)度的 4 字節(jié)表示形式。
這個(gè)表達(dá)式的作用是將 publicCertsContent
數(shù)組的長(zhǎng)度轉(zhuǎn)換為二進(jìn)制的 4 字節(jié)表示形式,并寫入輸出流(outputStream
)中。這樣在以后讀取合并文件時(shí),代碼可以知道該讀取多少字節(jié)屬于 publicCertsContent
。
有小伙伴這里可能有疑問(wèn),4字節(jié)夠存多大數(shù)字呢?
4 個(gè)字節(jié)(即 32 位)在計(jì)算機(jī)中用于存儲(chǔ)整數(shù),能表示的整數(shù)范圍如下:
- 有符號(hào)整數(shù)(
int
類型):- 范圍是:-2,147,483,648 到 2,147,483,647
- 其中 1 位用于符號(hào)(正負(fù)),31 位用于數(shù)值。
- 無(wú)符號(hào)整數(shù)(
unsigned int
,Java 中沒有直接支持,但可以通過(guò)轉(zhuǎn)換處理):- 范圍是:0 到 4,294,967,295
通常 Java 中的 int
類型是有符號(hào)的,因此 4 個(gè)字節(jié)可以存儲(chǔ)的整數(shù)范圍為 -2^31 到 2^31 - 1,即 -2,147,483,648 到 2,147,483,647。所以只要不超過(guò)這個(gè)限制都能存下來(lái)
package com.et; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; public class FileMerger { public static void main(String[] args) { String privateKeys = "D:/IdeaProjects/Java-demo/file/src/main/resources/privateKeys.keystore"; String publicCerts = "D:/IdeaProjects/Java-demo/file/src/main/resources/publicCerts.keystore"; merger(privateKeys,publicCerts); } public static String merger(String privateKeys, String publicCerts) { String directoryPath = FileUtils.extractDirectoryPath(privateKeys); String mergedFile =directoryPath+"merge.keystore"; try { byte[] privateKeysContent = FileUtils.readBinaryFile(privateKeys); byte[] publicCertsContent = FileUtils.readBinaryFile(publicCerts); // create outputStream ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // write keystore length(4 byte) outputStream.write(ByteBuffer.allocate(4).putInt(privateKeysContent.length).array()); // write keystore content outputStream.write(privateKeysContent); // witer license content(4 byte int ) outputStream.write(ByteBuffer.allocate(4).putInt(publicCertsContent.length).array()); // write license content outputStream.write(publicCertsContent); // write merge content to file FileUtils.writeBinaryFile(mergedFile, outputStream.toByteArray()); System.out.println("merge success " + mergedFile); } catch (IOException e) { e.printStackTrace(); } return mergedFile; } }
拆分代碼
拆分邏輯就很簡(jiǎn)單了,先讀取4字節(jié),獲取文件大小,然后依次獲取文件內(nèi)容就可以了 buffer.getInt()
可以正確讀取文件中的大小信息。具體來(lái)說(shuō),它會(huì)從 ByteBuffer
中讀取 4 個(gè)字節(jié),并將這些字節(jié)解釋為一個(gè)整數(shù)。 如果你按照之前的代碼將文件內(nèi)容合并時(shí),將文件大小以 4 字節(jié)整數(shù)形式寫入到文件中,那么你可以使用 buffer.getInt()
來(lái)讀取這個(gè)大小。例如,假設(shè)你在合并文件時(shí)使用了如下方式寫入大?。?/p>
outputStream.write(ByteBuffer.allocate(4).putInt(content.length).array());
在讀取合并文件時(shí),你可以這樣做:
ByteBuffer buffer = ByteBuffer.wrap(fileContent); // 假設(shè) fileContent 是讀取的字節(jié)數(shù)組 int size = buffer.getInt(); // 讀取前 4 個(gè)字節(jié),獲取文件大小
這里的 buffer.getInt()
會(huì)正確讀取到前 4 個(gè)字節(jié),并將其轉(zhuǎn)換為整數(shù),這樣你就得到了文件內(nèi)容的大小。 請(qǐng)確保在讀取文件時(shí),字節(jié)順序(字節(jié)序)與寫入時(shí)一致,默認(rèn)情況下是大端序(Big Endian)。如果你的應(yīng)用需要小端序(Little Endian),你可以使用 buffer.order(ByteOrder.LITTLE_ENDIAN)
來(lái)設(shè)置字節(jié)序。
package com.et; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FileSplitter { public static void main(String[] args) { String mergedFile = "D:/IdeaProjects/Java-demo/file/src/main/resources/merge.keystore"; split(mergedFile); } private static void debugContent(byte[] content, String fileName) { System.out.printf("File: %s%n", fileName); System.out.printf("Length: %d%n", content.length); System.out.print("Content: "); for (byte b : content) { System.out.printf("%02X ", b); } System.out.println(); } static String[] split(String mergedFile) { String directoryPath = FileUtils.extractDirectoryPath(mergedFile); String privateKeysFile = directoryPath + ".privateKeys.keystore"; String publicCertsFile = directoryPath + ".publicCerts.keystore"; String[] filePaths = new String[]{privateKeysFile, publicCertsFile}; try { // read merge content byte[] mergedContent = FileUtils.readBinaryFile(mergedFile); // use ByteBuffer parse ByteBuffer buffer = ByteBuffer.wrap(mergedContent); // read privateKeys content length int privateKeysLength = buffer.getInt(); // read privateKeys content byte[] privateKeysContent = new byte[privateKeysLength]; buffer.get(privateKeysContent); // read publicCerts content length int publicCertsLength = buffer.getInt(); // read publicCerts content byte[] publicCertsContent = new byte[publicCertsLength]; buffer.get(publicCertsContent); // write privateKeys and publicCerts content to file FileUtils.writeBinaryFile(privateKeysFile, privateKeysContent); FileUtils.writeBinaryFile(publicCertsFile, publicCertsContent); System.out.println("merge file split " + privateKeysFile + " and " + publicCertsFile); } catch (IOException e) { e.printStackTrace(); } return filePaths; } private static byte[] extractContent(byte[] mergedContent, byte[] beginMarker, byte[] endMarker) { int beginIndex = indexOf(mergedContent, beginMarker); int endIndex = indexOf(mergedContent, endMarker, beginIndex); if (beginIndex != -1 && endIndex != -1) { // Move past the start marker beginIndex += beginMarker.length; // Adjust endIndex to exclude the end marker int adjustedEndIndex = endIndex; // Extract content return Arrays.copyOfRange(mergedContent, beginIndex, adjustedEndIndex); } else { return new byte[0]; // Return empty array if markers are not found } } private static byte[] removeEmptyLines(byte[] content) { // Convert byte array to list of lines List<byte[]> lines = splitIntoLines(content); // Filter out empty lines lines = lines.stream() .filter(line -> line.length > 0) .collect(Collectors.toList()); // Reassemble content return mergeLines(lines); } private static List<byte[]> splitIntoLines(byte[] content) { List<byte[]> lines = new ArrayList<>(); int start = 0; for (int i = 0; i < content.length; i++) { if (content[i] == '\n') { // Line break lines.add(Arrays.copyOfRange(content, start, i)); start = i + 1; } } if (start < content.length) { lines.add(Arrays.copyOfRange(content, start, content.length)); } return lines; } private static byte[] mergeLines(List<byte[]> lines) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (byte[] line : lines) { try { outputStream.write(line); outputStream.write('\n'); // Re-add line break } catch (IOException e) { e.printStackTrace(); } } return outputStream.toByteArray(); } private static int indexOf(byte[] array, byte[] target) { return indexOf(array, target, 0); } private static int indexOf(byte[] array, byte[] target, int start) { for (int i = start; i <= array.length - target.length; i++) { if (Arrays.equals(Arrays.copyOfRange(array, i, i + target.length), target)) { return i; } } return -1; // Return -1 if target not found } }
工具類
package com.et; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class FileUtils { static byte[] readBinaryFile(String filePath) throws IOException { File file = new File(filePath); try (FileInputStream fis = new FileInputStream(file)) { byte[] fileBytes = new byte[(int) file.length()]; fis.read(fileBytes); return fileBytes; } } public static String extractDirectoryPath(String filePath) { File file = new File(filePath); return file.getParent()+File.separator; // 獲取文件所在的目錄 } static void writeBinaryFile(String filePath, byte[] content) throws IOException { try (FileOutputStream fos = new FileOutputStream(filePath)) { fos.write(content); } } }
以上只是一些關(guān)鍵代碼,所有代碼請(qǐng)參見下面代碼倉(cāng)庫(kù)
代碼倉(cāng)庫(kù)
3.測(cè)試
測(cè)試類
package com.et; public class Main { public static void main(String[] args) { System.out.println("Hello world!"); String privateKeys = "D:/IdeaProjects/Java-demo/file/src/main/resources/privateKeys.keystore"; String publicCerts = "D:/IdeaProjects/Java-demo/file/src/main/resources/publicCerts.keystore"; FileMerger.merger(privateKeys,publicCerts); String mergedFile = "D:/IdeaProjects/Java-demo/file/src/main/resources/merge.keystore"; FileSplitter.split(mergedFile); } }
運(yùn)行main方法,日志顯示如下
merge success D:\IdeaProjects\Java-demo\file\src\main\resources\merge.keystore merge file split D:\IdeaProjects\Java-demo\file\src\main\resources\.privateKeys.keystore and D:\IdeaProjects\Java-demo\file\src\main\resources\.publicCerts.keystore
拆分后文件于原文件大小一模一樣
以上就是Java使用ByteBuffer進(jìn)行多文件合并和拆分的代碼實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Java ByteBuffer文件合并和拆分的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot應(yīng)用訪問(wèn)zookeeper的流程
這篇文章主要介紹了springboot應(yīng)用訪問(wèn)zookeeper的流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01spring無(wú)法讀取properties文件數(shù)據(jù)問(wèn)題詳解
這篇文章主要介紹了spring無(wú)法讀取properties文件數(shù)據(jù)問(wèn)題詳解,需要的朋友可以參考下2020-02-02Spring Boot中的SpringSecurity基礎(chǔ)教程
Spring Security是一個(gè)功能強(qiáng)大且高度可定制的身份驗(yàn)證和訪問(wèn)控制框架。它實(shí)際上是保護(hù)基于spring的應(yīng)用程序的標(biāo)準(zhǔn)Spring Security是一個(gè)框架,側(cè)重于為Java應(yīng)用程序提供身份驗(yàn)證和授權(quán),這篇文章主要介紹了Spring Boot中的SpringSecurity學(xué)習(xí),需要的朋友可以參考下2023-01-01SpringBoot通過(guò)自定義注解實(shí)現(xiàn)配置類的自動(dòng)注入的實(shí)現(xiàn)
本文主要介紹了SpringBoot通過(guò)自定義注解實(shí)現(xiàn)配置類的自動(dòng)注入的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04解決JMap抓取heap使用統(tǒng)計(jì)信息報(bào)錯(cuò)的問(wèn)題
這篇文章主要介紹了解決JMap抓取heap使用統(tǒng)計(jì)信息報(bào)錯(cuò)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12spring @AfterReturning返回值問(wèn)題
這篇文章主要介紹了spring @AfterReturning返回值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05如何用Netty實(shí)現(xiàn)高效的HTTP服務(wù)器
這篇文章主要介紹了如何用Netty實(shí)現(xiàn)高效的HTTP服務(wù)器,對(duì)HTTP感興趣的同學(xué)可以參考一下2021-04-04