万级数据优化EasyExcel+mybatis流式查询导出封装

文章目录

    • 前言.万级数据优化
    • 一. 直接上流式查询封装工具代码
    • 二. 传统分页导出查询
    • 三. 流式查询概念
    • 游标查询


前言.万级数据优化

我们不妨先给大家讲一个概念,利用此概念我们正好给大家介绍一个数据库优化的小技巧: 需求如下:将一个地市表的数据导出70万条。

在这里插入图片描述

如果你不假思索,直接一条sql语句搞上去,直接就会内存溢出,因为mysql会将结果记录统一查询出来然后返还给内存:那内存可能直接OOM!

@Test
public void testQuery1()  {

    // 1、定义资源
    Connection connection = null;
    ResultSet resultSet = null;
    PreparedStatement statement = null;
    String sql = "select * from user";
    try {
        // 获取连接
        connection = DBUtil.getConnection();
        // 获取使用预编译的statement
        statement = connection.prepareStatement(sql);

        long start = System.currentTimeMillis();
        resultSet = statement.executeQuery();
        while (resultSet.next()){
            System.out.println("name---->" + resultSet.getString("nick_name") );
        }
        long end = System.currentTimeMillis();
        System.out.println(end -start);
    } catch (SQLException e){
        e.printStackTrace();
    } finally {
        // 关闭资源
        DBUtil.closeAll(connection,statement,resultSet);
    }
}

所以我们通常有如下几种解决方案:

一. 直接上流式查询封装工具代码

使用2核4G云服务器 下载速度在40-50s之间波动
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

表格美化 CustomCellWeightStrategy.class
/**
 * @author YuanJie
 * @projectName vector-server
 * @package com.vector.common.utils.easyexcel
 * @className com.vector.common.utils.easyexcel.CustomCellWeightStrategy
 * @copyright Copyright 2020 vector, Inc All rights reserved.
 * @date 2023/8/28 17:58
 */
public class CustomCellWeightStrategy extends AbstractColumnWidthStyleStrategy {
    private final Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>();

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (needSetWidth) {
            Map<Integer, Integer> maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<>());

            int columnWidth = this.dataLength(cellDataList, cell, isHead)+8;
            if (columnWidth >= 0) {
                if (columnWidth > 254) {
                    columnWidth = 254;
                }

                Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                    Sheet sheet = writeSheetHolder.getSheet();
                    sheet.setColumnWidth(cell.getColumnIndex(), columnWidth * 200);
                }

                //设置单元格类型
                cell.setCellType(CellType.STRING);
                // 数据总长度
                int length = cell.getStringCellValue().length();
                // 换行数
                int rows = cell.getStringCellValue().split("\n").length;
                // 默认一行高为20
                cell.getRow().setHeightInPoints(rows * 20);
            }
        }
    }


    /**
     * 计算长度
     *
     * @param cellDataList
     * @param cell
     * @param isHead
     * @return
     */
    private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData<?> cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        // 换行符(数据需要提前解析好)
                        int index = cellData.getStringValue().indexOf("\n");
                        return index != -1 ?
                                cellData.getStringValue().substring(0, index).getBytes().length + 1 : cellData.getStringValue().getBytes().length + 1;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}
封装工具类EasyExcelUtil.class
/**
 * @author YuanJie
 * @projectName vector-server
 * @package com.vector.common.utils
 * @className com.vector.common.utils.easyexcel.EasyExcelUtil
 * @copyright Copyright 2020 vector, Inc All rights reserved.
 * @date 2023/8/26 1:17
 */
@Slf4j
public class EasyExcelUtil {

    private static final String DATE_FORMAT = "yyyy-MM-dd";


    /**
     * 设置批量存储最大值,也影响sheet页数
     */
    private static final Integer MAX_SHEET_DATA = 50000;
    /**
     * 设置内存最大值
     */
    private static final Integer MAX_MEMORY_DATA = 1000;

    /**
     * 使用EasyExcel生成Excel  xls
     *
     * @param response      响应对象
     * @param fileNameParam 文件名
     * @param sheetName     表格名
     * @param clazz         导出实体类
     * @param t             查库入参
     * @param func          流式查询方法 Cursor<ResultVo> listOrders(@Param("userName") String userName);
     */
    public static <T> void writeExcelXls(HttpServletResponse response, String fileNameParam,
                                         String sheetName, Class<?> clazz, T t,
                                         Function<T, Cursor<?>> func) throws Exception {
        streamExportExcel(response, fileNameParam, sheetName, clazz, ExcelTypeEnum.XLS.getValue(), t, func);
    }

    /**
     * 使用EasyExcel生成Excel  xlsx
     *
     * @param response      响应对象
     * @param fileNameParam 文件名
     * @param sheetName     表格名
     * @param clazz         导出实体类
     * @param t             查库入参
     * @param func          流式查询方法 Cursor<ResultVo> listOrders(@Param("userName") String userName);
     */
    public static <T> void writeExcelXlsx(HttpServletResponse response, String fileNameParam,
                                          String sheetName, Class<?> clazz, T t,
                                          Function<T, Cursor<?>> func) throws Exception {
        streamExportExcel(response, fileNameParam, sheetName, clazz, ExcelTypeEnum.XLSX.getValue(), t, func);
    }

    /**
     * 流式导出 Excel
     *
     * @param response      响应对象
     * @param fileNameParam 文件名
     * @param sheetName     表格名
     * @param clazz         导出实体类
     * @param excelType     导出类型
     * @param t             查库入参
     * @param func          流式查询方法 Cursor<ResultVo> listOrders(@Param("userName") String userName);
     * @throws Exception 异常
     */
    private static <T> void streamExportExcel(HttpServletResponse response, String fileNameParam,
                                              String sheetName, Class<?> clazz, String excelType,
                                              T t, Function<T, Cursor<?>> func) throws Exception {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        String fileName = fileNameParam + dateTimeFormatter.format(LocalDateTime.now()) + excelType;
        ExcelWriter excelWriter = EasyExcel
                .write(getOutputStream(fileName, response, excelType), clazz)
                .registerWriteHandler(new CustomCellWeightStrategy())
                .build();

        // 内容样式
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 垂直居中
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置自动换行,前提内容中需要加「\n」才有效
        contentWriteCellStyle.setWrapped(true);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        HorizontalCellStyleStrategy horizontalCellStyleStrategy =
                new HorizontalCellStyleStrategy(null, contentWriteCellStyle);
        Cursor<?> cursor;
        List<Object> list = new ArrayList<>();
        int page = 0;
        WriteSheet writeSheet = EasyExcel
                .writerSheet(++page, sheetName + page)
                .registerWriteHandler(horizontalCellStyleStrategy)
                .build();
        // 流式数据库查询
        cursor = func.apply(t);
        int count = 0;
        try {
            for (Object o : cursor) {
                list.add(o);
                if(list.size() == MAX_MEMORY_DATA){
                    count += list.size();
                    excelWriter.write(list, writeSheet);
                    list.clear();
                    // 每个sheet页最大存储MAX_SHEET_DATA条数据
                    if (count >= MAX_SHEET_DATA) {
                        writeSheet = EasyExcel
                                .writerSheet(++page, sheetName + page)
                                .registerWriteHandler(horizontalCellStyleStrategy)
                                .build();
                    }
                }
            }
            // 处理最后不足MAX_SHEET_DATA的数据
            if (list.size() > 0) {
                writeSheet = EasyExcel
                        .writerSheet(++page, sheetName + page)
                        .registerWriteHandler(horizontalCellStyleStrategy)
                        .build();
                excelWriter.write(list, writeSheet);
                list.clear();
            }
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            String json = JacksonInstance.toJson(R.errorResult(EnumHttpCode.SYSTEM_ERROR, "下载文件失败" + e.getMessage()));
            response.getWriter().println(json);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 导出文件时为Writer生成OutputStream
     *
     * @param finalName 文件名
     * @param response 响应对象
     * @param excelType 导出文件类型
     * @return OutputStream
     */
    private static OutputStream getOutputStream(String finalName, HttpServletResponse response, String excelType) throws Exception {
        response.reset();
        finalName = URLEncoder.encode(finalName, StandardCharsets.UTF_8);
        if (ExcelTypeEnum.XLS.getValue().equals(excelType)) {
            response.setContentType("application/vnd.ms-excel");
        } else if (ExcelTypeEnum.XLSX.getValue().equals(excelType)) {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        }
        response.setCharacterEncoding("utf8");
        response.setHeader("Content-Disposition", "attachment; filename=" + finalName);
        response.setHeader("Pragma", "public");
        response.setHeader("Cache-Control", "no-store");
        response.addHeader("Cache-Control", "max-age=0");
        return response.getOutputStream();
    }
}
导出实体 Dto.class
@Data
public class Dto {
    @NumberFormat("#")
    @ExcelProperty(value = "地市编码", index = 0)
    Long code;
    @ExcelProperty(value = "地市名称", index = 1)
    String name;
    @NumberFormat("#")
    @ExcelProperty(value = "地市级别", index = 2)
    Integer level;
    @NumberFormat("#")
    @ExcelProperty(value = "地市父编码", index = 3)
    Long pcode;
    @NumberFormat("#")
    @ExcelProperty(value = "地市父名称", index = 4)
    Integer category;
}
测试用例 MeTestController.class
    @Resource
    private ExportMapper exportMapper;

    @Resource
    private HttpServletRequest request;
    @Resource
    private HttpServletResponse response;
    @GetMapping("/export")
    @Transactional
    public void export() throws Exception {
        Long params = 110101001000L;
        EasyExcelUtil.writeExcelXlsx(
                response,
                "地市信息",
                "地市区域",
                Dto.class,
                params,
                param -> exportMapper.export(null));
    }
测试Dao ExportMapper.class
public interface ExportMapper {
    @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
    @ResultType(Dto.class)
    @Select("select * from area_code_2023")
    Cursor<Dto> export(@Param("params") Long params);
}


二. 传统分页导出查询

大表的深度分页性能很差,也受制于表设计的影响
@Test
public void testQuery2()  {

    // 1、定义资源
    Connection connection = null;
    ResultSet resultSet = null;
    PreparedStatement statement = null;
    String sql = "select * from user limit ?,?";
    try {
        // 获取连接
        connection = DBUtil.getConnection();
        // 获取使用预编译的statement
        statement = connection.prepareStatement(sql);
        // 获取结果集
        long start = System.currentTimeMillis();
        long begin = 0L, offset = 10000L;
        while (true){
            statement.setLong(1,begin);
            statement.setLong(2,offset);
            begin += offset;
            resultSet = statement.executeQuery();
            boolean flag = resultSet.next();
            if(!flag) break;
            while (flag){
                System.out.println("name---->" + resultSet.getString("nick_name") );
                flag = resultSet.next();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(end -start);
    } catch (SQLException e){
        e.printStackTrace();
    } finally {
        // 关闭资源
        DBUtil.closeAll(connection,statement,resultSet);
    }
}

三. 流式查询概念

采用传统的Stream流式思想,将直接提供数据替换成提供获取数据的管道,客户端读取数据时直接从管道中遍历获取;整个读取的过程需要客户端保持和服务端的连接,也很好理解,它实际是一个管道,管道得通着才能取数据。

采用传统的Stream流式思想,将直接提供数据替换成提供获取数据的管道,客户端读取数据时直接从管道中遍历获取;整个读取的过程需要客户端保持和服务端的连接,也很好理解,它实际是一个管道,管道得通着才能取数据。

流式查询有两种使用方式,一种是用Cursor作为返回值,对数据进行遍历操作;一种是不设置返回值,在入参中传入一个ResultHandler作为回调处理数据。本文将基于Mybatis具体介绍使用方法。 这两种返回值的使用方式是相似的,唯一区别就是返回值不同。Mybatis查询有两种方式,一种是基于注解加在Mapper接口上方,一种是写在xml文件中,主要需要设置以下几个属性:
ResultSetType结果集读取方式
FetchSizeMySQL服务端单次发送至客户端的数据条数
ResultType这个眼熟吧,设置返回实体类映射

ResultSetType有4种可选项

DEFAULT(-1),
FORWARD_ONLY(1003),
SCROLL_INSENSITIVE(1004),
SCROLL_SENSITIVE(1005);

FORWARD_ONLY顾名思义只能向前,即数据只能向前读取,是不是就类似一个流水的管道,读一条就相当于水流过去一些。也是我们需要选用的。
SCROLL_INSENSITIVE不敏感滚动,和下面那个差不多,都是可以向后读或向前读;这意味着已读取过的数据不能丢掉,要继续保存在内存中,因为有可能会回去再次读取他们。
SCROLL_SENSITIVE敏感滚动,和上面那个差不多。
这么一比较就看得出来,当选的一定是FORWARD_ONLY,我们亟需解决的就是大数据量对内存的影响,再用后面两个还是会放在内存中。

FetchSize这个概念在许多服务中都有提及,例如RabbitMQ中是消费者取过来预处理的消息数量,但在MySQL中完全不是一个概念。MySQL的数据传输是基于C/S的阻塞机制,即Client设置FetchSize = 1000,而Server查出来10000条数据,按照常理应该是Server智能地使用分页策略1000条1000条取;实际不是,Server查出来多少就是多少,他会放在自己特定的内存空间内,只是会根据FetchSize的大小一点一点传送给Client——利用C/S的通讯阻塞,发1000条、堵一下、发1000条、堵一下……。

JDBC官方给出的答案是设置为“Integer.MIN_VALUE”,具体原因不清楚,但我猜是为了和游标查询区分开,因为一会你会发现流式查询和游标查询唯一的区别就是FetchSize的大小。

注解式

@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(ResultVo.class)
@Select("SELECT *, 0 orderType FROM `table`\n" +
        "        WHERE username = #{userName}")
Cursor<ResultVo> listOrders(@Param("userName") String userName);
 
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(ResultVo.class)
@Select("SELECT *, 0 orderType FROM `table`\n" +
        "        WHERE username = #{userName}")
void listOrders2(@Param("userName") String userName, ResultHandler<ResultVo> handler);

使用Mybatis的注解,在 @Options 中指定查询配置参数,在@ResultType中指定返回值类型 ,在 @Select中指定查询语句。最后用Cursor接收返回值,Cursor是可遍历的,所以直接Foreach遍历即可;或者返回void 用ResultHandler处理数据回调,在调用方式时传入new ResultHandler并写明处理逻辑。

xml式

<select id="listOrders" resultType="com.vo.ResultVo" resultSetType="FORWARD_ONLY" fetchSize="Integer.MIN_VALUE">
    SELECT *, 1 stuffCount, 1 orderType FROM `table`
    WHERE username = #{userName}
</select>

需要注意的是,不可以注解 + xml混合使用,比如注解指定fetchSize,xml只写查询语句,这种只有xml语句会生效!!!要不全用注解,要不全用xml!!!

流式查询由于需要保持客户端与服务端的连接,而一般查询提交完连接就会关闭;因此我们需要保持事务开启,否则会报“A Cursor is already closed.”,即Cursor已经关闭,没法再读取了。最简单的方法就是在方法上加@Transactional,在查询完毕以前事务会一直持有这个数据库连接,但我们在使用完毕后也要自行关闭连接,显式调用Cursor.close(),或者用try with resource语句。

游标查询和流式查询的区分是fetchSize = Integer.MIN_VALUE

Cursor 还提供了三个方法:

  1. isOpen():用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据;
  2. isConsumed():用于判断查询结果是否全部取完;
  3. getCurrentIndex():返回已经获取了多少条数据。
try(Cursor cursor = OrderMapper.listOrders()) {
  cursor.forEach(rowObject -> {
      // ...
  });
}

  OrderMapper.listOrders2(queryWrapper,resultContext -> {
    ResultVo result = resultContext.getResultObject();
   //这边循环调用就可以实现业务了

}

游标查询

和流式查询类似fetchSize不设置为MIN_VALUE即可
JDBC查询默认是不支持FetchSize属性的,需要在JDBC连接URL后面加上**“useCursorFetch=true”。**

useCursorFetch=true 是针对 MySQL 数据库的 JDBC 连接参数,用于启用服务器端游标获取数据。在 MyBatis 中,当使用流式查询(例如:分页查询、结果集处理和使用游标等)时,这个配置可以帮助逐行从服务器检索数据,而不是一次性将所有数据加载到内存中,从而降低内存占用。

当使用 MySQL 数据库时,在 JDBC 连接字符串中加入 useCursorFetch=true,并结合设置合适的 fetchSize,可以避免因一次性加载过多数据导致的内存溢出问题。注意,此配置仅对 MySQL 数据库有效。 如果不设置 useCursorFetch=true 这个配置,仅使用之前提到的那些配置(如设置 defaultFetchSize、分页查询、结果集处理和使用游标等),在大多数情况下,这些配置仍然可以有效地避免查询导致的内存溢出。

但需要注意的是,对于 MySQL 数据库,如果不启用服务器端游标获取数据,这可能会影响到流式查询的效果。因为在默认情况下,MySQL JDBC 驱动会一次性将所有数据加载到内存中。此时,即使使用了其他配置,也可能无法达到预期的内存优化效果。

总的来说,在使用 MySQL 数据库时,推荐在 JDBC 连接字符串中加入 useCursorFetch=true 配置,以更好地支持流式查询和降低内存占用。在其他数据库中,可以根据实际需求和场景选择合适的配置和策略来避免查询导致的内存溢出。

还要知道如何判断自己是否使用了流式查询或游标查询,下面是几个数据集的对应关系

普通分页ResultsetRowsStaticRowDataStatic
查询方式结果集类型行数据类型
流式查询ResultsetRowsStreamingRowDataDynamic
游标查询ResultsetRowsCursorRowDataCursor

这3种查询方式,常规非大数据模式下普通查询最快,其次是流式查询,最次是游标查询.

主要是由于游标查询需要和数据库进行多次网络交互,Client处理完这部分后再拉取下一部分数据,因此会比较慢。但是流式查询又会长时间占用同一个数据库连接,因此要取舍一下是能接受连接一直持有但是可能会堵住导致响应慢,还是可能占用较多连接数但单次响应快。当通过流式查询获取一个ResultSet后,在你通过next迭代出所有元素之前或者调用close关闭它之前,你不能使用同一个数据库连接去发起另外一个查询,否者抛出异常(第一次调用的正常,第二次的抛出异常)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/94604.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CSS中如何实现文字阴影效果(text-shadow)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 实现思路⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前…

视频汇聚/视频云存储/视频监控管理平台EasyCVR安全检查的相关问题及解决方法

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

解读GIS软件:从ArcGIS到山海鲸可视化的全方位介绍

在现代社会&#xff0c;地理信息系统&#xff08;GIS&#xff09;的应用已经渗透到了各个领域&#xff0c;为我们提供了丰富的地理数据分析和可视化工具。下面介绍几款常见的GIS工具软件&#xff0c;一起来了解它们的特点和优势。 1. ArcGIS: ArcGIS由Esri公司开发&#xff0c;…

php环境搭建步骤(与资源配套使用版)

1.将phpEnv.zip下载到D盘下 2.解压到当前文件夹 3.找到Apache24下的bin目录&#xff0c;执行cmd操作&#xff0c;回车。 4.在cmd中执行代码 Httpd -k install -n “Apache24” 4.使用winR键打开运行&#xff0c;输入services.msc &#xff0c;回车&#xff0c;进入服务 …

ipad有必要用手写笔吗?开学季实惠的电容笔推荐

iPad平板的机型经过了一次又一次的升级&#xff0c;增加了更多的功能&#xff0c;如今已有了与笔记本电脑匹敌的能力。而到了如今&#xff0c;科技的发展&#xff0c;iPad也从一个娱乐工具&#xff0c;变成了一个集学习、画画、办公于一体的强大工具。为了提高生产效率&#xf…

PHP教学资源管理系统Dreamweaver开发mysql数据库web结构php编程计算机网页

一、源码特点 PHP 教学资源管理系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 源码 https://download.csdn.net/download/qq_41221322/88260480 论文 https://downl…

入海排污口水质自动监测系统,助力把好入河入海“闸门”

随着经济社会的不断发展&#xff0c;污水的排放强度不断加大&#xff0c;大量的污水排入河流、湖泊和海洋中&#xff0c;造成了水体污染&#xff0c;严重影响着我国的用水安全、公众健康、经济发展与社会稳定。入河入海排污口是污染物进入河流和海洋的最后关口&#xff0c;也是…

im6ull-uboot(2021.07)移植(一)

文章目录 声明1 获取源码1.1 从u-boot官网获取1.2 从芯片厂商获取1.3 从开发板厂商获取 2 修改顶层Makefile3 xxx_defconfig配置文件3.1 拷贝生成自己的配置文件3.2 修改defconfig文件3.2.1 查看defconfig文件3.2.2 修改defconfig文件 3.3 添加其他配置文件3.3.1 添加配置头文件…

计算机视觉与人工智能在医美人脸皮肤诊断方面的应用

一、人脸皮肤诊断方法 近年来&#xff0c;随着计算机技术和人工智能的不断发展&#xff0c;中医领域开始逐渐探索利用这些先进技术来辅助面诊和诊断。在皮肤望诊方面&#xff0c;也出现了一些现代研究&#xff0c;尝试通过图像分析技术和人工智能算法来客观化地获取皮肤相关的…

微软 Visual Studio 现已内置 Markdown 编辑器,可直接修改预览 .md 文件

Visual Studio Code V1.66.0 中文版 大小&#xff1a;75.30 MB类别&#xff1a;文字处理 本地下载 Markdown 是一种轻量级标记语言&#xff0c;当开发者想要格式化代码但又不想牺牲易读性时&#xff0c;Markdown 是一个很好的解决方案&#xff0c;比如 GitHub 就使用 Markdo…

7个用于机器学习和数据科学的基本 Python 库

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建3D应用场景 这篇文章针对的是刚开始使用Python进行AI的人&#xff0c;以及那些有经验的人&#xff0c;但对下一步要学习什么有疑问的人。我们将不时花点时间向初学者介绍基本术语和概念。如果您已经熟悉它们&#xff0c;我们鼓…

windows服务器查看网络带宽

windows服务器查看网络带宽&#xff1f; 鼠标右键单击win标志&#xff0c;进入计算机管理 另外一个方法&#xff1a;

【golang】15、cobra cli 命令行库

Cobra 是 golang 最流行的命令行库&#xff0c;文档见 一、脚手架 mkdir pt && cd pt && go mod init cobra-cli init # 在项目下运行即可生成脚手架# tree . ├── LICENSE ├── cmd # 生成了cmd目录 │ └── root.go # 生成了root.go, 其中定义了ro…

他们朝我扔泥巴(scratch)

前言 纯~~~属~~~虚~~~构~~~&#xff08;同学看完短视频要我做&#xff0c;蟹蟹你&#xff09; 用scratch做的&#xff0c;幼稚得嘞(&#xffe3;_&#xffe3;|||)呵呵&#xff08;强颜欢笑&#xff09; 完成视频 视频试了好久&#xff0c;就是传不上来&#xff0c;私信我加我…

Flutter问题记录 - Unable to find bundled Java version

新版本的Android Studio真的移除了JRE&#xff0c;jre目录找不到&#xff0c;怪不得报错了&#xff0c;不过多了一个jbr目录&#xff0c;找了个以前的Android Studio版本对比 搜了一下jbr&#xff08;JetBrains Runtime&#xff09;&#xff0c;原来IDEA老早就开始用了&#xf…

一文速学-让神经网络不再神秘,一天速学神经网络基础(一)

前言 思索了很久到底要不要出深度学习内容&#xff0c;毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新&#xff0c;很多坑都没有填满&#xff0c;而且现在深度学习的文章和学习课程都十分的多&#xff0c;我考虑了很久决定还是得出神经网络系列文章&#xff0c;…

购买腾讯云服务器搭建网站全流程_新手建站

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网分享使用腾讯云服务器建站教程&#xff0c;新手站长搭…

SpringMVC 第二天

第 1 章 ModelAttribute 和 SessionAttribute[ 应 用 ] 1.1ModelAttribute 1.1.1 使用说明 作用&#xff1a; 该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。 出现在方法上&#xff0c;表示当前方法会在控制器的方法执行之前&#xff0c;先执行…

【Azure】Virtual Hub vWAN

虚拟 WAN 文档 Azure 虚拟 WAN 是一个网络服务&#xff0c;其中整合了多种网络、安全和路由功能&#xff0c;提供单一操作界面。 我们主要讨论两种连接情况&#xff1a; 通过一个 vWAN 来连接不通的 vNET 和本地网络。以下是一个扩展的拓扑 结合 vhub&#xff0c;可以把两个中…

【Centos8配置节点免密登陆】

登录Centos8 配置免密登录 为什么需要配置免密登录&#xff0c;玩大数据&#xff0c;玩集群的朋友们&#xff0c;都需要使用RPC通讯&#xff0c;完成集群命令同步&#xff0c;数据操作通讯。要实现RPC通讯&#xff0c;就需要配置节点之间的免密登录。 # 配置登录秘钥 ssh-key…