MultipartFile:底層實現原理


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的方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM