使用ServletInputStream()輸入流讀取圖片方式
問題描述
最近遇到需要用到上傳圖片到服務(wù)器上,學(xué)習(xí)了一下原生servlet中的form上傳圖片保存到指定目錄的情況
思路:前端提交–servlet獲取inputstream–輸出到本地
獲取輸入流后輸出到本地一直打不開提示損壞/0kb.從網(wǎng)上看到有說需要apache的兩個(gè)包io和fileupload包.我想的是不借助第三方工具包處理(tomcat也是第三方呵呵,純的應(yīng)該是利用socket吧)
項(xiàng)目結(jié)構(gòu)
如圖所示:并未使用其余組件,創(chuàng)建了一個(gè)動(dòng)態(tài)java項(xiàng)目即可
問題原因
網(wǎng)上查到一片文章,大概意思是,上傳文件不是單純的文件流,其與本地io不同其中多了些東西.按照本地上傳下載的方式無法解析出來.包括些分隔符\和表單的一些信息,需要重新處理
解決方法
手動(dòng)解析出圖片的流,并把其中的多余東西去掉,然后將得到的純文件流輸出到指定位置
總結(jié)回顧
該問題居然查不到當(dāng)前時(shí)間的帖子,一般都是2-3年前的讓我有點(diǎn)意外.知其然而不知其所以然早晚被人家掣肘.在框架琳瑯滿目的當(dāng)下抄抄寫寫確實(shí)能解決問題而且真的是事半功倍.
另寫代碼要多查多看api文檔
多動(dòng)手敲代碼,不然不知道所以然,這么點(diǎn)破問題弄了個(gè)周末
package server; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @SuppressWarnings("serial") @WebServlet(name = "streams",urlPatterns = "/UploadServlet.do") public class CsvTest extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); final int NONE = 0; // 狀態(tài)碼,表示沒有特殊操作 final int DATAHEADER = 1; // 表示下一行要讀到報(bào)頭信息 final int FILEDATA = 2; // 表示下面要讀的是上傳文件和二進(jìn)制數(shù)據(jù) final int FIELDDATA = 3; // 表示下面要讀到表單域的文本值 // 請(qǐng)求消息實(shí)體的總長(zhǎng)度(請(qǐng)求消息中除消息頭之外的數(shù)據(jù)長(zhǎng)度) int totalbytes = request.getContentLength(); File f; // 上傳文件儲(chǔ)存在服務(wù)器上 // 容納請(qǐng)求消息實(shí)體的字節(jié)數(shù)組 byte[] dataOrigin = new byte[totalbytes]; // 對(duì)于post多個(gè)文件的表單,b作為原始數(shù)據(jù)的副本提供提取文件數(shù)據(jù)的操作 byte[] b = new byte[totalbytes]; // 請(qǐng)求消息類型 String contentType = request.getContentType(); String fieldname = ""; // 表單域的名稱 String fieldvalue = ""; // 表單域的值 String fileFormName = ""; // 上傳的文件再表單中的名稱 String fileRealName = ""; // 上傳文件的真實(shí)名字 String boundary = ""; // 分界符字符串 String lastboundary = ""; // 結(jié)束分界符字符串 int fileSize = 0; // 文件長(zhǎng)度 // 容納表單域的名稱/值的哈希表 Map<String, String> formfieldsTable = new HashMap<String, String>(); // 容納文件域的名稱/文件名的哈希表 Map<String, String> filenameTable = new HashMap<String, String>(); // 在消息頭類型中找到分界符的定義 int pos = contentType.indexOf("boundary="); int pos2; // position2 if (pos != -1) { pos += "boundary=".length(); boundary = "--" + contentType.substring(pos); // 解析出分界符 lastboundary = boundary + "--"; // 得到結(jié)束分界符 } int state = NONE; // 起始狀態(tài)為NONE // 得到請(qǐng)求消息的數(shù)據(jù)輸入流 DataInputStream in = new DataInputStream(request.getInputStream()); in.readFully(dataOrigin); // 根據(jù)長(zhǎng)度,將消息實(shí)體的內(nèi)容讀入字節(jié)數(shù)組dataOrigin中 in.close(); // 關(guān)閉數(shù)據(jù)流 String reqcontent = new String(dataOrigin); // 從字節(jié)數(shù)組中得到表示實(shí)體的字符串 // 從字符串中得到輸出緩沖流 BufferedReader reqbuf = new BufferedReader(new StringReader(reqcontent)); // 設(shè)置循環(huán)標(biāo)志 boolean flag = true; // int i = 0; while (flag == true) { String s = reqbuf.readLine(); if (s == lastboundary || s == null) break; switch (state) { case NONE: if (s.startsWith(boundary)) { // 如果讀到分界符,則表示下一行一個(gè)頭信息 state = DATAHEADER; // i += 1; } break; case DATAHEADER: pos = s.indexOf("filename="); // 先判斷出這是一個(gè)文本表單域的頭信息,還是一個(gè)上傳文件的頭信息 if (pos == -1) { // 如果是文本表單域的頭信息,解析出表單域的名稱 pos = s.indexOf("name="); pos += "name=".length() + 1; // 1表示后面的"的占位 s = s.substring(pos); int l = s.length(); s = s.substring(0, l - 1); // 應(yīng)該是" fieldname = s; // 表單域的名稱放入fieldname out.print("fieldname=" + fieldname); state = FIELDDATA; // 設(shè)置狀態(tài)碼,準(zhǔn)備讀取表單域的值 } else { // 如果是文件數(shù)據(jù)的頭,先存儲(chǔ)這一行,用于在字節(jié)數(shù)組中定位 String temp = s; // 先解析出文件名 pos = s.indexOf("name="); pos += "name=".length() + 1; // 1表示后面的"的占位 pos2 = s.indexOf("filename="); String s1 = s.substring(pos, pos2 - 3); // 3表示";加上一個(gè)空格 fileFormName = s1; pos2 += "filename=".length() + 1; // 1表示后面的"的占位 s = s.substring(pos2); int l = s.length(); s = s.substring(0, l - 1); pos2 = s.lastIndexOf("\\"); // 對(duì)于IE瀏覽器的設(shè)置 s = s.substring(pos2 + 1); fileRealName = s; out.print("fileRealName=" + fileRealName + "<br>"); out.print("fileRealName.length()=" + fileRealName.length() + "<br>"); if (fileRealName.length() != 0) { // 確定有文件被上傳 // 下面這一部分從字節(jié)數(shù)組中取出文件的數(shù)據(jù) b = dataOrigin; // 復(fù)制原始數(shù)據(jù)以便提取文件 pos = byteIndexOf(b, temp, 0); // 定位行 // 定位下一行,2 表示一個(gè)回車和一個(gè)換行占兩個(gè)字節(jié) b = subBytes(b, pos + temp.getBytes().length + 2, b.length); // 再讀一行信息,是這一部分?jǐn)?shù)據(jù)的Content-type s = reqbuf.readLine(); // 設(shè)置文件輸入流,準(zhǔn)備寫文件 f = new File("C:" + File.separator +"Users" + File.separator +"Administrator" + File.separator +"Desktop" + File.separator +fileRealName); DataOutputStream fileout = new DataOutputStream( new FileOutputStream(f)); // 字節(jié)數(shù)組再往下一行,4表示兩回車換行占4個(gè)字節(jié),本行的回車換行2個(gè)字節(jié),Content-type的下 // 一行是回車換行表示的空行,占2個(gè)字節(jié) // 得到文件數(shù)據(jù)的起始位置 b = subBytes(b, s.getBytes().length + 4, b.length); pos = byteIndexOf(b, boundary, 0); // 定位文件數(shù)據(jù)的結(jié)尾 b = subBytes(b, 0, pos - 1); // 取得文件數(shù)據(jù) fileout.write(b, 0, b.length - 1); // 將文件數(shù)據(jù)存盤 fileout.close(); fileSize = b.length - 1; // 文件長(zhǎng)度存入fileSize out.print("fileFormName=" + fileFormName + " filename=" + fileRealName + " fileSize=" + fileSize + "<br>"); filenameTable.put(fileFormName, fileRealName); state = FILEDATA; } } break; case FIELDDATA: // 讀取表單域的值 s = reqbuf.readLine(); fieldvalue = s; // 存入fieldvalue out.print(" fieldvalue=" + fieldvalue + "<br>"); formfieldsTable.put(fieldname, fieldvalue); state = NONE; break; case FILEDATA: // 如果是文件數(shù)據(jù)不進(jìn)行分析,直接讀過去 while ((!s.startsWith(boundary)) && (!s.startsWith(lastboundary))) { s = reqbuf.readLine(); if (s.startsWith(boundary)) { state = DATAHEADER; } else { break; } } break; } } // 指定內(nèi)容類型,并且可以顯示中文 out.println("<HTML"); out.println("<HEAD><TITLE>文件上傳結(jié)果</TITLE></HEAD>"); out.println("<BODY>"); out.println("<H1>文件上傳結(jié)果</H1><hr>"); out.println("ID為" + formfieldsTable.get("FileID1") + "的文件" + filenameTable.get("FileData1") + "已經(jīng)上傳!<br>"); out.println("ID為" + formfieldsTable.get("FileID2") + "的文件" + filenameTable.get("FileData2") + "已經(jīng)上傳!<br>"); // out.println("i = " + i + "<br>"); out.println("</BODY>"); out.println("</HTML>"); } private static int byteIndexOf(byte[] b, String s, int start) { return byteIndexOf(b, s.getBytes(), start); } private static int byteIndexOf(byte[] b, byte[] s, int start) { int i; if (s.length == 0) { return 0; } int max = b.length - s.length; if (max < 0) { return -1; } if (start > max) { return -1; } if (start < 0) { start = 0; } // 在b中找到s的第一個(gè)元素 search: for (i = start; i <= max; i++) { if (b[i] == s[0]) { // 找到了s中的第一個(gè)元素后,比較剩余的部分是否相等 int k = 1; while (k < s.length) { if (b[k + i] != s[k]) { continue search; } k++; } return i; } } return -1; } private static byte[] subBytes(byte[] b, int from, int end) { byte[] result = new byte[end - from]; System.arraycopy(b, from, result, 0, end - from); return result; } private static String subBytesString(byte[] b, int from, int end) { return new String(subBytes(b, from, end)); } }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
為何修改equals方法時(shí)還要重寫hashcode方法的原因分析
這篇文章主要介紹了為何修改equals方法時(shí)還要重寫hashcode方法的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Sonar編譯問題對(duì)應(yīng):File [...] can''t be indexed twice.
今天小編就為大家分享一篇關(guān)于Sonar編譯問題對(duì)應(yīng):File [...] can't be indexed twice.,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12SpringBoot獲取當(dāng)前運(yùn)行環(huán)境三種方式小結(jié)
在使用SpringBoot過程中,我們只需要引入相關(guān)依賴,然后在main方法中調(diào)用SpringBootApplication.run(應(yīng)用程序啟動(dòng)類.class)方法即可,那么SpringBoot是如何獲取當(dāng)前運(yùn)行環(huán)境呢,接下來由小編給大家介紹一下SpringBoot獲取當(dāng)前運(yùn)行環(huán)境三種方式,需要的朋友可以參考下2024-01-01SpringBoot項(xiàng)目使用協(xié)同過濾的實(shí)現(xiàn)
協(xié)同過濾是一種常用的推薦系統(tǒng)算法,用于預(yù)測(cè)用戶可能喜歡的物品,本文主要介紹了SpringBoot項(xiàng)目使用協(xié)同過濾的實(shí)現(xiàn),感興趣的可以了解一下2023-09-09feign參數(shù)過多導(dǎo)致調(diào)用失敗的解決方案
這篇文章主要介紹了feign參數(shù)過多導(dǎo)致調(diào)用失敗的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03SpringBoot WebSocket實(shí)時(shí)監(jiān)控異常的詳細(xì)流程
最近做了一個(gè)需求,消防的設(shè)備巡檢,如果巡檢發(fā)現(xiàn)異常,通過手機(jī)端提交,后臺(tái)的實(shí)時(shí)監(jiān)控頁面實(shí)時(shí)獲取到該設(shè)備的信息及位置,然后安排員工去處理。這篇文章主要介紹了SpringBoot WebSocket實(shí)時(shí)監(jiān)控異常的全過程,感興趣的朋友一起看看吧2021-10-10淺談Servlet 實(shí)現(xiàn)網(wǎng)頁重定向的方法
本篇文章主要介紹了Servlet 實(shí)現(xiàn)重定向幾種方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08Java Swing程序設(shè)計(jì)實(shí)戰(zhàn)
今天教大家怎么用JavaSwing工具包實(shí)現(xiàn)一個(gè)程序的界面設(shè)計(jì),文中有非常詳細(xì)的代碼示例及注釋,對(duì)正在學(xué)習(xí)Java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05