公司开发一个文件监控程序,具体的需求为根据一个配置文件读取所监控的目录,当监控目录下的文件发生变化则复制只以一个目录下,并且监控此复制目录,读取复制完的文件数据,将数据实时的导入数据库。即开发了B/S模式,也开发了C/S模式。
以下为开发探索过程中的总结:
一.文件读取问题:
1.复制文件类型涉及了txt,rtf,excel等格式文件的数据读取,首先是rtf格式数据的中文乱码问题,rtf读取设置了编码为ISO8859_1在自己的IDE环境下测试通过,但随之在老大的IDE下测试发现还是乱码,之后通过谷歌查阅的《JAVA中文字符编码问题详解》和《Eclipse控制台中市输入中文保存到文件中,字符编码转换测试 demo大全》了解到文件在从文件流读出到载入内存和写到输出流中必须环环相扣的正确解码和编码,于是乎从rtf文件输入流的编码格式到IDEjava文件的编码格式到IDE项目的编码方式到IDE工作区间的编码方式到控制台输出的编码格式做了一系列实验才让RTF文件中文乱码正常,这里遇到的IDE的控制台设置为GBK编码可以正确显示中文乱码,但由于采用B/S模式,没有IDE的控制台编码设置,故发布到web服务,文件转换依旧乱码,最后将读取的数据在IDE下以GBK的方式编码再用GBK解码写入输出流中文乱码得以解决。
关键代码如:
public void readRTFEditorKit(String inputPath,String outPath) throws IOException{ String bodyText = null; DefaultStyledDocument styledDoc = new DefaultStyledDocument(); try { InputStream is = new FileInputStream(new File(inputPath)); InputStreamReader inputStreamReader = new InputStreamReader(is,"utf-8"); new RTFEditorKit().read(inputStreamReader, styledDoc, 0); //ISO8859_1 byte[] b2=styledDoc.getText(0, styledDoc.getLength()).getBytes("ISO8859_1"); bodyText = new String(b2,"GBK"); //提取文本 is.close(); inputStreamReader.close(); } catch (IOException e) { System.out.println("不能从RTF中摘录文本!"); } catch (BadLocationException e) { System.out.println("不能从RTF中摘录文本!"); } try { FileOutputStream fos = new FileOutputStream(outPath); OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK"); osw.write(bodyText); osw.flush(); fos.close(); osw.close(); } catch (Exception e) { e.printStackTrace(); } }总结:理解文件编码解码所在的环境和环环相扣的正确解码编码过程是解决此类问题的基础。
2.excel文件读取问题
在数据读取和写入数据库的编码应用到了多线程读取和写入Oracle数据库,在多线程并发的情况下发现有些文件读取和最终写入数据库耗时较大,起初以为时间消耗大是由于多线程写入同一张表数据库锁机制引起的大消耗,故一直在数据写入这里打转,后面经一位同事提醒分段打印出读取文件的执行时间和数据库写入的消耗时间,发现二者差别之大,大部分的消耗时间是在excel文件的读取过程中,当excel文件涉及的数据很大,时间消耗也随之上去,找出问题的症结,接下来就是寻找一种较快读取excel的方法,在现有Java读取excel的框架中有Apache的POI和XX的Fast Excel,jexcelApi三种方式,一个个实验后发现只有Apache的poi能读取较高版本的Excel文件,至于其他数据量,读取性能的比较也就没能进行了,依旧采用POI方式读取Excel文件。
总结:对于大消耗的优化需要根据代码的模块找出每个消耗,而不是通过自己的主观去定义大消耗的症结。
3.远程文件复制时延导致java.io.filenotfoundexception 另一个程序正在使用此文件 进程无法访问问题
采用了多线程去读取待复制的文件,但是由于网络的I/O问题,当监控文件发现到目录文件发现变化后想去读取该文件,而此时另外一个进程在写入该文件,线程会抛出上述异常,解决思路是该线程循环判断此文件是否被另外一个进程占用,是的话线程等待,否的话读取数据后退出循环,而判断文件是否被其他进程使用用到了File.renameTo()方法,据说在Linux平台上无法判断。关键代码如下:
public static void rtf2txt(String inputPath,String outPath) throws IOException, BadLocationException, InterruptedException{ String bodyText = null; File sourceFile=new File(inputPath); DefaultStyledDocument styledDoc = new DefaultStyledDocument(); while(true){ if(sourceFile.renameTo(sourceFile)){ try { InputStream is = new FileInputStream(sourceFile); InputStreamReader inputStreamReader = new InputStreamReader(is,"utf-8"); new RTFEditorKit().read(inputStreamReader, styledDoc, 0); //ISO8859_1 byte[] b2=styledDoc.getText(0, styledDoc.getLength()).getBytes("ISO8859_1"); bodyText = new String(b2,"GBK"); //提取文本 is.close(); inputStreamReader.close(); FileOutputStream fos = new FileOutputStream(outPath); OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK"); osw.write(bodyText); osw.flush(); fos.close(); osw.close(); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常"+e.getMessage()); } catch (BadLocationException e) { e.printStackTrace(); } break; }else{ TimeUnit.MILLISECONDS.sleep(1000); } } }二.jnotify多次回调问题
文件监控用到了jnotify去监控发生变化的文件,在监控fileModified方法的时候发现拷贝一个文件会回调4次或2次该方法,猜想是fileModified方法囊括了fileCreated、fileRenamed..等其他方法,一个监控文件方法被重复调用多次,如何让复制文件只触发一个回调事件,避免数据的重复读取和重复上传到数据库,在这里我们list去记录多次触发的同一个监控文件,没有则加入List,有则不加入,以此来避免数据重复读取和重复上传,然后在监控完毕后从List中移除该监控文件。
关键代码:
public void fileModified(int wd, String rootPath, String name){ final String sourceFilePath = rootPath + File.separator + name; if(FileMonitorUtil.isMonitorFiles(sourceFilePath)){//判断是否监控文件 for(int i=0; i<monitorArray.length; i++){ if(Integer.toString(wd).equals(monitorArray[i][watchIdPos])&&!files.contains(sourceFilePath)){ //触发事件是该监控目标 try { files.add(sourceFilePath); pool.execute(new modifyRunnable(sourceFilePath,monitorArray[i][monitorIdPos])); } catch (Exception e) { showLog(e.getMessage()); log.error(e.getMessage()); e.printStackTrace(); } } } } }
多线程机制下上述代码的file被实现为线程安全的CopyOnWriteArrayList<String> files;多次的重复回调都判断是否被包含到了list中,没有才触发监控事件,避免重复。记得在线程执行完毕删除该list下监控完毕的文件。
三.多线程往数据库写入数据
1.多线程写入数据库数据性能比较问题
多线程往同一张表写入数据问题,起初在用多线程往同一个数据库和同一张表的性能和单线程写入比较上存在怀疑,是否多线程比单线程存在优势(仅仅考虑数据写入),因为我觉得多线程写入同一个数据库和同一个表存在着数据库锁机制的消耗,而操作的又是同一个数据库和同一张表,而非分布式模式的多线程写入,性能上是否真的比单线程好,这点就需要你们告诉我了。但是现在不单单是数据写入的过程,还存在多文件读取的过程,这点多线程在这点还是占优势了,所以还是考虑用多线程去实现。
2.多线程写入数据库数据同步问题
多线程写入共享资源要解决的同步问题,起初怀疑多线程写入是因为还存在一个同步问题,但知道数据库锁机制后同时基于多文件读取的原因还是采用多线程机制,同时在上面的大消耗问题上怀疑是服务器开启了多个数据库Connection和Session导致的性能问题,决定采用共用Connection和Session是否会降低消耗,但是发现在此情况下会出现数据丢失的问题,网上有《jdbc 多线程公用一个connection PreparedStatement》这片博文已经就共用Connection等做了探讨,而我测试出来的会发现数据丢失,考虑到数据丢失后开始不共用Connection和Session利用ThreadLocal封装线程本地变量,后来也就没有数据丢失了,但是通过Jboos7的管理控制台发现Prepared Statement Cache会跟随拷贝文件的增加而增加,不知道是否对服务器会产生影响。
一个大的总结:毕业一年后就没再写码字了,平时都是码代码比较多,日子长了,人也懒了,总体觉得自己一年来成长性并非很大,一直想学多点东西,但却疏于总结,也许这算不上一名合格的程序猿!