字节流与字符流梳理
哈喽大家好今天又见面了,今天想写的是IO流的一些东西,之前承诺的单例模式的东西我在微信公众号(狗蛋科技)里面已经写好了,就是想着自己重新整理一下可能会比较好,所以今天我暂且不提单例模式,今天着重学习一下IO流的基础知识。
分类
首先上来就需要我们了解IO流的分类,之前面试有面试官问我,自己当时确实也不是很会,所以和工作机会错失良机,所以今天我们先看一张图:
首先我们可以看到IO流按照类型来分类的话,有两种,一种是字符流,一种是字节流。这个细分再往下看,可能你会觉得有很多,所以咱们暂且不看这张图,只需要了解一下就可以了。我接下来要讲的是File。
File
首先我们要知道,File是个类,也是需要实例化的,实例化过程和平时一样,这里着重介绍几个常用的方法:
public class TestCase {
public static void main(String[] args) {
File file = new File("src/com/hzy/IO/data/IO.txt");
//File file = new File("C:/...../IO.txt"); 绝对路径也是可以的。
File directory = new File("src/com/hzy/IO/data");
System.out.println(directory.isDirectory());
System.out.println(file.isFile());
}
}
首先这里我是踩过坑的,挖坑的地方就是自己当初创建文件夹时,是在src目录下创建了一个叫做“com.hzy”的文件夹,我以为路径是这样的:(src/com.hzy),实际上在文件夹里面,不是有个文件夹叫做“com.hzy”,而是src下有个com文件夹,com文件夹下面又有一个hzy的文件夹,所以路径应该是:(src/com/hzy)。
这里解释一下File里面的方法:
- isDirectory() 是否为文件夹
- isFile() 是否为一个文件
file.length()
这个是文件长度,常用于文件拷贝,拷贝前查看文件长度以及文件夹大小,进行比较,如果文件夹过小就拷贝失败,比他大就拷贝成功。
File file1 = new File("D:/");
System.out.println(file1.getFreeSpace());
这个是查看目录剩余空间大小的,这里是查看了D盘的剩余空间,出来的是一个很大的数字,因为是用字节b表示的,大家换算成GB就可以看懂了。(1千兆字节(gb)=1073741824字节(b))
File file2 = new File("src/com/hzy/IO/data/新建.txt");
file2.createNewFile();
这是创建一个新的文件,注意要throws Exception给JVM,否则编译不通过。
这里我复制了一下之前我的一份文件,重命名成IOO,并且设置成只读。
这里我用了一个方法叫做:
File file3 = new File("src/com/hzy/IO/data/IOO.txt");
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)
public class TestCase {
public static void main(String[] args) throws Exception{
File file = new File("src/com/hzy/IO/data/IO.txt");
InputStream inputStream = new FileInputStream(file);
System.out.println(inputStream.available());
System.out.println(inputStream.read());
System.out.println(inputStream.read());
System.out.println(inputStream.available());
}
}
这里InputStream inputStream = new FileInputStream(file);
就是运用多态实例化对象,这时文件就进入内存中了,这里我使用了一个方法叫做.available()
,这个方法可以在读写操作前先得知数据流里有多少个字节可以读取。我们可以看一下控制台输出:
44
213
226
42
这里的213和226是我文件里面第一个和第二的ACSII值,44是.available()
可用值,在经过两次阅读之后,.available()
值当然减小了两个变成了42.
这里别看他比较简单,通过这两个方法就可以将视频播放器的进度条做出来,并且还能完成播放器的断点播放功能。
如果有想法想自己动手的同学,可以了解一下.mark();
、.skip();
方法,一个是用来标记的,一个是用来跳过用的,这里咱们比较熟知的“跳过片头片尾”就是运用的这种方法。
这里我们可以再看一下.read()
方法的另一种返回值类型,那就是数组类型,这里我们创建一个数组,将当前可用的字节流传进去,转化成字符串类进行输出,就可以看到我们文件里面写的什么内容了~
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String string = new String(bytes);
System.out.println(string);
这里是控制台输出(也是文章水印hhh):
HZY CSDN blog
VX:652355283
ABCDEFGHIJKL
字节流输出流(OutputStream)
首先我们需要了解的是怎么去实例出这样一个字符节流输出流,其实具体方法还是和输入流是一样的,都是需要具体的一个类去实现,也就是用到了多态的方法。具体方法如下:
OutputStream outputStream = new FileOutputStream("src/com/IO_out.txt")
这里总结一下,输入流使用的FileInputStream
,输出流用的是FileOutputStream
。
OutputStream outputStream = new FileOutputStream("src/com/IO_out.txt");
outputStream.write(bytes);
outputStream.flush();//清空输出缓冲区
这里需要注意有一个.flush()
方法,具体注意事项是这样的:flush() 是清空输出缓冲区的意思,而不是刷新,一般主要用在IO中,即清空缓冲区数据,就是说你用读写流的时候,其实数据是先被读到了内存中,然后用数据写到文件中,当你数据读完的时候不代表你的数据已经写完了,因为还有一部分有可能会留在内存这个缓冲区中。这时候如果你调用了 close()方法关闭了读写流,那么这部分数据就会丢失,所以应该在关闭读写流之前先flush()
,先清空数据。
字节流读取复写到新文件(复制)
接下来我们就可以了解一下如何复制一个文件了!具体思想就很简单,将读入的东西一个个读出不就好了,所以代码如下:
public class Copy {
public static void main(String[] args) throws Exception{
FileInputStream in = new FileInputStream("src/com/data/github.jpg");
FileOutputStream out = new FileOutputStream("src/com/data/github_02.jpg");
int data = 0;
while ((data = in.read()) != -1){
out.write(data);
}
out.flush();
}
}
这里别说你了,我自己看的都麻烦,所以说有必要解释一下这个代码了,这里首先我们能看到的是实例化了一个输入流,一个输出流,然后通过循环语句用.read()
读取流中的第一个字节数据,一次读一个字节,依次读取后续的数据,最后将这些数据一个个输出到"src/com/data/github_02.jpg"
里面就完成了数据的复制。
BASE_64转码
这里说一个题外话,也是我的老师讲的,就是假如你给别人传递消息,比方说“新年快乐”这四个字,你不想让别人很轻易的获取这个文字,你就可以“加密”,让这四个字转化成“*&¥…@%…&#(反正就是别人看不懂的字符)”,这里的“加密”其实就是一种转码方式,叫做base64。
就比方说:JXU4RkQ5JXU5MUNDJXU2NjJGJXU1NDdDJXU1MzUzJXU1Qjg3JXU3Njg0Q1NETiV1NTM1QSV1NUJBMg==
这串字符翻译过来就是:这里是呼卓宇的CSDN博客
我是真没有骗大伙,网上可以找个base64解密工具看看,挺好玩的还是~
接下来我就具体说说这个转码工具怎么运行的,首先这个base_64来自sun.misc.BASE64Encoder;
这个包,先用输入流把文件读入内存中,然后声明一个数组,把读出来的东西都扔到数组里面,然后声明一个字符串,实例化一个对象BASE64Encoder encoder = new BASE64Encoder();
,然后把这个数组编码,然后出来就是base_64。具体代码如下:
import sun.misc.BASE64Encoder;
import java.io.FileInputStream;
public class TestCase {
public static void main(String[] args) throws Exception{
FileInputStream in = new FileInputStream("src/com/data/github.jpg");
byte[] bytes = new byte[in.available()];
in.read(bytes);
String base64 = new String();
base64.replaceAll("","+");
BASE64Encoder encoder = new BASE64Encoder();
base64 = encoder.encode(bytes);
System.out.println(base64);
}
}
Buffer的厉害之处
这里我还得穿插一个小知识点,大家跟着我慢慢来,先看代码:
public class Copy {
public static void main(String[] args) throws Exception{
FileInputStream in = new FileInputStream("src/com/data/github.jpg");
FileOutputStream out = new FileOutputStream("src/com/data/github_02.jpg");
long start = System.currentTimeMillis(); //※
int data = 0;
while ((data = in.read()) != -1){
out.write(data);
}
out.flush();
long end = System.currentTimeMillis(); //※
System.out.println(end-start);
}
}
这里计算了一下复制一个图片需要多长时间,答案190ms。此时要引入一个新的知识,那就是BufferInputStream 和 BufferOutputStream。
这个就和String和StringBuffer一样,一加buff就牛逼,这里也是一样的。之前相当于考试作弊,别人写一笔,你抄人家一笔,但是一加Buffer以后,相当于你扯过人家卷子就抄,因为有个Buffer以后,你有缓存区了,你牛逼了,抄的就快了,所以如果执行以下代码,时间自然而然缩短不少:
public class Copy {
public static void main(String[] args) throws Exception{
FileInputStream in = new FileInputStream("src/com/data/github.jpg");
FileOutputStream out = new FileOutputStream("src/com/data/github_02.jpg");
BufferedInputStream bin = new BufferedInputStream(in);
BufferedOutputStream bout = new BufferedOutputStream(out);
long start = System.currentTimeMillis();
int data = 0;
while ((data = bin.read()) != -1){
bout.write(data);
}
bout.flush();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
这里通过BufferedInputStream
和BufferedOutputStream
承接了一下字节流,用Buffer包裹住了字节流,架设在Buffer之上,让他就能拥有Buffer的功能。最后复制时间是5ms,数量级缩小了100倍,这就是有了Buff的力量。
BufferedInputStream bin = new BufferedInputStream(in);
BufferedOutputStream bout = new BufferedOutputStream(out);
字符流
字符流读取
public class TestCase {
public static void main(String[] args) throws Exception{
Reader reader = new InputStreamReader(new FileInputStream("src/com/hzy/IO/data/IO.txt"));
BufferedReader buffer = new BufferedReader(reader);
while (buffer.ready()){
System.out.println(buffer.readLine());
}
}
}
先看一段代码,听我慢慢解释,第一句Reader reader = new InputStreamReader(new FileInputStream("src/com/hzy/IO/data/IO.txt"));
是一个字符流,然后架设在了缓冲流上面BufferedReader buffer = new BufferedReader(reader);
,这两个,一个是数据源,一个是优化器,然后用一个循环语句判断缓冲流是否准备好,如果准备好那就“一行一行读”,buffer.readLine()
,返回的是字符串String类型。大家看这个返回值是不是不需要转化了,所以这就是一种高级流,所以这就是字符流。
字符流读取复写到新文件
public class TestCase_02 {
public static void main(String[] args) throws Exception{
Reader reader = new InputStreamReader(new FileInputStream("src/com/hzy/IO/data/IO.txt"));
Writer writer = new OutputStreamWriter(new FileOutputStream("src/com/hzy/IO/data/IOOUT.txt"));
BufferedReader buffer = new BufferedReader(reader);
BufferedWriter bwrite = new BufferedWriter(writer);
while (buffer.ready()){
String line = buffer.readLine();
bwrite.write(line);
bwrite.newLine();
bwrite.newLine();
}
bwrite.flush();
}
}
这里还是老样子,数据源+Buffer优化器,四条语句很快写完,然后通过循环语句,用字符串String类型承接读到的内容,然后进行打印输出,这里是用bwrite.newLine();
添加了两行空行(等同于回车)来作为区别。最后文件中的文字确实比之前多了两空行。