字节流与字符流
上一节我们学习了文件操作类File,但是File类虽然可以操作文件,但是却不能操作文件的内容。如果要进行文件内容的操作,就必须依靠流的概念来完成。流在实际中分为输入流和输出流两种,输入流和输出流是一种相对的概念,关键是要看参考点。
Java中针对数据流的操作也分为输入与输出两种方式,并且提供了以下的支持:
字节流:InputStream(输入字节流)、OutputStream(输出字节流);
字符流:Reader(输入字符流)、Writer(输出字符流)
注意:这四个操作流的类都属于抽象类,所以在使用这些类时,必须通过子类对象向上转型来进行抽象类的实例化操作。
流的基本操作形式:
- 通过File类定义一个要操作文件的路径;
- 通过字节流的子类对象为父类对象实例化;
- 进行数据的读(输入)、写(输出)操作;
- 数据流属于资源操作,资源操作必须关闭。
其中最重要的式第四步,不管何种情况,只要是资源操作(例如:网络、文件、数据库的操作都属于资源操作),就必须关闭连接(几乎每种类都会提供close()方法)。
字节输出流:OutputStream
OutputStream类的常用方法
No. | 方法 | 类型 | 描述 |
1 | public void close() throws IOException | 普通 | 关闭字节输出流 |
2 | public void flush() throws IOException | 普通 | 强制刷新 |
3 | public abstract void write(int b) throws IOException | 普通 | 输出单个字节 |
4 | public void write(byte[] b) throws IOException | 普通 | 输出全部字节数组数据 |
5 | public void write(byte[] b,int off,int len) throws IOException | 普通 | 输出部分字节数组数据 |
关于OutputStream类的组成说明
OutputStream是一个抽象类,该类的定义如下:
public abstract class OutputStream extends Object implement Closeable,Flushable
由上我们可以发现OutputStream类同时实现了Closeable与Flushable两个父接口,而这两个父接口的定义如下:
Closeable接口
public interface Closeable extends AutoCloseable{
public void close() throws IOException;
}
Flushable接口
public interface Flushable{
public void flush() throws IOException
}
通过定义我们可以发现Closeable接口声明继承了AutoCloseable(自动关闭)父接口,该接口定义如下:
public interface AutoCloseable{
public void close() throws Excption;
}
通过AutoCloseable接口系统会自动帮助我们(即用户)调用close()方法释放资源。
例:自动执行close()操作
package Project.Study.OutputStreamClass;
class Net implements AutoCloseable{
@Override
public void close()throws Exception {
System.out.println("资源自动关闭,释放资源");
}
public void info()throws Exception {//假设有异常抛出
System.out.println("...");
}
}
public class Test1 {
public static void main(String[]args){
try(Net net=new Net()) {
net.info();
}catch (Exception e){
e.printStackTrace();
}
}
}
//结果:
//...
//资源自动关闭,释放资源
OutputStream类本身是一个抽象类,这样就需要一个子类。所以,可以使用FileOutputStream子类完成操作。
FileOutputStream类的常用方法
No. | 方法 | 类型 | 描述 |
1 | public FileOutputStream(File file)throws FileNotFoundException | 构造 | 将内容输出到指定路径,如果文件已经存在,则使用新的内容覆盖旧的内容 |
2 | public FileOutputStream(File file,boolean append)throws FileNotFoundException | 构造 | 如果将布尔参数设置为true,表示追加新的内容到文件中 |
例:文件内容的输出
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test2 {
public static void main(String[]args)throws Exception{
//1.定义要输出文件的路径
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目录不存在
file.getParentFile().mkdirs(); //创建目录
}
//2.应使用OutputStream和其子类进行对象的实例化,此时目录存在,文件还不存在
OutputStream outputStream=new FileOutputStream(file);
//字节流输出需要使用byte类型,需要将String类对象变为字节数组
String str="Hello World!!!";
byte[]data=str.getBytes(); //将字符串变为字节数组
outputStream.write(data); //输出内容
outputStream.close(); //资源操作的最后一定要进行关闭
}
}
结果:
例:采用单个字节的方式输出
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test3 {
public static void main(String[]args)throws Exception{
//1.定义要输出文件的路径
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目录不存在
file.getParentFile().mkdirs(); //创建目录
}
//2.应使用OutputStream和其子类进行对象的实例化,此时目录存在,文件还不存在
OutputStream outputStream=new FileOutputStream(file);
//字节流输出需要使用byte类型,需要将String类对象变为字节数组
String str="Hello World!!!";
byte[]data=str.getBytes(); //将字符串变为字节数组
for (int x=0;x<data.length;x++){
outputStream.write(data[x]);//内容输出
}
outputStream.close(); //资源操作的最后一定要进行关闭
}
}
例:输出部分字节数组内容(设置数组的开始索引和长度)
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test4 {
public static void main(String[]args)throws Exception{
//1.定义要输出文件的路径
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目录不存在
file.getParentFile().mkdirs(); //创建目录
}
//2.应使用OutputStream和其子类进行对象的实例化,此时目录存在,文件还不存在
OutputStream outputStream=new FileOutputStream(file);
//字节流输出需要使用byte类型,需要将String类对象变为字节数组
String str="Hello World!!!";
byte[]data=str.getBytes(); //将字符串变为字节数组
outputStream.write(data,0,6);//内容输出
outputStream.close(); //资源操作的最后一定要进行关闭
}
}
结果:
注意:采用上面这种方法输出时,要注意数组越界的问题
我们上面三个文件操作都是对文件内容的覆盖,而如果要实现文件的追加操作可以使用public FileOutputStream(File file,boolean append)的构造方法。
例:文件追加
package Project.Study.OutputStreamClass;
import java.io.*;
public class Test3 {
public static void main(String[]args) throws Exception {
//1.定义要输出文件的路径
File file=new File("d:"+File.separator+"Test"+File.separator+"Test.txt");
if (!file.getParentFile().exists()){//文件目录不存在
file.getParentFile().mkdirs(); //创建目录
}
//2.应使用OutputStream和其子类进行对象的实例化,此时目录存在,文件还不存在
OutputStream outputStream=new FileOutputStream(file,true);//追加模式
//字节流输出需要使用byte类型,需要将String类对象变为字节数组
String str="Hello World!!!";
byte[]data=str.getBytes(); //将字符串变为字节数组
outputStream.write(data,5,9);//内容输出
outputStream.close(); //资源操作的最后一定要进行关闭
}
}
结果:
字节输入流:InputStream
如果要进行文件数据的读取操作,就可以用java.io.InputStream类完成,此类可以完成字节数据的读取操作。
InputStream类的常用方法
No. | 方法 | 类型 | 描述 |
1 | public void close() throws IOException | 普通 | 关闭字节输入流 |
2 | public abstract int read() throws IOException | 普通 | 读取单个字节 |
3 | public int read(byte[] b) throws IOException | 普通 | 将数据读取到字节数组中,同时返回读取长度 |
4 | public int read(byte[] b,int off,int len) throws IOException | 普通 | 将数据读取到部分字节数组中,同时返回读取的数据长度 |
InputStream类依旧属于一个抽象类,此类的定义如下:
public abstract class InputStream extends Object implements Closeable
通过定义可以发现InputStream类也实现了Closeable接口(继承了AutoCloseable接口),所以利用自动关闭的异常处理结构可以实现自动的资源释放。
关于InputStream类的三个方法的详细作用:
- 读取单个字节:public abstract int read() throws IOException;
返回值:返回读取的字节内容,如果已经没有内容,则读取后返回“-1”; - 将读取的数据保存在字节数组里(一次读取多个数据):public int read(byte[] b) throws IOException;
返回值:返回读取的数据长度,如果已经读取到结尾,则读取后返回“-1”; - 将读取的数据保存在部分字节数组里:public int read(byte[] b,int off,int len) throws IOException;
返回值:读取的部分数据的长度,如果已经读取到结尾,则读取后返回“-1”
java.io.InputStream是一个抽象类,所以如果要进行文件的读取,需要使用FileInputStream子类,而这个子类的构造方法如下:
方法 | 类型 | 描述 |
public FileInputStream (File file) throws FileNotFoundException | 普通 | 设置要读取文件数据的路径 |
例:数据读取操作
package Project.Study.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Test1 {
public static void main(String[]args) throws Exception {
File file=new File("d:"+File.separator+"Test"+File.separator+"test.txt");//定义要输出文件的路径
if (file.exists()){ //判断文件是否存在后才可以进行读取
InputStream inputStream=new FileInputStream(file);//使用InputStream进行读取
byte[]data=new byte[1024]; //准备一个1024的数组
int len=inputStream.read(data); //进行数据读取,将内容保存到字节数组中
inputStream.close(); //关闭输入流
System.out.println("【"+new String(data,0,len)+"】");//将读取出来的字节数组变为字符串进行输出
}
}
}
//结果:
//【Hello World!!!】
例:采用while循环实现输入流操作
package Project.Study.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class Test2 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test"+File.separator+"test.txt");//定义输出文件的路径
if (file.exists()){ //判断文件是否存在后才可以进行读取
InputStream inputStream=new FileInputStream(file); //使用InputStream进行读取
byte[]data=new byte[1024]; //准备一个1024的数组
int foot=0; //表示字节数组的操作脚标
int temp=0; //表示接收每次读取的字节数据
while((temp=inputStream.read())!=-1){ //当inputStream.read()!=-1,即输出文件中还有内容
data[foot++]=(byte)temp; //有内容就进行保存
}
inputStream.close(); //关闭输出流
System.out.println("【"+new String(data,0,foot)+"】");
}
}
}
//结果:
//【Hello World!!!】
字符输出流:Writer
利用Writer类可以直接实现字符数组(包含字符串)的输出。
Writer类的常用方法
No. | 方法 | 类型 | 描述 |
1 | public void close() throws IOException | 普通 | 关闭字节输出流 |
2 | public void flush() throws IOException | 普通 | 强制刷新 |
3 | public Writer append(CharSequence csq) throws IOException | 普通 | 追加数据 |
4 | public void write(String str) throws IOException | 普通 | 输出字符串数据 |
5 | public void write(char[] cbuf) throws IOException | 普通 | 输出字符数组数据 |
通过Writer类定义的方法可以发现,Writer类中直接提供了输出字符串数据的方法。
Writer类的定义:
Writer类也是属于抽象类,定义如下:
public abstract class Writer extends Object implement Appendable,Closeable,Flushable
通过继承结构我们可以发现,Writer类中除了实现Closeable与Flushable接口外,还实现了一个Appendable接口,该接口定义如下:
public interface Appendable{
public Appendable append(char c)throws IOException;
public Appendable append(CharSequence csq)throws IOException;
public Appendable append(CharSequence csq,int start,int end)throws IOException;
}
在Appendable接口中定义了一系列数据追加操作,而追加的类型可以是CharSequence(可以保存String、StringBuffer、StringBuilder类对象)。
因为Writer是一个抽象类,所以要使用java.io.FileWriter类实现Writer类对象的实例化操作。
FileWriter类的常用方法
No. | 方法 | 类型 | 描述 |
1 | public FileWriter(File file) throws IOException | 构造 | 设置输出文件 |
2 | public FileWriter(File file,boolean append) throws IOException | 普通 | 设置输出文件以及是否进行数据追加 |
例:使用Writer类实现内容输出
package Project.Study.WriterClass;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Test1 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test.txt");//定义要输出文件的路径
if (!file.getParentFile().exists()){//判断目录是否存在
file.getParentFile().mkdirs(); //创建文件目录
}
Writer writer=new FileWriter(file); //实例化了Writer类的对象
String str="Hello World!!!"; //定义输出内容
writer.write(str); //输出字符串内容
writer.close(); //关闭输出流
}
}
结果:
字符输入流:Reader
java.io.Reader类是实现字符数据输入的操作类,在进行数据读取时可以不使用字节数据,而直接依靠字符数据(方便处理中文)进行操作。
Reader类的常用方法
No. | 方法 | 类型 | 描述 |
1 | public void close() throws IOException | 普通 | 关闭字节输入流 |
2 | public int read() throws IOException | 普通 | 读取单个数据 |
3 | public int read() throws IOException | 普通 | 读取单个字符 |
4 | public int read(char[] cbuf) throws IOException | 普通 | 读取数据到字符数组中,返回读取长度 |
5 | public long skip(long n) throws IOException | 普通 | 跳过字节长度 |
Reader类的定义结构:
public abstract class Reader extends Object implement Readable,Closeable
Readable接口定义如下:
public interface Readable{
public int read(CharBuffer cb)throws IOException;
}
在Reader接口中定义的read()方法可以将数据保存在CharBuffer(字符缓冲,类似于StringBuffer)对象中,也就是说利用此类对象就可以替代字符数组的操作。
同样的,因为Reader类是一个抽象类,要实现文件数据的字符流读取,可以利用FileReader子类为Reader类对象实例化。
FileReader类的常用方法如下:
No. | 方法 | 类型 | 描述 |
1 | public FileReader(File file) throws FileNotFoundException | 构造 | 定义要读取的文件路径 |
例:使用Reader读取数据
package Project.Study.ReaderClass;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class Test1 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test.txt");//定义要输出的路径
if (file.exists()){ //判断文件是否存在
Reader reader=new FileReader(file); //为Reader对象实例化
char[]data=new char[1024]; //开辟字符数组,接收读取数据
int len=reader.read(data); //进行数据读取
reader.close(); //关闭输入流
System.out.println(new String(data,0,len));
}
}
}
//结果:
//Hello World!!!
字节流与字符流的区别
我们以文件操作为例,字节流与字符流最大的区别就是:字节流直接与终端文件进行数据交互,字符流需要将数据经过缓冲区处理才与终端文件数据交互。在开发中,对字节数据处理是比较多的,而字符流最大的好处是它可以进行中文的有效处理,因此,在开发中,如果要处理中文时应优先考虑字符流,如果没有中文问题,建议使用字节流。
在使用OutputStream输出数据时,即使最后没有关闭输出流,内容也可以正常输出,但是反过来如果使用的是字符输出流Writer,在执行到最后如果不关闭输出流,就表示在缓冲区中处理的内容不会被强制性清空,所以就不会输出数据。如果有特殊情况不能关闭字符输出流,可以使用flush()方法强制清空缓冲区。
例:错误示范(不关闭流)
package Project.Study.WriterClass;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Test2 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test2.txt");//定义输出文件的路径
if (!file.getParentFile().exists()){ //判断文件目录是否存在
file.getParentFile().mkdirs(); //若不存在就创建文件目录
}
Writer writer=new FileWriter(file); //实例化了Writer类的对象
String str="Hi!!!"; //定义输出内容
writer.write(str); //输出字符串数据
}
}
结果:
通过上程序执行的结果我们可以看到,此时并没有输出结果,输出文件中什么也没有。
例:强制清空字符流缓冲区
package Project.Study.WriterClass;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Test2 {
public static void main(String[]args)throws Exception{
File file=new File("d:"+File.separator+"Test1"+File.separator+"test2.txt");//定义输出文件的路径
if (!file.getParentFile().exists()){ //判断文件目录是否存在
file.getParentFile().mkdirs(); //若不存在就创建文件目录
}
Writer writer=new FileWriter(file); //实例化了Writer类的对象
String str="Hi!!!"; //定义输出内容
writer.write(str); //输出字符串数据
writer.flush(); //强制刷新缓冲区
}
}
结果:
上程序执行到最后并没有执行流的关闭操作,所以从本质上讲,内容将无法完整输出。但因为利用了flush()方法强制刷新缓冲区,所以它的内容完整输出了,也就是说,在不关闭流又要完整输出内容时就只能利用flush()方法强制刷新缓冲区。