文件上传——springboot大文件分片多线程上传功能,前端显示弹出上传进度框
一、项目搭建
-
创建 Spring Boot 项目: 创建一个新的 Spring Boot 项目,添加 Web 依赖。
-
添加依赖: 在
pom.xml
文件中添加以下依赖:
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version>
</dependency>
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version>
</dependency>
二、后端实现
-
配置 MultipartResolver: 在 Spring Boot 配置类中添加以下代码:
@Configuration
public class MyWebAppConfigurer implements WebMvcConfigurer {@Beanpublic MultipartResolver multipartResolver() {CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();multipartResolver.setMaxUploadSize(-1); // 设置最大上传大小,-1 表示无限制return multipartResolver;}
}
-
创建 FileUploadService: 创建一个服务类,用于处理文件上传逻辑:
@Service
public class FileUploadService {private String uploadDir = "upload/"; // 设置上传目录public String initUpload(String fileName, long fileSize, int chunkSize) {// 1. 生成任务 ID (UUID)String fileId = UUID.randomUUID().toString();//这里根据实际情况考虑到断点续传的功能,同一个文件生成的标识要一样,后期可以根据这个判断文件上传进度// 2. 创建临时目录: uploadDir/fileIdFile dir = new File(uploadDir, fileId);if (!dir.exists()) {dir.mkdirs();}// 3. 返回 fileIdreturn fileId;}public String uploadChunk(String fileId, int chunkIndex, int totalChunks, MultipartFile file) throws IOException {String fileUrl="";// 1. 获取分片文件String fileName = file.getOriginalFilename();// 2. 保存分片到临时目录: uploadDir/fileId/chunkIndexFile chunkFile = new File(uploadDir, fileId + "/" + chunkIndex);file.transferTo(chunkFile);// 检查所有分片是否已上传完成if (allChunksUploaded(fileId, totalChunks)) {String fileName=datePath()+"/"+fileId;//这里可以不用文件名,如果需要用,则要在controller上传请求增加一个fileName参数// 合并分片fileUrl = mergeChunks(fileId, fileName);}// 3. 校验分片 MD5 (可选)return fileUrl;}//判断所有的分片是否都上传完毕private boolean allChunksUploaded(String fileId, int totalChunks) {for (int i = 0; i < totalChunks; i++) {File chunkFile = new File(uploadDir + fileName + ".chunk" + i);if (!chunkFile.exists()) {return false;}}return true;}public String mergeChunks(String fileId, String fileName) throws IOException {// 1. 获取所有分片文件File dir = new File(uploadDir, fileId);File[] chunkFiles = dir.listFiles();// 2. 按顺序合并分片File mergedFile = new File(uploadDir, fileName);try (FileOutputStream fos = new FileOutputStream(mergedFile, true)) {for (File chunkFile : chunkFiles) {try (FileInputStream fis = new FileInputStream(chunkFile)) {IOUtils.copy(fis, fos);}}}// 3. 删除临时目录FileUtils.deleteDirectory(dir);// 4. 校验文件 MD5 (可选)// 5. 返回文件存储路径return uploadDir + fileName;}/*** 日期路径 即年/月/日 如2018/08/08*/public static final String datePath(){Date now = new Date();return DateFormatUtils.format(now, "yyyy/MM/dd");}
}
-
创建 FileUploadController: 创建一个控制器类,用于处理文件上传请求:
要在分片上传的基础上实现断点续传,需要在服务端记录每个文件的上传进度,并在客户端请求上传时返回已上传的分片信息。
- 使用数据库或其他存储机制记录每个文件的上传进度。
- 可以使用以下信息标识一个上传任务:
- fileId: 全局唯一标识符,例如 UUID
- 同一个文件的标识是一样的,这样保证接这上次的进度继续上传。
@RestController
public class FileUploadController {@Autowiredprivate FileUploadService fileUploadService;private final ExecutorService executorService = Executors.newFixedThreadPool(5); // 线程池大小可配置private final Map<String, Set<Integer>> uploadProgress = new HashMap<>(); // 使用内存存储上传进度,实际应用中建议使用数据库@PostMapping("/upload/init")public ResponseEntity<String> initUpload(@RequestParam("fileName") String fileName,@RequestParam("fileSize") long fileSize,@RequestParam("chunkSize") int chunkSize) {String fileId = fileUploadService.initUpload(fileName, fileSize, chunkSize);return ResponseEntity.ok(fileId);}@PostMapping("/upload/chunk")public ResponseEntity<Void> uploadChunk(@RequestParam("fileId") String fileId,@RequestParam("chunkIndex") int chunkIndex, @RequestParam("totalChunks") int totalChunks,@RequestParam("file") MultipartFile file) throws IOException {String filePath = "";try {// 获取或创建上传进度记录Set<Integer> uploadedChunks = uploadProgress.computeIfAbsent(fileId, k -> new HashSet<>());// 如果分片已上传,则跳过if (uploadedChunks.contains(chunkIndex)) {return ResponseEntity.ok(new UploadResponse(uploadedChunks));}// 使用线程池处理每个分片上传executorService.execute(() -> {try {filePath = fileUploadService.uploadChunk(fileId, chunkIndex, totalChunks, file);} catch (IOException e) {// 处理异常,例如记录日志或返回错误信息e.printStackTrace();}});return ResponseEntity.status(HttpStatus.ACCEPTED).body(new UploadResponse(uploadedChunks, filePath));} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error uploading chunk.");}//return ResponseEntity.ok().build();}}// 用于响应上传请求,携带已上传分片信息
class UploadResponse {Set<Integer> uploadedChunks;String filePath;public UploadResponse(Set<Integer> uploadedChunks, String filePath) {this.uploadedChunks = uploadedChunks;this.filePath = filePath;}public UploadResponse(Set<Integer> uploadedChunks) {this.uploadedChunks = uploadedChunks;}// ... getter setter 方法 ...
}
三、前端实现
-
HTML 页面: 创建一个简单的 HTML 页面,包含文件选择按钮、上传进度条和相关信息展示区域。
-
JavaScript 代码: 使用 JavaScript 实现文件分割、分片上传、合并请求和上传进度展示等功能。
// 选择文件
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {const file = event.target.files[0];// ... 文件分割、上传逻辑
});// 文件分割
const chunkSize = 4 * 1024 * 1024; // 4MB
const chunks = sliceFile(file, chunkSize);const totalChunks = Math.ceil(file.size / chunkSize);// 初始化上传
const fileId = await initUpload(file.name, file.size, chunkSize);// 并发上传分片
const uploadPromises = chunks.map((chunk, index) => uploadChunk(fileId, index, totalChunks, chunk)
);// 上传完成合并得到文件url
await Promise.all(uploadPromises);async function uploadChunk(fileId, chunkIndex, totalChunks, chunk) {// ... 创建 FormData ...const formData = new FormData();formData.append('file', chunk);formData.append('chunkIndex', chunkIndex);formData.append('totalChunks', totalChunks);formData.append('fileId', fileId); // 添加 fileId 参数const response = await fetch('/upload', {method: 'POST',body: formData,});//...处理响应数据
}function sliceFile(file, chunkSize) {let chunks = [];let count = Math.ceil(file.size / chunkSize);for (let i = 0; i < count; i++) {let offset = i * chunkSize;let chunk = file.slice(offset, offset + chunkSize + 1);chunks.push(chunk);}return chunks;
}
四、上传进度展示
-
后端: 在
FileUploadService
中添加方法,根据fileId
返回已上传分片数量或计算上传进度百分比。 -
前端: 使用
setInterval
定时请求后端获取上传进度,并更新进度条。
前端html代码使用示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>文件上传</title>
</head>
<body>
<h1>大文件分片上传</h1>
<input type="file" id="fileInput">
<button id="uploadBtn">上传</button>
<div>上传进度:<progress id="progressBar" value="0" max="100"></progress> <span id="progressText">0%</span></div>
<script>const fileInput = document.getElementById('fileInput');const uploadBtn = document.getElementById('uploadBtn');const progressBar = document.getElementById('progressBar');const progressText = document.getElementById('progressText');uploadBtn.addEventListener('click', uploadFile);async function uploadFile() {const file = fileInput.files[0];if (!file) {alert('请选择文件');return;}const chunkSize = 4 * 1024 * 1024; // 4MBconst fileId = await initUpload(file.name, file.size, chunkSize);const chunks = sliceFile(file, chunkSize);let uploadedChunks = 0;const uploadPromises = chunks.map((chunk, index) => {return uploadChunk(fileId, index, chunks.length, chunk).then(() => {uploadedChunks++;updateProgress(uploadedChunks / chunks.length);});});await Promise.all(uploadPromises);await mergeChunks(fileId);alert('上传完成!');}function sliceFile(file, chunkSize) {const chunks = [];let offset = 0;while (offset < file.size) {chunks.push(file.slice(offset, offset + chunkSize));offset += chunkSize;}return chunks;}async function initUpload(fileName, fileSize, chunkSize) {const response = await fetch('/upload/init', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({ fileName, fileSize, chunkSize })});return await response.text();}async function uploadChunk(fileId, chunkIndex, totalChunks, chunk) {const formData = new FormData();formData.append('fileId', fileId);formData.append('chunkIndex', chunkIndex);formData.append('totalChunks', totalChunks);formData.append('file', chunk);await fetch('/upload/chunk', {method: 'POST',body: formData});}async function mergeChunks(fileId) {await fetch(`/upload/merge?fileId=${fileId}`);}function updateProgress(progress) {progressBar.value = progress * 100;progressText.textContent = Math.round(progress * 100) + '%';}
</script>
</body>
</html>
五、存储上传进度
使用 Redis 存储上传进度
-
Redis 数据结构:
- 使用 Hash 结构存储每个文件的上传进度,key 为 fileId和chunkIndex,chunkIndex 为分片索引,value 为 true 或 false,表示分片是否已上传。
-
代码实现:
- 注入
RedisTemplate
:
@Autowired
private RedisTemplate<String, String> redisTemplate;
- 修改
uploadChunk
方法:
private void uploadChunk(String fileId, int chunkIndex, int totalChunks, MultipartFile file) throws IOException {// ... 保存分片文件 ...// 更新上传进度到 RedisString chunkKey = fileId + ":" + chunkIndex;redisTemplate.opsForValue().set(chunkKey, "true");// 检查所有分片是否已上传完成if (redisTemplate.opsForHash().size(fileId) == totalChunks) {// ... 合并分片 ...// 清除上传进度redisTemplate.delete(fileId);}
}
- 添加
/upload/progress
接口:
@GetMapping("/upload/progress")
public ResponseEntity<UploadResponse> getUploadProgress(@RequestParam("identifier") String identifier
) {Set<String> uploadedChunks = redisTemplate.keys(identifier + ":*");Set<Integer> uploadedChunkIndices = uploadedChunks.stream().map(s -> Integer.parseInt(s.substring((identifier + ":").length()))).collect(Collectors.toSet());return ResponseEntity.ok(new UploadResponse(uploadedChunkIndices));
}
- 前端js使用
// ... 其他代码 ...
// 选择文件
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {const file = event.target.files[0];// ... 文件分割、上传逻辑
});// 文件分割
const chunkSize = 4 * 1024 * 1024; // 4MB
const chunks = sliceFile(file, chunkSize);
const totalChunks = Math.ceil(file.size / chunkSize);// 初始化上传
const fileId = await initUpload(file.name, file.size, chunkSize);async function uploadFile(chunks) {// 获取已上传的分片信息const uploadedChunks = await getUploadedChunks(fileId, file.name);// ... 根据 uploadedChunks 调整分片上传逻辑 ...if(...){//获取uploadedChunks为分片的索引,表示当前文件上传的进度,根据uploadedChunks的具体数据类型去取for (let i = (uploadedChunks); i < chunks.length; i++) {//const start = i * chunkSize;//const end = Math.min(start + chunkSize, file.size);//const chunk = file.slice(start, end);const chunk = chunks[i];await uploadChunk(fileId, i, totalChunks, chunk);}}else{// 并发上传分片chunks.map((chunk, index) => uploadChunk(fileId, index, totalChunks, chunk));}
}async function getUploadedChunks(fileId, fileName) {const response = await fetch(`/upload/progress?identifier=${fileId}&fileName=${fileName}`);const data = await response.json();return data.uploadedChunks || [];
}async function uploadChunk(fileId, chunkIndex, totalChunks, chunk) {// ... 创建 FormData ...const formData = new FormData();formData.append('file', chunk);formData.append('chunkIndex', chunkIndex);formData.append('totalChunks', totalChunks);formData.append('fileId', fileId); // 添加 fileId 参数const response = await fetch('/upload', {method: 'POST',body: formData,});//...处理响应数据
}function sliceFile(file, chunkSize) {let chunks = [];let count = Math.ceil(file.size / chunkSize);for (let i = 0; i < count; i++) {let offset = i * chunkSize;let chunk = file.slice(offset, offset + chunkSize + 1);chunks.push(chunk);}return chunks;
}
重新上传获取进度:
- 用户重新选择同一个文件上传时,需要生成相同的 f
ileId
。 - 在前端上传前,调用
/upload/progress
接口,传入 fileId 获取已上传的分片信息。 - 根据返回的已上传分片信息,跳过已上传的分片,继续上传剩余分片。
六、注意事项
- 以上代码示例省略了部分细节,例如异常处理、MD5 校验等,请根据实际情况进行完善。
- 前端代码需要根据您使用的 JavaScript 框架进行调整。
- 建议您先学习 Spring Boot 文件上传、JavaScript 文件操作和 AJAX 等前端相关知识。
希望这些更详细的步骤和代码片段能够帮助您更好地理解和实现 Spring Boot 断点续传、多线程分片上传功能! 如果您还有其他问题,请随时提出。
相关文章:
文件上传——springboot大文件分片多线程上传功能,前端显示弹出上传进度框
一、项目搭建 创建 Spring Boot 项目: 创建一个新的 Spring Boot 项目,添加 Web 依赖。 添加依赖: 在 pom.xml 文件中添加以下依赖: <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId&…...
每日学术速递8.2
1.A Scalable Quantum Non-local Neural Network for Image Classification 标题: 用于图像分类的可扩展量子非局部神经网络 作者: Sparsh Gupta, Debanjan Konar, Vaneet Aggarwal 文章链接:https://arxiv.org/abs/2407.18906 摘要&#x…...
SAP-PLM创建物料主数据接口
FUNCTION zplm_d_0001_mm01. *"---------------------------------------------------------------------- *"*"本地接口: *" EXPORTING *" VALUE(EX_TOTAL) TYPE CHAR4 *" VALUE(EX_SUCCESSFUL) TYPE CHAR4 *" …...
超声波眼镜清洗机哪个品牌好?四款高性能超声波清洗机测评剖析
对于追求高生活质量的用户来说,眼镜的清洁绝对不能马虎。如果不定期清洁眼镜,时间久了,镜片的缝隙中会积累大量的灰尘和细菌,眼镜靠近眼部,对眼部健康有很大影响。在这种情况下,超声波清洗机显得尤为重要。…...
卸载Windows软件的正确姿势,你做对了吗?
前言 今天有小伙伴突然问我:她把软件都卸载了,但是怎么软件都还在运行? 这个问题估计很多小伙伴都是遇到过的,对于电脑小白来说,卸载Windows软件真的真的真的是一件很难的事情。所以,今天咱们就来讲讲&am…...
WEB前端14-Element UI(学生查询表案例/模糊查询/分页查询)
Vue2-Element UI 1.可重用组件的开发 可重用组件 我们一般将可重复使用的组件放在components目录之下,以便父组件的灵活调用 <!--可重用组件一般与css密切相关,使用可重用组件的目的是,将相似的组件放在一起,方便使用-->…...
使用swiftui自定义圆形进度条实现loading
实现的代码如下: // // LoadingView.swift // SwiftBook // // Created by Song on 2024/8/2. //import SwiftUIstruct LoadingView: View {State var process 0.5var body: some View {VStack(spacing: 20) {ZStack {Circle().stroke(.gray.opacity(0.3), lin…...
C# 设计模式之抽象工厂模式
总目录 前言 工厂方法模式是为了克服简单工厂模式的缺点而设计出来的,简单工厂模式的工厂类随着产品类的增加需要增加额外的代码,而工厂方法模式每个具体工厂类只完成单个实例的创建,所以它具有很好的可扩展性。但是在现实生活中,…...
Javascript前端面试基础(八)
window.onload和$(document).ready区别 window.onload()方法是必须等到页面内包括图片的所有元素加载完毕后才能执行$(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕 window.onload 触发时机:window.onload 事件会在整个页面…...
R 语言学习教程,从入门到精通,R的安装与环境的配置(2)
1、R的安装与环境的配置 R语言是一款完全免费且开源的软件,它的开源许可证是GNU通用公共许可证(GPL),这意味着任何人都可以自由地使用、复制、修改和发布R语言的源代码,甚至可以将其用于商业用途。 和python等其他语言…...
Python批量下载音乐功能
Python批量下载音乐功能 Python批量下载音乐,调用API接口,同时下载歌曲和歌词 先安排一下要用的模块,导入进来。 import re import json import requests目录结构 下载音乐 Awking_Class.pymusic.txt 文件文件写的是音乐名字,使用换行分割 new_music 注意这个 ne…...
用 Bytebase 实现批量、多环境、多租户数据库的丝滑变更
Bytebase 提供了多种功能来简化批量变更管理,适用于多环境或多租户情况。本教程将指导您如何使用 部署配置 和 数据库组 在不同场景下进行数据库批量变更。 默认流水线 vs 部署配置 图片数据库 vs 数据库组 1. 准备 请确保已安装 Docker,如果本地没有重…...
java之方法引用 —— ::
目录 一、简介 二、引用静态方法 1.格式 2.示例 编辑 3.条件解析 三、引用成员方法 1.格式 2.示例 四、引用构造方法 1.格式 2.示例 五、类名引用成员方法 1.格式 2.略微不同的方法引用规则 3.示例 六、引用数组的构造方法 1.格式 2.示例 一、简介 方…...
「测试线排查的一些经验-上篇」 后端工程师
文章目录 端口占用脚本失灵线上部署项目结构模版配置文件生效 一般产品研发过程所使用的环境可分为: 研发环境-dev测试环境-test生产环境-prod 软件开发中,完整测试环境包括:UT、IT、ST、UAT UT Unit Test 单元测试 IT System Integration …...
AOSP12_BatteryStats统计电池数据信息
前言 BatteryStats模块主要用于设备在电池供电是系统对各个模块电量使用的统计,Android提供的Battery Historain工具就是对此模块统计的数据进行解析和展示。 一 BatteryStats模块类图 模块主要类图如下:见根目录的模块类图 BatteryStats:抽象类,本模块的核心类,主要定…...
【Android Studio】UI 布局
文章目录 view布局LinearLayout view 在Android开发中,View是一个非常重要的概念,它是所有用户界面组件的基类。View类及其子类构成了Android应用中的用户界面。每个View都占用屏幕上的一个矩形区域,并可以响应用户输入(如触摸、按…...
虚拟机Windows server忘记密码解决方法
原理 utilman.exe是Windows辅助工具管理器程序,虽然它本身不是一个关键的系统进程,但通过修改这个文件,用户可以访问一些有用的UI设置。在某些情况下,比如忘记密码需要重置时,通过修改utilman.exe文件为c…...
【香橙派系列教程】(六)嵌入式SQLite数据库
【六】嵌入式SQLite数据库 文章目录 【六】嵌入式SQLite数据库1.简介2.SQLite数据库安装3.SQLite命令用法1.创建数据库2.创建和查看表格3.插入查看数据(记录)4.删除更改数据(记录) 4.SQLite编程操作1.打开/创建数据库的C接口2.创建…...
深入探讨PHP8的新特性与性能优化
本文由 ChatMoney团队出品 随着互联网技术的飞速发展,PHP作为后端开发领域的热门语言也在不断演进。近期,PHP8的发布引起了广泛关注。本文将为您详细介绍PHP8的新特性以及性能优化,并通过具体示例帮助您更好地理解和应用这些新特性。 一、PH…...
2024年06月 Scratch 图形化(四级)真题解析#中国电子学会#全国青少年软件编程等级考试
Scratch图形化等级考试(1~4级)全部真题・点这里 一、单选题(共10题,共30分) 第1题 运行下列程序,输入单词“PLAY”,最后角色说?( ) A:LY4AP B:AP4LY C:YA4PL D:PL4AY 答案:B 根据程序分析可知,首先获取单词字符数,然后奇数位的字母放在字符数左侧,偶数位…...
书生大模型全链路开源体系
书生大模型全链路开源体系 数据 预训练 微调 评测 部署 应用...
极简聊天室-websocket版(双向通信)
我们知道WebSocket是可以双向通信的,把极简聊天室代码又改了一下,前端发信息到后端也使用websocket,其实代码量更少了。。。 const express require(express); const app express(); var wsServer require(express-ws)(app)var msgs[];ap…...
从小白到架构师 | 缓存预热
缓存预热指的是在系统启动或上线前,提前将经常访问的数据加载到缓存中,以避免在系统刚启动时,用户大量请求直接访问数据库,导致数据库压力过大或系统性能下降。通过缓存预热,可以确保系统一上线就能提供快速的响应时间…...
Modbus -- TCP协议
MODBUS TCP协议是一种基于TCP/IP协议的Modbus变种,它允许Modbus协议在以太网网络上运行,使得设备之间可以通过IP网络交换数据。 一:协议概述 modbus_TCP协议:走的是网口,所以需要创建TCPclient对象进行通信,和modubus-RTU协议最大的区别是&a…...
python四舍五入取整数
在Python中,如果你想要对一个浮点数进行四舍五入并取整(即只保留整数部分),你可以使用内置的round()函数,但不指定第二个参数(即小数位数),或者明确指定为0。这样,Python…...
洛谷 P1868 饥饿的奶牛
原题 题目描述 有一条奶牛冲出了围栏,来到了一处圣地(对于奶牛来说),上面用牛语写着一段文字。 现用汉语翻译为: 有 N 个区间,每个区间x,y 表示提供的x∼y 共y−x1 堆优质牧草。你可以选择任意区间但不…...
Arco Design 之Table表格
此篇文章为table表格示例,包含列、data数据、展开、选中、自定义等相关属性 基础表格 <a-table :columns"columns1" :data"tableData1" />const columns1 [{ title: "编号", dataIndex: "no"},{ title: "名称…...
Python机器学习 模型
Python机器学习模型、回归预测模型、数据清洗、数据处理、数据挖掘、数据分析代做。 模型不仅限于线性回归、逻辑回归、决策树、SVM、随机森林、贝叶斯、XGBoost、LightGBM、CatBoost,聚类:K-Means、DBSCAN,机器学习都可。 时间序列分析&…...
基于 STM32 的 NAS私有云盘搭建:集成LwIP 协议、HTTP/HTTPS、WEB前端技术栈(代码示例)
项目概述 在本项目中,我们将搭建一个基于 STM32 的 NAS(网络附加存储)私盘,通过网络访问存储在外部 SATA 硬盘上的文件。该项目将使用 STM32 开发板、外接 SATA 硬盘、LwIP 协议栈以及 FATFS 文件系统来实现文件的上传、下载和管…...
蓝屏?死机?爆CPU?多开卡顿?你有关心过你的硬盘吗?
上来先叠甲 蓝屏、死机、爆cpu、多开卡顿,不一定是硬盘的问题,只是硬盘有问题都可能会引起这些现象,所以不要遇到这些问题就一定认为是硬盘的问题然后说我说的,只是给你一个排除问题的思路。本文会采用比较通俗所以不太专业的角度…...
jquery 苹果网站/seo关键词推广价格
CentOS7下修改主机名第一种:hostname 主机名01.hostname 主机名称 这种方式,只能修改临时的主机名,当重启机器后,主机名称又变回来了。第二种:hostnamectl set-hostname <hostname>命令行中输入01.hostnamectl s…...
新疆建设兵团职称查询官方网站/今日头条搜索引擎
如何选择适合深度学习的GPU?为什么GPU比CPU更适合机器学习或者深度学习?什么是张量处理单元(TPU)?目前主流的GPU厂商:Nvidia和AMD选择GPU时需要关注的主要属性1. GPU的内存需要多少?2. 需要多少核心&#…...
有域名了 怎么做网站/seo建设
如果你频繁的在你的系统中安装/卸载,那么不时的清理一下你的系统是十分必要的。 在Ubuntu终端中执行如下命令:sudo apt-get autoremove屏幕输出是这个样子的: Reading package lists… DoneBuilding dependency treeReading state informatio…...
自己做的网站怎么爬数据/搜索引擎推广的常见形式有
说起独立博客的技术演变,从数据库到纯文本放git是一大进步,从HTML到markdown又是一大进步。 解析技术有没有进步呢?既然markdown是纯文本了,再用PHP/Python/Ruby去实时解析,多么多此一举啊(比如github用的J…...
网站360自然排名要怎么做/新闻稿发布
在iOS app 中经常会嵌套html 代码,js运行alert时会出现这个问题,见图: 提示框的title为所在目录文件夹名字: 解决方案: 用 iOS native 的uiwebview 的扩展方法来监听 js的alert 然后自定义 alert 的title #import &…...
网站关键词排名全掉了/福州短视频seo获客
直接贴图出来观赏,图中所有UI功能都是Flex提供的,你并不需要编写额外的代码,可以自己调整颜色来适应自己的需要.当然这里几张图只体现一部分.除了UI美观功能外,Flex的语言机制在功能实现上也很方便;这也是自己喜欢它的原因....