浅谈Java之IO流

一、前言

流是一种抽象的概念,Java中也有许多不同用途的流,但我们可以具象化地理解他们,比如用于方便进行数组和集合操作的Stream流可以看作一条工厂中的流水线;又比如本篇文章要讲的用于读写数据的IO流则可以看作一条连通两地的河流。在我看来,Java中的每一个技术点若要仔细展开来讲,都需要付出较多的时间,因此对于初识IO流的读者们,我认为首先应该理解清楚以下几点问题:

  • 什么是IO流?IO流又有什么作用?
  • 怎么使用字节流或字符流进行简单的文件数据读取写入?
  • 字节流和字符流读取数据的方式有何不同?
  • 了解不同的字符集以及字符编码方案

本文会对以上问题进行解释并展示IO流常用的派生类。

二、简述IO流

作用
IO流中,I即Input指写入,O即Output指写出。其作用是进行文件数据的读写操作。
流的分类
按照读取数据单位的不同分为:字节流、字符流
按照流的方向的不同分为:输入流、输出流

三、使用字节流或字符流进行文件数据的简单读写

1、使用字节流进行文件的读写

我已经提前创建好了一个写有内容的文件,文件名为file其内容如下
在这里插入图片描述
使用字节输入流读取出文件内容

public class Demo {
    public static void main(String[] args) throws IOException {

        FileInputStream inputStream = new FileInputStream("file");
        //存放read读取一个字节并返回的对应十进制的值,读到数据末尾时read方法返回-1
        int temp;
        //利用while循环读取文件数据,判断条件就是temp是否为-1
        while ((temp = inputStream.read()) != -1) {
            System.out.print((char) temp);
        }
        inputStream.close();
    }
}

程序运行结果:
返回结果
需要注意的是!!!在代码最后应该关闭输入输出流,否则会导致资源泄露和内存泄漏,在我们直观看来就是文件中内容的读取可能不完整,程序的运行速度变慢。
使用字节输出流向指定文件中写入数据

public class Demo {
    public static void main(String[] args) throws IOException {

        FileOutputStream outputStream = new FileOutputStream("file"); //创建字节输出流
        outputStream.write(97); //小写字母a在ASCII码表中对应的十进制数据
        outputStream.write(98); //小写字母b在ASCII码表中对应的十进制数据
        outputStream.write(99); //小写字母c在ASCII码表中对应的十进制数据
        outputStream.write(100); //小写字母d在ASCII码表中对应的十进制数据
        outputStream.close(); //关闭字节输出流
    }
}

写入文件中的数据如下:
在这里插入图片描述
需要注意的是!!!字节输出流在用write方法写入数据时传入的参数也应是指定字符在ASCII码表中对应的十进制数值即int类型的值。

2、使用字符流进行文件的读写

指定被读取的文件的内容如下:
在这里插入图片描述
使用字符输入流读取出文件内容

public class Demo {
    public static void main(String[] args) throws IOException {

//        创建字符输入流
        FileReader reader = new FileReader("file");
        //存放read读取一个字符并返回的对应十进制的值,读到数据末尾时read方法返回-1
        int temp;
//        同样利用循环进行数据的读取
        while ((temp = reader.read()) != -1) {
            System.out.print((char) temp);
        }
        reader.close();
    }
}

运行结果如下:
在这里插入图片描述
使用字符输出流向指定的文件中写入数据

public class Demo {
    public static void main(String[] args) throws IOException {

		//创建字符输出流
        FileWriter writer = new FileWriter("file");
        writer.write(97); //小写字母a在ASCII码表中对应的十进制数据
        writer.write(98); //小写字母b在ASCII码表中对应的十进制数据
        writer.write(99); //小写字母c在ASCII码表中对应的十进制数据
        writer.write(30000); //汉字田在Unicode编码中对应的十进制数据
        writer.write("直接写入字符串数据");
        writer.close();
    }
}

写入文件中的数据如下:
在这里插入图片描述

四、字节流和字符流读取数据的方式有何不同?

从第三部分中我们知道了如何进行简单的读取和写入文件数据,但细心的读者可能发现了我在使用两种流进行数据的读写时内容是有所不同的,使用字节流读写文件时我并没有加入汉字,在使用字符流时我却加入了汉字。Why?
这里就是两种流读写数据方式不同而导致的,我在注释中也写明了,字节流读取数据时常用的方法是读取一个字节并返回ASCII码对应的十进制数值,而字符流是读取一个字符并返回Unicode表中所对应的十进制数值。
让我们来试试使用字节流读取含有汉字的文件:
文件中的数据同样是在这里插入图片描述
使用先前使用过的字节流读取文件的代码所读取到的数据如下
在这里插入图片描述
我们能明显看出读取出的数据与文件中的内容不符,而先前我们使用字符流读取时却能得到正确的内容。
我们可以先小小地总结一下,若是需要读取的文件存在汉字(其实还有其他字符)就使用字符流否则就使用字节流。更为准确的说法是:字节流可以读取所有类型的文件,包括文字、图像、二进制等,他会将所有数据都当作二进制数据来处理;字符流可以读取纯文本文件,并且可以在读取时进行字符集的转换。
补充:什么是二进制数据什么是文本数据?
二进制数据是计算机中以二进制代码表示的数据,通常用于存储和传输图像、音频、视频等非文本内容。二进制数据包含许多位,其中每一位都只有0或1两种状态。
文本数据则是由字符组成的序列,通常用于存储和传输文本文件、电子邮件、网页等文本内容。文本数据通常使用不同的编码标准将字符映射为数字或二进制代码,例如ASCII、Unicode等。

字符集与字符编码方案

字符集:字符集为每个字符都定义了属于自己的唯一编号,就像我们的身份证号码一样。每一种字符集就是一组可用字符的集合。
字符编码方案:字符编码方案是一种将指定的字符集中的每个字符映射成二进制编码的方式,一般情况下不同的编码方案对字符用的是不同的字节数表示。
总的来说:字符集是一种可用的字符集合,而字符编码方案则是将字符集映射成计算机可识别的二进制编码的方案。
常见的字符集:ASCII字符集、Unicode字符集。
ASCII字符集:ASCII是对拉丁字母的字符编码标准,采用的是七位的二进制数对应一个字符,因为一位只能为0或1所以ASCII总共可以表示128个不同的字符,但显然这在世界互联的时代是不够用的。
Unicode字符集:Unicode采用的是一种16位的编码,又称为万国码,因为他包含了世界上所有的字符符号,他正是为统一世界上所有语言字符符号而生的,他适用于不同的编码方案。需要知道的是,ASCII编码是被包含在Unicode编码中的,他是Unicode字符集的一部分。
常用的中文编码方案:GBK、UTF-8。
GBK编码方案:GBK编码方案是一种针对中文字符的编码方案,其中中文字符占两个字节,其他字符占一个字符
UTF-8编码方案:UTF-8编码方案是一种可变长的编码方式,因为他可以表示1到4个字节不等的字符,能够更好地处理不同语言字符集,是目前较为流行的编码方案。其中中文占三个字节,英文占一个字节。

1、产生乱码的原因

从以上的描述中我们可以总结出产生乱码的原因:

  • 读取数据时没有完整读取字符所对应的所有字节数据(比如字节流一次只读取一个字节)
  • 编码和解码方式的不统一(比如采用UTF-8向文件中写入数据,然后采用GBK方式读取文件内容)

2、乱码原因演示

乱码原因一:读取数据时没有完整读取字符所对应的所有字节数据

public class Demo {
    public static void main(String[] args) {

//        读取文件时
//        在本文第四部分——字节流和字符流读取数据的方式有何不同?
//        中我已经演示过了采用字节流读取含有中文的文件时会产生乱
//        码,其原因简单来讲就是存在需要大于一个字节来表示的字符,
//        而字节流一个就读取一个字节并返回对应ASCII码值
//        这里我就简单演示一下英文所占字节数与中文所占字节数的不同:
        String str1 = "abcd"; //只含有英文的数据,长度为4
        byte[] bytes1 = str1.getBytes(); //转为字节数组
        System.out.println(Arrays.toString(bytes1));

        String str2 = "a中文1"; //含有中文的数据,长度为4
        byte[] bytes2 = str2.getBytes();
        System.out.println(Arrays.toString(bytes2));
    }
}

在这里插入图片描述
根据控制台输出的字节数组可以看出中文占了三个字节,字节流读取时一次读取的是一个字节,所以我们直接对读取出的数据进行强转时是不会得到正确的字符的;而采用字符流的话会根据所用字符编码方案的不同读取不同长度的字节,其实字符流的底层就是字节流,比如UTF-8编码读取时,字符流会先读取第一个字节,然后通过字节的高位来判断该字符所占的字节数,然后根据所占字节数读取后续字节。
乱码原因二:编码和解码方式的不统一
现在我有一个字符串数据,我先用转换流指定UTF-8的方式将其写入一个文件中,再用指定GBK将其输出。
转换流简介:转换流是字符流和字节流之间的桥梁,它使得字节流可以使用字符流中的方法。

public class Demo {
    public static void main(String[] args) throws IOException {

//        使用转换流指定编码方式并写入数据
//        使用UTF-8的话可以不用声明,因为默认编码方式就是UTF-8
        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("file"), "UTF-8");
        writer.write("123中文abc");
        writer.close();

//        使用转换流指定编码方式并读出数据
//        使用GBK编码方式读出数据
        InputStreamReader reader = new InputStreamReader(new FileInputStream("file"), "GBK");
        int temp;
        while ((temp = reader.read()) != -1) {
            System.out.print((char) temp);
        }
        reader.close();
        System.out.println();
        System.out.println("========================");

//        我们再采用默认的UTF-8的编码方式读出数据
        InputStreamReader reader2 = new InputStreamReader(new FileInputStream("file"));
        int temp2;
        while ((temp2 = reader2.read()) != -1) {
            System.out.print((char) temp2);
        }
    }
}

写入文件中的数据:
在这里插入图片描述
控制台的输出数据:
在这里插入图片描述
我们通过验证,的确在编码和解码时采用不同的方式时是会产生乱码的。而GBK对于中文的编码规范是中文占两个字节而UTF-8对于中文是占三个字节。

案例:使用缓冲流进行文件的拷贝

需求:现需要对一个文件名为file的文件进行拷贝,并为拷贝出的文件随机生成文件名。文件内容如下
在这里插入图片描述

public class Demo {
    public static void main(String[] args) throws IOException {

        //创建字节缓冲输入流
        BufferedInputStream is = new BufferedInputStream(new FileInputStream("file"));
        //随机生成目标文件名
        String fileName = UUID.randomUUID().toString().replace("-", "");
        //创建字节缓冲输出流
        BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(fileName));
        int temp;
        while ((temp = is.read()) != -1) {
            os.write(temp);
            os.flush(); //推出缓存区中的数据
        }
//        关流
        os.close();
        is.close();
    }
}

随机生成的文件名:
在这里插入图片描述
写入生成文件中的数据:
在这里插入图片描述
案例代码虽然写的简单,但其实还有很多细节我没有写出来,比如检验被拷贝文件是否存在等异常的处理。还有flush方法的使用,由于我使用的是缓冲流,其底层定义了长度为8192的缓冲区来提高读写性能,当数据长度大于缓冲区长度时,缓冲区中的数据就会被推出,而未大于时就不会被推出。所以如果不调用flush方法且忘记关流的话,缓冲区中的数据就不会被推出,从而造成数据的缺失,因此不要忘记flush哦。

总结

我在本文章对IO流进行了简单使用的演示以及对初次接触IO可能遇见的问题进行了解释,希望对各位读者能有所帮助。当然想要更加详细或深入地了解IO的话,还需要各位不厌其烦地进行深入学习,要知道编程本就是逆天而行加油吧(ง •_•)ง。
如有错误还请指出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值