广东上海专业网站建设公司排名设计作品发布平台

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

广东上海专业网站建设公司排名,设计作品发布平台,小果seo实战培训课程,湛江网站建设皆选小罗24专业大家先来了解一下几个概念#xff1a; 「文件分块」#xff1a;将大文件拆分成小文件#xff0c;将小文件上传\下载#xff0c;最后再将小文件组装成大文件#xff1b; 「断点续传」#xff1a;在文件分块的基础上#xff0c;将每个小文件采用单独的线程进行上传\下载 「文件分块」将大文件拆分成小文件将小文件上传\下载最后再将小文件组装成大文件 「断点续传」在文件分块的基础上将每个小文件采用单独的线程进行上传\下载如果碰到网络故障可以从已经上传\下载的部分开始继续上传\下载未完成的部分而没有必要从头开始上传\下载 「文件秒传」资源服务器中已经存在该文件其他人上传时直接返回该文件的URI。 1、RandomAccessFile 平时我们都会使用FileInputStreamFileOutputStreamFileReader以及FileWriter等IO流来读取文件今天我们来了解一下RandomAccessFile。 它是一个直接继承Object的独立的类底层实现中它实现的是DataInput和DataOutput接口。该类支持随机读取文件随机访问文件类似于文件系统中存储的大字节数组。 它的实现基于「文件指针」一种游标或者指向隐含数组的索引文件指针可以通过getFilePointer方法读取也可以通过seek方法设置。 输入时从文件指针开始读取字节并使文件指针超过读取的字节如果写入超过隐含数组当前结尾的输出操作会导致扩展数组。该类有四种模式可供选择 r 以只读方式打开文件如果执行写入操作会抛出IOException;rw 以读、写方式打开文件如果文件不存在则尝试创建文件rws 以读、写方式打开文件要求对文件内容或元数据的每次更新都同步写入底层存储设备rwd 以读、写方式打开文件要求对文件内容的每次更新都同步写入底层存储设备 在rw模式下默认是使用buffer的只有cache满的或者使用RandomAccessFile.close()关闭流的时候才真正的写到文件。 1.1、API 1、void seek(long pos)设置下一次读取或写入时的文件指针偏移量通俗点说就是指定下次读文件数据的位置。 偏移量可以设置在文件末尾之外只有在偏移量设置超出文件末尾后才能通过写入更改文件长度 2、native long getFilePointer()返回当前文件的光标位置 3、native long length()返回当前文件的长度 4、「读」方法 5、「写」方法 6、readFully(byte[] b)这个方法的作用就是将文本中的内容填满这个缓冲区b。如果缓冲b不能被填满那么读取流的过程将被阻塞如果发现是流的结尾那么会抛出异常 7、FileChannel getChannel()返回与此文件关联的唯一FileChannel对象 8、int skipBytes(int n)试图跳过n个字节的输入丢弃跳过的字节 RandomAccessFile的绝大多数功能已经被JDK1.4的NIO的「内存映射」文件取代了即把文件映射到内存后再操作省去了频繁磁盘io。 2、文件分块 文件分块需要在前端进行处理可以利用强大的js库或者现成的组件进行分块处理。需要确定分块的大小和分块的数量然后为每一个分块指定一个索引值。 为了防止上传文件的分块与其它文件混淆采用文件的md5值来进行区分该值也可以用来校验服务器上是否存在该文件以及文件的上传状态。 如果文件存在直接返回文件地址如果文件不存在但是有上传状态即部分分块上传成功则返回未上传的分块索引数组如果文件不存在且上传状态为空则所有分块均需要上传。 fileRederInstance.readAsBinaryString(file); fileRederInstance.addEventListener(load, (e) {let fileBolb e.target.result;fileMD5 md5(fileBolb);const formData new FormData();formData.append(md5, fileMD5);axios.post(http /fileUpload/checkFileMd5, formData).then((res) {if (res.data.message 文件已存在) {//文件已存在不走后面分片了直接返回文件地址到前台页面success success(res);} else {//文件不存在存在两种情况一种是返回datanull代表未上传过 一种是data:[xxxx] 还有哪几片未上传if (!res.data.data) {//还有几片未上传情况断点续传chunkArr res.data.data;}readChunkMD5();}}).catch((e) {}); }); 在调用上传接口前通过slice方法来取出索引在文件中对应位置的分块。 const getChunkInfo (file, currentChunk, chunkSize) {//获取对应下标下的文件片段let start currentChunk * chunkSize;let end Math.min(file.size, start chunkSize);//对文件分块let chunk file.slice(start, end);return { start, end, chunk };}; 之后调用上传接口完成上传。 3、断点续传、文件秒传 后端基于spring boot开发使用redis来存储上传文件的状态和上传文件的地址。 如果文件完整上传返回文件路径部分上传则返回未上传的分块数组如果未上传过返回提示信息。 在上传分块时会产生两个文件一个是文件主体一个是临时文件。临时文件可以看做是一个数组文件为每一个分块分配一个值为127的字节。 校验MD5值时会用到两个值 文件上传状态只要该文件上传过就不为空如果完整上传则为true部分上传返回false文件上传地址如果文件完整上传返回文件路径部分上传返回临时文件路径。 /*** 校验文件的MD5**/ public Result checkFileMd5(String md5) throws IOException {//文件是否上传状态只要该文件上传过该值一定存在Object processingObj stringRedisTemplate.opsForHash().get(UploadConstants.FILE_UPLOAD_STATUS, md5);if (processingObj null) {return Result.ok(该文件没有上传过);}boolean processing Boolean.parseBoolean(processingObj.toString());//完整文件上传完成时为文件的路径如果未完成返回临时文件路径临时文件相当于数组为每个分块分配一个值为127的字节String value stringRedisTemplate.opsForValue().get(UploadConstants.FILE_MD5_KEY md5);//完整文件上传完成是true未完成返回falseif (processing) {return Result.ok(value,文件已存在);} else {File confFile new File(value);byte[] completeList FileUtils.readFileToByteArray(confFile);ListInteger missChunkList new LinkedList();for (int i 0; i completeList.length; i) {if (completeList[i] ! Byte.MAX_VALUE) {//用空格补齐missChunkList.add(i);}}return Result.ok(missChunkList,该文件上传了一部分);} } 说到这你肯定会问当这个文件的所有分块上传完成之后该怎么得到完整的文件呢接下来我们就说一下分块合并的问题。 4、分块上传、文件合并 上边我们提到了利用文件的md5值来维护分块和文件的关系因此我们会将具有相同md5值的分块进行合并由于每个分块都有自己的索引值所以我们会将分块按索引像插入数组一样分别插入文件中形成完整的文件。 分块上传时要和前端的分块大小、分块数量、当前分块索引等对应好以备文件合并时使用此处我们采用的是「磁盘映射」的方式来合并文件。 //读操作和写操作都是允许的 RandomAccessFile tempRaf new RandomAccessFile(tmpFile, rw); //它返回的就是nio通信中的file的唯一channel FileChannel fileChannel tempRaf.getChannel();//写入该分片数据 分片大小 * 第几块分片获取偏移量 long offset CHUNK_SIZE * multipartFileDTO.getChunk(); //分片文件大小 byte[] fileData multipartFileDTO.getFile().getBytes(); //将文件的区域直接映射到内存 MappedByteBuffer mappedByteBuffer fileChannel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length); mappedByteBuffer.put(fileData); // 释放 FileMD5Util.freedMappedByteBuffer(mappedByteBuffer); fileChannel.close(); 每当完成一次分块的上传还需要去检查文件的上传进度看文件是否上传完成。 RandomAccessFile accessConfFile new RandomAccessFile(confFile, rw); //把该分段标记为 true 表示完成 accessConfFile.setLength(multipartFileDTO.getChunks()); accessConfFile.seek(multipartFileDTO.getChunk()); accessConfFile.write(Byte.MAX_VALUE);//completeList 检查是否全部完成,如果数组里是否全部都是(全部分片都成功上传) byte[] completeList FileUtils.readFileToByteArray(confFile); byte isComplete Byte.MAX_VALUE; for (int i 0; i completeList.length isComplete Byte.MAX_VALUE; i) {//与运算, 如果有部分没有完成则 isComplete 不是 Byte.MAX_VALUEisComplete (byte) (isComplete completeList[i]); } accessConfFile.close(); 然后更新文件的上传进度到Redis中。 //更新redis中的状态如果是true的话证明是已经该大文件全部上传完成 if (isComplete Byte.MAX_VALUE) {stringRedisTemplate.opsForHash().put(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5(), true);stringRedisTemplate.opsForValue().set(UploadConstants.FILE_MD5_KEY multipartFileDTO.getMd5(), uploadDirPath / fileName); } else {if (!stringRedisTemplate.opsForHash().hasKey(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5())) {stringRedisTemplate.opsForHash().put(UploadConstants.FILE_UPLOAD_STATUS, multipartFileDTO.getMd5(), false);}if (!stringRedisTemplate.hasKey(UploadConstants.FILE_MD5_KEY multipartFileDTO.getMd5())) {stringRedisTemplate.opsForValue().set(UploadConstants.FILE_MD5_KEY multipartFileDTO.getMd5(), uploadDirPath / fileName .conf);} }