字节流与字符流
一、流操作简介
在java.io包中流分为两种:
(1)字节流:InputStream、OutputStream
注意区分我们的InputStream和OutputStream,
OutputStream:这是输出内容到文件中(就相当于是我们的Writer写东西到文件中)
InputStream:这是从文件中读取内容(就相当于是我们的Reader)
(2)字符流:Reader、Writer
对于字节流和字符流我们下面通过图片加文字的形式对它们进行解释:
字节流与字符流在本质上没有区别,它们最大的区别就是字节流是最原始的操作,而字符流是经过了加工处理的,它最开始传输的时候也是字节流,但是在进入终端之前由一个加工厂将字符流加工成了字符流。从终端出去的时候也是需要经过加工编程字节流。
不管我们使用的是字节流还是字符流,它们的操作基本都是一样的,以它们对文件的操作为例:
(1)根据文件路径创建File类对象
(2)根据字节流或字符流的子类实例化父类对象
(3)进行数据的读取或写入操作
(4)关闭流(close())
注意:对于I/O操作属于资源处理,所以我们最后需要关闭。
二、字节流
1.字节输出流
1.1
(1)如果想要通过程序进行内容写入文件,可以使用java.io.OutputStream。
OutputStream类的定义结构:
public abstract class OutputStream implements Close,Flushable
(2)从上面的结构我们可以看出OutputStream实现了Closeable,Flushable两个接口,这两个接口中的方法分别为:
Closeable:public void close() throws IOException;
Flushable:public void flush() throws IOException;
(3)此外OutputStream中还定义了其他的方法:
a.将给定的字节数组内容全部输出(写入文件中):
public void write(byte b[]) throws IOException
b.将部分字节数组内容输出(写入文件中):
public void write(byte b[],int off,int len) throws IOException
c.输出单(写入文件中)个字节:
public abstract void write (int b)throws IOException;
OutputStream是一个抽象类,那么如果我们想要它的实例化对象只能通过它的子类来实现;但是这写方法名称父类已经声明好了,所以我们在此处只需要关系子类的构造方法即可。比如,如果要进行文件的操作,可以使用FileOutputStream类来处理,这个类的构造方法如下:
a.接收File类(覆盖):public FileOutputStream(File file) throws FileNotFoundException
b.接收File类(追加):public FileOutputStream(FIle file,boolean appeand)
下面是例子:
//1.字节输出流(文件追加)
File file =new File("E:"+File.separator+"learn"+File.separator+"javaio"+File.separator+"a1");
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();//创建多级目录
}
//OutputStream是一个抽象类,所以要想实例化对象必须通过其子类来实例化对象
OutputStream output=new FileOutputStream(file,true);
//将第二个参数写为true,这表示我们这个文件可以进行追加,就是当我们每次写的时候都是从
//上一次的内容开始写的。如果不写第二个参数,那么默认的是false。表示这个文件不会进行追加
String msg="你好\n";
//要通过OutputStream实例化的对象调用write()方法,将内容写进文件中,需要传入的参数必须是一个字节数组
// 所以我们需要将内容变为字节数组,作为参数传递,将内容写进文件中。
output.write(msg.getBytes());
上面的操作我们可以将内容通过程序写进文件中,经过两次运行以后我们的结果是两个你好,表示这是在上一次的内容上进行追加的。
//部分内容输出
File file = new File("E:"+File.separator+"learn"+File.separator+"javaio"+File.separator+"a1") ;
if(!file.getParentFile().exists()){//必须保证父目录存在
file.getParentFile().mkdirs();
}
OutputStream output=new FileOutputStream(file,true);
//下面是输出到文件的内容
String msg="如果巴黎不快乐";
//将内容变为字符数组
output.write(msg.getBytes());
output.close();
}
}
1.2AutoCloseable自动关闭支持
对于上面的操作我们最后读需要做同一个动作就是将流关闭,但是在JDK1.7以后我们有了一个AutoCloseable自动关闭支持,虽然是出现了这种处理方法,但是它需要结合我们的try.catch使用
例子:
//2.使用自动关闭处理之前的操作
public class Practice{
public static void main(String[] args) {
File file =new File("E:"+File.separator+"learn"+File.separator+"javaio"+File.separator+"a1");
if(!file.getParentFile().exists()){
file.getParentFile().mkdirs();
}
//和之前输出到文件内容的操作方法不一样了。我们将定义对象这个操作方法放在了异常处理定义对象里面,
//OutputStream是实现了AutoCloseable的接口的。
try(OutputStream output = new FileOutputStream(file,true)){
String msg="你和我的倾城时光";
output.write(msg.getBytes());
//由于我们使用了这个异常处理,所以不需要在后面写一个关闭流语句。
} catch (Exception e) {
e.printStackTrace();
}
}
}
虽然我们有了自动关闭这个方法,但是这个使用的时候会引起结构混乱,所以我们还是使用手动关闭更好,不容易出错。
2.字节输入流:InputStream
前面我们讲了OutputStream输出流对文件内容的处理,下面我们学习InputStream类在程序中读取文件内容。
InputStream类的定义如下:
public abstract class InputStream implements Closeable
我们可以看到,我们的InputStream方法只实现了Closeable接口。下面是InputStream类中的方法:
(1)读取数据到字节数组中,返回数据的读取个数。它将会遇到下面几种情况:
如果此时开辟的字节数组大小大于读取的数据大小,则返回的就是读取个数;
如果读取的数据大于数组最大能放下的长度,那么返回的就是数组的长度;
如果没有数据了,还是进行读,那么返回的是-1;
public int read(byte b[]) throws IOExcepion.
(2)读取部分数据到字节数组中,每次只读取传递数组的内容:
那么这个时候如果读取满了就返回长度;
如果没有读取满,这返回读取的数据个数;
如果读取到最后没有数据了则返回-1;
public int reand(byte b[],int off,int len) throws IOException
(3)读取单个字节,每次读取一个字节的内容,直到没有数据了返回-1:
public abstract int read() throws IOException
2.1.实现文件信息的读取
public class TestInputStream {
public static void main(String[] args) throws IOException {
//1.定义文件路径-->采用Path的方法
Path path=Paths.get("E:","learn","javaio","a1");
File file=path.toFile();//将路径转化为文件
//注意:当我们使用Path方法实例化File对象的时候就不需要进行判断文件是否存在了,因为
//toFile()就是将路径转化为了文件,所以我们的文件是已经存在了的。
InputStream input = new FileInputStream(file);
byte[] data=new byte[1024];//每次可以读取的最大数量
int len =input.read(data);//此时已经将文件中的数据按照字节读取到了数组中
String result=new String(data,0,len);//将字节数组转化为String类型
System.out.println("读取到的内容为:"+result);
input.close();
}
}
三、字符流
1.字符输出流
字符主要适合处理中文数据,Writer是字符输出流的处理类,这个类的定义如下:
public abstract class Writer implements Appendable,Flushable
当然在我们的Writer类里面也提供了writer方法,而且该方法接收的类型都是char类型,此外Writer还提供了一个直接输出字符串的方法:
public void writer(String str) throws IOException
注意:要操作文件使用FileWriter子类。
下面是通过Writer实现输出流(将内容写进文件):
public class TestWriter {
//字符输出流(注意我们的磁盘数据保存的单位都是字节,所以我们的字符需要处理)
public static void main(String[] args) throws IOException {
Path path= Paths.get("E:","learn","javaio","a1");
File file=path.toFile();
String msg="裂缝中的阳光";
//Writer是字符输出流的类,它也是抽象类,实例化它的对象需要通过它的子类FileWriter
Writer out=new FileWriter(file);//在字符输出流中没有追加的这个方法
//由于在字符输出流类的方法中的这个write()方法的参数可以是字符串型的,所以我们可以直接将字符串传入,
// 然后将内容写到文件中。
out.write(msg);
out.close();
}
}
在使用字符输出流之前文件中的内容:
使用字符输出流以后文件中的内容:
从以上结果我们可以得到结论,虽然字符输出流方法与我们的OutputStream名方法非常相似,它直接提供了直接写入String内容的方法,不需要将字符串转化为字节数组再写入文件中。但是字符输出流没有追加功能,每次写入一次,文件中的内容就被更新以后再写入。
2.字符输出流:Reader
Reader也是一个抽象类。如果需要进行读取任然需要FileReader,在Writer中我们有直接写入的方法,在Reader中我们同样也有直接读取的方法。
eg:
通过文件读取数据:
//字符输入流(读取文件中的数据)
public class TestReader {
public static void main(String[] args) throws IOException {
File file=new File("E:"+ File.separator+"learn"+File.separator+"javaio"+File.separator+"a1");
if(file.exists()){
Reader in=new FileReader(file);
char[] data=new char[1024];//最大可以读取的范围
int len=in.read(data);//通过read返回的是读取到的长度
//将数组转化为字符串
String result=new String(data,0,len);
System.out.println("读取到的内容为:"+result);
in.close();
}
}
}
3.字节流与字符流的对比
通过上面对字节流和字符流的学习我们可以看到字节流和字符流在编写代码上大体是相同的,但是我们知道字节流适用于所有的数据类型,但是我们的字符流只适用于中文,所以我们有限选择的是字节流,只有在处理中文的时候我们才选取字符流;因为所有的字符流都需要通过内存缓冲来处理的,因为字符是经过了中间的处理的,从终端到输出(从输入到终端)的时候,缓冲区就是中间将字节处理为字符的地方,这些数据都先保存在缓冲区的。如果我们的字符流没有关闭,那么数据就可能保存在缓存中并没有输出到目标源。这个时候需要我们强制手动的刷新缓冲区才能得到完整的数据,所以它是有弊端的。
eg:
下面我们就看看刷新操作:
//字符流刷新操作
public class TestWriter {
//字符输出流(注意我们的磁盘数据保存的单位都是字节,所以我们的字符需要处理)
public static void main(String[] args) throws IOException {
Path path= Paths.get("E:","learn","javaio","a1");
File file=path.toFile();
String msg="裂缝中的阳光2222";
//Writer是字符输出流的类,它也是抽象类,实例化它的对象需要通过它的子类FileWriter
Writer out=new FileWriter(file);//在字符输出流中没有追加的这个方法
//由于在字符输出流类的方法中的这个write()方法的参数可以是字符串型的,所以我们可以直接将字符串传入,
// 然后将内容写到文件中。
out.write(msg);
out.flush();
}
}
没有关闭流并且没有刷新的情况:
没有关闭流但是手动刷新了:
所以我们总结一下:
以后再进行I/O处理的时候,如果处理的是图片、音乐、文字都可以使用字节流,但是只有在处理中文的时候才可以使用。