java 输入输出流

什么是字节

字节是指一小组相邻的二进制数码。通常是8位作为一个字节。它是构成信息的一个小单位,并作为一个整体来参加操作,比字小,是构成字的单位。

字节(Byte) 是一种计量单位,表示数据量的多少,它是计算机信息技术用于计量存储容量的一种计量单位.

什么是字符

我们想象一下,给你一串二进制码,要你来分辨它是什么含义,是代表数字还是字母还是汉字,你能有效的分辨吗?

显然不能,一般来说,我们是比较难以理解一串二进制码代表的含义的,而且一串二进制码是代表什么含义也无法很直观的表示出来。

我们比较好识别的是文字,字母和符号。

所以就有了字符,字符是指计算机中使用的文字和符号,比如1、2、3、A、B、C、~!·#¥%……—*()——+、等等。

字符在计算机中可以看做:字节+编码表

什么意思呢?

我们知道,计算机是只识别二进制的,但是我们日常操作电脑,需要输入文字,字母,数字这些,我们不可能先去记住一串二进制数字,比如说A这个字母的二进制是什么,因为这样太麻烦,也记不住,所以编码表,就诞生了,编码表的作用就是在我们进行输入的时候,将我们输入的字符转换成计算机能识别的二进制,在我们阅读数据的时候,将二进制转换成我们人能识别的文字字母和数字

最先普及的就要数ASCLL码表了,ASCLL码表是美国信息交换标准代码,是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。

看到这你肯定会有疑问,这ASCLL码表只有英语和西欧语呀,那汉语呢,其他语言呢?

是的,自从ASCLL码表推出之后,很多国家也都推出了本国语言的编码表。像中国就有GB2312GBK等等。

现在我们一起设想一个场景,当我们编辑一个文本文件,输入了很多字符,这些字符都用ASCLL码表编码,然后我们查看这个文本文件的时候,是使用的GBK码表解码,会出现什么问题吗?

相信你已经有答案了,这会出现软件开发中非常常见的问题:乱码

当我们对字节进行编码的时候使用的是一种编码表,而解码的时候使用的是另一种编码表的时候,就会出现乱码的问题了,是因为每一个编码表,它的字符对应二进制的字节是不一致的。

但是互联网是一个互联互通的平台,所以如果每个国家都使用自己的一套编码器,就会出现许多问题。

1992年的时候,推出了UTF-8编码规范,是一种针对Unicode的可变长度字符编码,又称万国码,UTF-8用1到6个字节编码Unicode字符。用在网页上可以统一页面显示中文简体繁体及其它语言(如英文,日文,韩文)。

UTF-8也是我们目前在应用开发中使用的最多的编码格式。

Java中默认采用的是Unicode编码格式(具体来说是UTF-16编码)。

什么是IO流

IO流中的IOInputOutput,输入和输出的意思,是用来处理设备与设备之间的数据传输的,不仅能处理内部设备(比如CPUGPU、内存),还能处理外部设备(比如手机和PC,客户端与服务器)。

在Java中定义数据按照流向,分为输入流输出流

首先我们来了解输入流,从字面上就很容易理解,凡是从外部流入的数据都可以通过输入流来处理。比如读取文件。

输出流,就表示从内部流出的数据,比如:我们编辑了一个文本文件,当我们按下ctrl+s的时候,就将该文件从内存保存到了硬盘,这就是一个将数据从内存中输出到硬盘的过程。

除了输出和输入流,流按照操作的数据还分为:字节流字符流

总体结构如下图:

输入流

我们通过一个示例,来看看输入流应该如何使用,首先我们在D盘下创建一个hello.txt文件。输入文本Hello Java Hello InputStream

main方法中加入如下代码:

输出:

Hello Java Hello InputStream

代码解释:

这个例子我们主要目的是,读取文件中的数据并将数据显示在控制台。

实现步骤是:首先读取文件转换成文件输入流(FileInputStream),然后定义一个字节数组作为容器用来存储即将读取到的数据。fs.read(b)函数的作用是将数据读取到b数组中,最后通过编码表,将字节数组编码成字符。

输出流

我们使用输出流将字符串hello educoder写入到一个文件中:

运行这段代码,打开D盘下你会发现test.txt文件被创建了,并且文件的内容是hello educoder

代码解释:

最佳实践

上面作为示例的两段代码都是存在很大问题的,什么问题呢?

因为在Java中对于流的操作是非常消耗资源的,如果我们使用流对一个资源进行操作了之后却没有释放它的资源,这就会造成系统资源的浪费,如果积累了很多这种空置的资源,最后可能会导致系统崩溃。

上述代码的最佳实践为:

 
  1. OutputStream out = null;
  2. try {
  3. String file = "D://test.txt";
  4. out = new FileOutputStream(file);
  5. String str = "hello educoder";
  6. byte[] b = str.getBytes();
  7. out.write(b);
  8. out.flush();
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. } finally {
  12. if (out != null) {
  13. try {
  14. out.close(); // 释放该输出流
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }

核心就是在使用完流之后,释放它所占用的资源。

read()方法

我们来看read方法的详细解释:

方法参数说明方法描述
int read(char[] cbuf)字符数组将字符流中的数据读入到字符数组中,如果读取到文件末尾则返回-1,否则返回读取到的长度

理解了read方法,之前的问题就好解决了。

代码:

 
  1. String file = "D://hello.txt";
  2. FileReader fr = new FileReader(file);
  3. char[] cbuf = new char[1024];
  4.  
  5. int len = fr.read(cbuf);//将数据读入到cbuf中并返回读取到的数据长度
  6.  
  7. StringBuilder builder = new StringBuilder();
  8.  
  9. builder.append(cbuf,0,len); //将cbuf 0 到len长度的数据添加到builder
  10.  
  11. System.out.println(builder.toString());
  12.  

运行这段代码,我们会发现输出是正确的,没有再打印出多余的空格。

可能我们又会有疑问了,如果文本文件大于1K,这段代码肯定就行不通了,怎么办呢?

很简单,加个循环就可以啦:

 
  1. String file = "D://hello.txt";
  2. FileReader fr = new FileReader(file);
  3. char[] cbuf = new char[1024];
  4.  
  5. int len = 0; // 每次读取的长度
  6.  
  7. StringBuilder builder = new StringBuilder();
  8.  
  9. while ((len = fr.read(cbuf)) != -1) {
  10. builder.append(cbuf,0,len);
  11. }
  12. System.out.println(builder.toString());

这样修改之后我们就可以读取任意的文件,并将其内容输出到控制台了。

write()方法

write()方法有两种常用的重载方法:

方法参数说明方法描述
void write(String str)字符串将字符串写入到字符流中
void write(char[] cbuf,int off,int len)字符数组,偏移量(从什么位置开始),写入的长度将字符数组按照偏移量写入到字符流中写入的长度和偏移量与len有关

理解了这两种方法,我们现在如果要复制一个文本文件就很方便了,现在我们就来将D盘下hello.txt文件复制到E盘下,并重命名为abc.txt

 
  1. FileReader fr = new FileReader("D://hello.txt"); //定义FileReader读取文件
  2. int len = 0; //每次读取的字符数量
  3. char[] cbuf = new char[1024]; //每次读取数据的缓冲区
  4.  
  5. FileWriter fw = new FileWriter("E://abc.txt"); //定义FileWriter写文件
  6. while((len = fr.read(cbuf)) != -1){
  7. fw.write(cbuf,0,len);
  8. }
  9. fw.close(); //释放资源 刷新缓冲区
  10. fr.close();

这段代码就是一个边读边写的过程,运行之后我们发现E盘下已经有了abc.txt文件并且内容和hello.txt一致。

使用字节流读写文件

到目前为止我们一直操作的都是文本文件,不过我们计算机中存储的文件可不止有文本文件,还有很多其他类型的,比如图片,视频,等等。

如果要对非文本类型的文件进行操作,应该怎么做呢?这个时候字符流还能不能派上用场呢?

答案是否定的,字符流只适用于操作字符类型的文件,不能操作非字符类型的。

所以这个时候应该用什么来操作呢?

相信你已经想到了:字节流。

是的我们需要使用字节流来操作非字符类文件。

接下来,我们使用字节流来复制一个图片文件,代码:

 
  1.  
  2. FileInputStream fs = new FileInputStream("D://user.jpg"); //定义文件输入流读取文件信息
  3. FileOutputStream fos = new FileOutputStream("E://new.jpg");//定义文件输出流写文件
  4.  
  5. int len = 0; //每次读取数据的长度
  6. byte[] bys = new byte[1024]; //数据缓冲区
  7.  
  8. while( (len = fs.read(bys)) != -1){
  9. fos.write(bys, 0, len);
  10. }
  11.  
  12. //释放资源 刷新缓冲区
  13. fs.close();
  14. fos.close();

运行即可看到E盘下生成了一个名为new.jpg的文件,且内容和user.jpg一致 可以发现上述代码和之前的字符流很像,确实原理都是类似的。

可能学到这,你会有很多疑问:

  1. 字节流既然可以用来读取非字符构成的文件,那可以读取字符类型的文件吗? 答案是可以的,字节流可以操作所有类型的文件,因为计算机中的数据都是以字节的方式存储的;

  2. 既然字节流可以用来操作所有的文件,那还要字符流干啥咧? 因为字符流操作字符类型的数据和文件要比字节流快很多。

扩展

使用BufferedReader读取字符文件的速度要比我们之前使用的字节流和FileReader快很多,示例代码:

 
  1. BufferedReader bf = new BufferedReader(new FileReader("D://hello.txt"));
  2. BufferedWriter writer = new BufferedWriter(new FileWriter("D://abc.txt"));
  3.  
  4. String str = "";
  5.  
  6. while( (str = bf.readLine()) != null){
  7. writer.write(str);
  8. }
  9.  
  10. bf.close();
  11. writer.close();
  • 12
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值