当前位置: 首页 > news >正文

文件上传 分片上传

分片上传则是将一个大文件分割成多个小块分别上传,最后再由服务器合并成完整的文件。这种做法的好处是可以并行处理多个小文件,提高上传效率;同时,如果某一部分上传失败,只需要重传这一部分,不影响其他部分。

初步实现

后端代码

/*** 分片上传** @param file 上传的文件* @param start 文件开始上传的位置* @param fileName 文件名称* @return  上传结果*/
@PostMapping("/fragmentUpload")
@ResponseBody
public AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {try {// 检查上传目录是否存在,如果不存在则创建File directory = new File(uploadPath);if (!directory.exists()) {directory.mkdirs();}// 设置上传文件的目标路径File targetFile = new File(uploadPath +File.separator+ fileName);// 创建 RandomAccessFile 对象以便进行文件的随机读写操作RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");// 获取 RandomAccessFile 对应的 FileChannelFileChannel channel = randomAccessFile.getChannel();// 设置文件通道的位置,即从哪里开始写入文件内容channel.position(start);// 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());// 关闭文件通道和 RandomAccessFile 对象channel.close();randomAccessFile.close();// 返回上传成功的响应return AjaxResult.success("上传成功");} catch (Exception e) {// 捕获异常并返回上传失败的响应return AjaxResult.error("上传失败");}
}/*** 检测文件是否存在* 如果文件存在,则返回已经存在的文件大小。* 如果文件不存在,则返回 0,表示前端从头开始上传该文件。* @param filename* @return*/
@GetMapping("/checkFile")
@ResponseBody
public AjaxResult checkFile(@RequestParam("filename") String filename) {File file = new File(uploadPath+File.separator + filename);if (file.exists()) {return AjaxResult.success(file.length());} else {return AjaxResult.success(0L);}
}

前端

var prefix = ctx + "/kuroshiro/file-upload";// 每次上传大小
const chunkSize = 1 * 1024 * 1024;/*** 开始上传*/
function startUpload(type) {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}if(type == 1){checkFile(filename).then(start => {uploadFile(file, start,Math.min(start + chunkSize, file.size));})}
}/*** 检查是否上传过* @param filename* @returns {Promise<unknown>}*/
function checkFile(filename) {return $fetch(prefix+`/checkFile?filename=${filename}`);
}/*** 开始分片上传* @param file 文件* @param start 开始位置* @param end 结束位置*/
function uploadFile(file, start,end) {if(start < end){const chunk = file.slice(start, end);const formData = new FormData();formData.append('file', chunk);formData.append('start', start);formData.append('fileName', file.name);$fetch(prefix+'/fragmentUpload', {method: 'POST',body: formData}).then(response => {console.log(`分片 ${start} - ${end} 上传成功`);// 递归调用uploadFile(file,end,Math.min(end + chunkSize, file.size))})}}function $fetch(url,requestInit){return new Promise((resolve, reject) => {fetch(url,requestInit).then(response => {if (!response.ok) {throw new Error('请求失败');}return response.json();}).then(data => {if (data.code === 0) {resolve(data.data);} else {console.error(data.msg);reject(data.msg)}}).catch(error => {console.error(error);reject(error)});});}

以上虽然实现的分片上传,但是它是某种意义上来说还是与整体上传差不多,它是一段一段的上传,某段上传失败后,后续的就不会再继续上传;不过比起整体上传来说,它会保存之前上传的内容,下一个上传时,从之前上传的位置接着上传。不用整体上传。下面进行优化。

优化

首先,之前的分片上传,后端是直接写入了一个文件中了,所以只能顺序的上传写入,虽然可以保存上传出错之前的内容,但是整体上看来是速度也不行。
优化逻辑:把分片按顺序单独保存下来,等到所有分片都上传成功后,把所有分片合并成文件。这样上传的时候就不用等着上一个上传成功才上传下一个了。

后端代码

/**
* 分片上传* @param file 文件* @param chunkIndex 分片下标*/
@PostMapping("/uploadChunk")
@ResponseBody
public AjaxResult uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkIndex") int chunkIndex,@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;File directory = new File(uploadDirectory);if (!directory.exists()||directory.isFile()) {directory.mkdirs();}String filePath = uploadDirectory + File.separator + fileName+ "_" + chunkIndex;try (OutputStream os = new FileOutputStream(filePath)) {os.write(file.getBytes());return AjaxResult.success("分片"+(chunkIndex+1)+"上传成功");}catch (Exception e){// 保存失败后如果文件建立了就删除,下次上传时重新保存,避免文件内容错误File chunkFile = new File(filePath);if(chunkFile.exists()) chunkFile.delete();e.printStackTrace();return AjaxResult.error("分片"+(chunkIndex+1)+"上传失败");}}/*** 检测分片是否存在* 如果文件存在,则返回已经存在的分片下标集合。存在的就不上传* 如果文件不存在,则返回空集合,表示前端从头开始上传该文件* @param fileName* @return*/
@GetMapping("/checkChunk")
@ResponseBody
public AjaxResult checkChunk(@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;List<Integer> list = new ArrayList<>();File file = new File(uploadDirectory);// 文件目录不存在if(!file.exists()||file.isFile()) return AjaxResult.success(list);File[] files = file.listFiles();// 文件目录下没有分片文件if(files == null) return AjaxResult.success(list);// 返回存在分片下标集合return AjaxResult.success(Arrays.stream(files).map(item->Integer.valueOf(item.getName().substring(item.getName().lastIndexOf("_")+1))).collect(Collectors.toList()));
}// 合并文件分片@PostMapping("/mergeChunks")@ResponseBodypublic AjaxResult mergeChunks(@RequestParam("fileName") String fileName, @RequestParam("totalChunks") int totalChunks) {String uploadDirectory = chunkUploadPath+File.separator+fileName;String mergedFilePath = uploadPath +File.separator+ fileName;try (OutputStream os = new FileOutputStream(mergedFilePath, true)) {for (int i = 0; i < totalChunks; i++) {Path chunkFilePath = Paths.get(uploadDirectory +File.separator+ fileName + "_" + i);Files.copy(chunkFilePath, os);Files.delete(chunkFilePath);}return AjaxResult.success();}catch (Exception e){e.printStackTrace();return AjaxResult.error(e.getMessage());}}

前端代码

/*** 开始上传*/
function startUpload(type) {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}const filename = file.name;if(type == 1){checkFile(filename).then(start => {uploadFile(file, start,Math.min(start + chunkSize, file.size));})}if(type == 2){checkChunk(filename).then(arr => {uploadChunk(file, arr);})}
}/**
* 切割文件为多个分片
* @param file
* @returns {*[]}
*/
function sliceFile(file) {const chunks = [];let offset = 0;while (offset < file.size) {const chunk = file.slice(offset, offset + chunkSize);chunks.push(chunk);offset += chunkSize;}return chunks;
}
/**
* 检查是否上传过
* @param filename
* @returns {Promise<unknown>}
*/
function checkChunk(filename) {return $fetch(prefix+`/checkChunk?fileName=${filename}`);
}/**
* 开始分片上传
* @param file 文件
* @param exists 存在的分片下标
*/
function uploadChunk(file,exists) {const chunkArr = sliceFile(file);Promise.all(chunkArr.map((chunk, index) => {if(!exists.includes(index)){const formData = new FormData();formData.append('file', chunk);formData.append('fileName', file.name);formData.append('chunkIndex', index);return $fetch(prefix+'/uploadChunk', {method: 'POST',body: formData});}})).then(uploadRes=> {// 合并分片const formData = new FormData();formData.append('fileName', file.name);formData.append('totalChunks', chunkArr.length);$fetch(prefix + '/mergeChunks', {method: 'POST',body:formData,}).then(mergeRes=>{console.log("合并成功")});});
}

以上优化后所有分片可以同时上传,所有分片上传都成功后进行合并。

最后是完整代码

@Controller()
@RequestMapping("/kuroshiro/file-upload")
public class FileUploadController {private String prefix = "kuroshiro/fragmentUpload";// 文件保存目录private final String uploadPath = RuoYiConfig.getUploadPath();// 分片保存目录private final String chunkUploadPath = uploadPath+File.separator+"chunks";/*** demo* @return*/@GetMapping("/demo")public String demo() {return prefix+"/demo";}/*** 分片上传** @param file 上传的文件* @param start 文件开始上传的位置* @param fileName 文件名称* @return  上传结果*/@PostMapping("/fragmentUpload")@ResponseBodypublic AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {try {// 检查上传目录是否存在,如果不存在则创建File directory = new File(uploadPath);if (!directory.exists()) {directory.mkdirs();}// 设置上传文件的目标路径File targetFile = new File(uploadPath +File.separator+ fileName);// 创建 RandomAccessFile 对象以便进行文件的随机读写操作RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");// 获取 RandomAccessFile 对应的 FileChannelFileChannel channel = randomAccessFile.getChannel();// 设置文件通道的位置,即从哪里开始写入文件内容channel.position(start);// 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());// 关闭文件通道和 RandomAccessFile 对象channel.close();randomAccessFile.close();// 返回上传成功的响应return AjaxResult.success("上传成功");} catch (Exception e) {// 捕获异常并返回上传失败的响应return AjaxResult.error("上传失败");}}/*** 检测文件是否存在* 如果文件存在,则返回已经存在的文件大小。* 如果文件不存在,则返回 0,表示前端从头开始上传该文件。* @param filename* @return*/@GetMapping("/checkFile")@ResponseBodypublic AjaxResult checkFile(@RequestParam("filename") String filename) {File file = new File(uploadPath+File.separator + filename);if (file.exists()) {return AjaxResult.success(file.length());} else {return AjaxResult.success(0L);}}/*** 分片上传* @param file 文件* @param chunkIndex 分片下标*/@PostMapping("/uploadChunk")@ResponseBodypublic AjaxResult uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkIndex") int chunkIndex,@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;File directory = new File(uploadDirectory);if (!directory.exists()||directory.isFile()) {directory.mkdirs();}String filePath = uploadDirectory + File.separator + fileName+ "_" + chunkIndex;try (OutputStream os = new FileOutputStream(filePath)) {os.write(file.getBytes());return AjaxResult.success("分片"+(chunkIndex+1)+"上传成功");}catch (Exception e){// 保存失败后如果文件建立了就删除,下次上传时重新保存,避免文件内容错误File chunkFile = new File(filePath);if(chunkFile.exists()) chunkFile.delete();e.printStackTrace();return AjaxResult.error("分片"+(chunkIndex+1)+"上传失败");}}/*** 检测分片是否存在* 如果文件存在,则返回已经存在的分片下标集合。存在的就不上传* 如果文件不存在,则返回空集合,表示前端从头开始上传该文件* @param fileName* @return*/@GetMapping("/checkChunk")@ResponseBodypublic AjaxResult checkChunk(@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;List<Integer> list = new ArrayList<>();File file = new File(uploadDirectory);// 文件目录不存在if(!file.exists()||file.isFile()) return AjaxResult.success(list);File[] files = file.listFiles();// 文件目录下没有分片文件if(files == null) return AjaxResult.success(list);// 返回存在分片下标集合return AjaxResult.success(Arrays.stream(files).map(item->Integer.valueOf(item.getName().substring(item.getName().lastIndexOf("_")+1))).collect(Collectors.toList()));}// 合并文件分片@PostMapping("/mergeChunks")@ResponseBodypublic AjaxResult mergeChunks(@RequestParam("fileName") String fileName, @RequestParam("totalChunks") int totalChunks) {String uploadDirectory = chunkUploadPath+File.separator+fileName;String mergedFilePath = uploadPath +File.separator+ fileName;try (OutputStream os = new FileOutputStream(mergedFilePath, true)) {for (int i = 0; i < totalChunks; i++) {Path chunkFilePath = Paths.get(uploadDirectory +File.separator+ fileName + "_" + i);Files.copy(chunkFilePath, os);Files.delete(chunkFilePath);}File chunkDir = new File(uploadDirectory);if (chunkDir.exists()) chunkDir.delete();return AjaxResult.success();}catch (Exception e){e.printStackTrace();return AjaxResult.error(e.getMessage());}}}
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head><th:block th:include="include :: header('分片上传')" />
</head>
<body class="gray-bg">
<div class="container-div" id="chunk-div"><div class="row"><div class="col-sm-12 search-collapse"><form id="formId"><div class="select-list"><ul><li><label>选择文件:</label><input type="file" id="fileInput"/></li><li><a class="btn btn-primary btn-rounded btn-sm" @click="startUpload(1)"><i class="fa fa-upload"></i>&nbsp;开始上传1</a><a class="btn btn-primary btn-rounded btn-sm" @click="startUpload(2)"><i class="fa fa-upload"></i>&nbsp;开始上传2</a></li></ul></div></form></div><div class="col-sm-12" style="padding-left: 0;"><div class="ibox"><div class="ibox-content"><h3>上传进度</h3><ul class="sortable-list connectList agile-list" v-if="uploadMsg"><li v-for="item in uploadMsg" :class="item.status+'-element'">{{item.title}}<div class="agile-detail">{{item.result}}</div></li></ul></div></div></div></div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">var prefix = ctx + "/kuroshiro/file-upload";new Vue({el: '#chunk-div',data: {// 每次上传大小chunkSize: 100 * 1024 * 1024,uploadMsg:{},startTime:0,},methods: {/*** 开始上传*/startUpload: function(type){const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}const filename = file.name;this.uploadMsg = {};this.startTime = (new Date()).getTime();Vue.set(this.uploadMsg, 'checkMsg', {title:`文件检测`,result: "检测中... ...",status:"info"});if(type == 1){this.checkFile(filename).then(start => {this.uploadMsg['checkMsg'].result = `检测成功:已存在文件,大小为 ${start}`this.uploadFile(file, start,Math.min(start + this.chunkSize, file.size));},err => {this.uploadMsg['checkMsg'].result = `检测失败:${err}`})}if(type == 2){this.checkChunk(filename).then(arr => {this.uploadMsg['checkMsg'].result = `检测成功:已存在文件分片 ${arr.length}`this.uploadChunk(file, arr);},err => {this.uploadMsg['checkMsg'].result = `检测失败:${err}`this.uploadMsg['checkMsg'].status = `info`})}},/*** 检查是否上传过* @param filename* @returns {Promise<unknown>}*/checkFile: function(filename) {return this.$fetch(prefix+`/checkFile?filename=${filename}`);},/*** 开始分片上传* @param file 文件* @param start 开始位置* @param end 结束位置*/uploadFile: function(file, start,end) {if(start < end){const chunk = file.slice(start, end);const formData = new FormData();formData.append('file', chunk);formData.append('start', start);formData.append('fileName', file.name);Vue.set(this.uploadMsg, 'uploadMsg_'+start, {title:`分片 ${start} - ${end} 上传`,result: "上传中... ...",status:"info"});this.$fetch(prefix+'/fragmentUpload', {method: 'POST',body: formData}).then(response => {this.uploadMsg['uploadMsg_'+start].result = `上传成功`;// 递归调用this.uploadFile(file,end,Math.min(end + this.chunkSize, file.size))},err=>{this.uploadMsg['uploadMsg_'+start].result = `上传失败:${err}`;this.uploadMsg['uploadMsg_'+start].status = `danger`;})}else{this.uploadMsg['uploadSuccess'] = {title:`文件已上传`,result:`耗时:`+((new Date()).getTime()-this.startTime),status:"info"};}},/*** 切割文件为多个分片* @param file* @returns {*[]}*/sliceFile: function(file) {const chunks = [];let offset = 0;while (offset < file.size) {const chunk = file.slice(offset, offset + this.chunkSize);chunks.push(chunk);offset += this.chunkSize;}return chunks;},/*** 检查是否上传过* @param filename* @returns {Promise<unknown>}*/checkChunk: function(filename) {return this.$fetch(prefix+`/checkChunk?fileName=${filename}`);},/*** 开始分片上传* @param file 文件* @param exists 存在的分片下标*/uploadChunk: function(file,exists) {const chunkArr = this.sliceFile(file);Promise.all(chunkArr.map(async (chunk, index) => {if (!exists.includes(index)) {const formData = new FormData();formData.append('file', chunk);formData.append('fileName', file.name);formData.append('chunkIndex', index);Vue.set(this.uploadMsg, "upload_" + index, {title: `分片 ${index + 1} 上传`,result: "上传中... ...",status: "info"});return new Promise((resolve, reject) => {this.$fetch(prefix+'/uploadChunk', {method: 'POST',body: formData}).then(res => {resolve(res)this.uploadMsg["upload_"+index].result = "上传成功";},err => {reject(err)this.uploadMsg["upload_"+index].result = err;this.uploadMsg["upload_"+index].status = "danger";});})}})).then(uploadRes=> {this.uploadMsg["uploadSuccess"] = {title:`上传成功`,result: "耗时:"+((new Date()).getTime()-this.startTime),status:"info"};// 合并分片const formData = new FormData();formData.append('fileName', file.name);formData.append('totalChunks', chunkArr.length);Vue.set(this.uploadMsg, 'mergeChunks', {title:`合并分片`,result: "合并中... ...",status:"info"});this.$fetch(prefix + '/mergeChunks', {method: 'POST',body:formData,}).then(mergeRes=>{this.uploadMsg["mergeChunks"].result = "合并成功";},err => {this.uploadMsg["mergeChunks"].result = `合并失败:${err}`;this.uploadMsg["mergeChunks"].status = "danger";});});},$fetch: function(url,requestInit){return new Promise((resolve, reject) => {fetch(url,requestInit).then(response => {if (!response.ok) {throw new Error('请求失败');}return response.json();}).then(data => {if (data.code === 0) {resolve(data.data);} else {reject(data.msg)}}).catch(error => {reject(error)});});},}});</script>
</body>
</html>

相关文章:

文件上传 分片上传

分片上传则是将一个大文件分割成多个小块分别上传&#xff0c;最后再由服务器合并成完整的文件。这种做法的好处是可以并行处理多个小文件&#xff0c;提高上传效率&#xff1b;同时&#xff0c;如果某一部分上传失败&#xff0c;只需要重传这一部分&#xff0c;不影响其他部分…...

【0391】Postgres内核 checkpointer process ① 启动初始化

相关文章: 【0108】checkpointer运行原理(概念篇)(1) 【0278】checkpointer 共享内存(CheckpointerShmem)初始化(3) 文章目录 1. 启动 checkpointer process1.1 初始化 checkpointer PID1.2 注册 signal1.3 初始化 last checkpoint time2. 确认 config 的 shared memo…...

链路追踪SkyWalking

链路追踪 链路追踪作用链路追踪的关键概念链路追踪的工作原理常用链路追踪工具链路追踪的实现步骤链路追踪的典型场景 SkyWalkingSkyWalking 的主要功能SkyWalking 的架构安装 SkyWalking从 SkyWalking 的官方 GitHub 仓库 下载最新版本。配置后端存储SkyWalking使用&#xff0…...

Uniapp判断设备是安卓还是 iOS,并调用不同的方法

在 UniApp 中&#xff0c;可以通过 uni.getSystemInfoSync() 方法来获取设备信息&#xff0c;然后根据系统类型判断当前设备是安卓还是 iOS&#xff0c;并调用不同的方法。 示例代码 export default {onLoad() {this.checkPlatform();},methods: {checkPlatform() {// 获取系…...

计算机网络 (42)远程终端协议TELNET

前言 Telnet&#xff08;Telecommunication Network Protocol&#xff09;是一种网络协议&#xff0c;属于TCP/IP协议族&#xff0c;主要用于提供远程登录服务。 一、概述 Telnet协议是一种远程终端协议&#xff0c;它允许用户通过终端仿真器连接到远程主机&#xff0c;并在远程…...

rtthread学习笔记系列-- 23 环形缓冲块 ringblock

文章目录 23 环形缓冲块 ringblock23.1 初始化23.2 PUT & GET 块23.3 块释放23.4 rt_rbb_blk_queue_get23.5 rt_rbb_blk_alloc https://github.com/wdfk-prog/RT-Thread-Study 23 环形缓冲块 ringblock 环形块状缓冲区简称为&#xff1a;rbb。与传统的环形缓冲区不同的是&…...

HunyuanVideo 文生视频模型实践

HunyuanVideo 文生视频模型实践 flyfish 运行 HunyuanVideo 模型使用文本生成视频的推荐配置&#xff08;batch size 1&#xff09;&#xff1a; 模型分辨率(height/width/frame)峰值显存HunyuanVideo720px1280px129f60GHunyuanVideo544px960px129f45G 本项目适用于使用 N…...

Qt——QTableWidget 限制单元格输入范围的方法(正则表达式输入校验法、自定义代理类MyItemDelegrate)

【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C++语言开发基础总结》 《从0到1学习嵌入式Linux开发》...

深度学习论文: CAS-ViT: Convolutional Additive Self-attention Vision Transformers

深度学习论文: CAS-ViT: Convolutional Additive Self-attention Vision Transformers for Efficient Mobile Applications CAS-ViT: Convolutional Additive Self-attention Vision Transformers for Efficient Mobile Applications PDF:https://arxiv.org/pdf/2408.03703 PyT…...

PyCharm文档管理

背景&#xff1a;使用PyCharmgit做文档管理 需求&#xff1a;需要PyCharm自动识别docx/xslx/vsdx等文件类型&#xff0c;并在PyCharm内点击文档时唤起系统内关联应用(如word、excel、visio) 设置步骤&#xff1a; 1、file -》 settings -》file types 2、在Files opened i…...

QNAP 上常用的几款软件

当我们谈到 NAS&#xff08;Network Attached Storage&#xff09;时&#xff0c;QNAP 凭借多年的产品迭代、稳定的硬件性能和不断丰富的软件生态&#xff0c;已成为很多家庭及中小型企业的首选。除了存储本身&#xff0c;QNAP 提供的各种官方软件和应用&#xff0c;也为用户带…...

LabVIEW智能水肥一体灌溉控制系统

本文详细介绍了一种基于LabVIEW的智能水肥一体灌溉控制系统的设计与实现。该系统采用模糊控制策略&#xff0c;能够自动调节土壤湿度和肥液浓度&#xff0c;满足不同作物在不同生长阶段的需求&#xff0c;有效提高水肥利用效率&#xff0c;对现代精准农业具有重要的实践和推广价…...

提问:玩游戏输入法总弹出来咋回事哎

玩游戏时输入法总弹出来的问题&#xff0c;通常与电脑的输入法设置、操作系统配置以及游戏程序的兼容性有关。以下是一些常见的解决方法&#xff1a; 一、修改输入法快捷键 禁用不必要的输入法&#xff1a; 在系统的语言设置中&#xff0c;暂时禁用非活动的输入法&#xff0c;…...

链家房价数据爬虫和机器学习数据可视化预测

完整源码项目包获取→点击文章末尾名片&#xff01;...

【微服务】面试题 5、分布式系统理论:CAP 与 BASE 详解

分布式系统理论&#xff1a;CAP 与 BASE 详解 一、CAP 定理 背景与定义&#xff1a;1998 年由加州大学科学家埃里克布鲁尔提出&#xff0c;分布式系统存在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;、分区容错性&#xff08;Part…...

第十二章:算法与程序设计

文章目录&#xff1a; 一&#xff1a;基本概念 1.算法与程序 1.1 算法 1.2 程序 2.编译预处理 3.面向对象技术 4.程序设计方法 5.SOP标志作业流程 6.工具 6.1 自然语言 6.2 流程图 6.3 N/S图 6.4 伪代码 6.5 计算机语言 二&#xff1a;程序设计 基础 1.常数 …...

RAG技术:是将知识库的文档和问题共同输入到LLM中

RAG技术 RAG技术是将知识库的文档和问题共同输入到LLM中 RAG技术是先从知识库中检索出与问题相关的文档片段,然后将这些检索到的文档片段与问题一起输入到LLM中进行回答。具体过程如下: 文本分块 由于LLM的上下文窗口有限,需要将长文本资料分割成较小的块,以便LLM能够有…...

持续集成 01|Gitee介绍、Pycharm使用Gitee

目录 一、理论 二、 git的简介与安装 三、Gitee 1、注册网易163邮箱 2、注册Gitee账号 3、git和gitee管理代码工作原理 三、PyCharm安装配置Gitee 四、Pycharm使用Gitee插件的五种场景 1、将 Gitee的新仓库 Checkout&#xff08;检出&#xff09;到 Pycharm中 2、推送…...

信息安全、网络安全和数据安全的区别和联系

信息安全、网络安全和数据安全是信息安全领域的三大支柱&#xff0c;它们之间既存在区别又相互联系。以下是对这三者的详细比较&#xff1a; 一.区别 1.信息安全 定义 信息安全是指为数据处理系统建立和采用的技术和管理的安全保护&#xff0c;保护计算机硬件、软件和数据不…...

C++实现设计模式---抽象工厂模式 (Abstract Factory)

抽象工厂模式 (Abstract Factory) 抽象工厂模式 是一种创建型设计模式&#xff0c;提供一个接口&#xff0c;用于创建一组相关或互相依赖的对象&#xff0c;而无需指定它们的具体类。 意图 提供一个创建一组相关对象的接口&#xff0c;而无需指定它们的具体类。解决产品对象之…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...

Webpack性能优化:构建速度与体积优化策略

一、构建速度优化 1、​​升级Webpack和Node.js​​ ​​优化效果​​&#xff1a;Webpack 4比Webpack 3构建时间降低60%-98%。​​原因​​&#xff1a; V8引擎优化&#xff08;for of替代forEach、Map/Set替代Object&#xff09;。默认使用更快的md4哈希算法。AST直接从Loa…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

pycharm 设置环境出错

pycharm 设置环境出错 pycharm 新建项目&#xff0c;设置虚拟环境&#xff0c;出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

ubuntu22.04 安装docker 和docker-compose

首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...