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

推荐订阅源

IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
ThreatConnect
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
H
Help Net Security
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
G
Google Developers Blog
量子位
U
Unit 42
I
InfoQ
V
V2EX
F
Fox-IT International blog
P
Privacy & Cybersecurity Law Blog
V
Visual Studio Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
C
CERT Recently Published Vulnerability Notes
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
The Hacker News
The Hacker News
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
C
Cisco Blogs
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
Recorded Future
Recorded Future
T
Tenable Blog
W
WeLiveSecurity
腾讯CDC
Stack Overflow Blog
Stack Overflow Blog
T
The Blog of Author Tim Ferriss
www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
Docker
C
Cybersecurity and Infrastructure Security Agency CISA
PCI Perspectives
PCI Perspectives

技术改变视野

为程序员、设计师打造的摸鱼社区:摸鱼派! - 技术改变视野 我在 GitHub 上的开源项目 - 技术改变视野 为IDEA/Git配置GPG密钥,点亮GitHub Verified Commit标识 - 技术改变视野 从 SpringMVC 转为使用 SpringBoot —— 手把手带你使用 Intellij IDEA 最快速地搭建 SpringBoot+MyBatis(无 XML)+Thymeleaf - 技术改变视野 无题 - 技术改变视野 年轻人的第一辆京牌电动车 - 技术改变视野 我们忙碌且漫长的一生,有多久是为自己而活? 说说《后浪》 - 技术改变视野 离线使用 Google Fonts:让你的网站更精致 | goofoffline 神器,一键下载生成离线字体库! - 技术改变视野 🎨 适用于 Solo / Bolo 的清新高级主题 —— Sakura | 移植自 WordPress 主题 - 技术改变视野 大白话之Java级联调用:一个类,一条语句,同时调用好几个方法,串成一串 - 技术改变视野 大白话 | Java初学指♂男:“说说初学的误区与死结”( 壹 | 反射与Field ) - 技术改变视野 一个实例理解Java的接口(interface)用处与用法 一个实例带你理解JavaBean WebFilter-SpringBoot过滤器注解实例讲解 实例带你搞懂Java多线程&&线程池之(壹):线程池与多线程的关系和区别 实例带你搞懂Java多线程&&线程池之(贰):简单的线程池应用 - 技术改变视野 实例带你获取多线程Thread的返回值之 (壹) - Callable的运行 实例带你学会简单的Java Thread多线程 实例带你获取多线程Thread的返回值之 (贰) - Callable配合线程池返回数据 一个实例理解Java Runnable多线程用处与用法 一个最简单的实例理解Semaphore在Java中的作用 从零开始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 困扰三周の问题:使用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开始?别闹。

后语

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