使用双异步后,从 191s 优化到 2s

使用双异步后,从 191s 优化到 2s

一般我会这样做:

  • 通过POI读取需要导入的Excel;

  • 以文件名为表名、列头为列名、并将数据拼接成sql;

  • 通过JDBC或mybatis插入数据库;

  • 在这里插入图片描述

  • 操作起来,如果文件比较多,数据量都很大的时候,会非常慢。

  • 访问之后,感觉没什么反应,实际上已经在读取 + 入库了,只是比较慢而已。

  • 读取一个10万行的Excel,居然用了191s,我还以为它卡死了呢!

  • private void readXls(String filePath, String filename) throws Exception {
        @SuppressWarnings("resource")
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new FileInputStream(filePath));
        // 读取第一个工作表
        XSSFSheet sheet = xssfWorkbook.getSheetAt(0);
        // 总行数
        int maxRow = sheet.getLastRowNum();
    
        StringBuilder insertBuilder = new StringBuilder();
    
        insertBuilder.append("insert into ").append(filename).append(" ( UUID,");
    
        XSSFRow row = sheet.getRow(0);
        for (int i = 0; i < row.getPhysicalNumberOfCells(); i++) {
            insertBuilder.append(row.getCell(i)).append(",");
        }
    
        insertBuilder.deleteCharAt(insertBuilder.length() - 1);
        insertBuilder.append(" ) values ( ");
    
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 1; i <= maxRow; i++) {
            XSSFRow xssfRow = sheet.getRow(i);
            String id = "";
            String name = "";
            for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {
                if (j == 0) {
                    id = xssfRow.getCell(j) + "";
                } else if (j == 1) {
                    name = xssfRow.getCell(j) + "";
                }
            }
    
            boolean flag = isExisted(id, name);
            if (!flag) {
                stringBuilder.append(insertBuilder);
                stringBuilder.append('\'').append(uuid()).append('\'').append(",");
                for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {
                    stringBuilder.append('\'').append(value).append('\'').append(",");
                }
                stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                stringBuilder.append(" )").append("\n");
            }
        }
    
        List<String> collect = Arrays.stream(stringBuilder.toString().split("\n")).collect(Collectors.toList());
        int sum = JdbcUtil.executeDML(collect);
    }
    
    private static boolean isExisted(String id, String name) {
        String sql = "select count(1) as num from " + static_TABLE + " where ID = '" + id + "' and NAME = '" + name + "'";
        String num = JdbcUtil.executeSelect(sql, "num");
        return Integer.valueOf(num) > 0;
    }
    
    private static String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }
    

谁写的?拖出去,斩了!

  • 先查询全部数据,缓存到map中,插入前再进行判断,速度快了很多。

  • 如果单个Excel文件过大,可以采用 异步 + 多线程 读取若干行,分批入库。

  • 在这里插入图片描述

  • 如果文件数量过多,可以采一个Excel一个异步,形成完美的双异步读取插入。

  • 在这里插入图片描述

  • 使用双异步后,从 191s 优化到 2s,你敢信?

  • 下面贴出异步读取Excel文件、并分批读取大Excel文件的关键代码。

readExcelCacheAsync控制类

  • @RequestMapping(value = "/readExcelCacheAsync", method = RequestMethod.POST)
    @ResponseBody
    public String readExcelCacheAsync() {
        String path = "G:\\测试\\data\\";
        try {
    		// 在读取Excel之前,缓存所有数据
            USER_INFO_SET = getUserInfo();
    
            File file = new File(path);
            String[] xlsxArr = file.list();
            for (int i = 0; i < xlsxArr.length; i++) {
                File fileTemp = new File(path + "\\" + xlsxArr[i]);
                String filename = fileTemp.getName().replace(".xlsx", "");
                readExcelCacheAsyncService.readXls(path + filename + ".xlsx", filename);
            }
        } catch (Exception e) {
            logger.error("|#ReadDBCsv|#异常: ", e);
            return "error";
        }
        return "success";
    }
    

分批读取超大Excel文件

  • @Async("async-executor")
    public void readXls(String filePath, String filename) throws Exception {
        @SuppressWarnings("resource")
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new FileInputStream(filePath));
        // 读取第一个工作表
        XSSFSheet sheet = xssfWorkbook.getSheetAt(0);
        // 总行数
        int maxRow = sheet.getLastRowNum();
        logger.info(filename + ".xlsx,一共" + maxRow + "行数据!");
        StringBuilder insertBuilder = new StringBuilder();
    
        insertBuilder.append("insert into ").append(filename).append(" ( UUID,");
    
        XSSFRow row = sheet.getRow(0);
        for (int i = 0; i < row.getPhysicalNumberOfCells(); i++) {
            insertBuilder.append(row.getCell(i)).append(",");
        }
    
        insertBuilder.deleteCharAt(insertBuilder.length() - 1);
        insertBuilder.append(" ) values ( ");
    
        int times = maxRow / STEP + 1;
        //logger.info("将" + maxRow + "行数据分" + times + "次插入数据库!");
        for (int time = 0; time < times; time++) {
            int start = STEP * time + 1;
            int end = STEP * time + STEP;
    
            if (time == times - 1) {
                end = maxRow;
            }
    
            if(end + 1 - start > 0){
                //logger.info("第" + (time + 1) + "次插入数据库!" + "准备插入" + (end + 1 - start) + "条数据!");
                //readExcelDataAsyncService.readXlsCacheAsync(sheet, row, start, end, insertBuilder);
                readExcelDataAsyncService.readXlsCacheAsyncMybatis(sheet, row, start, end, insertBuilder);
            }
        }
    }
    

异步批量入库

  • @Async("async-executor")
    public void readXlsCacheAsync(XSSFSheet sheet, XSSFRow row, int start, int end, StringBuilder insertBuilder) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = start; i <= end; i++) {
            XSSFRow xssfRow = sheet.getRow(i);
            String id = "";
            String name = "";
            for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {
                if (j == 0) {
                    id = xssfRow.getCell(j) + "";
                } else if (j == 1) {
                    name = xssfRow.getCell(j) + "";
                }
            }
    
    		// 先在读取Excel之前,缓存所有数据,再做判断
            boolean flag = isExisted(id, name);
            if (!flag) {
                stringBuilder.append(insertBuilder);
                stringBuilder.append('\'').append(uuid()).append('\'').append(",");
                for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {
                    stringBuilder.append('\'').append(value).append('\'').append(",");
                }
                stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                stringBuilder.append(" )").append("\n");
            }
        }
    
        List<String> collect = Arrays.stream(stringBuilder.toString().split("\n")).collect(Collectors.toList());
        if (collect != null && collect.size() > 0) {
            int sum = JdbcUtil.executeDML(collect);
        }
    }
    
    private boolean isExisted(String id, String name) {
        return ReadExcelCacheAsyncController.USER_INFO_SET.contains(id + "," + name);
    }
    

异步线程池工具类

  • @Async的作用就是异步处理任务。
    1. 在方法上添加@Async,表示此方法是异步方法;
    2. 在类上添加@Async,表示类中的所有方法都是异步方法;
    3. 使用此注解的类,必须是Spring管理的类;
    4. 需要在启动类或配置类中加入@EnableAsync注解,@Async才会生效;

    在使用@Async时,如果不指定线程池的名称,也就是不自定义线程池,@Async是有默认线程池的,使用的是Spring默认的线程池SimpleAsyncTaskExecutor。

  • 默认线程池的默认配置如下:
    1. 默认核心线程数:8;
    2. 最大线程数:Integet.MAX_VALUE;
    3. 队列使用LinkedBlockingQueue;
    4. 容量是:Integet.MAX_VALUE;
    5. 空闲线程保留时间:60s;
    6. 线程池拒绝策略:AbortPolicy;

    从最大线程数可以看出,在并发情况下,会无限制的创建线程,我勒个吗啊。

  • 也可以通过yml重新配置:
    • spring:
        task:
          execution:
            pool:
              max-size: 10
              core-size: 5
              keep-alive: 3s
              queue-capacity: 1000
              thread-name-prefix: my-executor
      
    • 也可以自定义线程池,下面通过简单的代码来实现以下@Async自定义线程池。

    • @EnableAsync// 支持异步操作
      @Configuration
      public class AsyncTaskConfig {
      
          /**
           * com.google.guava中的线程池
           * @return
           */
          @Bean("my-executor")
          public Executor firstExecutor() {
              ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("my-executor").build();
              // 获取CPU的处理器数量
              int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
              ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 100,
                      200, TimeUnit.SECONDS,
                      new LinkedBlockingQueue<>(), threadFactory);
              threadPool.allowsCoreThreadTimeOut();
              return threadPool;
          }
      
          /**
           * Spring线程池
           * @return
           */
          @Bean("async-executor")
          public Executor asyncExecutor() {
              ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
              // 核心线程数
              taskExecutor.setCorePoolSize(24);
              // 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
              taskExecutor.setMaxPoolSize(200);
              // 缓存队列
              taskExecutor.setQueueCapacity(50);
              // 空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
              taskExecutor.setKeepAliveSeconds(200);
              // 异步方法内部线程名称
              taskExecutor.setThreadNamePrefix("async-executor-");
      
              /**
               * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
               * 通常有以下四种策略:
               * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
               * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
               * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
               * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
               */
              taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
              taskExecutor.initialize();
              return taskExecutor;
          }
      }
      
    • 在这里插入图片描述

异步失效的原因

  • 注解@Async的方法不是public方法;
  • 注解@Async的返回值只能为void或Future;
  • 注解@Async方法使用static修饰也会失效;
  • 没加@EnableAsync注解;
  • 调用方和@Async不能在一个类中;
  • 在Async方法上标注@Transactional是没用的,但在Async方法调用的方法上标注@Transcational是有效的;

线程池中的核心线程数设置问题

  • 有一个问题,一直没时间摸索,线程池中的核心线程数CorePoolSize、最大线程数MaxPoolSize,设置成多少,最合适,效率最高。
  • 借着这个机会,测试一下。
我记得有这样一个说法,CPU的处理器数量
  • 将核心线程数CorePoolSize设置成CPU的处理器数量,是不是效率最高的?

    // 获取CPU的处理器数量
    int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
    

    Runtime.getRuntime().availableProcessors()获取的是CPU核心线程数,也就是计算资源。

    • CPU密集型,线程池大小设置为N,也就是和cpu的线程数相同,可以尽可能地避免线程间上下文切换,但在实际开发中,一般会设置为N+1,为了防止意外情况出现线程阻塞,如果出现阻塞,多出来的线程会继续执行任务,保证CPU的利用效率。
    • IO密集型,线程池大小设置为2N,这个数是根据业务压测出来的,如果不涉及业务就使用推荐。

    在实际中,需要对具体的线程池大小进行调整,可以通过压测及机器设备现状,进行调整大小。

    如果线程池太大,则会造成CPU不断的切换,对整个系统性能也不会有太大的提升,反而会导致系统缓慢。

    我的电脑的CPU的处理器数量是24。

    那么一次读取多少行最合适呢?

    测试的Excel中含有10万条数据,10万/24 = 4166,那么我设置成4200,是不是效率最佳呢?

    测试的过程中发现,好像真的是这样的。

我记得大家都习惯性的将核心线程数CorePoolSize和最大线程数MaxPoolSize设置成一样的,都爱设置成200。
  • 是随便写的,还是经验而为之?
  • 测试发现,当你将核心线程数CorePoolSize和最大线程数MaxPoolSize都设置为200的时候,第一次它会同时开启150个线程,来进行工作。
  • 这个是为什么?
经过数十次的测试
  • 发现核心线程数好像差别不大
  • 每次读取和入库的数量是关键,不能太多,因为每次入库会变慢;
  • 也不能太少,如果太少,超过了150个线程,就会造成线程阻塞,也会变慢;

通过EasyExcel读取并插入数据库

  • EasyExcel的方式,我就不写双异步优化了,大家切记陷入低水平勤奋的怪圈。
ReadEasyExcelController
  • @RequestMapping(value = "/readEasyExcel", method = RequestMethod.POST)
    @ResponseBody
    public String readEasyExcel() {
        try {
            String path = "G:\\测试\\data\\";
            String[] xlsxArr = new File(path).list();
            for (int i = 0; i < xlsxArr.length; i++) {
                String filePath = path + xlsxArr[i];
                File fileTemp = new File(path + xlsxArr[i]);
                String fileName = fileTemp.getName().replace(".xlsx", "");
                List<UserInfo> list = new ArrayList<>();
                EasyExcel.read(filePath, UserInfo.class, new ReadEasyExeclAsyncListener(readEasyExeclService, fileName, batchCount, list)).sheet().doRead();
            }
        }catch (Exception e){
            logger.error("readEasyExcel 异常:",e);
            return "error";
        }
        return "suceess";
    }
    
ReadEasyExeclAsyncListener
  • public ReadEasyExeclService readEasyExeclService;
    	// 表名
        public String TABLE_NAME;
        // 批量插入阈值
        private int BATCH_COUNT;
        // 数据集合
        private List<UserInfo> LIST;
    
        public ReadEasyExeclAsyncListener(ReadEasyExeclService readEasyExeclService, String tableName, int batchCount, List<UserInfo> list) {
            this.readEasyExeclService = readEasyExeclService;
            this.TABLE_NAME = tableName;
            this.BATCH_COUNT = batchCount;
            this.LIST = list;
        }
    
        @Override
        public void invoke(UserInfo data, AnalysisContext analysisContext) {
            data.setUuid(uuid());
            data.setTableName(TABLE_NAME);
            LIST.add(data);
            if(LIST.size() >= BATCH_COUNT){
                // 批量入库
                readEasyExeclService.saveDataBatch(LIST);
            }
        }
    
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {
            if(LIST.size() > 0){
            	// 最后一批入库
                readEasyExeclService.saveDataBatch(LIST);
            }
        }
    
        public static String uuid() {
            return UUID.randomUUID().toString().replace("-", "");
        }
    }
    
ReadEasyExeclServiceImpl
  • @Service
    public class ReadEasyExeclServiceImpl implements ReadEasyExeclService {
    
        @Resource
        private ReadEasyExeclMapper readEasyExeclMapper;
    
        @Override
        public void saveDataBatch(List<UserInfo> list) {
        	// 通过mybatis入库
            readEasyExeclMapper.saveDataBatch(list);
            // 通过JDBC入库
            // insertByJdbc(list);
            list.clear();
        }
        
        private void insertByJdbc(List<UserInfo> list){
            List<String> sqlList = new ArrayList<>();
            for (UserInfo u : list){
                StringBuilder sqlBuilder = new StringBuilder();
                sqlBuilder.append("insert into ").append(u.getTableName()).append(" ( UUID,ID,NAME,AGE,ADDRESS,PHONE,OP_TIME ) values ( ");
                sqlBuilder.append("'").append(ReadEasyExeclAsyncListener.uuid()).append("',")
                                .append("'").append(u.getId()).append("',")
                                .append("'").append(u.getName()).append("',")
                                .append("'").append(u.getAge()).append("',")
                                .append("'").append(u.getAddress()).append("',")
                                .append("'").append(u.getPhone()).append("',")
                                .append("sysdate )");
                sqlList.add(sqlBuilder.toString());
            }
    
            JdbcUtil.executeDML(sqlList);
        }
    }
    
UserInfo
  • @Data
    public class UserInfo {
    
        private String tableName;
    
        private String uuid;
    
        @ExcelProperty(value = "ID")
        private String id;
    
        @ExcelProperty(value = "NAME")
        private String name;
    
        @ExcelProperty(value = "AGE")
        private String age;
    
        @ExcelProperty(value = "ADDRESS")
        private String address;
    
        @ExcelProperty(value = "PHONE")
        private String phone;
    }
    

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

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

相关文章

springboot精品源码

springboot精品源码 所有项目都包括&#xff1a;源码数据库文件开题LW说明文档运行视频 请看主页资料联系。 项目类型包括: 1 SpringBoot学生心理咨询评估系统 2 基于SpringBoot的网上订餐系统 3 大学生租房平台的设计与实现 4 SpringBoot房屋租赁系统 5 基于SpringBoot的课…

tcp 协议详解

什么是 TCP 协议 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制。TCP 是一个传输层的协议。 如下图&#xff1a; 我们接下来在讲解 TCP/IP 协议栈的下三层时都会先解决这两个问题&#xff1a; 报头与有效载荷如何…

大数据------javase基础------day18(完结)

类加载器 作用 负责将编译后的java文件&#xff08;即.class文件&#xff09;加载到内存中供虚拟机执行 类加载的时机------总结一句话&#xff1a;用到类就加载&#xff0c;不用就不加载 创建类的实例调用类的方法访问类或者接口的类变量&#xff0c;或者为该类变量赋值使用反…

阿里云幻兽帕鲁4核16G和8核32G服务器优惠价格

2024阿里云幻兽帕鲁专用服务器价格表&#xff1a;4核16G幻兽帕鲁专用服务器26元一个月、149元半年&#xff0c;默认10M公网带宽&#xff0c;8核32G幻兽帕鲁服务器10M带宽价格90元1个月、271元3个月。阿里云提供的Palworld服务器是ECS经济型e实例&#xff0c;CPU采用Intel Xeon …

Linux:详解https协议

文章目录 什么是https协议信息窃取常见的加密数据摘要和数据指纹https的工作过程只使用对称加密只使用非对称加密都使用非对称加密非对称加密对称加密 证书数据签名https方案 本篇要总结的内容是关于https协议的相关内容 什么是https协议 在讲述https协议之前&#xff0c;首先…

差分约束系统

差分约束系统 差分约束系统&#xff08;spfa&#xff09;1、概述2、过程模拟3、推理 差分约束系统&#xff08;spfa&#xff09; 1、概述 x j − x i ≤ w k x_j-x_i\le w_k xj​−xi​≤wk​转换为&#xff1a; x j ≤ w k x i x_j\le w_kx_i xj​≤wk​xi​ 在松弛操作中&…

dubbo 源码系列之-集群三板斧---负载均衡(-)

dubbo 源码系列之-负载均衡 概述核心接口 LoadBalanceDubbo 提供了 5 种负载均衡实现&#xff0c;分别是&#xff1a;LoadBalance 接口AbstractLoadBalance ConsistentHashLoadBalance 一致性hash1. 一致性 Hash 简析1.0 hash 算法2.0 一致性Hash算法3.0 一致性hash算法 引入槽…

K8S--SpringCloud应用整合Nacos实战

原文网址&#xff1a;K8S--SpringCloud应用整合Nacos实战-CSDN博客 简介 本文介绍K8S部署SpringCloud应用整合Nacos实战。 本文是将原来的SpringCloud项目&#xff08;闪速优选&#xff09;迁移到K8S上&#xff0c;一行代码都不需要改动。用K8S运行Nacos、Gateway、SpringCl…

PHP 读取嵌入式数据 SQLite3

SQLite3 属于轻量级开源的嵌入式关系型数据库&#xff0c;但它支持 ACID(Atomicity,Consistency,Isolation,Durability) 事务。 SQLite Download Page: https://www.sqlite.org/download.html 第一步&#xff1a;在 php.ini 中开启 extensionsqlite3 第二步&#xff1a;连接数…

Redis的String类型为什么重新设计使用了SDS数据结构呢

Redis 选择重新设计其 String 类型的底层数据结构&#xff0c;采用 SDS&#xff08;Simple Dynamic String&#xff09;而不是直接使用 C 语言标准库提供的原生字符串&#xff08;char*&#xff09;的原因主要包括以下几点&#xff1a; O(1) 时间复杂度获取长度&#xff1a; 在…

机器学习金融应用技术指南

1 范围 本文件提供了金融业开展机器学习应用涉及的体系框架、计算资源、数据资源、机器学习引擎、机 器学习服务、安全管理、内控管理等方面的建议。 本文件适用于开展机器学习金融应用的金融机构、技术服务商、第三方安全评估机构等。 2 规范性引用文件 下列文件中的内容通过…

C#,图论与图算法,用于检查给定图是否为欧拉图(Eulerian Graph)的算法与源程序

1 欧拉图 欧拉图是指通过图(无向图或有向图)中所有边且每边仅通过一次通路, 相应的回路称为欧拉回路。具有欧拉回路的图称为欧拉图(Euler Graph), 具有欧拉通路而无欧拉回路的图称为半欧拉图。 对欧拉图的一个现代扩展是蜘蛛图,它向欧拉图增加了可以连接的存在点。 这给…

目标检测预测框可视化python代码实现--OpenCV

import numpy as np import cv2 import colorsys from PIL import Image, ImageDraw, ImageFontdef puttext_cn(img, text, pt, color(255,0,0), size16):if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2…

【HarmonyOS】ArkUI - 状态管理

在声明式 UI 中&#xff0c;是以状态驱动视图更新&#xff0c;如图1所示&#xff1a; 图1 其中核心的概念就是状态&#xff08;State&#xff09;和视图&#xff08;View&#xff09;&#xff1a; 状态&#xff08;State&#xff09;&#xff1a;指驱动视图更新的数据&#xf…

BI技巧丨个性化视觉对象

BOSS&#xff1a;那个&#xff0c;那个谁&#xff0c;最近用户反映了&#xff0c;说是你们做的报表不太行啊&#xff1f;&#xff01; 白茶&#xff1a;&#xff08;&#xff1f;&#xff1f;&#xff1f;&#xff09;老板&#xff0c;怎么说&#xff1f; BOSS&#xff1a;就是…

pytest之统一接口请求封装

pytest之统一接口请求封装 pytest的requests_util.pyrequests_util.py 接口自动化测试框架的封装yaml文件如何实现接口关联封装yaml文件如何实现动态参数的处理yaml文件如何实现文件上传有参数化时候&#xff0c;怎么实现断言yaml的数据量大怎么处理接口自动化框架的扩展&#…

CSK6 接入聆思平台(LSPlatform)

一、开发环境 硬件&#xff1a;视觉语音大模型AI开发套件 二、使用大语言模型 官方指导文档&#xff1a; 开始使用 | 聆思文档中心 获取API密钥 | 聆思文档中心 1、注册 提交申请之后需要将注册电话号码通过微信发送给聆思科技工作人员&#xff0c;工作人员授权后&#xff…

阿里云4核16G服务器价格26.52元1个月、149.00元半年,ECS经济型e实例

阿里云4核16G服务器优惠价格26.52元1个月、79.56元3个月、149.00元半年&#xff0c;配置为阿里云服务器ECS经济型e实例ecs.e-c1m4.xlarge&#xff0c;4核16G、按固定带宽 10Mbs、100GB ESSD Entry系统盘&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接打开如下图&a…

Docker搭建LNMP环境实战(02):Win10下安装VMware

实战开始&#xff0c;先安装 VMware 虚拟机。话不多说&#xff0c;上手就干&#xff01; 1、基本环境检查 1.1、本机Bios是否支持虚拟化 进入&#xff1a;任务管理器- 性能&#xff0c;查看“虚拟化”是否启用&#xff0c;如果已启用&#xff0c;则满足要求&#xff0c;如果未…

Linux 中的vim和gdb

目录 vim命令模式(常用)nyy-----复制n次np------黏贴n次u------撤销dd-----剪切/删除$-----将光标定位到当前行结尾^-----将光标定位到最开始。gg------将光标定位文本开始shiftg-----将光标定位文件尾。nshiftg----将光标定位到第n行上下左右键&#xff1a;h j k l (左下上右)…
最新文章