前言
抽出時間,我會將文章系統的整理到GitHub上。目前已經整理好了JavaSE階段的知識章節內容。我會持續的向GitHub中添加各類知識點。歡迎大家來我的GitHub查房!還請客官們給一個Star!GitHub中有我微信,可以添加我微信,我們在學習交流羣中不見不散!
GitHub地址:https://github.com/Ziphtracks/JavaLearningmanual
一、文件上傳
1.1 文件上傳的核心思想
將電腦中的本地文件根據網絡協議並通過IO流的讀寫傳遞到另一臺電腦(服務器)中儲存!
1.2 文件上傳中JSP的三要素
- 表單提交方式必須是post(method=post)
- 原因get提交方式只能提交小文件,而給文件上傳做了很大的限制;post可以提交大文件
- 表單中需要文件上傳項(<input type=“file”>)
- 既然要實現文件上傳,那麼就需要文件上傳按鈕和上傳文件框
- 表單設置請求體格式(enctype=multipart/form-data)
- 設置表單的MIME編碼。默認情況編碼格式是application/x-www-form-urlencoded,不能用於文件上傳。只有設置了multipart/form-data,才能完整的傳遞文件數據
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上傳</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
文件描述:<input type="text" name="description"><br>
<input type="file" name="file"><br>
<br>
<button type="submit">上傳</button>
</form>
</body>
</html>
1.3 上傳文件核心jar包
此jar包可以自行下載,也可以在我的github中下載,我在github上的中也整理出來的jar包庫!
commons-fileupload-1.4.jar
commons-io-2.6.jar
1.4 文件上傳的細節處理
1.4.1 文件名重複覆蓋問題
爲了防止客戶端傳向服務器的文件名稱一致發生文件的覆蓋,我們需要將文件名稱唯一化!
// 文件名稱保證唯一性(文件名稱 = 當前時間毫秒值 + 文件名稱)
fileName = System.currentTimeMillis() + "-" + fileItem.getName();
1.4.2 使用目錄分離算法存儲文件
爲了防止將大量文件存儲在服務器的一個文件夾中,我們需要目錄使用Hash的目錄分離算法來存儲文件(類似給存儲文件的文件夾做了負載均衡)
注意:在後面的沒有使用該工具類實現,而且嵌入到了Servlet中
package com.mylifes1110.java.utils;
import java.io.File;
/**
* 目錄分離算法
*/
public class UploadUtils {
// 爲防止一個目錄下文件過多,使用hash算法打散目錄
public static String newFilePath(String basePath, String fileName) {
// 獲取文件hash碼
int hashCode = fileName.hashCode();
// 使用hash碼進行與運算並生成二級目錄
int path2 = hashCode & 15;
// 使用hash碼進行與運算並生成三級目錄
int path3 = (hashCode >> 4) & 15;
// 將一級、二級、三級目錄拼接並生成完整目錄路徑
String path = basePath + File.separator + path2 + File.separator + path3;
File file = new File(path);
// 創建Hash打散後的多級目錄
if (!file.exists()) {
file.mkdirs();
}
// 返回完整目錄路徑
return path;
}
}
1.4.3 文件上傳jar包API
ServletFileUpload:核心解析類
方法 | 描述 |
---|---|
parseRequest(HttpServletRequest request) | 解析請求,並獲取相關文件項 |
setHeaderEncoding(String encoding) | 解決中文文件名亂碼 |
FileItem:文件項
方法 | 描述 |
---|---|
boolean isFormField() | 返回爲true爲普通字段。返回爲false爲文件 |
String getFieldName() | 獲取表單字段 |
String getString(String encoding) | 根據指定編碼格式獲取字段值 |
String getName() | 獲取上傳文件名稱 |
InputStream getInputStream() | 獲取上傳文件對應的輸入流 |
二、文件下載
2.1 文件下載的核心思想
將服務器中的文件根據網絡協議並通過IO流讀取傳遞到另外一臺電腦中並儲存!
2.2 文件下載的兩種格式
- 超鏈接
- 如果瀏覽器支持這個格式的文件.可以在瀏覽器中打開.如果瀏覽器不支持這個格式的文 件纔會提示下載
- 手動編寫代碼的方式下載
2.3 手動編寫代碼方式的三要素
- 設置響應對象的媒體類型
- 設置下載窗口
- 設置下載窗口是通過響應對象對響應頭的操作(“Content-Disposition”)
- IO流讀寫文件
2.4 文件下載的細節處理
2.4.1 解決下載文件後名稱中文亂碼問題
各種瀏覽器的編碼解碼都是不同的,所以不同的瀏覽器對文件名稱的編碼方式不同,我們要以Google瀏覽器爲代表的是以utf-8對文件名稱進行編碼,其他的一些瀏覽器以base64對文件名稱進行編碼,這就需要判斷響應頭中使用的瀏覽器的類型(User-Agent)
/**
* base64編碼
*
* @param fileName 文件名稱
* @return 返回一個處理編碼後的文件名稱
*/
public String base64EncodeFileName(String fileName) {
BASE64Encoder base64Encoder = new BASE64Encoder();
try {
return "=?UTF‐8?B?" + new String(base64Encoder.encode(fileName.getBytes("utf-8"))) + "?=";
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 不同的瀏覽器對文件名稱的編碼方式不同,以google瀏覽器爲代表的是以utf-8對文件名稱進行編碼
* 其他的一些瀏覽器以base64對文件名稱進行編碼
* 判斷使用的瀏覽器的類型User-Agent
*/
String userAgent = request.getHeader("User-Agent");
String newFileName = null;
if (userAgent.contains("Chrome")) {
// Google瀏覽器以utf-8進行編碼
newFileName = URLEncoder.encode(fileName, "utf-8");
} else {
// 其他瀏覽器以base64進行編碼
newFileName = base64EncodeFileName(fileName);
}
// Google瀏覽器:new String(fileName.getBytes("utf-8"), "ISO8859-1")
// 註釋中的Google瀏覽器的代碼只針對Google瀏覽器,不具有普適性!
// response.setHeader("Content-Disposition", "attachement;filename=" + new String(fileName.getBytes("utf-8"), "ISO8859-1"));
response.setHeader("Content-Disposition", "attachement;filename=" + newFileName);
三、文件上傳和下載案例
3.1 項目結構
3.4 項目所用jar包
3.3 項目代碼演示
庫表操作
create table tb_uploadfiles
(
id int auto_increment
primary key,
fileName varchar(200) null,
filePath varchar(200) null,
description varchar(200) null
);
c3p0.properties文件
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf-8
c3p0.user=root
c3p0.password=123456
c3p0連接池工具類
package com.mylifes1110.java.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DBUtils {
private static ComboPooledDataSource dataSource;
static {
dataSource = new ComboPooledDataSource();
}
public static ComboPooledDataSource getDataSource() {
return dataSource;
}
}
Files實體類
package com.mylifes1110.java.bean;
public class Files {
private Integer id;
private String fileName;
private String filePath;
private String description;
public Files() {}
public Files(Integer id, String fileName, String filePath, String description) {
this.id = id;
this.fileName = fileName;
this.filePath = filePath;
this.description = description;
}
@Override
public String toString() {
return "Files{" + "id=" + id + ", fileName='" + fileName + '\'' + ", filePath='" + filePath + '\''
+ ", description='" + description + '\'' + '}';
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
upload.jsp頁面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上傳</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
文件描述:<input type="text" name="description"><br>
<input type="file" name="file"><br>
<br>
<button type="submit">上傳</button>
</form>
</body>
</html>
UploadServlet
package com.mylifes1110.java.controller;
import java.io.*;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.mylifes1110.java.bean.Files;
import com.mylifes1110.java.service.FilesService;
import com.mylifes1110.java.service.impl.FilesServiceImpl;
@WebServlet(
name = "UploadServlet",
value = "/upload"
)
public class UploadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 創建磁盤文件項工廠
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 創建核心解析對象
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
// 解決上傳服務器後的文件名稱中文亂碼問題
servletFileUpload.setHeaderEncoding("utf-8");
try {
// 解析上傳請求,獲取文件項(包括上傳文件和描述文本)
List<FileItem> fileItems = servletFileUpload.parseRequest(request);
String fileName = null;
String description = null;
int path2 = 0;
int path3 = 0;
for (FileItem fileItem : fileItems) {
// 判斷fileItem是否是一個上傳文件
if (fileItem.isFormField()) {
// 描述文本(打印描述文本)
// 指定描述文本編碼爲utf-8
description = fileItem.getString("utf-8");
System.out.println(description);
} else {
// 上傳文件
// 獲取上傳文件的輸入流(該輸入流包含了上傳文件的數據)
InputStream inputStream = fileItem.getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
// 獲取服務器真實路徑
String realPath = request.getServletContext().getRealPath("upload");
File file = new File(realPath);
// 判斷該文件夾是否存在,如果不存在就創建此文件夾
if (!file.exists()) {
file.mkdir();
}
// 文件名稱保證唯一性(文件名稱 = 當前時間毫秒值 + 文件名稱)
fileName = System.currentTimeMillis() + "-" + fileItem.getName();
// 獲取文件hash碼
int hashCode = fileName.hashCode();
// 使用hash碼進行與運算並生成二級目錄
path2 = hashCode & 15;
// 使用hash碼進行與運算並生成三級目錄
path3 = (hashCode >> 4) & 15;
// 將一級、二級、三級目錄拼接並生成完整目錄路徑
String path = realPath + File.separator + path2 + File.separator + path3;
File newFile = new File(path);
// 創建多級目錄
if (!newFile.exists()) {
newFile.mkdirs();
}
// 準備讀寫文件
FileOutputStream fileOutputStream = new FileOutputStream(path + File.separator + fileName);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
byte[] bytes = new byte[8192];
int len = -1;
while ((len = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes, 0, len);
}
bufferedInputStream.close();
bufferedOutputStream.close();
}
}
FilesService filesService = new FilesServiceImpl();
Files files = new Files();
files.setFileName(fileName);
// 封裝後準備存入數據庫(注意:該路徑不是盤符服務器目錄的真實路徑,而是項目資源目錄路徑)
files.setFilePath("/fileupload" + File.separator + "upload" + path2 + File.separator + path3
+ File.separator + fileName);
files.setDescription(description);
filesService.addFiles(files);
response.sendRedirect(request.getContextPath() + "/list");
} catch (FileUploadException | SQLException e) {
e.printStackTrace();
}
}
}
DownloadServlet
package com.mylifes1110.java.controller;
import java.io.*;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sun.misc.BASE64Encoder;
@WebServlet(
name = "DownloadServlet",
value = "/download"
)
public class DownloadServlet extends HttpServlet {
/**
* base64編碼
*
* @param fileName 文件名稱
* @return 返回一個處理編碼後的文件名稱
*/
public String base64EncodeFileName(String fileName) {
BASE64Encoder base64Encoder = new BASE64Encoder();
try {
return "=?UTF‐8?B?" + new String(base64Encoder.encode(fileName.getBytes("utf-8"))) + "?=";
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String fileName = request.getParameter("filename");
// 獲取下載文件的媒體類型
String mimeType = request.getServletContext().getMimeType(fileName);
// 1.設置媒體類型
response.setContentType(mimeType);
// 2.設置下載窗口 "Content-Disposition"
/**
* 不同的瀏覽器對文件名稱的編碼方式不同,以google瀏覽器爲代表的是以utf-8對文件名稱進行編碼
* 其他的一些瀏覽器以base64對文件名稱進行編碼
* 判斷使用的瀏覽器的類型User-Agent
*/
String userAgent = request.getHeader("User-Agent");
String newFileName = null;
if (userAgent.contains("Chrome")) {
// Google瀏覽器以utf-8進行編碼
newFileName = URLEncoder.encode(fileName, "utf-8");
} else {
// 其他瀏覽器以base64進行編碼
newFileName = base64EncodeFileName(fileName);
}
// Google瀏覽器:new String(fileName.getBytes("utf-8"), "ISO8859-1")
response.setHeader("Content-Disposition", "attachement;filename=" + newFileName);
// 3.IO讀寫
String path = request.getServletContext().getRealPath("upload") + File.separator + fileName;
System.out.println(path);
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(response.getOutputStream());
byte[] bytes = new byte[8192];
int len = -1;
while ((len = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes, 0, len);
}
bufferedInputStream.close();
bufferedOutputStream.close();
}
}
FilesListServlet
package com.mylifes1110.java.controller;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.mylifes1110.java.bean.Files;
import com.mylifes1110.java.service.FilesService;
import com.mylifes1110.java.service.impl.FilesServiceImpl;
@WebServlet(
name = "FilesListServlet",
value = "/list"
)
public class FilesListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
FilesService filesService = new FilesServiceImpl();
try {
List<Files> filesList = filesService.selectFiles();
request.setAttribute("files", filesList);
request.getRequestDispatcher("/filesList.jsp").forward(request, response);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
filesList.jsp文件列表展示頁面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
Created by IntelliJ IDEA.
User: mylif
Date: 2020/5/7
Time: 14:16
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件列表</title>
<style>
a{
text-decoration: none;
}
</style>
</head>
<body>
<table border="1px" cellpadding="10px" cellspacing="0px">
<tr>
<td>編號</td>
<td>文件名稱</td>
<td>文件描述</td>
<td>操作</td>
</tr>
<c:forEach items="${files}" var="file">
<tr>
<td>${file.id}</td>
<td><a href="${file.filePath}">${file.fileName}<a/></td>
<td>${file.description}</td>
<td><a href="${pageContext.request.contextPath}/download?filename=${file.fileName}">下載</a></td>
</tr>
</c:forEach>
</table>
</body>
</html>
FilesDao
package com.mylifes1110.java.dao;
import com.mylifes1110.java.bean.Files;
import java.sql.SQLException;
import java.util.List;
public interface FilesDao {
void addFiles(Files files) throws SQLException;
List<Files> selectFiles() throws SQLException;
}
FilesDaoImpl
package com.mylifes1110.java.dao.impl;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import com.mylifes1110.java.bean.Files;
import com.mylifes1110.java.dao.FilesDao;
import com.mylifes1110.java.utils.DBUtils;
public class FilesDaoImpl implements FilesDao {
@Override
public void addFiles(Files files) throws SQLException {
new QueryRunner(DBUtils.getDataSource()).update(
"insert into tb_uploadfiles (filename, filepath, description) values (?, ?, ?)",
files.getFileName(),
files.getFilePath(),
files.getDescription());
}
@Override
public List<Files> selectFiles() throws SQLException {
return new QueryRunner(DBUtils.getDataSource()).query("select * from tb_uploadfiles",
new BeanListHandler<Files>(Files.class));
}
}
FilesService
package com.mylifes1110.java.service;
import java.sql.SQLException;
import java.util.List;
import com.mylifes1110.java.bean.Files;
public interface FilesService {
void addFiles(Files files) throws SQLException;
List<Files> selectFiles() throws SQLException;
}
FilesServiceImpl
package com.mylifes1110.java.service.impl;
import java.sql.SQLException;
import java.util.List;
import com.mylifes1110.java.bean.Files;
import com.mylifes1110.java.dao.FilesDao;
import com.mylifes1110.java.dao.impl.FilesDaoImpl;
import com.mylifes1110.java.service.FilesService;
public class FilesServiceImpl implements FilesService {
private FilesDao filesDao = new FilesDaoImpl();
@Override
public void addFiles(Files files) throws SQLException {
filesDao.addFiles(files);
}
@Override
public List<Files> selectFiles() throws SQLException {
return filesDao.selectFiles();
}
}
四、自定義文件上傳
4.1 文件上傳分析
- 【客戶端】輸入流,從硬盤讀取文件數據到程序中
- 【客戶端】輸出流,寫出文件數據到服務端
- 【服務端】輸入流,讀取文件數據到服務端程序
- 【服務端】輸出流,寫出文件數據到服務器硬盤中
4.2 自定義文件上傳細節處理
4.2.1 文件名稱重複發生覆蓋問題
服務器端保存文件的名稱如果寫死,那麼最終導致服務器硬盤,只會保留一個文件(文件發生了覆蓋),建議使 用系統時間優化,保證文件名稱唯一
String path = "C:\\Users\\mylif\\Desktop\\" + System.currentTimeMillis() + "-java.jpg";
4.2.2 服務器端接收一次後關閉問題
正常的服務器理論來說是不會關閉的,除非你想停止服務。如果想繼續服務就不能關閉服務器。但是寫的代碼,接收客戶端的一次請求就關閉了怎麼辦?我們這時需要加一個while(true),對,沒錯。就是死循環(無限循環),加一個無限循環來保持服務器一直處在運行狀態!
while(true){
Socket accept = serverSocket.accept();
//服務器處理的代碼
}
4.2.3 多線程問題
大家都知道,服務器是接收用戶請求的,但是不只是接收一個用戶請求。普通的編寫服務器代碼在接收大文件時,可能耗費幾秒鐘的時間,此時不能接收其他用戶上傳,所以,使用多線程技術優化服務器!
while(true){
Socket accept = serverSocket.accept();
// accept 交給子線程處理.
new Thread(() ‐> {
//服務器代碼
InputStream bis = accept.getInputStream();
//服務器代碼
}).start();
4.3 自定義文件上傳代碼實現
- port(端口):自定義端口,不要與其他應用服務器端口發生衝突
- IP地址:cmdDOS命令窗口執行ipconfig查看自己的ip地址,也可以在項目中寫localhost(默認本機ip)
注意:IO流程的關閉必須寫在finally塊中,以保證服務器不提前關閉和IO流程最終關閉;啓動時,先啓動服務器,後啓動客戶端
服務器端代碼
package com.mylifes1110.java.upload;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 文件上傳接收端(服務器端)
*/
public class Receiver {
public static void main(String[] args) {
try {
// 獲取接收端的套接字
ServerSocket serverSocket = new ServerSocket(10200);
while (true) {
new Thread() {
@Override
public void run() {
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
Socket socket = serverSocket.accept();
// 獲取對應的輸入流,包含了發送端發送過來的數據
bufferedInputStream = new BufferedInputStream(socket.getInputStream());
String path = "C:\\Users\\mylif\\Desktop\\" + System.currentTimeMillis() + "-java.jpg";
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(path));
byte[] bytes = new byte[8192];
int len = -1;
while ((len = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufferedInputStream != null) {
bufferedInputStream.close();
bufferedInputStream = null;
}
if (bufferedOutputStream != null) {
bufferedOutputStream.close();
bufferedOutputStream = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客戶端代碼
package com.mylifes1110.java.upload;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
/**
* 文件上傳發送端(客戶端)
*/
public class Sender {
public static void main(String[] args) {
// 獲取本地文件對應的輸入流
String path = "C:\\Users\\mylif\\Desktop\\素材\\圖標\\java.jpg";
BufferedOutputStream bufferedOutputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
bufferedInputStream = new BufferedInputStream(new FileInputStream(path));
Socket socket = new Socket(InetAddress.getByName("localhost"), 10200);
bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[8192];
int len = -1;
while ((len = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufferedOutputStream != null) {
bufferedOutputStream.close();
bufferedOutputStream = null;
}
if (bufferedInputStream != null) {
bufferedInputStream.close();
bufferedInputStream = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
持續更新中…
有興趣的可以添加微信,我們在學習交流羣中不見不散!