字节流与字符流梳理

谁践踏了优雅 2022-03-20 09:29 432阅读 0赞

字节流与字符流梳理

哈喽大家好今天又见面了,今天想写的是IO流的一些东西,之前承诺的单例模式的东西我在微信公众号(狗蛋科技)里面已经写好了,就是想着自己重新整理一下可能会比较好,所以今天我暂且不提单例模式,今天着重学习一下IO流的基础知识。

分类

首先上来就需要我们了解IO流的分类,之前面试有面试官问我,自己当时确实也不是很会,所以和工作机会错失良机,所以今天我们先看一张图:
IO流分类
首先我们可以看到IO流按照类型来分类的话,有两种,一种是字符流,一种是字节流。这个细分再往下看,可能你会觉得有很多,所以咱们暂且不看这张图,只需要了解一下就可以了。我接下来要讲的是File。

File

首先我们要知道,File是个类,也是需要实例化的,实例化过程和平时一样,这里着重介绍几个常用的方法:

  1. public class TestCase {
  2. public static void main(String[] args) {
  3. File file = new File("src/com/hzy/IO/data/IO.txt");
  4. //File file = new File("C:/...../IO.txt"); 绝对路径也是可以的。
  5. File directory = new File("src/com/hzy/IO/data");
  6. System.out.println(directory.isDirectory());
  7. System.out.println(file.isFile());
  8. }
  9. }

首先这里我是踩过坑的,挖坑的地方就是自己当初创建文件夹时,是在src目录下创建了一个叫做“com.hzy”的文件夹,我以为路径是这样的:(src/com.hzy),实际上在文件夹里面,不是有个文件夹叫做“com.hzy”,而是src下有个com文件夹,com文件夹下面又有一个hzy的文件夹,所以路径应该是:(src/com/hzy)。

这里解释一下File里面的方法:

  1. isDirectory() 是否为文件夹
  2. isFile() 是否为一个文件

  1. file.length()

这个是文件长度,常用于文件拷贝,拷贝前查看文件长度以及文件夹大小,进行比较,如果文件夹过小就拷贝失败,比他大就拷贝成功。


  1. File file1 = new File("D:/");
  2. System.out.println(file1.getFreeSpace());

这个是查看目录剩余空间大小的,这里是查看了D盘的剩余空间,出来的是一个很大的数字,因为是用字节b表示的,大家换算成GB就可以看懂了。(1千兆字节(gb)=1073741824字节(b))


  1. File file2 = new File("src/com/hzy/IO/data/新建.txt");
  2. file2.createNewFile();

这是创建一个新的文件,注意要throws Exception给JVM,否则编译不通过。


这里我复制了一下之前我的一份文件,重命名成IOO,并且设置成只读。
只读
这里我用了一个方法叫做:

  1. File file3 = new File("src/com/hzy/IO/data/IOO.txt");
  2. System.out.println(file3.canWrite());

答案是false,要记住,设置成只读,意思就是不能修改重写了,所以用Boolean类型的canWrite()方法答案是false。如果是canRead()当然就是true啦~
当然你也不需要这么麻烦,你可以直接set就可以轻松修改权限:
在这里插入图片描述
这个只要还是看你平时的积累,比方说还有一个方法叫做lastModified()方法,可以看出你最后一次多会修改的文件。


当然你还可以删除文件:file3.delete();


获取文件名:file3.getName();


重命名:file3.renameTo();




字节流

说完File类的常用方法,我们了解一下字节流,之前我们只是会操控文件了,可以进行一下增删查改的操作,那么如何将文件加载到内存中去呢?这里我们就得用到字节流的知识点了。
首先字节流和字符流都是分为读和写两个分支的,但是有所不同,字符流是分为(Reader 和 Writer),字节流分为(InputStream 和 OutputStream)
更简单来说就是:
字符流是人能看懂的文字,字节流是更专注于底层的一种代码。
举例说一下图片吧,看似能看懂,其实底层都是那种乱七八糟的代码,这就是字节流。视频也是如此。
这四个类都是抽象类,意味着我们不能直接new出来对象。
所以需要用多态的方法具体new出来实例


字节流输入流(InputStream)

  1. public class TestCase {
  2. public static void main(String[] args) throws Exception{
  3. File file = new File("src/com/hzy/IO/data/IO.txt");
  4. InputStream inputStream = new FileInputStream(file);
  5. System.out.println(inputStream.available());
  6. System.out.println(inputStream.read());
  7. System.out.println(inputStream.read());
  8. System.out.println(inputStream.available());
  9. }
  10. }

这里InputStream inputStream = new FileInputStream(file);就是运用多态实例化对象,这时文件就进入内存中了,这里我使用了一个方法叫做.available(),这个方法可以在读写操作前先得知数据流里有多少个字节可以读取。我们可以看一下控制台输出:

  1. 44
  2. 213
  3. 226
  4. 42

这里的213和226是我文件里面第一个和第二的ACSII值,44是.available()可用值,在经过两次阅读之后,.available()值当然减小了两个变成了42.

这里别看他比较简单,通过这两个方法就可以将视频播放器的进度条做出来,并且还能完成播放器的断点播放功能。

如果有想法想自己动手的同学,可以了解一下.mark();.skip();方法,一个是用来标记的,一个是用来跳过用的,这里咱们比较熟知的“跳过片头片尾”就是运用的这种方法。


这里我们可以再看一下.read()方法的另一种返回值类型,那就是数组类型,这里我们创建一个数组,将当前可用的字节流传进去,转化成字符串类进行输出,就可以看到我们文件里面写的什么内容了~

  1. byte[] bytes = new byte[inputStream.available()];
  2. inputStream.read(bytes);
  3. String string = new String(bytes);
  4. System.out.println(string);

这里是控制台输出(也是文章水印hhh):

  1. HZY CSDN blog
  2. VX:652355283
  3. ABCDEFGHIJKL

字节流输出流(OutputStream)

首先我们需要了解的是怎么去实例出这样一个字符节流输出流,其实具体方法还是和输入流是一样的,都是需要具体的一个类去实现,也就是用到了多态的方法。具体方法如下:

  1. OutputStream outputStream = new FileOutputStream("src/com/IO_out.txt")

这里总结一下,输入流使用的FileInputStream,输出流用的是FileOutputStream

  1. OutputStream outputStream = new FileOutputStream("src/com/IO_out.txt");
  2. outputStream.write(bytes);
  3. outputStream.flush();//清空输出缓冲区

这里需要注意有一个.flush()方法,具体注意事项是这样的:flush() 是清空输出缓冲区的意思,而不是刷新,一般主要用在IO中,即清空缓冲区数据,就是说你用读写流的时候,其实数据是先被读到了内存中,然后用数据写到文件中,当你数据读完的时候不代表你的数据已经写完了,因为还有一部分有可能会留在内存这个缓冲区中。这时候如果你调用了 close()方法关闭了读写流,那么这部分数据就会丢失,所以应该在关闭读写流之前先flush(),先清空数据。


字节流读取复写到新文件(复制)

接下来我们就可以了解一下如何复制一个文件了!具体思想就很简单,将读入的东西一个个读出不就好了,所以代码如下:

  1. public class Copy {
  2. public static void main(String[] args) throws Exception{
  3. FileInputStream in = new FileInputStream("src/com/data/github.jpg");
  4. FileOutputStream out = new FileOutputStream("src/com/data/github_02.jpg");
  5. int data = 0;
  6. while ((data = in.read()) != -1){
  7. out.write(data);
  8. }
  9. out.flush();
  10. }
  11. }

这里别说你了,我自己看的都麻烦,所以说有必要解释一下这个代码了,这里首先我们能看到的是实例化了一个输入流,一个输出流,然后通过循环语句用.read()读取流中的第一个字节数据,一次读一个字节,依次读取后续的数据,最后将这些数据一个个输出到"src/com/data/github_02.jpg"里面就完成了数据的复制。


BASE_64转码

这里说一个题外话,也是我的老师讲的,就是假如你给别人传递消息,比方说“新年快乐”这四个字,你不想让别人很轻易的获取这个文字,你就可以“加密”,让这四个字转化成“*&¥…@%…&#(反正就是别人看不懂的字符)”,这里的“加密”其实就是一种转码方式,叫做base64

就比方说:JXU4RkQ5JXU5MUNDJXU2NjJGJXU1NDdDJXU1MzUzJXU1Qjg3JXU3Njg0Q1NETiV1NTM1QSV1NUJBMg==
这串字符翻译过来就是:这里是呼卓宇的CSDN博客

我是真没有骗大伙,网上可以找个base64解密工具看看,挺好玩的还是~


接下来我就具体说说这个转码工具怎么运行的,首先这个base_64来自sun.misc.BASE64Encoder;这个包,先用输入流把文件读入内存中,然后声明一个数组,把读出来的东西都扔到数组里面,然后声明一个字符串,实例化一个对象BASE64Encoder encoder = new BASE64Encoder();,然后把这个数组编码,然后出来就是base_64。具体代码如下:

  1. import sun.misc.BASE64Encoder;
  2. import java.io.FileInputStream;
  3. public class TestCase {
  4. public static void main(String[] args) throws Exception{
  5. FileInputStream in = new FileInputStream("src/com/data/github.jpg");
  6. byte[] bytes = new byte[in.available()];
  7. in.read(bytes);
  8. String base64 = new String();
  9. base64.replaceAll("","+");
  10. BASE64Encoder encoder = new BASE64Encoder();
  11. base64 = encoder.encode(bytes);
  12. System.out.println(base64);
  13. }
  14. }

Buffer的厉害之处

这里我还得穿插一个小知识点,大家跟着我慢慢来,先看代码:

  1. public class Copy {
  2. public static void main(String[] args) throws Exception{
  3. FileInputStream in = new FileInputStream("src/com/data/github.jpg");
  4. FileOutputStream out = new FileOutputStream("src/com/data/github_02.jpg");
  5. long start = System.currentTimeMillis(); //※
  6. int data = 0;
  7. while ((data = in.read()) != -1){
  8. out.write(data);
  9. }
  10. out.flush();
  11. long end = System.currentTimeMillis(); //※
  12. System.out.println(end-start);
  13. }
  14. }

这里计算了一下复制一个图片需要多长时间,答案190ms。此时要引入一个新的知识,那就是BufferInputStream 和 BufferOutputStream

这个就和String和StringBuffer一样,一加buff就牛逼,这里也是一样的。之前相当于考试作弊,别人写一笔,你抄人家一笔,但是一加Buffer以后,相当于你扯过人家卷子就抄,因为有个Buffer以后,你有缓存区了,你牛逼了,抄的就快了,所以如果执行以下代码,时间自然而然缩短不少:

  1. public class Copy {
  2. public static void main(String[] args) throws Exception{
  3. FileInputStream in = new FileInputStream("src/com/data/github.jpg");
  4. FileOutputStream out = new FileOutputStream("src/com/data/github_02.jpg");
  5. BufferedInputStream bin = new BufferedInputStream(in);
  6. BufferedOutputStream bout = new BufferedOutputStream(out);
  7. long start = System.currentTimeMillis();
  8. int data = 0;
  9. while ((data = bin.read()) != -1){
  10. bout.write(data);
  11. }
  12. bout.flush();
  13. long end = System.currentTimeMillis();
  14. System.out.println(end-start);
  15. }
  16. }

这里通过BufferedInputStreamBufferedOutputStream承接了一下字节流,用Buffer包裹住了字节流,架设在Buffer之上,让他就能拥有Buffer的功能。最后复制时间是5ms,数量级缩小了100倍,这就是有了Buff的力量。

  1. BufferedInputStream bin = new BufferedInputStream(in);
  2. BufferedOutputStream bout = new BufferedOutputStream(out);

字符流

字符流读取

  1. public class TestCase {
  2. public static void main(String[] args) throws Exception{
  3. Reader reader = new InputStreamReader(new FileInputStream("src/com/hzy/IO/data/IO.txt"));
  4. BufferedReader buffer = new BufferedReader(reader);
  5. while (buffer.ready()){
  6. System.out.println(buffer.readLine());
  7. }
  8. }
  9. }

先看一段代码,听我慢慢解释,第一句Reader reader = new InputStreamReader(new FileInputStream("src/com/hzy/IO/data/IO.txt"));是一个字符流,然后架设在了缓冲流上面BufferedReader buffer = new BufferedReader(reader);这两个,一个是数据源,一个是优化器,然后用一个循环语句判断缓冲流是否准备好,如果准备好那就“一行一行读”,buffer.readLine()返回的是字符串String类型。大家看这个返回值是不是不需要转化了,所以这就是一种高级流,所以这就是字符流。


字符流读取复写到新文件

  1. public class TestCase_02 {
  2. public static void main(String[] args) throws Exception{
  3. Reader reader = new InputStreamReader(new FileInputStream("src/com/hzy/IO/data/IO.txt"));
  4. Writer writer = new OutputStreamWriter(new FileOutputStream("src/com/hzy/IO/data/IOOUT.txt"));
  5. BufferedReader buffer = new BufferedReader(reader);
  6. BufferedWriter bwrite = new BufferedWriter(writer);
  7. while (buffer.ready()){
  8. String line = buffer.readLine();
  9. bwrite.write(line);
  10. bwrite.newLine();
  11. bwrite.newLine();
  12. }
  13. bwrite.flush();
  14. }
  15. }

这里还是老样子,数据源+Buffer优化器,四条语句很快写完,然后通过循环语句,用字符串String类型承接读到的内容,然后进行打印输出,这里是用bwrite.newLine();添加了两行空行(等同于回车)来作为区别。最后文件中的文字确实比之前多了两空行。

发表评论

表情:
评论列表 (有 0 条评论,432人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java IO——节流字符

    1、字节流与字符流 1.1 流的概念 在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使

    相关 节流字符梳理

    字节流与字符流梳理 哈喽大家好今天又见面了,今天想写的是IO流的一些东西,之前承诺的单例模式的东西我在微信公众号(狗蛋科技)里面已经写好了,就是想着自己重新整理一下可能会