这两个分别是POI加载Excel文件方法和读取文件行的数据的片段,从这里暴露出POI处理大数据EXCEL的问题所在。首先,是将EXCEL文件加载到XSSFSheet对象的一个过程需要消耗很多时间及内存资源,通过测试得出,当EXCEL数据记录在6万以上,文件大小超过30M时,此处会出现异常“JVM内存溢出”,原因是在加载EXCEL文件时开销太大了,即使我们在启动服务器时扩大JVM内存分配,这也只是治标不治本的方法;其次,在循环读取EXCEL行数据时需要预先获得EXCEL中总行数,而获得总行数的方法则是通过函数sheet.getLastRowNum() - sheet.getFirstRowNum()+ 1 实现,在此又出现了另一个问题,sheet.getLastRowNum()返回值是一个int类型的数据,其大值只可能是65535,那么它又如何可以计算出超过65535行记录的行数呢?所以得出结论:对于大数据的读取,不能采用POI读取EXCEL的方式。

  不采用EXCEL作为数据源文件,可以采用CSV文件代之。CSV是EXCEL可另存为的数据文件格式,其本质上是以逗号分隔的文本文件,因此,对于此类文件的读取,我们可以采用传统IO读取文件的形式,通过字符串分割获得每个单元格数据,拼接到SQL里面,形成SQL的数组。

  第二步,多线程执行数据导入。

  第一步已经将SQL储存到SQL的数组中,接下来是利用多线程执行批量插入。此处实现需要将SQL执行类实现Runnable接口或者继承Thread类,在常量中设置批量插入的记录数,当加载的SQL数据大小达到该数目时,系统自动启动一个线程执行该数组的批量数据导入。为了确保线程管理的安全,我们需要定义一个大线程数,当处于运行的线程数达到大值时,新开启的线程将利用wait()方法实现等待,等待有线程执行完毕后才开始执行。如此调用,直到加载的数据记录全部都已开始导入才停止线程的启动,此时为了监控数据导入是否全部完成,我们会再新开启一个后台线程,设置为守护进程setDaemon(true),该类线程特点是在所有线程结束后将自动结束,其用于收集每个导入线程的执行状态,当所分配的所有线程状态都不是active时表示数据导入完成。

  利用该方案实现的数据导入较单线程执行的批量数据导入效率提高多倍,从测试导入40万数据结果来看,单线程批量导入耗时19分钟,而基于多线程的导入只用了5分钟左右的时间。但从性能消耗上来看,多线程方案平均同时工作线程数为15个左右,CPU利用率高达90%,内存消耗约500M,对于服务器本身已造成了一定的压力,虽然在速度上提升了,其对于服务器的稳定性将造成安全隐患。

  此外,对于多线程工作效率的探索上也有一点心得。多线程的出现更多的是迎合多核处理技术的革新,在单CPU工作的主机上,多线程看起来貌似是多个线程并发执行,但从操作系统的角度出发其仍然处于串行状态,因为在同一时间,处理器只对一个任务进行调度,只不过是轮询的时间间隙较短不容易发觉。如果在多核处理的主机上,会有多个处理器同时处理并发的线程,这样才能实现真正意义上的并发调度,所以多线程还是依赖于硬件本身。为了验证效率,当我们把执行导入的各个线程以webService的形式部署到不同的虚拟机中去执行时,效果不一样了,效率明显还会提升。由此引出一个当今IT行业的一个热点,虚拟化技术的实现与应用,有利于资源的优化配置,在有限的资源上实现更大的利用价值,该技术在云计算领域也是颇受关注的。

  (二)利用SQLLOADER执行大数据导入

  SQLLOADER是oralce内置的一个命令工具,其可实现将CSV数据文件或者文本数据文件一次性导入数据库中。它的使用需要有两个文件,一个是需要导入的数据文件,另一个是控制导入的控制文件。该方案的执行需要三个步骤,第一是格式化数据文件,第二是生成控制文件,后是执行导入命令。

  第一,格式化数据文件。

  数据文件不是已经上传了,为什么需要格式化呢?原因在于SQLLOADER对于导入数据文件要求的严格性。利用SQLLOADER导入的数据文件有如下要求:

  1、数据行之间要有合法换行标志,数据与数据之间需要有合法分隔符,一般情况下数据之间用逗号分隔,数据记录之间用换行符分隔;

  2、要导入的数据是直接导入到数据库的内容,不能包含表头信息,对于数据文件中有表头的需要将其去除;

  3、数据中不能包含既定的数据分隔符,比如逗号,因为其在导入时对于数据个数不匹配的将视为错误数据被拒绝。

  因此,为了确保数据能够准确的被导入到数据库,在数据导入之前我们需要验证原数据文件,并读取其数据信息生成格式化的新数据文件提供导入。这也许是这种导入方式繁琐的地方,但出于安全又不得不做。

  第二,生成控制文件。

  SQLLOADER执行数据导入并非直接通过命题操纵数据文件,而是通过一个控制文件规定了导入数据的方式,包括数据文件路径及导入数据字段等等,它通过执行该控制文件来实现数据的导入。下面来看一个控制文件内容。

load data                                                
infile 'e: est舱位数据源(20120101-20120331).csv'   (1)
append into table CZDS_CABIN                           (2)
fields terminated by ','                                (3)
( CARRIER,                                                 (4)
  ORGCITY,
  ARRIVALCITY,
  FLYDATE "to_date(:FLYDATE,'YYYY-MM-DD')",
  FLIGHTNO,
  FLIGHTSEG,
  AIRLINE,
  AIRLINETOANDFROM,
  FLIGHTTYPE,
  ORGUNITS,
  MOTHERCABIN,
  SONSPACE,
  DISCOUNTFACTOR,
  CLASSES,
  FLIGHTNAVIGATIONSECTION,
  BOARDINGTIMES,
  GROUPTICKETRNUMBER,
  REVENUEFORECASTING,
  SEATINGSECTOR,
  SEATINGNAVIGATIONSECTION,
  SEATINGSONTNSECTION      )