最近在项目中需要读取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);// 20M缓存

(需要注意编码的问题,否则中文会出现乱码)

分割大文件

这里也可以将一个大文件按行分割成若干个文件,再分别对每个小文件进行处理。

相应代码如下:

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;//设读取文件的缓存为20MB

//建立缓冲文本输入流
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