Kotlin實現文件下載斷點續傳(RandomAccessFile全解析)

本文將深入探討如何使用Kotlin和RandomAccessFile實現高效的斷點續傳功能,涵蓋原理分析、完整代碼實現、性能優化及工程實踐要點。

一、斷點續傳核心原理

1.1 HTTP斷點續傳協議
Client Server GET /file (Range: bytes=500-) 206 Partial Content Content-Range: bytes 500-999/1500 200 OK (完整文件) alt [支持斷點續傳] [不支持] Client Server
1.2 RandomAccessFile核心優勢
特性傳統FileInputStreamRandomAccessFile
隨機訪問能力??
大文件處理效率??????
內存占用
斷點續傳實現復雜度
文件修改能力??

二、服務端完整實現(Kotlin + Spring Boot)

2.1 依賴配置
// build.gradle.kts
dependencies {implementation("org.springframework.boot:spring-boot-starter-web")implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
}
2.2 控制器實現
@RestController
class DownloadController {@GetMapping("/download/{filename}")suspend fun downloadFile(@PathVariable filename: String,request: HttpServletRequest,response: HttpServletResponse) {val file = File("/data/files/$filename").takeIf { it.exists() } ?: throw FileNotFoundException("File not found")// 解析Range請求頭val (start, end) = parseRangeHeader(request, file.length())// 設置HTTP響應頭response.configureHeaders(file, start, end)// 使用RandomAccessFile進行文件傳輸transferFileContent(file, response, start, end)}private fun parseRangeHeader(request: HttpServletRequest, fileLength: Long): Pair<Long, Long> {val rangeHeader = request.getHeader("Range")?.takeIf { it.startsWith("bytes=") }?: return 0L to fileLength - 1val range = rangeHeader.substring(6).split("-")val start = range[0].toLongOrNull() ?: 0Lval end = range.getOrNull(1)?.toLongOrNull() ?: fileLength - 1return start to min(end, fileLength - 1)}private fun HttpServletResponse.configureHeaders(file: File, start: Long, end: Long) {val fileLength = file.length()val contentLength = end - start + 1status = if (start > 0) HttpStatus.PARTIAL_CONTENT.value() else HttpStatus.OK.value()contentType = "application/octet-stream"setHeader("Accept-Ranges", "bytes")setHeader("Content-Disposition", "attachment; filename=\"${file.name}\"")setHeader("Content-Length", contentLength.toString())if (status == HttpStatus.PARTIAL_CONTENT.value()) {setHeader("Content-Range", "bytes $start-$end/$fileLength")}}private suspend fun transferFileContent(file: File, response: HttpServletResponse,start: Long, end: Long) = withContext(Dispatchers.IO) {RandomAccessFile(file, "r").use { raf ->raf.seek(start)val output = response.outputStreamval buffer = ByteArray(8192)var bytesRemaining = end - start + 1while (bytesRemaining > 0) {val readSize = min(bytesRemaining, buffer.size.toLong()).toInt()val bytesRead = raf.read(buffer, 0, readSize)if (bytesRead == -1) breakoutput.write(buffer, 0, bytesRead)output.flush()bytesRemaining -= bytesRead}}}
}
2.3 關鍵代碼解析

1. 文件指針定位

raf.seek(start) // 將文件指針移動到斷點位置

2. 分塊傳輸邏輯

while (bytesRemaining > 0) {val readSize = min(bytesRemaining, buffer.size.toLong()).toInt()val bytesRead = raf.read(buffer, 0, readSize)// ... 寫入輸出流
}

3. HTTP頭處理

// 部分內容響應
setHeader("Content-Range", "bytes $start-$end/$fileLength")
status = HttpStatus.PARTIAL_CONTENT.value()

三、客戶端完整實現(Kotlin)

3.1 文件下載器類
class ResumableDownloader(private val url: String,private val savePath: String,private val chunkSize: Int = 8192
) {private var downloadedBytes: Long = 0private val progressListeners = mutableListOf<(Long, Long) -> Unit>()fun addProgressListener(listener: (Long, Long) -> Unit) {progressListeners.add(listener)}suspend fun startDownload() = withContext(Dispatchers.IO) {val file = File(savePath)downloadedBytes = if (file.exists()) file.length() else 0Lwhile (true) {try {val connection = URL(url).openConnection() as HttpURLConnectionconnection.setRequestProperty("Range", "bytes=$downloadedBytes-")if (connection.responseCode !in 200..299) {if (connection.responseCode == 416) { // 范圍請求錯誤file.delete() // 刪除無效文件downloadedBytes = 0continue}throw IOException("HTTP error: ${connection.responseCode}")}// 獲取文件總大小val contentRange = connection.getHeaderField("Content-Range")val totalSize = contentRange?.split("/")?.last()?.toLongOrNull() ?: connection.contentLengthLong.takeIf { it > 0 } ?: -1// 執行下載downloadChunks(connection, file, totalSize)break} catch (e: SocketTimeoutException) {println("Timeout, retrying...")} catch (e: IOException) {if (e.message?.contains("reset") == true) {println("Connection reset, retrying...")} else {throw e}}}}private suspend fun downloadChunks(connection: HttpURLConnection,file: File,totalSize: Long) {RandomAccessFile(file, "rw").use { raf ->raf.seek(downloadedBytes)val input = connection.inputStreamval buffer = ByteArray(chunkSize)while (true) {val bytesRead = input.read(buffer)if (bytesRead == -1) breakraf.write(buffer, 0, bytesRead)downloadedBytes += bytesRead// 更新進度if (totalSize > 0) {progressListeners.forEach { it(downloadedBytes, totalSize) }}}}}
}
3.2 使用示例
fun main() = runBlocking {val downloader = ResumableDownloader(url = "https://example.com/large-file.zip",savePath = "downloads/large-file.zip")downloader.addProgressListener { current, total ->val percent = (current.toDouble() / total * 100).toInt()println("Downloaded: $current/$total ($percent%)")}try {downloader.startDownload()println("Download completed successfully!")} catch (e: Exception) {println("Download failed: ${e.message}")println("Resume position: ${File("downloads/large-file.zip").length()} bytes")}
}

四、性能優化策略

4.1 內存映射文件加速
private fun transferWithMemoryMap(file: File, start: Long, end: Long, output: OutputStream) {RandomAccessFile(file, "r").use { raf ->val channel = raf.channelval buffer = channel.map(FileChannel.MapMode.READ_ONLY, start, end - start + 1)output.write(buffer.array(), buffer.arrayOffset(), buffer.remaining())}
}
4.2 零拷貝技術(Linux系統)
private fun transferZeroCopy(file: File, response: HttpServletResponse, start: Long, end: Long) {FileInputStream(file).use { fis ->val channel = fis.channelval outputChannel = Channels.newChannel(response.outputStream)var position = startval totalBytes = end - start + 1var remaining = totalByteswhile (remaining > 0) {val transferred = channel.transferTo(position, remaining, outputChannel)position += transferredremaining -= transferred}}
}

五、工程實踐要點

5.1 斷點存儲設計
// 斷點信息數據類
data class DownloadState(val url: String,val filePath: String,val downloaded: Long,val totalSize: Long,val timestamp: Long = System.currentTimeMillis()
)// 持久化存儲
class DownloadStateRepository {private val states = ConcurrentHashMap<String, DownloadState>()fun saveState(key: String, state: DownloadState) {states[key] = state// 實際項目應持久化到數據庫或文件}fun loadState(key: String): DownloadState? {return states[key]}
}
5.2 多線程下載實現
class MultiThreadDownloader(private val url: String,private val savePath: String,private val threadCount: Int = 4
) {suspend fun download() = coroutineScope {val totalSize = getFileSize()val chunkSize = totalSize / threadCount// 創建臨時文件RandomAccessFile(savePath, "rw").use {it.setLength(totalSize) // 預分配空間}// 啟動多個下載協程(0 until threadCount).map { threadId ->async(Dispatchers.IO) {val start = threadId * chunkSizeval end = if (threadId == threadCount - 1) {totalSize - 1} else {(threadId + 1) * chunkSize - 1}downloadChunk(start, end)}}.awaitAll()}private suspend fun downloadChunk(start: Long, end: Long) {val connection = URL(url).openConnection() as HttpURLConnectionconnection.setRequestProperty("Range", "bytes=$start-$end")RandomAccessFile(savePath, "rw").use { raf ->raf.seek(start)connection.inputStream.use { input ->input.copyTo(raf.channel)}}}
}

六、完整解決方案對比

方案實現復雜度大文件支持內存效率適用場景
RandomAccessFile???????????通用文件傳輸
內存映射????????????超大文件讀取
NIO零拷貝??????????????高性能服務器
多線程分塊下載?????????????高速下載環境

七、總結與最佳實踐

核心要點總結

  1. HTTP協議:正確處理Range請求頭和Content-Range響應頭
  2. 文件定位:使用RandomAccessFile.seek()實現精確跳轉
  3. 分塊傳輸:采用8-16KB緩沖區平衡內存與IO效率
  4. 錯誤恢復
    • 捕獲ClientAbortException處理客戶端中斷
    • 實現自動重試機制(3次重試策略)
  5. 進度監控:實時回調下載進度用于UI更新

生產環境建議

// 1. 添加超時控制
connection.connectTimeout = 30_000
connection.readTimeout = 120_000// 2. 限流保護
val maxSpeed = 1024 * 1024 // 1MB/s
val startTime = System.currentTimeMillis()
var bytesTransferred = 0Lwhile (/*...*/) {// ... 傳輸邏輯bytesTransferred += bytesRead// 限速控制val elapsed = System.currentTimeMillis() - startTimeval expectedTime = bytesTransferred * 1000 / maxSpeedif (elapsed < expectedTime) {delay(expectedTime - elapsed)}
}// 3. 文件校驗
fun verifyFile(file: File, expectedHash: String): Boolean {val digest = MessageDigest.getInstance("SHA-256")file.forEachBlock { buffer, bytesRead ->digest.update(buffer, 0, bytesRead)}return digest.digest().joinToString("") { "%02x".format(it) } == expectedHash
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/85399.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/85399.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/85399.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux-headers-$(uname -r)和kmod是什么?

2025年6月16日&#xff0c;周一清晨 Linux-headers-$(uname -r)與kmod包詳解 一、linux-headers-$(uname -r)包 linux-headers-(uname -r)是Linux系統中與當前運行內核版本匹配的內核頭文件包&#xff0c;其中(uname -r)會自動替換為當前內核版本號&#xff08;如5.13.0-19-g…

使用axios及和spirng boot 交互

Axios Axios是一個基于Promise的HTTP庫&#xff0c;可以發送get、post等請求&#xff0c;它作用于瀏覽器和Node.js中。當運行在瀏覽器時&#xff0c;使用XMLHttpRequest接口發送請求&#xff1b;當運行在Node.js時&#xff0c;使用HTTP對象發送請求。 使用步驟&#xff1a; 第…

布局文件的逐行詳細解讀

總覽 源碼 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto&…

VTK 顯示大量點云數據及交互(點云拾取、著色、測量等)功能

VTK (Visualization Toolkit) 是一個強大的開源可視化庫&#xff0c;非常適合處理點云數據。下面將介紹如何使用 VTK 顯示大量點云數據&#xff0c;并實現點云拾取、著色、測量等功能。 基本點云顯示 創建一個基本的點云顯示程序&#xff1a; cpp #include <vtkSmartPoi…

性能優化 - 高級進階: 性能優化全方位總結

文章目錄 Pre1. 概述&#xff1a;性能優化提綱與使用場景2. 準備階段2.1 明確優化范圍與目標2.2 環境與工具準備 3. 數據收集與指標確認3.1 關鍵資源維度與指標項3.2 監控體系搭建與初始采集3.3 日志與追蹤配置 4. 問題定位思路4.1 從整體到局部的分析流程4.2 常見瓶頸維度檢查…

Mybatis之Integer類型字段為0,入庫為null

背景&#xff1a; 由于項目某個功能用到優先級字段來判斷&#xff0c;需要在mysql表中定義一個字段XX&#xff0c;類型為int&#xff0c;默認為0&#xff0c;具體值由后臺配置&#xff0c;正常入庫即可 問題&#xff1a; 由于后臺配置存量其他類型的數據無需該字段&#xff0c…

上海市計算機學會競賽平臺2022年3月月賽丙組洗牌

題目描述 給定一個整數 nn&#xff0c;表示 nn 張牌&#xff0c;牌的編號為 11 到 nn。 再給定一個洗牌置換 f1,f2,…,fnf1?,f2?,…,fn?&#xff0c;進行一次洗牌操作時&#xff0c;應將第一號位置的牌交換到第 f1f1? 號位置&#xff0c;將第 ii 號位置的牌交換到第 fifi…

DINO-R1:激勵推理能力的視覺基礎模型

摘要 近期&#xff0c;人們對大型語言模型&#xff08;如DeepSeek-R1&#xff09;推理能力的關注呈爆炸式增長&#xff0c;通過基于強化學習的微調框架&#xff08;如組相對策略優化&#xff08;Group Relative Policy Optimization&#xff0c;GRPO&#xff09;方法&#xff…

Linux--LVM邏輯卷擴容

Linux–LVM邏輯卷擴容 文章目錄 Linux--LVM邏輯卷擴容?? LVM 常用命令分類及基本格式? 1. 物理卷(PV)相關命令? 2. 卷組(VG)相關命令? 3. 邏輯卷(LV)相關命令?? 三、查看類命令簡寫說明使用命令及基本格式:lvm邏輯卷擴容步驟:1.添加硬盤設備2.檢測新增硬盤 添加…

C#基礎語法與控制臺操作

1. 控制臺操作基礎 控制臺程序是學習C#的起點。以下是一些常用的控制臺操作方法&#xff1a; 1.1. 清除控制臺 Console.Clear(); // 清除控制臺內容1.2. 輸出字符串 Console.WriteLine("Hello World!"); // 在屏幕的當前位置換行輸出字符串 Console.Write("…

100.Complex[]同時儲存實數和虛數兩組double的數組 C#例子

在信號處理中&#xff0c;IQ 數據&#xff08;In-phase and Quadrature&#xff09;通常表示復數形式的信號&#xff0c;其中實部表示同相分量&#xff0c;虛部表示正交分量。Complex[] data 是一個包含 IQ 數據的數組&#xff0c;每個元素是一個復數&#xff0c;表示一個信號樣…

停止追逐 React 重渲染

大多數開發者都在浪費時間對抗多余的重渲染。真正的 React 架構師根本讓問題無從產生——下面就來揭開他們的思路&#xff0c;以及為何大多數所謂的性能優化技巧反而拖慢了你的應用。 重渲染的無盡輪回 先來直擊痛點&#xff1a;如果還在項目里到處撒 useMemo、useCallback&…

流水線的安全與合規 - 構建可信的交付鏈

流水線的安全與合規 - 構建可信的交付鏈 “安全左移 (Shift-Left Security)”的理念 “安全左移”是 DevSecOps 的核心理念,指的是將安全測試和考量,從軟件開發生命周期 (SDLC) 的末端(發布前),盡可能地向左移動到更早的階段(如編碼、構建、測試階段)。 為何對 SRE 至…

???????神經網絡基礎講解 一

??一.神經網絡 ? ??1. 全連接神經網絡&#xff08;Fully Connected Network, FCN&#xff09;?? ??核心概念&#xff1a;?? ??輸入層??&#xff1a;接收原始數據&#xff08;如數字、圖片像素等&#xff09; 數字矩陣 。??隱藏層??&#xff1a;對數據…

MySQL 8.0 OCP 英文題庫解析(二十二)

Oracle 為慶祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免費考取原價245美元的MySQL OCP 認證。 從今天開始&#xff0c;將英文題庫免費公布出來&#xff0c;并進行解析&#xff0c;幫助大家在一個月之內輕松通過OCP認證。 本期公布試題201~210 試題2…

【大模型推理】PD分離場景下decoder負載均衡,如何選取decoder

https://mp.weixin.qq.com/s?__bizMzg4NTczNzg2OA&mid2247507420&idx1&sn4b32726abd205c7f94144bcb9105330f&chksmce64b9fc7f1d8de04a40b0153302dee52262c6f104c67195e2586e75c8093b8be493f252c8a3#rd 在非 Local 場景下&#xff0c;Prefill 定時獲取 Decode …

【IP地址】IP應用場景的使用方向

網絡安全領域 通過IP地址查詢&#xff0c;安全系統能夠實時監控網絡流量&#xff0c;識別異常訪問行為。例如&#xff0c;當某個IP地址在短時間內頻繁發起大量請求&#xff0c;且訪問模式與正常用戶存在明顯差異時&#xff0c;系統可將其標記為可疑IP&#xff0c;觸發風險預警…

3-18 WPS JS宏 顏色設置實例應用(按條件設置單元格顏色)學習筆記

前面講解了關于單元格的一些格式的設置&#xff0c;本節課再講解一下各種清除方法。 1.函數解析與用法 Range().clear()//清除全部 Range().Value2null //清除內容 Range().ClearContents()//清除內容 Range().ClearFormats()//清除格式 Range().EntireRow.Range()//以Ra…

從零開始的云計算生活——第二十天,腳踏實地,SSH與Rsync服務

目錄 一.故事背景 二.SSH帶外管理 1.概述 2. 配置文件 3.命令解析 4.登錄方式配置 a.用戶名密碼登錄 b.公鑰驗證登錄 5.實操生成密鑰對 三.Rsyncsersync實現數據實時同步 1.rsync概述 2.rsync運行原理 3.rsync部署 4.備份測試 配置備份目錄 5.rsyncsersync 實現…

SpringAI + DeepSeek大模型應用開發 - 初識篇

一、認識AI 1. AI的發展 AI&#xff0c;人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;使機器能像人類一樣思考、學習和解決問題的技術。 2. 大模型及其原理 在自然語言處理&#xff08;Natural Language Processing, NLP&#xff09;中&#xff0c;…