MultipartFile是一個接口,實現類是CommonsMultipartFile,
public class CommonsMultipartFile implements MultipartFile, Serializable {
protected static final Log logger = LogFactory.getLog(CommonsMultipartFile.class);
private final FileItem fileItem;//看到這個FileItem,我就知道還有下一層/(ㄒoㄒ)/~~,CommonsMultipartFile就是對fileTiem做了封裝。
private final long size;
private boolean preserveFilename = false;
/**
* Create an instance wrapping the given FileItem.
* @param fileItem the FileItem to wrap
*/
public CommonsMultipartFile(FileItem fileItem) {
this.fileItem = fileItem;//從這就可有看出是對fileIem做了封面封裝
this.size = this.fileItem.getSize();
}
好吧,繼續向下找FileItem的源碼,然后就會發現FileItem是一個接口,它的實現類是DiskFileItem。
下面是Diskfiletem的源碼
package org.apache.commons.fileupload.disk;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemHeaders;
import org.apache.commons.fileupload.FileItemHeadersSupport;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ParameterParser;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.DeferredFileOutputStream;
/**
* 接口org.apache.commons.fileupload.FileItem FileItem的默認實現
* 從org.apache.commons.fileupload.DiskFileUpload實例檢索此類的實例后,
* 1. 可以使用#get()一次性請求文件的所有內容,
* 2. 也可以使用#getInputStream()獲取java.io.InputStream InputStream並處理該文件,而不嘗試將其加載到內存中,這可能會使用大型文件。
*
* 當使用DiskFileItemFactory時,您應該考慮以下幾點:
* 1.臨時文件一旦不再需要就會自動刪除。(更准確地說,當對應的{@link java.io.File}實例被垃圾回收時。)
* 2.這是通過所謂的reaper線程完成的,這個線程在類org.apache.commons.io.FileCleaner被加載時自動啟動
*/
public class DiskFileItem implements FileItem, FileItemHeadersSupport {
private static final long serialVersionUID = 2237570099615271025L;
// 默認的內容編碼
public static final String DEFAULT_CHARSET = "ISO-8859-1";
private static final String UID = new java.rmi.server.UID().toString().replace(':', '_').replace('-', '_');
// 用於唯一標識符的生成
private static int counter = 0;
// 瀏覽器提供的表單字段名
private String fieldName;
// 瀏覽器傳遞的內容類型,如果未定義為null
private String contentType;
// 當前FileItem是否是一個簡單的表單字段
private boolean isFormField;
// 上傳文件的文件名,如果當前FileItem是表單字段為null
private String fileName;
// 當前FileItem的大小,單位為字節
private long size = -1;
// 閾值,上傳文件大小小於此值保存在內存,大於此值緩存到本地
private int sizeThreshold;
// 上傳的文件將被存儲在磁盤上的目錄
private File repository;
// 文件的緩存內容
private byte[] cachedContent;
// 當前FileItem的默認輸出流
private transient DeferredFileOutputStream dfos;
// 使用的臨時文件
private transient File tempFile;
// 用於序列化當前FileItem內容的文件
private File dfosFile;
// 當前FileItem的header
private FileItemHeaders headers;
/**
* Constructs a new DiskFileItem instance.
* @param fieldName 表單字段名
* @param contentType 瀏覽器傳遞的內容類型,如果未指定為null
* @param isFormField 當前FileItem是否是一個表單字段,否則就是一個上傳文件
* @param fileName 上傳文件的原始文件名,如果當前FielItem為表單字段,fileName為null
* @param sizeThreshold 閾值,上傳文件大於此值緩存到磁盤,小於就保存在內存
* @param repository 當上傳文件大於sizeThreshold,上傳文件緩存到磁盤的文件路徑
*/
public DiskFileItem(String fieldName, String contentType, boolean isFormField, String fileName, int sizeThreshold, File repository) {
this.fieldName = fieldName;
this.contentType = contentType;
this.isFormField = isFormField;
this.fileName = fileName;
this.sizeThreshold = sizeThreshold;
this.repository = repository;
}
/*
* 返回一個用於存儲上傳文件數據的輸出流
* FileUploadBase # parseRequest(RequestContext ctx) # Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
* 當FileItem剛實例化,dfos為null,調用getOutputStream()獲取緩存文件Xxx.tmp,將上傳文件緩存到Xxx.tmp中
*/
public OutputStream getOutputStream() throws IOException {
if (dfos == null) {
// 返回一個用於存儲上傳文件的臨時文件:Xxx.tmp
File outputFile = getTempFile();
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
}
return dfos;
}
// 返回上傳文件的輸入流
public InputStream getInputStream() throws IOException {
if (!isInMemory()) {// 上傳文件緩存在磁盤,打開文件流
return new FileInputStream(dfos.getFile());
}
// 上傳文件保存在內存
if (cachedContent == null) {
}
return new ByteArrayInputStream(cachedContent);
}
// 從ContenType中解析出內容編碼
public String getCharSet() {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
Map params = parser.parse(getContentType(), ';');
return (String) params.get("charset");
}
// 使用默認的編碼將文件的內容作為字符串返回
public String getString() {
byte[] rawdata = get();
String charset = getCharSet();
if (charset == null) {
charset = DEFAULT_CHARSET;
}
try {
return new String(rawdata, charset);
} catch (UnsupportedEncodingException e) {
return new String(rawdata);
}
}
// 使用指定的編碼將文件的內容作為字符串返回
public String getString(final String charset) throws UnsupportedEncodingException {
return new String(get(), charset);
}
/**
* 將上傳文件的內容作為字節數組返回
* 如果上傳文件沒有緩存在內存中,它們將從磁盤加載並緩存
*/
public byte[] get() {
if (isInMemory()) {// 緩存在內存中
if (cachedContent == null) {
cachedContent = dfos.getData();
}
return cachedContent;
}
byte[] fileData = new byte[(int) getSize()];
FileInputStream fis = null;
try {
fis = new FileInputStream(dfos.getFile());
// 從輸入流中讀取,存儲在fileData中
fis.read(fileData);
} catch (IOException e) {
fileData = null;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// ignore
}
}
}
return fileData;
}
// 返回上傳文件的大小,單位為字節
public long getSize() {
if (size >= 0) {
return size;
} else if (cachedContent != null) {
return cachedContent.length;
} else if (dfos.isInMemory()) {
return dfos.getData().length;
} else {
return dfos.getFile().length();
}
}
/**
* 將上傳文件寫入磁盤的方便方法
*/
public void write(File file) throws Exception {
if (isInMemory()) {// 上傳文件緩存在內存中
FileOutputStream fout = null;
try {
fout = new FileOutputStream(file);
fout.write(get());
} finally {
if (fout != null) {
fout.close();
}
}
} else {// 緩存到磁盤
File outputFile = getStoreLocation();
if (outputFile != null) {
// Save the length of the file
size = outputFile.length();
// 上傳的文件正在存儲在磁盤上的臨時位置,以便將其移動到所需的文件。
if (!outputFile.renameTo(file)) {
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(outputFile));
out = new BufferedOutputStream(new FileOutputStream(file));
IOUtils.copy(in, out);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// ignore
}
}
}
}
} else {
/*
* 無論什么原因,我們無法將文件寫入磁盤。
*/
throw new FileUploadException("Cannot write uploaded file to disk!");
}
}
}
/**
* 序列化, 寫入此對象的狀態
* @param out 要被寫入流的狀態
*/
private void writeObject(ObjectOutputStream out) throws IOException {
// Read the data
if (dfos.isInMemory()) {
cachedContent = get();
} else {
cachedContent = null;
dfosFile = dfos.getFile();
}
// write out values
out.defaultWriteObject();
}
// 反序列化,
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// read values
in.defaultReadObject();
OutputStream output = getOutputStream();
if (cachedContent != null) {// 緩存數據
output.write(cachedContent);
} else {
FileInputStream input = new FileInputStream(dfosFile);
IOUtils.copy(input, output);
dfosFile.delete();
dfosFile = null;
}
output.close();
cachedContent = null;
}
// 返回一個用於存儲上傳文件的臨時文件
protected File getTempFile() {
if (tempFile == null) {
// 臨時文件路勁
File tempDir = repository;
if (tempDir == null) {
tempDir = new File(System.getProperty("java.io.tmpdir"));
}
// 臨時文件名
String tempFileName = "upload_" + UID + "_" + getUniqueId() + ".tmp";
// 臨時文件
tempFile = new File(tempDir, tempFileName);
}
return tempFile;
}
// 刪除FileItem的底層存儲,包括刪除任何關聯的臨時磁盤文件
public void delete() {
cachedContent = null;
File outputFile = getStoreLocation();
if (outputFile != null && outputFile.exists()) {
outputFile.delete();
}
}
// 返回當前FileItem存儲在磁盤上的位置,如果存儲在內存中,返回null
public File getStoreLocation() {
return dfos == null ? null : dfos.getFile();
}
// 從臨時存儲中刪除文件內容
protected void finalize() {
File outputFile = dfos.getFile();
if (outputFile != null && outputFile.exists()) {
outputFile.delete();
}
}
// 返回一個唯一標識符
private static String getUniqueId() {
final int limit = 100000000;
int current;
synchronized (DiskFileItem.class) {
current = counter++;
}
String id = Integer.toString(current);
if (current < limit) {
id = ("00000000" + id).substring(id.length());
}
return id;
}
public String toString() {
return "name=" + this.getName() + ", StoreLocation=" + String.valueOf(this.getStoreLocation())
+ ", size=" + this.getSize() + "bytes, " + "isFormField=" + isFormField() + ", FieldName=" + this.getFieldName();
}
// 返回瀏覽器傳遞的內容類型,如果未指定返回null
public String getContentType() {
return contentType;
}
// 返回上傳文件的原始文件名
public String getName() {
return fileName;
}
// 當前上傳文件是否存儲在內存中
public boolean isInMemory() {
if (cachedContent != null) {
return true;
}
return dfos.isInMemory();
}
// 返回字段名
public String getFieldName() {
return fieldName;
}
// 設置字段名
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
// 返回當前FileItem是否是一個表單字段,false表示是一個上傳文件
public boolean isFormField() {
return isFormField;
}
public void setFormField(boolean state) {
isFormField = state;
}
// 返回當前FileItem的headers
public FileItemHeaders getHeaders() {
return headers;
}
public void setHeaders(FileItemHeaders pHeaders) {
headers = pHeaders;
}
}
找到源碼就很多疑惑就解開了,比如MultpatrFile是怎么返回字節數組的,一步步下來就會發現其實是調用了 DiskFileItem的get()方法
而get()的實現原理是利用FileInputStream接口,而FileInputStream的底層是native方法說明有實現類但不是java類
public byte[] get() {
if (isInMemory()) {// 緩存在內存中
if (cachedContent == null) {
cachedContent = dfos.getData();
}
return cachedContent;
}
byte[] fileData = new byte[(int) getSize()];//創建了一個文件長度的字節數組
FileInputStream fis = null;
try {
fis = new FileInputStream(dfos.getFile());
// 從輸入流中讀取,存儲在fileData中
fis.read(fileData);
} catch (IOException e) {
fileData = null;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// ignore
}
}
}
return fileData;
}
看來是要多了解了解IO和native的方法