java读取超大文件方式

Java NIO读取大文件已经不是什么新鲜事了,但根据网上示例写出的代码来处理具体的业务总会出现一些奇怪的Bug。

针对这种情况,我总结了一些容易出现Bug的经验

1.编码格式

由于是使用NIO读文件通道的方式,拿到的内容都是byte[],在生成String对象时一定要设置与读取文件相同的编码,而不是项目编码。

2.换行符

一般在业务中,多数情况都是读取文本文件,在解析byte[]时发现有换行符时则认为该行已经结束。

在我们写Java程序时,大多数都认为\r\n为一个文本的一行结束,但这个换行符根据当前系统的不同,换行符也不相同,比如在Linux/Unix下换行符是\n,而在Windows下则是\r\n。如果将换行符定为\r\n,在读取由Linux系统生成的文本文件则会出现乱码。

3.读取正常,但中间偶尔会出现乱码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public static void main(String[] args) throws Exception {

 int bufSize = 1024;

 byte[] bs = new byte[bufSize];

 ByteBuffer byteBuf = ByteBuffer.allocate(1024);

 FileChannel channel = new RandomAccessFile("d:\\filename","r").getChannel();

 while(channel.read(byteBuf) != -1) {

 int size = byteBuf.position();

 byteBuf.rewind();

 byteBuf.get(bs);

 // 把文件当字符串处理,直接打印做为一个例子。

 System.out.print(new String(bs, 0, size));

 byteBuf.clear();

 }

 }

这是网上大多数使用NIO来读取大文件的例子,但这有个问题。中文字符根据编码不同,会占用2到3个字节,而上面程序中每次都读取1024个字节,那这样就会出现一个问题,如果该文件中第1023,1024,1025三个字节是一个汉字,那么一次读1024个字节就会将这个汉字切分成两瓣,生成String对象时就会出现乱码。
解决思路是判断这读取的1024个字节,最后一位是不是\n,如果不是,那么将最后一个\n以后的byte[]缓存起来,加到下一次读取的byte[]头部。

以下为代码结构:

NioFileReader

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

package com.okey.util;

  

import java.io.*;

import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;

  

/**

 * Created with Okey

 * User: Okey

 * Date: 13-3-14

 * Time: 上午11:29

 * 读取文件工具

 */

public class NIOFileReader {

  

 // 每次读取文件内容缓冲大小,默认为1024个字节

 private int bufSize = 1024;

 // 换行符

 private byte key = "\n".getBytes()[0];

 // 当前行数

 private long lineNum = 0;

 // 文件编码,默认为gb2312

 private String encode = "gb2312";

 // 具体业务逻辑监听器

 private ReaderListener readerListener;

  

 /**

 * 设置回调方法

 * @param readerListener

 */

 public NIOFileReader(ReaderListener readerListener) {

 this.readerListener = readerListener;

 }

  

 /**

 * 设置回调方法,并指明文件编码

 * @param readerListener

 * @param encode

 */

 public NIOFileReader(ReaderListener readerListener, String encode) {

 this.encode = encode;

 this.readerListener = readerListener;

 }

  

 /**

 * 普通io方式读取文件

 * @param fullPath

 * @throws Exception

 */

 public void normalReadFileByLine(String fullPath) throws Exception {

 File fin = new File(fullPath);

 if (fin.exists()) {

 BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fin), encode));

 String lineStr;

 while ((lineStr = reader.readLine()) != null) {

 lineNum++;

 readerListener.outLine(lineStr.trim(), lineNum, false);

 }

 readerListener.outLine(null, lineNum, true);

 reader.close();

 }

 }

  

 /**

 * 使用NIO逐行读取文件

 *

 * @param fullPath

 * @throws java.io.FileNotFoundException

 */

 public void readFileByLine(String fullPath) throws Exception {

 File fin = new File(fullPath);

 if (fin.exists()) {

 FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();

 try {

 ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);

 // 每次读取的内容

 byte[] bs = new byte[bufSize];

 // 缓存

 byte[] tempBs = new byte[0];

 String line = "";

 while (fcin.read(rBuffer) != -1) {

  int rSize = rBuffer.position();

  rBuffer.rewind();

  rBuffer.get(bs);

  rBuffer.clear();

  byte[] newStrByte = bs;

  // 如果发现有上次未读完的缓存,则将它加到当前读取的内容前面

  if (null != tempBs) {

  int tL = tempBs.length;

  newStrByte = new byte[rSize + tL];

  System.arraycopy(tempBs, 0, newStrByte, 0, tL);

  System.arraycopy(bs, 0, newStrByte, tL, rSize);

  }

  int fromIndex = 0;

  int endIndex = 0;

  // 每次读一行内容,以 key(默认为\n) 作为结束符

  while ((endIndex = indexOf(newStrByte, fromIndex)) != -1) {

  byte[] bLine = substring(newStrByte, fromIndex, endIndex);

  line = new String(bLine, 0, bLine.length, encode);

  lineNum++;

  // 输出一行内容,处理方式由调用方提供

  readerListener.outLine(line.trim(), lineNum, false);

  fromIndex = endIndex + 1;

  }

  // 将未读取完成的内容放到缓存中

  tempBs = substring(newStrByte, fromIndex, newStrByte.length);

 }

 // 将剩下的最后内容作为一行,输出,并指明这是最后一行

 String lineStr = new String(tempBs, 0, tempBs.length, encode);

 readerListener.outLine(lineStr.trim(), lineNum, true);

 } catch (Exception e) {

 e.printStackTrace();

 } finally {

 fcin.close();

 }

  

 } else {

 throw new FileNotFoundException("没有找到文件:" + fullPath);

 }

 }

  

 /**

 * 查找一个byte[]从指定位置之后的一个换行符位置

 * @param src

 * @param fromIndex

 * @return

 * @throws Exception

 */

 private int indexOf(byte[] src, int fromIndex) throws Exception {

  

 for (int i = fromIndex; i < src.length; i++) {

 if (src[i] == key) {

 return i;

 }

 }

 return -1;

 }

  

 /**

 * 从指定开始位置读取一个byte[]直到指定结束位置为止生成一个全新的byte[]

 * @param src

 * @param fromIndex

 * @param endIndex

 * @return

 * @throws Exception

 */

 private byte[] substring(byte[] src, int fromIndex, int endIndex) throws Exception {

 int size = endIndex - fromIndex;

 byte[] ret = new byte[size];

 System.arraycopy(src, fromIndex, ret, 0, size);

 return ret;

 }

  

}

ReaderListener

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

package com.okey.util;

  

import java.util.ArrayList;

import java.util.List;

  

/**

 * Created with Okey

 * User: Okey

 * Date: 13-3-14

 * Time: 下午3:19

 * NIO逐行读数据回调方法

 */

public abstract class ReaderListener {

  

 // 一次读取行数,默认为500

 private int readColNum = 500;

  

 private List<String> list = new ArrayList<String>();

  

 /**

 * 设置一次读取行数

 * @param readColNum

 */

 protected void setReadColNum(int readColNum) {

 this.readColNum = readColNum;

 }

  

 /**

 * 每读取到一行数据,添加到缓存中

 * @param lineStr 读取到的数据

 * @param lineNum 行号

 * @param over 是否读取完成

 * @throws Exception

 */

 public void outLine(String lineStr, long lineNum, boolean over) throws Exception {

 if(null != lineStr)

 list.add(lineStr);

 if (!over && (lineNum % readColNum == 0)) {

 output(list);

 list.clear();

 } else if (over) {

 output(list);

 list.clear();

 }

 }

  

 /**

 * 批量输出

 *

 * @param stringList

 * @throws Exception

 */

 public abstract void output(List<String> stringList) throws Exception;

  

}

ReadTxt(具体业务逻辑)

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

32

package com.okey.util;

  

  

import java.io.File;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

  

  

/**

 * Created with IntelliJ IDEA.

 * User: Okey

 * Date: 14-3-6

 * Time: 上午11:02

 * To change this template use File | Settings | File Templates.

 */

public class ReadTxt {

 public static void main(String[] args) throws Exception{

 String filename = "E:/address_city.utf8.txt";

 ReaderListener readerListener = new ReaderListener() {

 @Override

 public void output(List<String> stringList) throws Exception {

 for (String s : stringList) {

  System.out.println("s = " + s);

 }

 }

 };

 readerListener.setReadColNum(100000);

 NIOFileReader nioFileReader = new NIOFileReader(readerListener,"utf-8");

 nioFileReader.readFileByLine(filename);

 }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值