服务端如何将一个大视频文件做切分,分段响应给客户端,让浏览器可以渐进式地播放。

Spring-Boot实现HTTP分片下载断点续传,从而实现H5页面的大视频播放问题,实现渐进式播放,每次只播放需要播放的内容就可以了,不需要加载整个文件到内存中。

文件的断点续传、文件多线程并发下载(迅雷就是这么玩的)等。

代码实现

package com.example.insurance.controller;import com.example.insurance.common.MediaContentUtil;import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.NioUtil;
import cn.hutool.core.io.StreamProgress;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.unit.DataSize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;/*** 内容资源控制器*/
@SuppressWarnings("unused")
@Slf4j
@RestController("resourceController")
@RequestMapping(path = "/resource", produces = MediaType.APPLICATION_JSON_VALUE)
public class ResourceController {/*** 获取文件内容** @param fileName 内容文件名称* @param response 响应对象*/@GetMapping("/media/{fileName}")public void getMedia(@PathVariable String fileName, HttpServletRequest request, HttpServletResponse response,@RequestHeader HttpHeaders headers) {//        printRequestInfo(fileName, request, headers);String filePath = MediaContentUtil.filePath();try {this.download(fileName, filePath, request, response, headers);} catch (Exception e) {log.error("getMedia error, fileName={}", fileName, e);}}/*** 获取封面内容** @param fileName 内容封面名称* @param response 响应对象*/@GetMapping("/cover/{fileName}")public void getCover(@PathVariable String fileName, HttpServletRequest request, HttpServletResponse response,@RequestHeader HttpHeaders headers) {//        printRequestInfo(fileName, request, headers);String filePath = MediaContentUtil.filePath();try {this.download(fileName, filePath, request, response, headers);} catch (Exception e) {log.error("getCover error, fileName={}", fileName, e);}}// ======= internal =======private static void printRequestInfo(String fileName, HttpServletRequest request, HttpHeaders headers) {String requestUri = request.getRequestURI();String queryString = request.getQueryString();log.debug("file={}, url={}?{}", fileName, requestUri, queryString);log.info("headers={}", headers);}/*** 缓冲区大小 16KB** @see NioUtil#DEFAULT_BUFFER_SIZE* @see NioUtil#DEFAULT_LARGE_BUFFER_SIZE*/
//    private static final int BUFFER_SIZE = NioUtil.DEFAULT_MIDDLE_BUFFER_SIZE;private static final int BUFFER_SIZE = (int) DataSize.ofKilobytes(16L).toBytes();private static final String BYTES_STRING = "bytes";/*** 设置请求响应状态、头信息、内容类型与长度 等。* <pre>* <a href="https://www.rfc-editor.org/rfc/rfc7233">*     HTTP/1.1 Range Requests</a>* 2. Range Units* 4. Responses to a Range Request** <a href="https://www.rfc-editor.org/rfc/rfc2616.html">*     HTTP/1.1</a>* 10.2.7 206 Partial Content* 14.5 Accept-Ranges* 14.13 Content-Length* 14.16 Content-Range* 14.17 Content-Type* 19.5.1 Content-Disposition* 15.5 Content-Disposition Issues** <a href="https://www.rfc-editor.org/rfc/rfc2183">*     Content-Disposition</a>* 2. The Content-Disposition Header Field* 2.1 The Inline Disposition Type* 2.3 The Filename Parameter* </pre>** @param response     请求响应对象* @param fileName     请求的文件名称* @param contentType  内容类型* @param contentRange 内容范围对象*/private static void setResponse(HttpServletResponse response, String fileName, String contentType,ContentRange contentRange) {// http状态码要为206:表示获取部分内容response.setStatus(HttpStatus.PARTIAL_CONTENT.value());// 支持断点续传,获取部分字节内容// Accept-Ranges:bytes,表示支持Range请求response.setHeader(HttpHeaders.ACCEPT_RANGES, BYTES_STRING);// inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"inline;filename=" + MediaContentUtil.encode(fileName));// Content-Range,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]// Content-Range: bytes 0-10/3103,格式为bytes 开始-结束/全部response.setHeader(HttpHeaders.CONTENT_RANGE, toContentRange(contentRange));response.setContentType(contentType);// Content-Length: 11,本次内容的大小response.setContentLengthLong(applyAsContentLength(contentRange));}/*** 组装内容范围的响应头。* <pre>* <a href="https://www.rfc-editor.org/rfc/rfc7233#section-4.2">*     4.2. Content-Range - HTTP/1.1 Range Requests</a>* Content-Range: "bytes" first-byte-pos "-" last-byte-pos  "/" complete-length** For example:* Content-Range: bytes 0-499/1234* </pre>** @param range 内容范围对象* @return 内容范围的响应头*/private static String toContentRange(ContentRange range) {return BYTES_STRING + ' ' + range.start + '-' + range.end + '/' + range.length;}/*** 计算内容完整的长度/总长度。** @param range 内容范围对象* @return 内容完整的长度/总长度*/private static long applyAsContentLength(ContentRange range) {return range.end - range.start + 1;}/*** <a href="https://www.jianshu.com/p/08db5ba3bc95">*     Spring Boot 处理 HTTP Headers</a>*/private void download(String fileName, String path, HttpServletRequest request, HttpServletResponse response,HttpHeaders headers)throws IOException {Path filePath = Paths.get(path + fileName);if (!Files.exists(filePath)) {log.warn("file not exist, filePath={}", filePath);return;}long fileLength = Files.size(filePath);
//        long fileLength2 = filePath.toFile().length() - 1;
//        // fileLength=1184856, fileLength2=1184855
//        log.info("fileLength={}, fileLength2={}", fileLength, fileLength2);// 开始下载位置long firstBytePos;// 结束下载位置long lastBytePos;/** 3.1. Range - HTTP/1.1 Range Requests* https://www.rfc-editor.org/rfc/rfc7233#section-3.1* Range: "bytes" "=" first-byte-pos "-" [ last-byte-pos ]** For example:* bytes=0-* bytes=0-499*/// Range:告知服务端,客户端下载该文件想要从指定的位置开始下载List<HttpRange> httpRanges = headers.getRange();if (CollectionUtils.isEmpty(httpRanges)) {firstBytePos = 0;lastBytePos = fileLength - 1;} else {HttpRange httpRange = httpRanges.get(0);firstBytePos = httpRange.getRangeStart(fileLength);lastBytePos = httpRange.getRangeEnd(fileLength);}ContentRange contentRange = new ContentRange(firstBytePos, lastBytePos, fileLength);String range = request.getHeader(HttpHeaders.RANGE);// httpRanges=[], range=null// httpRanges=[448135688-], range=bytes=448135688-log.debug("httpRanges={}, range={}", httpRanges, range);// 要下载的长度long contentLength = applyAsContentLength(contentRange);log.debug("contentRange={}, contentLength={}", contentRange, contentLength);// 文件类型String contentType = request.getServletContext().getMimeType(fileName);// mimeType=video/mp4, CONTENT_TYPE=nulllog.debug("mimeType={}, CONTENT_TYPE={}", contentType, request.getContentType());setResponse(response, fileName, contentType, contentRange);// 耗时指标统计StopWatch stopWatch = new StopWatch("downloadFile");stopWatch.start(fileName);try {// case-1.参考网上他人的实现
//            if (fileLength >= Integer.MAX_VALUE) {//                copy(filePath, response, contentRange);
//            } else {//                copyByChannelAndBuffer(filePath, response, contentRange);
//            }// case-2.使用现成APIcopyByBio(filePath, response, contentRange);
//            copyByNio(filePath, response, contentRange);// case-3.视频分段渐进式播放
//            if (contentType.startsWith("video")) {//                copyForBufferSize(filePath, response, contentRange);
//            } else {//                // 图片、PDF等文件
//                copyByBio(filePath, response, contentRange);
//            }} finally {stopWatch.stop();log.info("download file, fileName={}, time={} ms", fileName, stopWatch.getTotalTimeMillis());}}/*** <pre>* <a href="https://blog.csdn.net/qq_32099833/article/details/109703883">*     Java后端实现视频分段渐进式播放</a>* 服务端如何将一个大的视频文件做切分,分段响应给客户端,让浏览器可以渐进式地播放。* 文件的断点续传、文件多线程并发下载(迅雷就是这么玩的)等。** <a href="https://blog.csdn.net/qq_32099833/article/details/109630499">*     大文件分片上传前后端实现</a>* </pre>*/private static void copyForBufferSize(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();RandomAccessFile randomAccessFile = null;OutputStream outputStream = null;try {// 随机读文件randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");// 移动访问指针到指定位置randomAccessFile.seek(contentRange.start);// 注意:缓冲区大小 2MB,视频加载正常;1MB时有部分视频加载失败int bufferSize = BUFFER_SIZE;//获取响应的输出流outputStream = new BufferedOutputStream(response.getOutputStream(), bufferSize);// 每次请求只返回1MB的视频流byte[] buffer = new byte[bufferSize];int len = randomAccessFile.read(buffer);//设置此次相应返回的数据长度response.setContentLength(len);// 将这1MB的视频流响应给客户端outputStream.write(buffer, 0, len);log.info("file download complete, fileName={}, contentRange={}", fileName, toContentRange(contentRange));} catch (ClientAbortException | IORuntimeException e) {// 捕获此异常表示用户停止下载log.warn("client stop file download, fileName={}", fileName);} catch (Exception e) {log.error("file download error, fileName={}", fileName, e);} finally {IoUtil.close(outputStream);IoUtil.close(randomAccessFile);}}/*** 拷贝流,拷贝后关闭流。** @param filePath     源文件路径* @param response     请求响应* @param contentRange 内容范围*/private static void copyByBio(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();InputStream inputStream = null;OutputStream outputStream = null;try {RandomAccessFile randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");randomAccessFile.seek(contentRange.start);inputStream = Channels.newInputStream(randomAccessFile.getChannel());outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);StreamProgress streamProgress = new StreamProgressImpl(fileName);long transmitted = IoUtil.copy(inputStream, outputStream, BUFFER_SIZE, streamProgress);log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException | IORuntimeException e) {// 捕获此异常表示用户停止下载log.warn("client stop file download, fileName={}", fileName);} catch (Exception e) {log.error("file download error, fileName={}", fileName, e);} finally {IoUtil.close(outputStream);IoUtil.close(inputStream);}}/*** 拷贝流,拷贝后关闭流。* <pre>* <a href="https://www.cnblogs.com/czwbig/p/10035631.html">*     Java NIO 学习笔记(一)----概述,Channel/Buffer</a>* </pre>** @param filePath     源文件路径* @param response     请求响应* @param contentRange 内容范围*/private static void copyByNio(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();InputStream inputStream = null;OutputStream outputStream = null;try {RandomAccessFile randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");randomAccessFile.seek(contentRange.start);inputStream = Channels.newInputStream(randomAccessFile.getChannel());outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);StreamProgress streamProgress = new StreamProgressImpl(fileName);long transmitted = NioUtil.copyByNIO(inputStream, outputStream, BUFFER_SIZE, streamProgress);log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException | IORuntimeException e) {// 捕获此异常表示用户停止下载log.warn("client stop file download, fileName={}", fileName);} catch (Exception e) {log.error("file download error, fileName={}", fileName, e);} finally {IoUtil.close(outputStream);IoUtil.close(inputStream);}}/*** <pre>* <a href="https://blog.csdn.net/lovequanquqn/article/details/104562945">*     SpringBoot Java实现Http方式分片下载断点续传+实现H5大视频渐进式播放</a>* SpringBoot 实现Http分片下载断点续传,从而实现H5页面的大视频播放问题,实现渐进式播放,每次只播放需要播放的内容就可以了,不需要加载整个文件到内存中。* 二、Http分片下载断点续传实现* 四、缓存文件定时删除任务* </pre>*/private static void copy(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();// 要下载的长度long contentLength = applyAsContentLength(contentRange);BufferedOutputStream outputStream = null;RandomAccessFile randomAccessFile = null;// 已传送数据大小long transmitted = 0;try {randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");randomAccessFile.seek(contentRange.start);outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);// 把数据读取到缓冲区中byte[] buffer = new byte[BUFFER_SIZE];int len = BUFFER_SIZE;//warning:判断是否到了最后不足4096(buffer的length)个byte这个逻辑((transmitted + len) <= contentLength)要放前面//不然会会先读取randomAccessFile,造成后面读取位置出错;while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buffer)) != -1) {outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}//处理不足buffer.length部分if (transmitted < contentLength) {len = randomAccessFile.read(buffer, 0, (int) (contentLength - transmitted));outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException e) {// 捕获此异常表示用户停止下载log.warn("client stop file download, fileName={}, transmitted={}", fileName, transmitted);} catch (Exception e) {log.error("file download error, fileName={}, transmitted={}", fileName, transmitted, e);} finally {IoUtil.close(outputStream);IoUtil.close(randomAccessFile);}}/*** 通过数据传输通道和缓冲区读取文件数据。* <pre>* 当文件长度超过{@link Integer#MAX_VALUE}时,* 使用{@link FileChannel#map(FileChannel.MapMode, long, long)}报如下异常。* java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE*   at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:863)*   at com.example.insurance.controller.ResourceController.download(ResourceController.java:200)* </pre>** @param filePath     源文件路径* @param response     请求响应* @param contentRange 内容范围*/private static void copyByChannelAndBuffer(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();// 要下载的长度long contentLength = applyAsContentLength(contentRange);BufferedOutputStream outputStream = null;FileChannel inChannel = null;// 已传送数据大小long transmitted = 0;long firstBytePos = contentRange.start;long fileLength = contentRange.length;try {inChannel = FileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.WRITE);// 建立直接缓冲区MappedByteBuffer inMap = inChannel.map(FileChannel.MapMode.READ_ONLY, firstBytePos, fileLength);outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);// 把数据读取到缓冲区中byte[] buffer = new byte[BUFFER_SIZE];int len = BUFFER_SIZE;// warning:判断是否到了最后不足4096(buffer的length)个byte这个逻辑((transmitted + len) <= contentLength)要放前面// 不然会会先读取file,造成后面读取位置出错while ((transmitted + len) <= contentLength) {inMap.get(buffer);outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}// 处理不足buffer.length部分if (transmitted < contentLength) {len = (int) (contentLength - transmitted);buffer = new byte[len];inMap.get(buffer);outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException e) {// 捕获此异常表示用户停止下载log.warn("client stop file download, fileName={}, transmitted={}", fileName, transmitted);} catch (Exception e) {log.error("file download error, fileName={}, transmitted={}", fileName, transmitted, e);} finally {IoUtil.close(outputStream);IoUtil.close(inChannel);}}/*** 内容范围对象* <pre>* <a href="https://www.rfc-editor.org/rfc/rfc7233#section-4.2">*     4.2. Content-Range - HTTP/1.1 Range Requests</a>* Content-Range: "bytes" first-byte-pos "-" last-byte-pos  "/" complete-length** For example:* Content-Range: bytes 0-499/1234* </pre>** @see org.apache.catalina.servlets.DefaultServlet.Range*/@AllArgsConstructorprivate static class ContentRange {/*** 第一个字节的位置*/private final long start;/*** 最后一个字节的位置*/private long end;/*** 内容完整的长度/总长度*/private final long length;/*** Validate range.** @return true if the range is valid, otherwise false*/public boolean validate() {if (end >= length) {end = length - 1;}return (start >= 0) && (end >= 0) && (start <= end) && (length > 0);}@Overridepublic String toString() {return "firstBytePos=" + start +", lastBytePos=" + end +", fileLength=" + length;}}/*** 数据流进度条*/private static class StreamProgressImpl implements StreamProgress {private final String fileName;public StreamProgressImpl(String fileName) {this.fileName = fileName;}@Overridepublic void start() {log.info("start progress {}", fileName);}@Overridepublic void progress(long progressSize) {log.debug("progress {}, progressSize={}", fileName, progressSize);}@Overridepublic void finish() {log.info("finish progress {}", fileName);}}
}
package com.example.insurance.common;import java.nio.charset.StandardCharsets;import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.net.URLEncoder;/*** 文件内容辅助方法集*/
public final class MediaContentUtil {public static String filePath() {String osName = System.getProperty("os.name");String filePath = "/data/files/";if (osName.startsWith("Windows")) {filePath = "D:\\" + filePath;}
//        else if (osName.startsWith("Linux")) {//            filePath = MediaContentConstant.FILE_PATH;
//        }else if (osName.startsWith("Mac") || osName.startsWith("Linux")) {filePath = "/home/admin" + filePath;}return filePath;}public static String encode(String fileName) {return URLEncoder.DEFAULT.encode(fileName, StandardCharsets.UTF_8);}public static String decode(String fileName) {return URLDecoder.decode(fileName, StandardCharsets.UTF_8);}
}

Spring-Boot实现HTTP大文件断点续传分片下载-大视频分段渐进式播放相关推荐

  1. spring boot基础教程之文件上传下载

    一文件上传 文件上传主要分以下几个步骤: (1)新建maven java project: (2)在pom.xml加入相应依赖: (3)新建一个文件上传表单页面; (4)编写controller; ( ...

  2. python下载大文件-golang/python 下载大文件时怎样避免oom

    问题场景:高频系统中,agent 会向ATS 服务器发出刷新和预缓存的请求,这里的请求head 里面有GET ,PURGE等,因为一般的预缓存都是小文件,但是某天,突然服务器oom...罪魁祸首发现是 ...

  3. python下载大文件-python requests 下载大文件不完整

    目前我有一个脚本, 从帝联的 CDN 外链获取一下文件作为备份. 使用的是 Python 2.7.10 requests 2.8.1 目前碰到一个情况, 使用 requests 下载大文件的时候会出现 ...

  4. 【vue】 前端 基于 vue-simple-uploader 实现大文件断点续传和分片上传

    文章目录 一.前言 二.后端部分 新建Maven 项目 后端 pom.xml 配置文件 application.yml HttpStatus.java AjaxResult.java CommonCo ...

  5. vue+element-ui大文件的分片上传和断点续传js-spark-md5和browser-md5-file

    注意:以下共两份代码片段,第一份为原博主链接代码,第二份自己写的整体代码(比较乱) 1.参考 https://www.cnblogs.com/kelelipeng/p/10158599.html (j ...

  6. 精品分享:基于 SpringBoot + Vue 开发的云盘系统(含大文件断点续传剖析)

    引言 作为开发人员,我们经常需要存储和下载文件,为了使用方便,通常都会将文件存储在云端,市面上使用率最高的云端存储莫过于百度网盘了,但使用别人的东西难免会受到各种各样的限制,必须花钱才会享受到更好的服 ...

  7. html5解决大文件断点续传6,解决html5大文件断点续传

    一.概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载.在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了.一般断点下载时才用到Range和Content- ...

  8. javascript 大文件下载,分片下载,断点续传

    javascript 大文件下载,分片下载,断点续传 文章目录 javascript 大文件下载,分片下载,断点续传 1:获取文件大小: 2:切片下载 3:合并数据 4:下载到本地 5:成功 6:完整 ...

  9. 文件上传控件-如何上传文件-大文件断点续传

    需求: 项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在20G内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以20G来进行限制. PC端全平台支持,要求支持Window ...

  10. node 生产的env文件怎么注入_前端各种文件上传攻略,从小图片到大文件断点续传...

    写在前面 今年国庆假期终于可以憋在家里了不用出门了,不用出去看后脑了,真的是一种享受.这么好的光阴怎么浪费,睡觉.吃饭.打豆豆这怎么可能(耍多了也烦),完全不符合我们程序员的作风,赶紧起来把文章写完. ...

最新文章

  1. 隔空投送所有人安全吗_Find X2将采用2K+120Hz屏幕,支持65W;安卓将拥有“隔空投送”;“杀毒霸主”Avast被曝偷卖4.35亿用户数据;...
  2. http://www.cnblogs.com/QJohnson/archive/2011/06/24/2089414.html
  3. api 规则定义_API有规则,而且功能强大
  4. java byte num =1 3_java中把byte[]{1,2,3}通过怎样的转换,可以让其最终在TextView中显示为123...
  5. oracle中如何创建表的自增ID(通过序列)
  6. Nginx 附录A 编码风格 和 附录B 常用API
  7. 如何安装html网站模板,网站模板安装说明
  8. @程序员,React 使用如何避坑?
  9. Codeforces Round #401 (Div. 2) E. Hanoi Factory 栈
  10. SnakeWords开发--Android 2.2
  11. 奥维怎么记录沿线轨迹_奥维地图怎么将已有线路画为轨迹
  12. 蓝桥杯矩阵求和_刷蓝桥杯官网习题,准备蓝桥杯的小伙伴,一起来交流吧(✪ω✪)。(2月27日更新)...
  13. 【腾讯地图】纯手写微信定位考勤小程序,内附完整源码!
  14. STK11.2 计算卫星A关于卫星B的相对位置 (三维和二维)
  15. 2.IDEA修改主题
  16. 2012-7-05可樂词汇积累#9312;
  17. java集合之TreeMap 构造器 方法 比较器
  18. sklearn机器学习之svm案例(预测明天是否会下雨)
  19. 除留余数法构造哈希函数并用链地址法处理哈希冲突【C++实现】
  20. 图谱实战 | 丁香园医疗领域图谱的构建与应用

热门文章

  1. c语言数字和字母运算,计算器中的字母CE、C、MR、MC、MS、M+、M-等等各是什么意思?让我来告诉你吧!...
  2. 语言表达能力强的人真的就情商高吗?
  3. html文档半结构化数据,半结构化数据
  4. C/C++黑魔法-另类switch
  5. python调整dicom窗宽窗位_【基础篇】kaggle || RSNA脑溢血金牌案例技术分享!如何使用dicom格式的数据的?...
  6. 中文手机评论情感分析系列(二)
  7. 计算机网络带宽是什么意思,带宽是什么有什么意义
  8. java xlsm_poi读取excel(xls和xlsx,xlsm)给定单元格内容
  9. setAttribute(Qt::WA_DeleteOnClose) 导致程序崩溃问题
  10. 网站设计65条原则 作者:小柯