欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java大文件上傳處理方法實(shí)例代碼

 更新時(shí)間:2025年09月22日 09:34:18   作者:jsonformat  
在Java中實(shí)現(xiàn)大文件上傳功能,確實(shí)需要考慮到文件大小可能超出內(nèi)存限制、網(wǎng)絡(luò)傳輸穩(wěn)定性等因素,這篇文章主要介紹了java大文件上傳處理方法的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

文件處理是業(yè)務(wù)中最常見的操作了,但對(duì)于個(gè)人來(lái)說(shuō),百兆大文件的處理還是挺少的,本文記錄下大文件處理的主流處理方案,順便更新下老舊API(File)。

一、前后端大文件上傳

1.方案描述

當(dāng)前主流的大文件上傳方案以分片上傳 + 斷點(diǎn)續(xù)傳 + 秒傳為核心架構(gòu),以實(shí)現(xiàn)高效穩(wěn)定上傳。

  • 秒傳:根據(jù)文件的唯一標(biāo)識(shí)如hash值校驗(yàn)服務(wù)端是否存在此文件,若存在則是秒傳
  • 分片上傳:將大文件分割為多個(gè)小文件分開上傳
  • 斷點(diǎn)續(xù)傳:只用傳未成功的分片

前端:秒傳、文件分塊、文件相關(guān)信息上傳、獲取文件已上傳的分片、文件分片上傳、文件分片合并。
后端:提供相應(yīng)的功能接口,秒傳校驗(yàn)、初始化文件信息、查詢已上傳的文件分片、分片上傳、合并分片。增加批處理對(duì)規(guī)定時(shí)間范圍內(nèi)未完成上傳的大文件進(jìn)行郵件告警通知。
存儲(chǔ):創(chuàng)建臨時(shí)目錄存儲(chǔ)各分片文件資源,數(shù)據(jù)表記錄分片上傳記錄和文件基本信息,最終合并各分片寫入指定目錄文件中,更新數(shù)據(jù)表中的文件記錄,刪除分片信息。

2.后端代碼

技術(shù)選型

java8+springboot2.0+mybatis

yaml配置

# 服務(wù)器端口
server:
  port: 8080
  
  # 文件上傳配置
file:
  upload:
    root-path: ./upload-files/  # 最終文件存儲(chǔ)根目錄
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 20MB  # 單個(gè)分片最大大小
      max-request-size: 100MB  # 單次請(qǐng)求最大大小
  
  # 數(shù)據(jù)庫(kù)配置(使用H2內(nèi)存庫(kù),無(wú)需安裝)
  datasource:
    url: jdbc:mysql://localhost:3306/my-test?characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
    driverClassName: com.mysql.jdbc.Driver


mybatis:
  mapperLocations: classpath:/mapper/*.xml
  typeAliasesPackage: org.example.entity
  configuration:
    mapUnderscoreToCamelCase: true

logging:
  level:
    org.example.dao: DEBUG

mysql table

CREATE TABLE `f_file_record` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `file_id` varchar(64) NOT NULL COMMENT '文件唯一標(biāo)識(shí)(UUID)',
  `file_name` varchar(255) NOT NULL COMMENT '原始文件名',
  `file_size` bigint(20) NOT NULL COMMENT '文件總大?。ㄗ止?jié))',
  `file_hash` varchar(64) NOT NULL COMMENT '文件MD5哈希值(用于秒傳)',
  `file_path` varchar(512) DEFAULT NULL COMMENT '最終文件存儲(chǔ)路徑',
  `chunk_total` int(11) NOT NULL COMMENT '總分片數(shù)',
  `chunk_size` int(11) NOT NULL COMMENT '單片大?。ㄗ止?jié))',
  `status` tinyint(4) NOT NULL COMMENT '狀態(tài):0-上傳中,1-已完成,2-失敗',
  `create_time` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間',
  `update_time` datetime NOT NULL COMMENT '更新時(shí)間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_file_id` (`file_id`) COMMENT '文件ID唯一索引',
  KEY `idx_file_hash` (`file_hash`) COMMENT '哈希索引(秒傳查詢用)'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='文件上傳記錄表';


CREATE TABLE `f_chunk_record` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `file_id` varchar(64) NOT NULL COMMENT '關(guān)聯(lián)文件ID',
  `chunk_index` int(11) NOT NULL COMMENT '分片索引(從0開始)',
  `chunk_path` varchar(512) NOT NULL COMMENT '分片臨時(shí)存儲(chǔ)路徑',
  `chunk_size` bigint(20) NOT NULL COMMENT '分片實(shí)際大小(字節(jié))',
  `create_time` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_file_chunk` (`file_id`,`chunk_index`) COMMENT '文件+分片索引唯一(避免重復(fù)上傳)',
  KEY `idx_file_id` (`file_id`) COMMENT '文件ID索引(查詢分片列表用)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件分片記錄表';

對(duì)應(yīng)實(shí)體

@Data
public class FileRecord {

    private String fileId; // 文件唯一標(biāo)識(shí)(建議用UUID)
    private String fileName; // 原始文件名
    private String fileHash; // 文件MD5哈希(用于秒傳)
    private Long fileSize; // 文件總大小(字節(jié))
    private Integer chunkTotal; // 總分片數(shù)
    private Integer chunkSize; // 單片大?。ㄗ止?jié))
    private String filePath; // 最終存儲(chǔ)路徑
    private Integer status; // 狀態(tài):0-上傳中 1-已完成 2-失敗
    private Date createTime;
    private Date updateTime;
}

@Data
public class ChunkRecord {
    private Long id;
    private String fileId; // 關(guān)聯(lián)文件ID
    private Integer chunkIndex; // 分片索引(從0開始)
    private String chunkPath; // 分片臨時(shí)存儲(chǔ)路徑
    private Long chunkSize; // 分片實(shí)際大小
    private Date createTime;

}


controller

@RestController
@RequestMapping("/upload")
public class FileUploadController {

    @Autowired
    private FileUploadService uploadService;

    /**
     * 秒傳校驗(yàn)接口
     */
    @PostMapping("/check")
    public ResponseEntity<Map<String, Object>> checkFile(@RequestParam String fileHash,
                                                         @RequestParam Long fileSize) {
        Map<String, Object> result = new HashMap<>();
        FileRecord file = uploadService.checkFile(fileHash);
        if (file != null && Objects.equals(fileSize, file.getFileSize())) {
            result.put("success", true);
            result.put("exists", true);
            result.put("fileId", file.getFileId());
            result.put("filePath", file.getFilePath());
        } else {
            result.put("success", true);
            result.put("exists", false);
        }
        return ResponseEntity.ok(result);
    }

    /**
     * 初始化上傳接口
     */
    @PostMapping("/init")
    public ResponseEntity<Map<String, Object>> initUpload(
            @RequestParam String fileName,
            @RequestParam Long fileSize,
            @RequestParam String fileHash,
            @RequestParam Integer chunkTotal,
            @RequestParam Integer chunkSize) {

        Map<String, Object> result = new HashMap<>();
        try {
            String fileId = uploadService.initUpload(fileName, fileSize, fileHash, chunkTotal, chunkSize);
            result.put("success", true);
            result.put("fileId", fileId);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("success", false);
            result.put("msg", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 查詢已上傳分片接口
     */
    @GetMapping("/chunks/{fileId}")
    public ResponseEntity<Map<String, Object>> getUploadedChunks(@PathVariable String fileId) {
        Map<String, Object> result = new HashMap<>();
        List<Integer> chunks = uploadService.getUploadedChunks(fileId);
        result.put("success", true);
        result.put("uploadedChunks", chunks);
        return ResponseEntity.ok(result);
    }

    /**
     * 分片上傳接口
     */
    @PostMapping("/chunk")
    public ResponseEntity<Map<String, Object>> uploadChunk(
            @RequestParam String fileId,
            @RequestParam Integer chunkIndex,
            @RequestParam MultipartFile chunk) {

        Map<String, Object> result = new HashMap<>();
        try {
            boolean success = uploadService.uploadChunk(fileId, chunkIndex, chunk);
            result.put("success", success);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            result.put("success", false);
            result.put("msg", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 合并分片接口
     */
    @PostMapping("/merge")
    public ResponseEntity<Map<String, Object>> mergeChunks(@RequestParam String fileId) {
        Map<String, Object> result = new HashMap<>();
        try {
            FileRecord file = uploadService.mergeChunks(fileId);
            if (file != null) {
                result.put("success", true);
                result.put("fileId", file.getFileId());
                result.put("filePath", file.getFilePath());
                return ResponseEntity.ok(result);
            } else {
                result.put("success", false);
                result.put("msg", "合并失敗");
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
            }
        } catch (Exception e) {
            result.put("success", false);
            result.put("msg", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }
}

service

public interface FileUploadService {

    // 檢查文件是否已存在(秒傳)
    FileRecord checkFile(String fileHash);

    // 初始化上傳任務(wù)
    String initUpload(String fileName, Long fileSize, String fileHash,
                    Integer chunkTotal, Integer chunkSize);

    // 獲取已上傳的分片索引
    List<Integer> getUploadedChunks(String fileId);

    // 上傳分片
    boolean uploadChunk(String fileId, Integer chunkIndex, MultipartFile chunkFile);

    // 合并分片
    FileRecord mergeChunks(String fileId);
}

@Service
public class FileUploadServiceImpl implements FileUploadService {

    // 最終文件存儲(chǔ)根路徑(配置在application.properties)
    @Value("${file.upload.root-path}")
    private String rootPath;

    // 臨時(shí)分片存儲(chǔ)路徑
    private final Path tempChunkPath;

    @Autowired
    private FileRecordMapper fileRepo;

    @Autowired
    private ChunkRecordMapper chunkRepo;

    // 初始化臨時(shí)目錄(使用NIO)
    public FileUploadServiceImpl() throws IOException {
        // 臨時(shí)目錄路徑:系統(tǒng)臨時(shí)目錄 + upload-chunks
        tempChunkPath = Paths.get(System.getProperty("java.io.tmpdir"), "upload-chunks");
        // 若目錄不存在則創(chuàng)建(支持多級(jí)目錄)
        Files.createDirectories(tempChunkPath);
    }

    /**
     * 上傳分片:使用NIO的Files.copy替代傳統(tǒng)File操作
     */
    @Override
    @Transactional
    public boolean uploadChunk(String fileId, Integer chunkIndex, MultipartFile chunkFile) {
        try {
            // 檢查分片是否已存在
            ChunkRecord existing = chunkRepo.findByFileIdAndChunkIndex(fileId, chunkIndex);
            if (existing != null) {
                return true;
            }

            // 構(gòu)建分片存儲(chǔ)路徑(NIO Path)
            Path chunkDir = Paths.get(tempChunkPath.toString(), fileId);
            Files.createDirectories(chunkDir); // 創(chuàng)建目錄(NIO方法)
            Path chunkPath = Paths.get(chunkDir.toString(), chunkIndex.toString());

            // 使用NIO復(fù)制文件(替代transferTo)
            try (InputStream in = chunkFile.getInputStream()) {
                Files.copy(in, chunkPath, StandardCopyOption.REPLACE_EXISTING);
            }

            // 記錄分片信息
            ChunkRecord chunk = new ChunkRecord();
            chunk.setFileId(fileId);
            chunk.setChunkIndex(chunkIndex);
            chunk.setChunkPath(chunkPath.toString()); // 存儲(chǔ)路徑字符串
            chunk.setChunkSize(Files.size(chunkPath)); // 使用NIO獲取文件大小
            chunk.setCreateTime(new Date());
            chunkRepo.save(chunk);

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 合并分片:使用NIO的Path處理文件路徑
     */
    @Override
    @Transactional
    public FileRecord mergeChunks(String fileId) {
        try {
            // 獲取文件信息
            FileRecord file = fileRepo.findById(fileId);
            if (file == null) {
                new RuntimeException("文件記錄不存在");
            }
            // 獲取所有分片(按索引排序)
            List<ChunkRecord> chunks = chunkRepo.findByFileIdOrderByChunkIndexAsc(fileId);
            if (chunks.size() != file.getChunkTotal()) {
                throw new RuntimeException("分片不完整,無(wú)法合并");
            }

            // 創(chuàng)建最終文件存儲(chǔ)目錄(按日期分目錄,使用NIO)
            String dateDir = new Date().toString().substring(0, 10).replace(" ", "-");
            Path saveDir = Paths.get(rootPath, dateDir);
            Files.createDirectories(saveDir); // NIO創(chuàng)建目錄

            // 生成最終文件名(UUID+原擴(kuò)展名)
            String ext = file.getFileName().contains(".")
                    ? file.getFileName().substring(file.getFileName().lastIndexOf("."))
                    : "";
            String finalFileName = UUID.randomUUID().toString() + ext;
            Path finalPath = Paths.get(saveDir.toString(), finalFileName);

            // 合并分片(使用RandomAccessFile + NIO Path)
            try (RandomAccessFile raf = new RandomAccessFile(finalPath.toFile(), "rw")) {
                for (ChunkRecord chunk : chunks) {
                    Path chunkPath = Paths.get(chunk.getChunkPath()); // NIO Path
                    try (InputStream fis = Files.newInputStream(chunkPath)) { // NIO獲取輸入流
                        byte[] buffer = new byte[1024 * 1024]; // 1MB緩沖區(qū)
                        int len;
                        while ((len = fis.read(buffer)) != -1) {
                            raf.write(buffer, 0, len);
                        }
                    }
                }
            }

            // 更新文件記錄
            file.setFilePath(finalPath.toString());
            file.setStatus(1); // 1-已完成
            file.setUpdateTime(new Date());
            fileRepo.update(file);

            // 清理臨時(shí)分片(使用NIO刪除)
            cleanTempChunks(fileId);

            return file;
        } catch (Exception e) {
            e.printStackTrace();
            FileRecord f = fileRepo.findById(fileId);
            if (f != null) {
                f.setStatus(2); // 2-失敗
                fileRepo.update(f);
            }
            return null;
        }
    }

    /**
     * 清理臨時(shí)分片:使用NIO的Files.walk遞歸刪除
     */
    private void cleanTempChunks(String fileId) throws IOException {
        Path chunkDir = Paths.get(tempChunkPath.toString(), fileId);
        if (Files.exists(chunkDir)) {
            // 遞歸刪除目錄及內(nèi)容(NIO方式)
            try (Stream<Path> stream = Files.walk(chunkDir)){
                stream.sorted(Comparator.reverseOrder())// 逆序刪除(先文件后目錄)
                        .forEach(path -> {
                            try {
                                Files.delete(path);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        });
            }
        }
        // 刪除數(shù)據(jù)庫(kù)分片記錄
        chunkRepo.deleteByFileId(fileId);
    }

    // 其他方法(checkFile/initUpload/getUploadedChunks)保持不變
    @Override
    public FileRecord checkFile(String fileHash) {
        return fileRepo.findByFileHashAndStatus(fileHash, 1);
    }

    @Override
    @Transactional
    public String initUpload(String fileName, Long fileSize, String fileHash,
                           Integer chunkTotal, Integer chunkSize) {
        FileRecord file = new FileRecord();
        file.setFileId(UUID.randomUUID().toString().replace("-",""));
        file.setFileName(fileName);
        file.setFileSize(fileSize);
        file.setFileHash(fileHash);
        file.setChunkTotal(chunkTotal);
        file.setChunkSize(chunkSize);
        file.setStatus(0);
        file.setCreateTime(new Date());
        file.setUpdateTime(new Date());
        fileRepo.save(file);
        return file.getFileId();
    }

    @Override
    public List<Integer> getUploadedChunks(String fileId) {
        List<ChunkRecord> chunks = chunkRepo.findByFileIdOrderByChunkIndexAsc(fileId);
        return chunks.stream()
                .map(ChunkRecord::getChunkIndex)
                .collect(Collectors.toList());
    }
}

mapper

//文件mapper
public interface FileRecordMapper {

    // 通過(guò)文件哈希查詢(用于秒傳)
    FileRecord findByFileHashAndStatus(String fileHash, Integer status);

    FileRecord findById(String fileId);

    void update(FileRecord file);

    void save(FileRecord file);
}


//分片mapper
public interface ChunkRecordMapper {

    // 查詢文件的所有分片(按索引排序)
    List<ChunkRecord> findByFileIdOrderByChunkIndexAsc(String fileId);
    // 查詢指定分片
    ChunkRecord findByFileIdAndChunkIndex(String fileId, Integer chunkIndex);
    // 刪除文件的所有分片
    void deleteByFileId(String fileId);

    void save(ChunkRecord chunk);
}

mapper-xml

------------------文件mapper------------------
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.example.dao.FileRecordMapper">

    <select id="findByFileHashAndStatus" resultType="org.example.entity.FileRecord">
        select * from f_file_record where file_hash = #{fileHash} and status = #{status}
    </select>

    <select id="findById" resultType="org.example.entity.FileRecord">
        select * from f_file_record where file_id = #{fileId}
    </select>

    <update id="update">
        update f_file_record set status = #{status},file_path=#{filePath}  where file_id = #{fileId}
    </update>

    <insert id="save">
        insert into f_file_record (file_id,file_hash, file_name, file_size,chunk_total,chunk_size, status, create_time, update_time)
        values (#{fileId},#{fileHash},#{fileName},#{fileSize},#{chunkTotal},#{chunkSize},#{status},#{createTime},#{updateTime})
    </insert>

</mapper>

------------------分片mapper------------------

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.example.dao.ChunkRecordMapper">

    <select id="findByFileIdOrderByChunkIndexAsc" resultType="org.example.entity.ChunkRecord">
        select * from f_chunk_record where file_id = #{fileId} order by chunk_index asc
    </select>

    <select id="findByFileIdAndChunkIndex" resultType="org.example.entity.ChunkRecord">
        select * from f_chunk_record where file_id = #{fileId} and chunk_index = #{chunkIndex}
    </select>

    <delete id="deleteByFileId">
        delete from f_chunk_record where file_id = #{fileId}
    </delete>

    <insert id="save">
        insert into f_chunk_record (file_id, chunk_index, chunk_path, chunk_size, create_time) values (#{fileId}, #{chunkIndex}, #{chunkPath}, #{chunkSize}, #{createTime})
    </insert>

</mapper>

3.驗(yàn)證

這里使用ApiPost進(jìn)行模擬測(cè)試

預(yù)處理文件

  1. 計(jì)算文件的hash值,用以實(shí)現(xiàn)秒傳(Linux/Mac:md5sum “文件路徑”)
  2. 計(jì)算分片:總片數(shù)=文件大小/每片大?。ㄏ蛏先≌?/li>
  3. 文件分片(Linux/Mac:split -b 10m test.zip chunk_ 將test.zip切割為10MB的分片,命名為chunk_aa、chunk_ab…)

接口調(diào)用

秒傳校驗(yàn)

不存在

存在,秒傳成功

文件初始化信息

需要對(duì)文件進(jìn)行預(yù)處理

查詢已上傳的分片

分片上傳

chunk表

臨時(shí)目錄存放的分片文件

合并分片

狀態(tài)已改變

分片記錄刪除

臨時(shí)目錄中的分片文件也刪除了

二、純后端大文件處理

1.方案描述

后端處理百兆級(jí)大文件,可以使用java nio包中的FileChannel和ByteBuffer使用零拷貝技術(shù)上傳,免去內(nèi)核與用戶態(tài)的切換節(jié)省CPU和內(nèi)存資源。

2.后端代碼

零拷貝

利用FileChannel.transferTo實(shí)現(xiàn)內(nèi)核級(jí)數(shù)據(jù)傳輸,減少用戶空間拷貝次數(shù)。

//服務(wù)端

public class ZeroCopyServer {
    public static void main(String[] args) throws Exception {
        Path destination = Paths.get("./upload-files/move/testdemo.zip");
        int port = 8080;

        // 預(yù)先獲取目標(biāo)文件的父目錄
        Path parentDir = destination.getParent();
        if (!Files.exists(parentDir)) {
            Files.createDirectories(parentDir);
        }

        try (ServerSocketChannel server = ServerSocketChannel.open()) {
            server.bind(new InetSocketAddress(port));
            System.out.println("Server listening on port " + port);

            try (SocketChannel client = server.accept();
                 FileChannel outChannel = FileChannel.open(
                         destination,
                         StandardOpenOption.CREATE,
                         StandardOpenOption.WRITE,
                         StandardOpenOption.TRUNCATE_EXISTING)) {

                long totalBytes = 0;
                long bytesTransferred;

                // 持續(xù)接收直到連接關(guān)閉
                do {
                    bytesTransferred = outChannel.transferFrom(client, totalBytes, Long.MAX_VALUE);
                    if (bytesTransferred > 0) {
                        totalBytes += bytesTransferred;
                        System.out.printf("Received %.2f MB%n", bytesTransferred / (1024.0 * 1024.0));
                    }
                } while (bytesTransferred > 0);

                System.out.println("File transfer completed. Total size: "
                        + totalBytes + " bytes");
            }
        }
    }
}

客戶端

public static void main(String[] args) throws Exception {
        Path source = Paths.get("/Users/xxxxx/Downloads/books/testdemo.zip");
        long chunkSize = 50 * 1024 * 1024;//分片大小

        try (SocketChannel socket = SocketChannel.open();
             FileChannel inChannel = FileChannel.open(source, StandardOpenOption.READ)) {

            // 設(shè)置連接
            socket.socket().setSoTimeout(30000);
            socket.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8080));

            long fileSize = inChannel.size();
            long position = 0;
            System.out.println("Starting file transfer. Total size: "
                    + fileSize + " bytes");

            while (position < fileSize) {
                long remaining = fileSize - position;
                long transferSize = Math.min(chunkSize, remaining);

                long transferred = inChannel.transferTo(position, transferSize, socket);

                if (transferred > 0) {
                    position += transferred;
                    System.out.printf("Sent %.2f MB (%.1f%%)%n",
                            transferred / (1024.0 * 1024.0),
                            (position * 100.0) / fileSize);
                }
            }

            // 優(yōu)雅關(guān)閉輸出(通知服務(wù)端傳輸結(jié)束)
            socket.shutdownOutput();
            System.out.println("File upload completed");
        }
    }

3.驗(yàn)證

  1. 啟動(dòng)服務(wù)端,執(zhí)行客戶端發(fā)送請(qǐng)求(注意大文件分片)
  2. 這里是簡(jiǎn)單的本地處理實(shí)現(xiàn),實(shí)際業(yè)務(wù)中常用的影像資料上傳或者日志文件處理可以參考使用(10M以上的)。
Starting file transfer. Total size: 455759002 bytes
Sent 50.00 MB (11.5%)
Sent 50.00 MB (23.0%)
Sent 50.00 MB (34.5%)
Sent 50.00 MB (46.0%)
Sent 50.00 MB (57.5%)
Sent 50.00 MB (69.0%)
Sent 50.00 MB (80.5%)
Sent 50.00 MB (92.0%)
Sent 34.65 MB (100.0%)
File upload completed

三、java文件API更替

jdk1.7 nio包中提供的文件處理類相對(duì)于File來(lái)說(shuō)更安全便捷

原來(lái)用File對(duì)于文件或目錄的操作(增、刪、讀、寫、校驗(yàn)),現(xiàn)用Path和Files進(jìn)行替換

新舊API對(duì)比

public class NioFileExamples {
    public static void main(String[] args) {
        String filePath = "./file-api/test01/test01.txt";
        String copyPath = "./file-api/test02/test01copy.txt";
        String dirPath = "./file-api-001/test01";

        // 1. 文件讀取示例
        readFileExample(filePath);

        // 2. 文件寫入示例
        writeFileExample(filePath, "Hello, NIO! This is a test.");

        // 3. 文件復(fù)制示例
        copyFileExample(filePath, copyPath);

        // 4. 目錄創(chuàng)建示例
        createDirectoryExample(dirPath);

        // 5. 列出目錄內(nèi)容示例
        listDirectoryExample("./file-api-001");

        // 6. 文件刪除示例
        deleteFileExample(copyPath);
        deleteFileExample(filePath);
        deleteDirectoryExample(dirPath);
    }

    /**
     * 文件讀取示例:對(duì)比傳統(tǒng)IO和NIO方式
     */
    private static void readFileExample(String filePath) {
        System.out.println("\n--- 文件讀取示例 ---");

        // 傳統(tǒng)IO方式
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(new FileInputStream(filePath), StandardCharsets.UTF_8))) {
            String line;
            System.out.println("傳統(tǒng)IO讀取:");
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("傳統(tǒng)IO讀取失敗: " + e.getMessage());
        }

        // NIO方式
        try {
            // 讀取所有行
            List<String> lines = Files.readAllLines(Paths.get(filePath), StandardCharsets.UTF_8);
            System.out.println("\nNIO讀取所有行:");
            lines.forEach(System.out::println);

            // 流式讀取
            System.out.println("\nNIO流式讀取:");
            Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)
                    .forEach(System.out::println);
        } catch (IOException e) {
            System.out.println("NIO讀取失敗: " + e.getMessage());
        }
    }

    /**
     * 文件寫入示例:對(duì)比傳統(tǒng)IO和NIO方式
     */
    private static void writeFileExample(String filePath, String content) {
        System.out.println("\n--- 文件寫入示例 ---");

        // 傳統(tǒng)IO方式
        try (BufferedWriter bw = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(filePath), StandardCharsets.UTF_8))) {
            bw.write(content);
            System.out.println("傳統(tǒng)IO寫入成功");
        } catch (IOException e) {
            System.out.println("傳統(tǒng)IO寫入失敗: " + e.getMessage());
        }

        // NIO方式 - 寫入字符串
        try {
            Files.write(Paths.get(filePath), content.getBytes(StandardCharsets.UTF_8));
            System.out.println("NIO寫入字符串成功");
        } catch (IOException e) {
            System.out.println("NIO寫入字符串失敗: " + e.getMessage());
        }

        // NIO方式 - 寫入多行
        List<String> lines = Arrays.asList("第一行", "第二行", "第三行");
        try {
            Files.write(Paths.get(filePath), lines, StandardCharsets.UTF_8);
            System.out.println("NIO寫入多行成功");
        } catch (IOException e) {
            System.out.println("NIO寫入多行失敗: " + e.getMessage());
        }
    }

    /**
     * 文件復(fù)制示例:對(duì)比傳統(tǒng)IO和NIO方式
     */
    private static void copyFileExample(String sourcePath, String targetPath) {
        System.out.println("\n--- 文件復(fù)制示例 ---");

        // 傳統(tǒng)IO方式
        try (InputStream is = new FileInputStream(sourcePath);
             OutputStream os = new FileOutputStream(targetPath)) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = is.read(buffer)) > 0) {
                os.write(buffer, 0, length);
            }
            System.out.println("傳統(tǒng)IO復(fù)制成功");
        } catch (IOException e) {
            System.out.println("傳統(tǒng)IO復(fù)制失敗: " + e.getMessage());
        }

        // NIO方式
        try {
            Files.copy(Paths.get(sourcePath), Paths.get(targetPath),
                    StandardCopyOption.REPLACE_EXISTING);
            System.out.println("NIO復(fù)制成功");
        } catch (IOException e) {
            System.out.println("NIO復(fù)制失敗: " + e.getMessage());
        }
    }

    /**
     * 目錄創(chuàng)建示例:對(duì)比傳統(tǒng)IO和NIO方式
     */
    private static void createDirectoryExample(String dirPath) {
        System.out.println("\n--- 目錄創(chuàng)建示例 ---");

        // 傳統(tǒng)IO方式
        File dir = new File(dirPath);
        if (dir.mkdirs()) {
            System.out.println("傳統(tǒng)IO創(chuàng)建目錄成功");
        } else {
            System.out.println("傳統(tǒng)IO創(chuàng)建目錄失敗或目錄已存在");
        }

        // NIO方式
        try {
            Files.createDirectories(Paths.get(dirPath));
            System.out.println("NIO創(chuàng)建目錄成功");
        } catch (IOException e) {
            System.out.println("NIO創(chuàng)建目錄失敗: " + e.getMessage());
        }
    }

    /**
     * 列出目錄內(nèi)容示例:對(duì)比傳統(tǒng)IO和NIO方式
     */
    private static void listDirectoryExample(String dirPath) {
        System.out.println("\n--- 列出目錄內(nèi)容示例 ---");

        // 傳統(tǒng)IO方式
        File dir = new File(dirPath);
        String[] files = dir.list();
        if (files != null) {
            System.out.println("傳統(tǒng)IO列出目錄內(nèi)容:");
            for (String file : files) {
                System.out.println(file);
            }
        }

        // NIO方式
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(dirPath))) {
            System.out.println("\nNIO列出目錄內(nèi)容:");
            for (Path path : stream) {
                System.out.println(path.getFileName());
            }
        } catch (IOException e) {
            System.out.println("NIO列出目錄內(nèi)容失敗: " + e.getMessage());
        }
    }

    /**
     * 文件刪除示例:對(duì)比傳統(tǒng)IO和NIO方式
     */
    private static void deleteFileExample(String filePath) {
        System.out.println("\n--- 文件刪除示例 ---");

        // 傳統(tǒng)IO方式
        File file = new File(filePath);
        if (file.delete()) {
            System.out.println("傳統(tǒng)IO刪除文件成功");
        } else {
            System.out.println("傳統(tǒng)IO刪除文件失敗或文件不存在");
        }

        // NIO方式
        try {
            Files.deleteIfExists(Paths.get(filePath));
            System.out.println("NIO刪除文件成功");
        } catch (IOException e) {
            System.out.println("NIO刪除文件失敗: " + e.getMessage());
        }
    }

    /**
     * 目錄刪除示例:NIO方式(傳統(tǒng)方式需要遞歸實(shí)現(xiàn))
     */
    private static void deleteDirectoryExample(String dirPath) {
        System.out.println("\n--- 目錄刪除示例 ---");

        try {
            // NIO刪除目錄(包括目錄中的內(nèi)容)
            Files.walk(Paths.get(dirPath))
                    .sorted(Comparator.reverseOrder()) // 逆序排序,先刪除文件再刪除目錄
                    .forEach(path -> {
                        try {
                            Files.delete(path);
                        } catch (IOException e) {
                            System.out.println("刪除失敗: " + path + " - " + e.getMessage());
                        }
                    });
            System.out.println("NIO刪除目錄成功");
        } catch (IOException e) {
            System.out.println("NIO刪除目錄失敗: " + e.getMessage());
        }
    }
}

總結(jié)

  1. 大文件上傳若是用戶行為,跟前端配合使用分塊上傳處理
  2. 大文件上傳若是后端行為,可以使用FileChannel等零拷貝技術(shù)或者直接內(nèi)存映射技術(shù)
  3. 推薦使用java nio包中Path、Files來(lái)替代File對(duì)象進(jìn)行文件操作和流操作,安全又便捷。

到此這篇關(guān)于java大文件上傳處理方法的文章就介紹到這了,更多相關(guān)java大文件上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于postman傳參的幾種格式 list,map 等

    關(guān)于postman傳參的幾種格式 list,map 等

    這篇文章主要介紹了postman傳參的幾種格式 list,map等,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java私有構(gòu)造函數(shù)作用原理解析

    Java私有構(gòu)造函數(shù)作用原理解析

    這篇文章主要介紹了Java私有構(gòu)造函數(shù)作用原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Spring Boot緩存實(shí)戰(zhàn) Caffeine示例

    Spring Boot緩存實(shí)戰(zhàn) Caffeine示例

    本篇文章主要介紹了Spring Boot緩存實(shí)戰(zhàn) Caffeine示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • 詳解如何獲取java中類的所有對(duì)象實(shí)例

    詳解如何獲取java中類的所有對(duì)象實(shí)例

    如何在運(yùn)行時(shí)獲取一個(gè)Java類的所有對(duì)象實(shí)例呢,本文給大家介紹一種底層實(shí)現(xiàn)的方式,基于jvmti,代碼用C++實(shí)現(xiàn),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-10-10
  • Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法

    Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法

    MDC(Mapped Diagnostic Contexts)映射診斷上下文,該特征是logback提供的一種方便在多線程條件下的記錄日志的功能,這篇文章主要介紹了Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法,需要的朋友可以參考下
    2024-04-04
  • SpringCloud Edgware.SR3版本中Ribbon的timeout設(shè)置方法

    SpringCloud Edgware.SR3版本中Ribbon的timeout設(shè)置方法

    今天小編就為大家分享一篇關(guān)于SpringCloud Edgware.SR3版本中Ribbon的timeout設(shè)置方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • Java的List集合框架之LinkedList詳細(xì)解析

    Java的List集合框架之LinkedList詳細(xì)解析

    這篇文章主要介紹了Java的List集合框架之LinkedList詳細(xì)解析,LinkedList底層是內(nèi)部Node類的存儲(chǔ),prev、next、item值,同時(shí)最外層還有first、last節(jié)點(diǎn),需要的朋友可以參考下
    2023-11-11
  • Spring中的Sentinel熔斷降級(jí)原理詳解

    Spring中的Sentinel熔斷降級(jí)原理詳解

    這篇文章主要介紹了Spring中的Sentinel熔斷降級(jí)原理詳解,熔斷是為了起到保護(hù)作用,如果某個(gè)目標(biāo)服務(wù)調(diào)用比較慢或者大量的超時(shí),這個(gè)時(shí)候如果觸發(fā)熔斷機(jī)制,則可以保證后續(xù)的請(qǐng)求不會(huì)繼續(xù)發(fā)送到目標(biāo)服務(wù)上,而是直接返回降級(jí)的邏輯并且快速釋放資源,需要的朋友可以參考下
    2023-09-09
  • 一篇文章帶你入門Java變量及整形

    一篇文章帶你入門Java變量及整形

    這篇文章主要介紹了簡(jiǎn)單了解JAVA變量類型及代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-08-08
  • SpringBoot請(qǐng)求參數(shù)傳遞與接收示例詳解

    SpringBoot請(qǐng)求參數(shù)傳遞與接收示例詳解

    本文給大家介紹SpringBoot請(qǐng)求參數(shù)傳遞與接收示例詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2025-08-08

最新評(píng)論