最近在项目中需要读取sql文件,并修改里面的一部分内容。主要问题是sql文件里的插入语句中的日期是Oracle形式的,需要将其替换为Mysql形式的,其他的不变。项目中的sql文件有几十万条记录,数据量比较大,这可能在读取文件的时候带来一些问题。
如上图所示,由于MySQL中没有to_date函数,而且里面的日期格式不对,也就是需要将画红线的部分替换为’2014-06-25’。(注意一行中有多个to_date)
使用正则表达式进行查找替换
最直接想到就是使用Java中的正则表达式来匹配到相应的字符串并替换。这里可以先对少量几行的数据进行测试,如果成功了再考虑对整个文件进行替换。
Java中的正则表达式主要是两个类:Pattern类和Matcher类。Pattern类将正则表达式字符串编译为pattern对象以调用相应的匹配方法。Matcher类是对输入字符串进行解释和匹配操作的引擎。一个典型的使用正则表达式的代码如下:
1 2 3
| Pattern p = Pattern.compile("a*b"); Matcher m = p.matcher("aaaaab"); boolean b = m.matches();
|
matches()对整个字符串进行匹配,只有整个字符串都匹配了才返回true ,这显然和我们的要求不符,Matcher类中还提供了一个find方法可以进行多次匹配。如果在文本中多次匹配,find() 方法返回第一个,之后每次调用 find() 都会返回下一个,而且可以调用start()和end()方法返回匹配到的字符串在整个字符串中的下标。
1 2 3 4 5 6 7 8 9 10
| String patternString = "to_date.{2}\\d{2}-\\d{2}-\\d{4}.\\d{2}:\\d{2}:\\d{2}.{27}"; Pattern pattern = Pattern.compile(patternString) Matcher matcher = pattern.matcher(line); int count = 0; while(matcher.find()) { String findStr = line.substring(matcher.start(), matcher.end()); count++; System.out.println("found: " + count + " : " + matcher.start() + " - " + matcher.end()); }
|
这样我们定义一个匹配to_date日期的正则表达式,通过每次调用find方法既可依次匹配到所有的。这里还需要对匹配的字符串进行替换,我们可以用String substring方法将需要的日期截取出来,然后使用正则表达式中的替换方法。
正则表达式的替换方法主要有以下几个:
1.replaceAll()
2.replaceFirst()
3.appendReplacement()
4.appendTail()
如果直接使用replaceFirst每次都只会替换第一个,而我们一行中有多个需要替换,如果使用replaceAll会一次将所有的替换为相同的,而我们每次替换的内容都不一样,所以需要进行动态替换,这里可以使用appendReplacement()和appendTail()方法。具体参考如下:http://ifeve.com/java-regex/
关于读写大文件的问题
缓冲流读写
在对测试的小数据替换成功后就需要对整个大文件进行替换了。这里需要读取每行进行替换后并写入到新的文件里,如果每次读取一行就写如一行,由于频繁的IO操作会导致速度很慢。但如果一次性将其读取到StringBuffer里然后再一次写入,会因为数据量大而导致内存堆栈溢出。
相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| BufferedReader br = null; BufferedWriter bw = null; String line = null; StringBuffer buf = new StringBuffer();
InputStreamReader read = new InputStreamReader(new FileInputStream(new File(rfileName)), "utf-8"); br = new BufferedReader(read);
OutputStreamWriter write = new OutputStreamWriter(new FileOutputStream(new File(wfileName)),"utf-8"); bw = new BufferedWriter(write); while((line = br.readLine()) != null) { ... buf.append(line.toString()).append("\n"); } bw.write(buf.toString());
|
这里可以使用缓冲的读写,不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低,带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!(注意需要调用flush方法刷新缓冲区)
1 2
| BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File(rfileName))); br = new BufferedReader(new InputStreamReader(bis, "utf-8"), 20 * 1024 * 1024);
|
(需要注意编码的问题,否则中文会出现乱码)
分割大文件
这里也可以将一个大文件按行分割成若干个文件,再分别对每个小文件进行处理。
相应代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| long timer = System.currentTimeMillis(); int bufferSize = 20 * 1024 * 1024; File file = new File("E:\\Code-Sublime\\Java\\jysg.sql"); FileInputStream fileInputStream = new FileInputStream(file); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); InputStreamReader inputStreamReader = new InputStreamReader(bufferedInputStream, "gbk"); BufferedReader input = new BufferedReader(inputStreamReader, bufferSize); int splitNum = 10-1; int fileLines = 920080; long perSplitLines = fileLines / splitNum; for (int i = 0; i <= splitNum; ++i) { FileWriter output = new FileWriter("E:\\Code-Sublime\\Java\\split" + i + ".sql"); String line = null; for (long lineCounter = 0; lineCounter < perSplitLines && (line = input.readLine()) != null; ++lineCounter) { output.append(line + "\r\n"); } output.flush(); output.close(); output = null; } input.close(); timer = System.currentTimeMillis() - timer; System.out.println("处理时间:" + timer);
|
总结和感想
学会发现和定位问题,并在规定时间内解决(不管用什么方法)
参考资料:
http://ifeve.com/java-regex/
https://blog.csdn.net/niyingxunzong/article/details/33335485