java导入大文件数据的解决方案

2018年11月5号于南昌 中海蓝域小区 卧榻伴音弦

最近在做项目,一个20G大小的文件,要按行读入到数据库,妈呀,有什么好方法吗?

20G如果按照行读入的方式,需要20多个小时才能入库成功。主要性能瓶颈不是在内存,而是在数据库连接的次数。

比如批量单次插入1000条和单次插入200条,其实是五倍性能差距。

但是这个批量插入的条数是根据数据库缓存大小设置而决定的,数据库缓存不可能太大,比如一个8G的服务器,数据库缓存建议最大200M等,单次插入条数最大上限是2000条每次,如果有5000w行数据,那么需要多少次数据连接呢?答案是:2.5万次连接,我天,可想而知,能不慢吗?连接数据库,每次都有通过网络访问,然后数据库有行级锁,再提交事务,单次连接如果1s,那么2.5万次连接就是
2.5万s,一小时是3600s,那么需要多少个小时呢

结果:2.5万除以0.36万,大概是7个小时。

大家知道了,上面并没有考虑读文件,放入内存的时间,算是这段时间,
时间就会更长,为什么忽略这个,因为在我看来,数据库连接所消耗的时间要远大于内存加载文件中的数据所消耗的时间。

分析问题要切中要点,忽略次要的因素。

大家脑海中可能想过以下几个方法:
原始方法如下:

@Override
	public void addDataFromBigDatFileOfScReturnRegister(String fileEndDate) throws IOException {
		String currentDate = fileEndDate;// DateUtils.formatDate(new Date(),
											// "yyyyMMdd");
		String dataFileFlagName = LoyParam.DOWN_LOCAL_PATH_BEFORE.concat(currentDate)
				.concat(LoyParam.DOWN_LOCAL_PATH_AFTER).concat(LoyParam.FILE_CIR_RETURN_REGISTER_).concat(currentDate)
				.concat(LoyParam.DOWN_FILENAME_FLG);

		String dataFileName = LoyParam.DOWN_LOCAL_PATH_BEFORE.concat(currentDate).concat(LoyParam.DOWN_LOCAL_PATH_AFTER)
				.concat(LoyParam.FILE_CIR_RETURN_REGISTER_).concat(currentDate).concat(LoyParam.DOWN_FILENAME_DAT);

		boolean flag_flg = FileUtil.isExistence(dataFileFlagName);
		boolean flag_dat = FileUtil.isExistence(dataFileName);
		if (!(flag_flg && flag_dat)) {
			// 其中一个不存在over
			return;
		}

		Map<String, String> fileMap = this.getDataFlagInfo(dataFileFlagName);
		if (fileMap.isEmpty()) {
			logger.info("要访问的文件不存在" + dataFileFlagName);
			return;
		}

		int datasize = 0;
		if (!StringCommonUtils.isEmpty(fileMap.get("dataSum"))) {
			datasize = Integer.parseInt(fileMap.get("dataSum"));
			logger.info("数据文件名为:" + fileMap.get("dataFile"));
			logger.info("数据文件大小为:" + fileMap.get("fileSize"));
			logger.info("数据总数为:" + datasize);
		}

		// 年月日时分秒
		String starttime = formatter.format(new Date());
		// 10位的年月日
		String currentY_M_D = formatterY_M_D.format(new Date());
		long start = System.currentTimeMillis();
		FileInputStream fis = new FileInputStream(dataFileName);
		Scanner sc = new Scanner(fis, "GBK");
		//创建备份表,插入备份表
		this.deleteAndCreateTmpTable(LoyParam.TABLE_SC_RETURN_REGISTER);

		int DB_COMMIT_COUNT=1000;
		int y = datasize % DB_COMMIT_COUNT;
		int inum = 0;
		List<SCT> ststlist = new ArrayList<SCT>();
		ScProblemRecordPojo problemPojo=null;
		List<SCT> problemList=new ArrayList<SCT>();
		ScReturnRegister scReturnRegister=null;
		while (sc.hasNextLine()) {
			String line = sc.nextLine();
			String[] data = line.split("\\x03");
			if (data.length > 0) {
				inum++;
				// logger.info("第"+inum+"行:"+line);
				Map<Integer, String> datamap = new HashMap<Integer, String>();
				for (int i = 0; i < data.length; i++) {
					String data_ = StringCommonUtils.replaceBlank(data[i]);
					datamap.put(i, data_);
				}
				try{
					String dataDate = StringFormat.trimNull(datamap.get(0));
					String loanNo = StringFormat.trimNull(datamap.get(1));
					String orgNo = StringFormat.trimNull(datamap.get(2));
					String custNo = StringFormat.trimNull(datamap.get(3));
					String custName = StringFormat.trimNull(datamap.get(4));
					String tranType = StringFormat.trimNull(datamap.get(5));
					String returnInt = StringFormat.trimNull(datamap.get(6));
					String returnAmt = StringFormat.trimNull(datamap.get(7));
					String tbdate = starttime.substring(0, 10);
	
					scReturnRegister = new ScReturnRegister();
					BeanUtil.setProperty(scReturnRegister, "dataDate", dataDate);
					BeanUtil.setProperty(scReturnRegister, "loanNo", loanNo);
					BeanUtil.setProperty(scReturnRegister, "orgNo", orgNo);
					BeanUtil.setProperty(scReturnRegister, "custNo", custNo);
					BeanUtil.setProperty(scReturnRegister, "custName", custName);
					BeanUtil.setProperty(scReturnRegister, "tranType", tranType);
					BeanUtil.setProperty(scReturnRegister, "returnInt", returnInt);
					BeanUtil.setProperty(scReturnRegister, "returnAmt", returnAmt);
					BeanUtil.setProperty(scReturnRegister, "tbdate", tbdate);
				}catch(Exception ex){
					logger.error("转换信息异常"+ex.getMessage());
					logger.info("========》我出错了 "+inum);
					problemPojo=new ScProblemRecordPojo(
							scReturnRegister==null?"":scReturnRegister.toString(),
									DateUtils.YYYYMMDD,
									DateUtils.YMDHMS,
									dataFileName,
									((Integer)inum).toString()
							);
					problemList.add(problemPojo);
					this.scCredTmTxnHstService.insertBatch(problemList, dataFileName);
					continue;
				}
				ststlist.add(scReturnRegister);
				datamap.clear();

				if (ststlist.size() == DB_COMMIT_COUNT) {
					scCredTmTxnHstService.insertBatch(ststlist,dataFileName);
					ststlist = null;
					ststlist = new ArrayList<SCT>();
					continue;
				}
				if (y > 0 && inum == datasize) {
					scCredTmTxnHstService.insertBatch(ststlist,dataFileName);
				    break;
				}
			}
		}
		logger.info("解析数据完成!共计" + inum + "条数据");
		sc.close();
		fis.close();
		long end = System.currentTimeMillis();
		System.out.println("时长为:---------------" + (start - end) + "毫秒---------------");
		if (inum == datasize) {
			FileOperate.delete(dataFileName);
			scEngineeEtlmxService.addScEngineeEtlmx(LoyParam.FILE_CIR_RETURN_REGISTER_, "数仓数据文件之"+dataFileName, starttime,
					formatter.format(new Date()), StringFormat.msecondToTime(end - start), currentY_M_D, datasize, inum,
					currentY_M_D, "Y");
			//如果成功做一系列处理
			this.insertDBSuccessProcessTable(LoyParam.TABLE_SC_RETURN_REGISTER);

		}

	}

代码很好理解,这里不多解释,主要性能问题大家也知道

		scCredTmTxnHstService.insertBatch(ststlist,dataFileName);

对,就在这里,将list数据,连接数据库,插入,提交事务,这个过程需要2.5万次,我天

考虑java解决问题的方式

1,多线程

多线程就是,将串行的方式,改成并行,比如:
排队去食堂打饭,就一个窗口,这是串行
排队去食堂打饭,但是有多个窗口共同执行,你可以在任何一个窗口排队,并行。

并行增加了效率,提高了打饭速度,随之而来的问题也很多。那就是并发问题。A人付款了,准备拿饭缸接住饭,这时候,由于并发,B人的没有付款,但是售货员把饭打到了B人的饭缸,造成了A付了款而没有收到饭,或者A可能收到了C人的饭,C收到了D的饭,乱套了。

解决这个方法,java当然也有解决方案,呵呵。

Synchronized神方法,不错,但是有一个问题,我们要对非原子的逻辑都要加Synchronized,所以方法块,或者干脆加到方法上。

言归正传,拥多线程并不能解决我们的问题,因为我们这个读取的方法,就这么一个,里面大多数都是非原子的逻辑,加载方法上,不管你启多少个线程,效果和串行一样,因为只有一个线程获取了锁,并且执行,而且速度也不快,所以这个方法解决不了问题

2,多进程
这个方法咋拥,其实就是把上面的方法,比如A 我们复制多个,分别叫
A A1 A2 A3
这里就有了4个方法,但是不能同时读取一个文件吧,这样数据会重复,有人会说数据库强加唯一索引,我说如果数据杂乱无章,没有主键呢?
这样就考虑将大文件切割,切割拥linux的split命令,见我下一篇文章,会有详细的解释

比如我们把文件F切割成 F1 F2 F3 F4

然后A方法读F1 入库
A1 读F2入库
A2 读F3入库
A3 读F4入库

这样我们可以启动4个进程,代码虽然缀余,但这不是我们讨论的重点,我们是讨论如何解决之,那么我们发现,这样的话,效率会增大,但是不会太明显,为什么,此时虽然说2.5万次连接,分了4个进程去连接,一个进程去执行2.5w除以4次连接,同时进行,相当于多线程,但他是多线程,
但为什么效果不明显,原因是数据库有行级锁,虽然同时连接的效率高了,但是对于数据库来说,处理插入是按照队列来的,是吧。

这样瓶颈就在数据库的行级锁,那么此时就没有办法了。

3 Channel 读取文件
这种方法,是java提供的读取同一个文件入库的多线程方法,大家有兴趣可以看一下,但是我没研究过,其实性能不在内存,而在数据库连接和数据库行级锁上面。

以上就是java导入大文件的解决方案,实际工作中的经验,分享之,愿大神有高见,指点一二,不胜感激。

======================
V。18612372242
励志语:
愿你我不忘初心,选一行爱一行,就像爱一个人终老一样

You are My love ForEver !

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 非常感谢您的提问。关于使用Java来实现数据PDF文件导出的问题,可以使用Java PDF库来实现。常用的Java PDF库有iText和PDFBox等。这些库可以让您使用Java代码来创建、修改和导出PDF文件。您可以使用这些库来创建包含表格、图形和文本等元素的PDF文件,然后导出为PDF格式。使用这些库需要一定的Java编程知识,但是它们都提供了丰富的文档和示例代码,方便开发者使用。 ### 回答2: 使用Java实现数据PDF文件导出可以通过使用第三方库来实现。其中最常用且功能强大的库是iText库。以下是使用iText库来实现数据PDF文件导出的步骤: 1. 首先,需要在项目中导入iText库的相关jar包。 2. 创建一个PDF文档对象,可以通过`Document`类来实现,例如:`Document document = new Document();` 3. 使用`PdfWriter`类来指定输出的文件路径和文件名,例如:`PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));` 4. 打开文档对象:`document.open();` 5. 根据需求,可以添加标题、表格、图像等内容来填充PDF文件。例如,可以使用`Paragraph`类来添加标题:`document.add(new Paragraph("标题"));` 6. 在PDF文件中添加数据表格,可以使用`PdfPTable`类来创建表格,然后使用`PdfPCell`类来添加表格内容。 7. 可以通过循环遍历数据来将数据填充到表格中。 8. 设置样式,可以使用`Font`类来设置字体、颜色等样式。 9. 关闭文档:`document.close();` 10. 最后,导出的PDF文件会保存在指定路径中。 除了iText库,还有其他一些可供选择的库,例如Apache PDFBox等,都可以用来实现数据PDF文件导出。但是无论使用哪个库,基本的步骤和原理是相似的。 总之,使用Java实现数据PDF文件导出可以通过使用第三方库来简化开发过程,并且可以实现丰富的样式和内容。 ### 回答3: PDF是一种常用的文件格式,用于显示和打印文档,并且可以保持文档的格式不变。Java是一种常用的编程语言,具有强大的文档处理能力和丰富的库和工具。 要使用Java来实现PDF文件导出,可以使用第三方库或工具,如iText、Apache PDFBox等。这些库提供了丰富的API,可以让开发人员创建、操作和导出PDF文件。 首先,我们需要使用Java代码创建一个PDF文档对象。可以设置文档的属性,如页面大小、边距等,然后可以添加文本、图像、表格等内容到文档中。 对于添加文本,可以使用库提供的API来设置字体、大小、颜色等,然后将文本添加到页面中的指定位置。 对于添加图像,可以使用库提供的API来加载图像文件,然后将图像添加到页面中的指定位置。 对于添加表格,可以使用库提供的API来定义表格结构,设置表格样式和内容,然后将表格添加到页面中的指定位置。 最后,可以使用Java代码将创建的PDF文档保存到本地硬盘或输出流中,以供用户下载或进一步处理。 使用Java来实现PDF文件导出,可以方便地生成和操作PDF文件,满足各种场景下对于文档的需求。无论是自动生成报告、生成电子书籍还是其他需要保留格式的文档,Java都可以提供灵活而强大的解决方案

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知乎关注八戒来了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值