读取文件

刚学Java的IO流部分时,书上说只能使用字节流去读取图片、视频等非文本二进制文件,不能使用字符流,否则文件会损坏。所以我就一直记住这一点了,但是为什么不能使用,这一直是我的一个疑惑。今天,我又想到了这个问题,所以干脆就一鼓作气把它解决了吧。

先来看一个关于图片复制的代码示例:
注意:我的电脑是存在 D:/DB这个路径的,如果你没有,DB这个文件夹,必须建立一个。

package dragon;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;public class ReadImage {public static void main(String[] args) throws IOException {String imgPath = "D:/DB/husky/kkk.jpeg";String byteImgCopyPath = "D:/DB/husky/byteCopykkk.jpeg";String charImgCopyPath = "D:/DB/husky/charCopykkk.jpeg";Path srcPath = Paths.get(imgPath);Path desPath1 = Paths.get(byteImgCopyPath);Path desPath2 = Paths.get(charImgCopyPath);byteRead(srcPath.toFile(), desPath1.toFile());System.out.println("字节复制执行成功!");characterRead(srcPath.toFile(), desPath2.toFile());System.out.println("字符复制执行成功!");}static void byteRead(File src, File des) throws IOException {try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des))) {int hasRead = 0;byte[] b = new byte[1024];while ((hasRead = bis.read(b)) != -1) {bos.write(b, 0, hasRead);}}}static void characterRead(File src, File des) throws IOException {try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(src), "UTF-8"));BufferedWriter writer = new BufferedWriter(new FileWriter(des))) {int hasRead = 0;char[] c = new char[1024];while ((hasRead = reader.read(c)) != -1) {writer.write(c, 0, hasRead);}}}
}

运行结果:
可见,使用字符流确实无法读取图片这样的二进制文件,必须使用字节流。

图片大小变化:
可见,使用字符流后图片大小变化了,使用字节流则不会。

为什么会这样呢?

通过上面那个例子,我们可以看到确实是无法使用字符流复制文件,并且使用字符流复制文件后,文件的大小也会变化,这就引出我们今天要讨论的标题了。

我们先来想一想,为什么文本文件打开可以显示文字?
我们都知道计算机处理的文件无论是文本还是非文本的文件,最终在计算机内部都是以二进制的形式存储的。

使用文本编辑器的16进制模式打开一个文本文件:

使用编辑器的16进制模式打开上面程序使用的图片文件:

对比两张图片中的数据,应该发现不了什么区别吧,但是为什么文本数据就可以显示出文字呢?这是一个非常基础的问题,大学里面的基础课都是讲过这方面的内容–字符编码表
我最开始学习的是 C 语言,接触最早的编码表是 ASCII(美国信息交换标准代码),后来学习java接触的是 Unicode(万国码,这个名字和它的起源很契合。我们目前最常使用的是UTF-8,是针对Unicode的一种可变长度字符编码。)

注意:
使用 UTF-8 也是分为含有 BOM(Byte Order Mark,字节顺序标记) 和 没有的两种形式,而且混用会导致错误,感兴趣的可以去了解一下。

字符编码表的作用体现在编码上,引述百科的一段话:

在显示器上看见的文字、图片等信息在电脑里面其实并不是我们看见的样子,即使你知道所有信息都存储在硬盘里,把它拆开也看不见里面有任何东西,只有些盘片。假设,你用显微镜把盘片放大,会看见盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化;凸起的地方代表数字1,凹的地方代表数字0。硬盘只能用0和1来表示所有文字、图片等信息。那么字母”A”在硬盘上是如何存储的呢?可能小张计算机存储字母”A”是1100001,而小王存储字母”A”是11000010,这样双方交换信息时就会误解。比如小张把1100001发送给小王,小王并不认为1100001是字母”A”,可能认为这是字母”X”,于是小王在用记事本访问存储在硬盘上的1100001时,在屏幕上显示的就是字母”X”。也就是说,小张和小王使用了不同的编码表。

所以字符编码表就是二进制数字和字符之间的一个一一映射,例如 65 (数字)代表 A,所以下面这段代码会在屏幕上输出 A。

char c = 65;
System.out.println(c);

我们使用一个循环来测试一下:

char c = 0;
for (int i  = 9999; i < 10009; i++) {c = (char) i;System.out.print(c+" ");
}

测试结果:(当然了,这个取决于你的当前的字符编码表,如果使用 ASCII,估计就有意思了。)

这样就解释了前面那个问题(为什么文本文件打开可以显示文字?),我们之所以可以看见文本文件的字符是因为计算机按照我们文件的编码(ASCII、UTF-8或者GBK等),从字符编码表中找出来对应的字符。 所以,当我们使用记事本打开二进制文件会看到乱码,这就是原因。文件的复制过程也是复制的二进制数据,而不是真实的文字。

因此可以这样理解文件复制的过程:
字符流:二进制数据 --编码-> 字符编码表 --解码-> 二进制数据
字节流:二进制数据 —> 二进制数据

所以问题就是出现在编码和解码的过程中,既然是字符的编码表,那它就是包含所有的字符,但是字符的数量是有限的,这就意味着它不能表示一些超过编码表的字符,因为根本不存在表中。所以,JVM 会使用一些字符进行替换,基本上都是乱码(所以大小会发生变化),而且如果有一个数据恰好是-1,那么读取就会中断,引起数据丢失。



例如如下代码使用字符流读取就会错误:

 String filename = "D:/DB/fos.txt";     //文件名byte[] b = new byte[] {-1, -1};      //两个字节,127的二进制就是 1111 1111//数据写入文件try (FileOutputStream fos = new FileOutputStream(filename)) {fos.write(b, 0, b.length);  //将两个127连续写入,就是 1111 1111 1111 1111}File file = new File(filename);//输出文件的大小System.out.println("file length: " + file.length());char[] c = new char[2];//使用字符流读取文件try (FileReader reader = new FileReader(filename)) {int count = reader.read(c);    //Java使用Unicode编码,读取的是从 0-65535 之间的数字。System.out.println("以文本形式输出:" + new String(c, 0, count)+"   "+count);for (char d : c) {  System.out.println("字符为:" + d);}}System.out.println("表示字符:" + c[0]);//再写入文件try (FileWriter writer = new FileWriter(filename)) {writer.write(c, 0, 2);}File f = new File(filename);System.out.println("file length: " + f.length());

结果:



说明:
我将两个1字节的-1写入(字节流)了文本文件(注意是字节:-1,不是字符:-1),然后再读取(字符流),再写入(字符流)就已经出现了问题。读取出的字符显示了一个奇怪的符号,而且它的值为:65533,这个值如果用字节表示的话,一个字节是不够的,所以文件的大小就会变化。在非文本的二进制数据中,出现这种情况都是正常的,因为本来就不是按照字符编码的。

因为字符都是正数,而非字符编码的话,字节数可能是负数(很可能),但是负数在字符看来就是正数,这也是为什么-1,被读成 65533的原因。可以看出来,读取就已经错误了。

注意: 这里的重点是对于使用字符流读取非文本文件,在读取-写入的过程中的问题。



总结

这个问题算是基本解决了,如果想要了解更多,估计需要阅读一些专业的书籍才行了,不过到了这一步,我觉得已经可以了。它也要求我们掌握关于计算机的一些基本的入门知识了。虽然这个问题拖了很久才解决,但是也是因为我最近开始使用Java的IO流进行编程,以前的话,只是记住了那句话,但是动手实践却没有去做,这也是应该多动手编程、多积累才能解决问题。

【转】为什么不能使用字符流读取非文本的二进制文件?相关推荐

  1. 字符流读取,乱码问题

    碰到问题,字符流读取文本文件,读取输出,强转成char出现乱码问题. 题目 用流统计文本文件的字符个数 public static int getSum() {int count = 0;Buffer ...

  2. Java中使用字符流读取UTF-8和写出txt文件 乱码 问题

    乱码问题一直都是非常难受的问题,本文解决Java中使用字符流读取UTF-8和写出txt文件 乱码 话不多说,直接上图 输出结果: 使用代码: 解决:

  3. Java测试字节流和字符流,以及带缓存的字符流读取速度对比

    测试用的文件为txt格式文件,大小为2.12MB package IO;import java.io.BufferedReader; import java.io.File; import java. ...

  4. java 字节流读取图片,字符流读取,二进制读取

    这两天在学习java中如何对文件进行读取,首先当然是对最简单的文件txt文件进行操练了.并且,逐渐的了解了Java中IO流是如何对文件进行操作的 操练了一段时间,便开始了对图片进行读取,开始的想法如下 ...

  5. fileReader字符流读取中文乱码的解决办法

    在使用字符流读取文件中的数据时,中文会因为编码的原因出现乱码: windows系统默认编码可以在DOS中查看,936中国 - 简体中文(GB2312): idea的默认编码为utf-8: 所以在使用f ...

  6. 重学 Java 之 5种字符流读取方法

    Reader java 中的 IO 输入流不是只有 InputStream 还有按字符输入的 Reader. 和 InputStream 一样,Reader 也是所有字符输入流的超类.主要的方法是:p ...

  7. Stream流、FiLe和IO流、IO流(字节流-拷贝文件_和_字符流-读取文本中的数据写入文本文件中)9-10-11

    package com.streamdemo; import java.util.ArrayList; import java.util.List; /*** 体验Stream流** 创建一个集合,存 ...

  8. java 字符流读取_Java 字符流读写文件

    据说,java读写文件要写很多,贼麻烦,不像c艹,几行代码就搞定.只能抄抄模板拿来用了. 输入输出流分字节流和字符流.先看看字符流的操作,字节转化为字符也可读写. 一.写入文件 1.FileWrite ...

  9. 字符流读取的全部方法

    备忘总结: import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; impor ...

最新文章

  1. No module named ‘fvcore.nn.distributed‘
  2. 计算机技能测试小学老师,小学信息技术教师专业技能测习题-20210726171728.docx-原创力文档...
  3. mysql执行出错:Table 'k_user' is read only
  4. 深入浅出Yolo系列之Yolov3Yolov4Yolov5核心基础知识完整讲解
  5. VS2008 ,TFS2008破解序列号
  6. Java多线程 - 线程组
  7. mysql自动生成日期序列号_mysql – 在一天内为实体生成唯一的序列号
  8. oracle 10g 关库,Oracle Db10g 启动和关闭数据库
  9. 使用log4Net 输出日志到mongodb
  10. 化学方程式作评、数学上成知识百科……那些“别人家的老师”有何特别?
  11. 90后,是时候想想你的副业了
  12. LeetCode 287. Find the Duplicate Number
  13. elixir 关键字列表
  14. js获取action中返回的值
  15. paip. mysql如何临时 暂时 禁用 关闭 触发器
  16. xp系统snmp安装包_一款纯净的PE系统
  17. 做教学直播时,如何做PPT课件直播?
  18. 计算机硬件驱动备份,驱动备份还原软件工具
  19. 互联网系统应用架构基础分析
  20. Java开发的发展前景和未来规划方向

热门文章

  1. 【C语言进阶深度学习记录】五 C语言中变量的属性
  2. 20172327 2018-2019-1 《程序设计与数据结构》第八周学习总结
  3. C#-interface
  4. 第四周读书笔记《构建之法》
  5. 动画---图形图像与动画(三)Animation效果的XML实现
  6. Visual Studio调试之断点基础篇
  7. 150 Evaluate Reverse Polish
  8. 机器学习的一些注意事项
  9. 【数据结构与算法】【算法思想】回溯算法
  10. Java学习笔记7-1——注解与反射