惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
aimingoo的专栏
aimingoo的专栏
B
Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
Engineering at Meta
Engineering at Meta
IT之家
IT之家
V
Visual Studio Blog
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
A
About on SuperTechFans
博客园 - 聂微东
Blog — PlanetScale
Blog — PlanetScale
N
News and Events Feed by Topic
A
Arctic Wolf
WordPress大学
WordPress大学
小众软件
小众软件
C
CERT Recently Published Vulnerability Notes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Y
Y Combinator Blog
T
Threat Research - Cisco Blogs
Latest news
Latest news
Simon Willison's Weblog
Simon Willison's Weblog
Cyberwarzone
Cyberwarzone
S
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy International News Feed
J
Java Code Geeks
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
I
Intezer
L
LangChain Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
G
GRAHAM CLULEY
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News and Events Feed by Topic
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
Security Archives - TechRepublic
Security Archives - TechRepublic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

技术改变视野

为程序员、设计师打造的摸鱼社区:摸鱼派! - 技术改变视野 大白话 | Java初学指♂男:“说说初学的误区与死结”( 壹 | 反射与Field ) - 技术改变视野 大白话之耦合性:什么是耦合性和内聚性?用编程语言实例讲解! - 技术改变视野 🎨 适用于 Solo / Bolo 的清新高级主题 —— Sakura | 移植自 WordPress 主题 - 技术改变视野 我在 GitHub 上的开源项目 - 技术改变视野 为IDEA/Git配置GPG密钥,点亮GitHub Verified Commit标识 - 技术改变视野 从 SpringMVC 转为使用 SpringBoot —— 手把手带你使用 Intellij IDEA 最快速地搭建 SpringBoot+MyBatis(无 XML)+Thymeleaf - 技术改变视野 无题 - 技术改变视野 年轻人的第一辆京牌电动车 - 技术改变视野 我们忙碌且漫长的一生,有多久是为自己而活? - 技术改变视野 一个最简单的实例理解Semaphore在Java中的作用 - 技术改变视野 说说《后浪》 - 技术改变视野 离线使用 Google Fonts:让你的网站更精致 | goofoffline 神器,一键下载生成离线字体库! - 技术改变视野 实例带你学会简单的Java Thread多线程 - 技术改变视野 一个实例理解Java的接口(interface)用处与用法 - 技术改变视野 一个实例带你理解JavaBean - 技术改变视野 WebFilter-SpringBoot过滤器注解实例讲解 - 技术改变视野 实例带你搞懂Java多线程&&线程池之(壹):线程池与多线程的关系和区别 - 技术改变视野 实例带你搞懂Java多线程&&线程池之(贰):简单的线程池应用 - 技术改变视野 实例带你获取多线程Thread的返回值之 (壹) - Callable的运行 - 技术改变视野 实例带你获取多线程Thread的返回值之 (贰) - Callable配合线程池返回数据 - 技术改变视野 一个实例理解Java Runnable多线程用处与用法 - 技术改变视野 从零开始OpenSSL之 (壹) - 使用genrsa、rsa、rsautl生成公私钥 - 技术改变视野 从零开始 OpenSSL 之 (贰) - 使用 rsautl 解密文件 - 技术改变视野 大白话之Docker(壹):快速入门&&简单官方实例 - 技术改变视野 大白话之Docker(贰):简单部署一个Tomcat服务并发布内容 - 技术改变视野 大白话之从零讲解DVWA(壹)-SQL注入(SQL Injection) Low Level - 技术改变视野 大白话之从零讲解DVWA(贰)-SQL注入(SQL Injection) Medium/High Level - 技术改变视野 Java日志插件-Slog4J下载 - 技术改变视野 大白话之必会Java Atomic | 线程一点也不安全(一):比自增和synchronized更快速、靠谱的原子操作(调用C语言) - 技术改变视野 Log-MySQL root用户登录后无法查看数据库全部表/正常访问数据库 Access denied for user 'root'@'localhost' to database - 技术改变视野 大白话之Docker(叁):制作一个运行Tomcat服务端的Docker镜像 - 技术改变视野 Github仅保留指定文件/文件夹当前Commit,删除所有历史记录,清除「敏感信息」 - 技术改变视野 将Tomcat、MySQL从Linux迁移到Windows的心路历程(干货):令人恐惧的字符编码 - 技术改变视野 Spring: java.lang.NoSuchMethodError: clearCache | ClassNotFoundException | Error during artifact deployment 思路及解决办法 - 技术改变视野 大白话之Java反射-初学最迷的概念:能干啥?咋用? - 技术改变视野 大白话之Java面向接口编程:最“正经”的中文实例讲解,看不懂来打我! - 技术改变视野 大白话 | 课堂实践:使用Iterator对数组进行遍历 - 技术改变视野 大白话之fail-fast | fail-safe:为什么会有这个机制?它有什么作用? - 技术改变视野 刨根问底 | 大白话:在使用注解后,框架是怎么知道你哪个方法使用了注解的?用@RequestMapping注解举例详解! - 技术改变视野 大白话之必会Java Atomic | 线程一点也不安全(二):Atomic的ABA问题会导致什么情况?如何解决? - 技术改变视野 随笔 | 奇淫技巧 | Java:记return和短路运算符的妙用 - 技术改变视野 随笔 | Tomcat:续-从Linux迁移到Windows编码问题彻底解决 - 技术改变视野 大白话之AutoClosable接口 | try-with-resources:JDK1.7的新特性,提高重复回收效率 - 技术改变视野 大白话之Java多线程join方法:开局一张图,试学一分钟,你就费和我一样,理解介个方法 - 技术改变视野 大白话之Java Stream流:将类数组流化,便捷批量修改,通俗讲解! - 技术改变视野 大白话之Java级联调用:一个类,一条语句,同时调用好几个方法,串成一串 - 技术改变视野
Java 困扰三周の问题:使用byte[]或skip()方法读取字节流Stream文件尾部多/少/缺字节解决方法 - 技术改变视野
adlered · 2019-12-08 · via 技术改变视野

前言

最近在造一个最强兼容性的FTP服务端轮子,但在使用InputStreamOutputStream及它的子类时,我遇到了很奇怪也很严重的问题:数据尾部随机缺少/多出数据。这个问题简直太致命了。

问题复现

先出一段代码,我之前操作数据流的步骤如下(请注意看注释):

 1// 获取文件输出流FileOutputStream和网络输入流InputStream
 2FileOutputStream fileOutputStream = new FileOutputStream(file);
 3InputStream inputStream = socket.getInputStream();
 4// 新建一个字节数组
 5byte[] bytes = new byte[8192];
 6// 从网络输入流inputStream,读取字节并写入到字节数组byte[] bytes
 7while ((inputStream.read(bytes)) != -1) {
 8    // 写入到本地文件中
 9    fileOutputStream.write(bytes);
10}
11// 以防万一,刷新一下缓存
12fileOutputStream.flush();
13// 关闭流
14inputStream.close();
15fileOutputStream.close();

无论使用何种字节流类,我使用new byte[8192]这种字节数组读取数据时,最终的结果总会多出来或者少出来很多字节,非常奇怪,但在逻辑上,我以防万一,还将fileOutputStream中的缓存刷新了出来,按理来讲应该没有问题了。

这个问题在我尝试了BufferedXxxStream、FileXxxStream、XxxStream了以后还是没有得到解决,在无数次的尝试之后,浪费了我三个星期的时间去解决。但在之后的一次偶然write()方法的查阅中,我找到了解决问题的线索。

问题解决过程

首先,我查阅了InputStream的read方法:

方法 用法
read() 从输入流中读取数据的下一个字节,返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回-1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。

解决思路

注:下面的解题思路中,read方法我只调用了一次,忽略了while,方便阅读理解。

  1. 我使用read()方法逐个读取字节写入文件是没有问题的,但是这样逐个读取速度太慢、CPU爆满
1int data;
2data = inputStream.read();
3// 此时data中应存储了输入流中的一个字节
  1. 使用read(byte[] b)的方法,出现了字节错乱的问题,方法也是我一开始认为正确的:
1byte[] bytes = new byte[8192];
2inputStream.read(bytes);
3// 此时bytes中应存储了输入流中的前8192字节(这里是我之前的误解,下面有解释)
  1. 然后突然想到了一个问题:如果网络不好的话,输入流真的每次都能存满8192字节吗?
  2. 果然老师说的没错,不把题读完整,坑的一定是自己。看read(byte[] b)方法解释的后半段,以整数形式返回实际读取的字节数,也就是说,如果我用int接住它的返回值,就能得到本次read方法读取到的真实长度?试试看:
1byte[] bytes = new byte[8192];
2int len = -1;
3len = inputStream.read(bytes);
4// 此时bytes中应存储了相应大小的字节,而len中存储的应是读取到的真实字节长度。
5// 注意,len中存储的是长度,而不是read()不传参方法中的字节。
6// 由于我省略掉了while,实际上下面的方法应该打印了很多次。
7System.out.println("Length: " + len);

运行以后,我获得到的部分结果:

1Length: 8192
2Length: 8192
3Length: 7000
4Length: 8192
5Length: 8092

果不其然啊,在第三次read方法时,实际上只有7000个字节被写入了,还剩下1192个字节直接被填满了0,在多出了一堆数据的同时,文件数据也变得不连贯,直接导致损坏。

解决方案

问题找到了,其它的就简单了,只要我们不把它额外填充的字节写入就可以了。查阅一下OutputStream.write方法的使用:

1write(byte[] b, int off, int len)
2从指定的字节数组写入len个字节,从偏移off开始输出到此输出流。

Wow, AMAZING.

这就简单了,在已知长度n的条件下,我们只需要使用write(bytes, 0, n)就可以了:

1byte[] bytes = new byte[8192];
2int len = -1;
3len = inputStream.read(bytes);
4outputStream.write(bytes, 0, len);

从第0个字节开始,写入到len中指定的下标,OK。
至于为什么从0开始?别闹。

后语

在出现问题时,一定要仔细翻阅官方文档!这是次教训,也是个经验。