专做丰田车货款的网站,湛江做寄生虫网站,怎么创建一个属于自己的平台,做html5视频网站目录 一、一般我会这样做#xff1a;操作起来#xff0c;如果文件比较多#xff0c;数据量都很大的时候#xff0c;会非常慢。 二、谁写的#xff1f;拖出去#xff0c;斩了#xff01;优化1#xff1a;先查询全部数据#xff0c;缓存到map中#xff0c;插入前再进行… 目录 一、一般我会这样做操作起来如果文件比较多数据量都很大的时候会非常慢。 二、谁写的拖出去斩了优化1先查询全部数据缓存到map中插入前再进行判断速度快了很多。优化2如果单个Excel文件过大可以采用 异步 多线程 读取若干行分批入库。优化3如果文件数量过多可以采一个Excel一个异步形成完美的双异步读取插入。1、readExcelCacheAsync控制类2、分批读取超大Excel文件3、异步批量入库4、异步线程池工具类Async的作用就是异步处理任务。默认线程池的默认配置如下也可以通过yml重新配置 5、异步失效的原因 三、线程池中的核心线程数设置问题1、我记得有这样一个说法CPU的处理器数量2、我记得大家都习惯性的将核心线程数CorePoolSize和最大线程数MaxPoolSize设置成一样的都爱设置成200。3、经过数十次的测试 四、通过EasyExcel读取并插入数据库1、ReadEasyExcelController2、ReadEasyExeclAsyncListener3、ReadEasyExeclServiceImpl4、UserInfo 大家好我是哪吒。
在开发中我们经常会遇到这样的需求将Excel的数据导入数据库中。
一、一般我会这样做
通过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);}}ListString 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(-, );
}二、谁写的拖出去斩了
优化1先查询全部数据缓存到map中插入前再进行判断速度快了很多。
优化2如果单个Excel文件过大可以采用 异步 多线程 读取若干行分批入库。 优化3如果文件数量过多可以采一个Excel一个异步形成完美的双异步读取插入。 使用双异步后从 191s 优化到 2s你敢信
下面贴出异步读取Excel文件、并分批读取大Excel文件的关键代码。
1、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;
}2、分批读取超大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);}}
}3、异步批量入库
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);}}ListString 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);
}4、异步线程池工具类
Async的作用就是异步处理任务。
在方法上添加Async表示此方法是异步方法在类上添加Async表示类中的所有方法都是异步方法使用此注解的类必须是Spring管理的类需要在启动类或配置类中加入EnableAsync注解Async才会生效
在使用Async时如果不指定线程池的名称也就是不自定义线程池Async是有默认线程池的使用的是Spring默认的线程池SimpleAsyncTaskExecutor。
默认线程池的默认配置如下
默认核心线程数8最大线程数Integet.MAX_VALUE队列使用LinkedBlockingQueue容量是Integet.MAX_VALUE空闲线程保留时间60s线程池拒绝策略AbortPolicy
从最大线程数可以看出在并发情况下会无限制的创建线程我勒个吗啊。
也可以通过yml重新配置
spring:task:execution:pool:max-size: 10core-size: 5keep-alive: 3squeue-capacity: 1000thread-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;}
}5、异步失效的原因
注解Async的方法不是public方法注解Async的返回值只能为void或Future注解Async方法使用static修饰也会失效没加EnableAsync注解调用方和Async不能在一个类中在Async方法上标注Transactional是没用的但在Async方法调用的方法上标注Transcational是有效的
三、线程池中的核心线程数设置问题
有一个问题一直没时间摸索线程池中的核心线程数CorePoolSize、最大线程数MaxPoolSize设置成多少最合适效率最高。
借着这个机会测试一下。
1、我记得有这样一个说法CPU的处理器数量
将核心线程数CorePoolSize设置成CPU的处理器数量是不是效率最高的
// 获取CPU的处理器数量
int curSystemThreads Runtime.getRuntime().availableProcessors() * 2;Runtime.getRuntime().availableProcessors()获取的是CPU核心线程数也就是计算资源。
CPU密集型线程池大小设置为N也就是和cpu的线程数相同可以尽可能地避免线程间上下文切换但在实际开发中一般会设置为N1为了防止意外情况出现线程阻塞如果出现阻塞多出来的线程会继续执行任务保证CPU的利用效率。IO密集型线程池大小设置为2N这个数是根据业务压测出来的如果不涉及业务就使用推荐。
在实际中需要对具体的线程池大小进行调整可以通过压测及机器设备现状进行调整大小。
如果线程池太大则会造成CPU不断的切换对整个系统性能也不会有太大的提升反而会导致系统缓慢。
我的电脑的CPU的处理器数量是24。
那么一次读取多少行最合适呢
测试的Excel中含有10万条数据10万/24 4166那么我设置成4200是不是效率最佳呢
测试的过程中发现好像真的是这样的。
2、我记得大家都习惯性的将核心线程数CorePoolSize和最大线程数MaxPoolSize设置成一样的都爱设置成200。
是随便写的还是经验而为之
测试发现当你将核心线程数CorePoolSize和最大线程数MaxPoolSize都设置为200的时候第一次它会同时开启150个线程来进行工作。
这个是为什么 3、经过数十次的测试
发现核心线程数好像差别不大每次读取和入库的数量是关键不能太多因为每次入库会变慢也不能太少如果太少超过了150个线程就会造成线程阻塞也会变慢 四、通过EasyExcel读取并插入数据库
EasyExcel的方式我就不写双异步优化了大家切记陷入低水平勤奋的怪圈。
1、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, );ListUserInfo 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;
}2、ReadEasyExeclAsyncListener
public ReadEasyExeclService readEasyExeclService;// 表名public String TABLE_NAME;// 批量插入阈值private int BATCH_COUNT;// 数据集合private ListUserInfo LIST;public ReadEasyExeclAsyncListener(ReadEasyExeclService readEasyExeclService, String tableName, int batchCount, ListUserInfo list) {this.readEasyExeclService readEasyExeclService;this.TABLE_NAME tableName;this.BATCH_COUNT batchCount;this.LIST list;}Overridepublic void invoke(UserInfo data, AnalysisContext analysisContext) {data.setUuid(uuid());data.setTableName(TABLE_NAME);LIST.add(data);if(LIST.size() BATCH_COUNT){// 批量入库readEasyExeclService.saveDataBatch(LIST);}}Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {if(LIST.size() 0){// 最后一批入库readEasyExeclService.saveDataBatch(LIST);}}public static String uuid() {return UUID.randomUUID().toString().replace(-, );}
}3、ReadEasyExeclServiceImpl
Service
public class ReadEasyExeclServiceImpl implements ReadEasyExeclService {Resourceprivate ReadEasyExeclMapper readEasyExeclMapper;Overridepublic void saveDataBatch(ListUserInfo list) {// 通过mybatis入库readEasyExeclMapper.saveDataBatch(list);// 通过JDBC入库// insertByJdbc(list);list.clear();}private void insertByJdbc(ListUserInfo list){ListString 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);}
}4、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;
}哪吒多年工作总结Java学习路线总结搬砖工逆袭Java架构师。 华为OD机试 2023B卷题库疯狂收录中刷题点这里 刷的越多抽中的概率越大每一题都有详细的答题思路、详细的代码注释、样例测试发现新题目随时更新全天CSDN在线答疑。