java 网络字节_Java网络字节流读取的一次实现记录

场景

前段时候做数据管理,提供了一个文件读取的接口。协议规范大致如下:

e561ec07d830c9806939d12ff1085fd0.png

客户端通过http接口获取数据流,在获取过程中今天暴露了一些问题,晒一晒, 希望看到的人免踩坑吧。

最开始的实现

此处省去了网络请求部分,直接看对流读取的部分;

/**

* 一个文件就是一条数据

* @param result 数据存储的对象,是一个{@link JSONObject}列表

* @param input 数据输入流 {@link InputStream}

* @param dataInfo {@link DataInfo}

* @param fileLength 文件长度

* @throws IOException

* @throws UnsupportedEncodingException

*/

private void readDataByFile(List result, InputStream input, DataInfo dataInfo, int fileLength)

throws IOException {

ByteArrayOutputStream bos = new ByteArrayOutputStream();

byte[] fileBytes = new byte[fileLength];

input.read(fileBytes);

bos.write(fileBytes);

result.add(preProcessor.run(new String(bos.toByteArray(), StandardCharsets.UTF_8), dataInfo));

}

方法中 InputStream 参数通过网络获取的输入流,DataInfo 和业务相关,不需要关注,fileLength 是按协议取到的一个文件的长度, 代码的目的是获取到一个文件的内容,并且通过一些其他处理最终转为了一个jsonObject;

方法开始使用是没有问题的,因为读取的文件都比较小,但是前两天刚刚上线一个业务,文件相较大,当读取频率较高时,系统内存溢出了;

针对内存溢出,做了两处改进

一次少读点;

不要再去转字节数组;

1. 一次少读点

byte[] fileBytes = new byte[fileLength];

代码每次都new了一个byte大对象,GC来不及回收,容易造成内存溢出;

要读到fileLength个字节,当字节数小于4096时,一次读完;当大于4096时,每次读取4096个,改造后代码大致如下:

private void readDataByFile(List result, InputStream input, DataInfo dataInfo, int fileLength)

throws IOException {

// 若文件长度小于4096

ByteArrayOutputStream bos = new ByteArrayOutputStream();

if (fileLength < 4096) {

byte[] fileBytes = new byte[fileLength];

input.read(fileBytes);

bos.write(fileBytes);

} else {

int left = fileLength % 4096;

int x = fileLength / 4096;

byte[] buffer = new byte[4096];

int bytesRead = -1;

for (int i = 0; i < x; i++) {

bytesRead = input.read(buffer);

bos.write(buffer);

}

if (left > 0) {

byte[] leftBytes = new byte[left];

input.read(leftBytes);

bos.write(leftBytes);

}

}

result.add(preProcessor.run(new String(bos.toByteArray(), StandardCharsets.UTF_8) , dataInfo));

}

1. 不要再去转字节数组

看最后一行代码

new String(bos.toByteArray(), StandardCharsets.UTF_8)

这句看了下ByteArrayOutputStream 源码,toByteArray 其实是调用了Arrays.copy来复制数组

/**

* Creates a newly allocated byte array. Its size is the current

* size of this output stream and the valid contents of the buffer

* have been copied into it.

*

* @return the current contents of this output stream, as a byte array.

* @see java.io.ByteArrayOutputStream#size()

*/

public synchronized byte toByteArray()[] {

return Arrays.copyOf(buf, count);

}

实际上 ByteArrayOutputStream 有个toString方法可以直接调用。都是API没研究好啊,多new个数组好多余

/**

* Converts the buffer's contents into a string decoding bytes using the

* platform's default character set. The length of the new String

* is a function of the character set, and hence may not be equal to the

* size of the buffer.

*

*

This method always replaces malformed-input and unmappable-character

* sequences with the default replacement string for the platform's

* default character set. The {@linkplain java.nio.charset.CharsetDecoder}

* class should be used when more control over the decoding process is

* required.

*

* @return String decoded from the buffer's contents.

* @since JDK1.1

*/

public synchronized String toString() {

return new String(buf, 0, count);

}

还没完。。。

在运行代码,发现后续还是会报错,又经过一番排查,发现读取的内容偶尔会有缺失,定位到InputStream 因为是网络输入流,一次read很可能读取不完,再次改造代码

private void readDataByFile(List result, InputStream input, DataInfo dataInfo, int fileLength)

throws IOException {

ByteArrayOutputStream out = new ByteArrayOutputStream();

int len = -1;

int total = 0; // 记录总读取的字节数

int last = 0; // 记录最后一批要读取的数量

if (fileLength <= 4096 ) {

last = fileLength;

}

// 要读取fileLength个字节

byte[] fileBytes = new byte[4096];

while (last=0 && (len = input.read(fileBytes)) > -1) {

out.write(fileBytes, 0, len);

total += len;

// 读到最后一批待读字节结束

if (fileLength - total <= 4096) {

last = fileLength - total;

}

}

// 读取最后一批待读字节

byte[] lastBuffer = new byte[last];

while (total < fileLength && (len = input.read(lastBuffer)) > -1) {

out.write(lastBuffer, 0, len);

total += len;

}

result.add(preProcessor.run(out.toString("UTF-8"), dataInfo));

}

OK! 顺便说下,ByteArrayOutputStream 之所以不用释放资源,不妨看下它的close方法做了什么。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值