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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;…