最近真的被圖片上傳的功能給煩惱了。在web的項目中,咱們常常會有上傳圖片的業務場景,最典型的是上傳頭像。爲了解決頭像上能夠有以下的實現:javascript
multipart/form-data
上傳用戶信息和頭像,也便是使用html裏面的<form></form>
。如 gitlab中修改用戶信息的頭像。這裏將討論的是第三中實現方法的中的圖片與Base64編碼互轉。 css
在網頁中,會有以下兩種處理圖片的方式,一種是直接src="/avatar/avatar.jpg"
,另外一種則是 src="data:image/jpeg;base64,xxxxxx="
的方式。第二種方式就是前端將發給後臺的內容,數據由[數據描述],[數據Base64]組成,[數據描述]將告知咱們該圖片的類別,能夠從中分析出圖片的拓展名,[數據Base64]爲圖片Base64編碼以後的數據,爲圖片文件完整的數據。爲了可以完整地回覆圖片的內容和拓展名,須要前端發送[數據描述],[數據Base64]
到後臺。該格式實際上是Data URI Scheme
,完整後面再作講解。html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <p>Data URLs Image:</p> <!-- 常見的方式 --> <img src="/avatar/avatar.jpg"> <!-- 將 img.src 的內容複製粘貼到瀏覽器的輸入框中,能夠看到圖片的內容 --> <img src="data:image/jpeg;base64,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" /> </body> </html>
這裏利用兩個map,分別記錄數據描述映射到拓展名和拓展名映射到數據描述,從而方便數據描述和拓展名的獲取。前端
import java.util.HashMap; import java.util.Map; import java.io.File; public class ImageDataURISchemeMapper { private static Map<String, String> scheme2Extension = new HashMap<String, String>(); private static Map<String, String> extension2Scheme = new HashMap<String, String>(); static { initSchemeSupported(); } public static String getScheme(String imageExtension) { if (imageExtension == null || imageExtension.isEmpty()) { return ""; } String result = extension2Scheme.get(imageExtension.toLowerCase()); return result == null ? "" : result; } public static String getScheme(File image) { if (image == null) { return ""; } String name = image.getName(); int lastPointIndex = name.lastIndexOf("."); return lastPointIndex < 0 ? "" : getScheme(name.substring(lastPointIndex + 1)); } public static String getExtension(String dataUrlScheme) { return scheme2Extension.get(dataUrlScheme); } public static String getExtensionFromImageBase64(String imageBase64, String defaultExtension) { int firstComma = imageBase64.indexOf(","); if(firstComma < 0) { return defaultExtension; } return scheme2Extension.get(imageBase64.subSequence(0, firstComma + 1)); } private static void initSchemeSupported() { addScheme("jpg", "data:image/jpg;base64,"); addScheme("jpeg", "data:image/jpeg;base64,"); addScheme("png", "data:image/png;base64,"); addScheme("gif", "data:image/gif;base64,"); addScheme("icon", "data:image/x-icon;base64,"); } private static void addScheme(String extension, String dataUrl) { scheme2Extension.put(dataUrl, extension); extension2Scheme.put(extension, dataUrl); } }
圖片轉Base64:java
Base64轉圖片:ios
import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.IOUtils; /** * 目標處理的圖片類別有:png,jpg,jpeg * * 參考: * <ul> * <li>[淺析data:image/png;base64的應用](https://www.cnblogs.com/ECJTUACM-873284962/p/9245474.html)</li> * <li>[Base64](https://zh.wikipedia.org/wiki/Base64)</li> * <li>[Data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) * </ul> * * @author DoneSpeak * @date 2019/06/26 */ public class ImageConvertBase64 { /** * 將圖片文件轉化爲 byte 數組 * * @param image * 待處理圖片文件 * @return 圖片文件轉化爲的byte數組 */ public static byte[] toBytes(File image) { try (FileInputStream input = new FileInputStream(image)) { // InputStream 的 available() 返回的值是該InputStream 在不被阻塞的狀況下,一次能夠讀取到的數據長度。 // byte[] imageBytes = new byte[input.available()]; // input.read(imageBytes); return IOUtils.toByteArray(input); } catch (IOException e) { return null; } } public static String toBase64(byte[] bytes) { return bytesEncode2Base64(bytes); } /** * 將圖片轉化爲 base64 的字符串 * * @param image * 待處理圖片文件 * @return 圖片文件轉化出來的 base64 字符串 */ public static String toBase64(File image) { return toBase64(image, false); } /** * 將圖片轉化爲 base64 的字符串。若是<code>appendDataURLScheme</code>的值爲true,則爲圖片的base64字符串拓展Data URL scheme。 * @param image 圖片文件的路徑 * @param appendDataURLScheme 是否拓展 Data URL scheme 前綴 * @return 圖片文件轉化爲的base64字符串 */ public static String toBase64(File image, boolean appendDataURLScheme) { String imageBase64 = bytesEncode2Base64(toBytes(image)); if(appendDataURLScheme) { imageBase64 = ImageDataURISchemeMapper.getScheme(image) + imageBase64; } return imageBase64; } private static String bytesEncode2Base64(byte[] bytes) { return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); } private static byte[] base64Decode2Bytes(String base64) { return Base64.getDecoder().decode(base64); } /** * 將byte數組恢復爲圖片文件 * * @param imageBytes * 圖片文件的 byte 數組 * @param imagePath * 恢復的圖片文件的保存地址 * @return 若是生成成功,則返回生成的文件路徑,此時結果爲參數的<code>imagePath</code>。不然返回 null */ public static File toImage(byte[] imageBytes, File imagePath) { if (!imagePath.getParentFile().exists()) { imagePath.getParentFile().mkdirs(); } try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(imagePath))) { bos.write(imageBytes); return imagePath; } catch (IOException e) { return null; } } /** * 將base64字符串恢復爲圖片文件 * * @param imageBase64 * 圖片文件的base64字符串 * @param imagePath * 恢復的圖片文件的保存地址 * @return 若是生成成功,則返回生成的文件路徑,此時結果爲參數的<code>imagePath</code>。。不然返回 null */ public static File toImage(String imageBase64, File imagePath) { // base64 字符串中沒有 "," int firstComma = imageBase64.indexOf(","); if(firstComma >= 0) { imageBase64 = imageBase64.substring(firstComma + 1); } return toImage(base64Decode2Bytes(imageBase64), imagePath); } /** * 保存 imageBase64 到指定文件中。若是<code>fileName</code>含有拓展名,則直接使用<code>fileName</code>的拓展名。 * 不然,若是 <code>imageBase64</code> 爲Data URLs,則更具前綴的來判斷拓展名。若是沒法判斷拓展名,則使用「png」做爲默認拓展名。 * @param imageBase64 圖片的base64編碼字符串 * @param dir 保存圖片的目錄 * @param fileName 圖片的名稱 * @return 若是生成成功,則返回生成的文件路徑。不然返回 null */ public static File toImage(String imageBase64, File dir, String fileName) { File imagePath = null; if(fileName.indexOf(".") < 0) { String extension = ImageDataURISchemeMapper.getExtensionFromImageBase64(imageBase64, "png"); imagePath = new File(dir, fileName + "." + extension); } else { imagePath = new File(dir, fileName); } return toImage(imageBase64, imagePath); } }
這裏使用的工具類爲 org.apache.commons.io.*
:git
<!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
獲取文件拓展名
int lastPointIndex = filename.lastIndexOf("."); String extension = lastPointIndex < 0 ? "" : getScheme(filename.substring(lastPointIndex + 1)); // 簡化 String extension = FilenameUtils.getExtension(filename);
文件轉 byte[]
File image = new File("avatar.jpg"); IOUtils.toByteArray(new FileInputStream(image)); // 或者 byte[] bytes = FileUtils.readFileToByteArray(image);
byte[] 保存爲文件
File image = new File("avatar.jpg") FileUtils.writeByteArrayToFile(image, bytes);
實現圖片轉Base64和Base64轉圖片的代碼其實就只要以下的幾行代碼。web
public static void main(String[] args) throws IOException { File image = new File("src/test/resources/imageConvertBase64.jpeg"); File newImage = new File("src/test/resources/new-imageConvertBase64.jpeg"); // 編碼爲 Base64 字符串 byte[] bytes = FileUtils.readFileToByteArray(image); String base64 = new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); System.out.println(base64); // Base64 保存爲圖片 bytes = Base64.getDecoder().decode(base64); FileUtils.writeByteArrayToFile(newImage, bytes); }
Data URLs, URLs prefixed with the data: scheme, allow content creators to embed small files inline in documents.-- Data URLs @Mozilla算法
Data URLs 由以下四個部分組成,如文章開篇的 data:image/jpeg;base64,/9j/4AAQSkxxxxxxx
。apache
data:[<mediatype>][;base64],<data>
data:
: 協議固定前綴。[<mediatype>]
: 是一個 MIME type,好比 image/jpeg
,你也能夠在 "Incomplete list of MIME types"中找到一些類型。[;base64]
: 是編碼方式。 這裏用的 base64
。<data>
: 編碼後的字符串。Data URL schema 是在 RFC2397 中被定義的URL scheme。其有以下的優缺點:
優勢
缺點
目前Data URL schema支持的類型有:
類型 | 描述 |
---|---|
data:, | 文本數據 |
data:text/plain, | 文本數據 |
data:text/html, | HTML代碼 |
data:text/html;base64, | base64編碼的HTML代碼 |
data:text/css, | CSS代碼 |
data:text/css;base64, | base64編碼的CSS代碼 |
data:text/javascript, | Javascript代碼 |
data:text/javascript;base64, | base64編碼的Javascript代碼 |
data:image/gif;base64, | base64編碼的gif圖片數據 |
data:image/png;base64, | base64編碼的png圖片數據 |
data:image/jpeg;base64, | base64編碼的jpeg圖片數據 |
data:image/x-icon;base64, | base64編碼的icon圖片數據 |
更加詳細的內容能夠看:淺析data:image/png;base64的應用 @Angel_Kitty
Base64是一種基於64個可打印字符來表示二進制數據的表示方法。任何數據都是二進制數據,也就是說Base64能夠表示任何數據。Base64經常使用於在一般處理文本數據的場合,表示、傳輸、存儲一些二進制數據,包括MIME的電子郵件及XML的一些複雜數據。其主要的主要做用不在於安全,而在於讓數據在網絡中無錯傳輸。
因爲 2^6=64,因此每6個bit爲一個單元,對應某個可打印字符。3個字節(byte)有24個bit,對應於4個Base64單元(24/6=4),即3個字節可由4個可打印字符來表示。這就致使編碼後的數據比原始數據增長了1/3的長度。每6個bit的取值按照 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
進行選擇。如原數據長度不是3的倍數,則剩下一個輸入字符,添加一個=
,兩個輸入字符,編碼結果則增長一個=
。
編碼「Man」
在最後的一位(A)或者兩位字符(BC)時進行編碼
解碼
編碼的逆過程,將 =
轉化爲 0
便可,在 ASCII 碼中,0字符爲空字符。
以上的內容基原本自 維基百科 Base64。因爲+/
的特殊性,爲了適應不一樣的場景,會使用不一樣的字符替換掉原來算法中的 +/
,而造成新的算法。
在Java 8中,JDK提供了Base64的編解碼工具。提供了 Basic編碼
URL編碼
MIME編碼
以及 對流的封裝
,文章 Java 8實現BASE64編解碼 中對 Base64
工具作了不錯的介紹。
Base64 轉化工具: