Java基础之I/O流
输入/输出
输入/输出(I/O)是指程序与外部设备或者计算机之间的交互操作,例如从键盘输入数据,在显示器显示数据等,通过输入/输出操作可以从外界接收信息,或者是把信息传递给外界.在Java中,将这种不同的输入输出设备之间的数据传输抽象为"流",通过"流"的方式在输入/输出设备之间进行数据传递.由于Java中的流都位于java.io包中,因此称为I/O流.
I/O流分为很多种,按照操作数据的不同,可以分为字节流和字符流,按照数据传输方向的不同又可以分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据.在I/O包中,字节流的输入/输出流分别用java.io.InputStream和java.io.OutputStream表示,字符流的输入/输出分别用java.io.Reader和java.io.Write表示,具体分类如下图.
字节流
字节流是指针对字节输入/输出提供的一系列流. 在计算机中,无论是文本,图片还是音频和视频,都是以字节的形式存在的,因此要对这些内容进行传输就需要使用字节流.字节流是程序中最常用的流,根据数据传输方向的不同可以将其分为字节输入流和字节输出流.在JDK中,提供了两个抽象类InputStream和OutputStream表示字节输入流和字节输出流,它们是字节流的两个顶级父类,字节输入流都是InputStream的子类,字节输出流都是OutputStream的子类.字节流的继承体系如下图所示.
上图列出的I/O流都是程序中很常见的,并且可以看出InputStream和OutputStream的子类有很多是大致对应的.
下面介绍InputStream和OutputStream中读/写数据的方法,InputStream中常用方法如下表
方法声明 |
功能描述 |
int read() |
从输入流中读取数据的下一个字节 |
int read(byte[] b) |
从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中 |
int read(byte[] b,int off,int len) |
从输入流中读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组开始保存数据的起始下标,len表示读取的字节数目 |
void close()
|
关闭此输入流并释放与该流关联的所有系统资源 |
与InputStream对应的是OutputStream.OutputStream用于写数据,因此OutputStream提供了一些与写数据有关的方法,如下表
方法名称 |
方法描述 |
void write(int b) |
将指定的字节写入此输入流 |
void write(byte[] b) |
将b.length个字节从指定的byte数组写入此输出流 |
void write(byte[] b,int off,int len) |
将指定byte数组中从偏移量off开始的len个字节写入此输出流 |
void flush() |
刷新此输入流并强制写出所有缓冲的输出字节 |
void close() |
关闭此输出流并释放与此流有关的所有系统资源 |
字节流的读/写操作
由于计算机中的数据基本都保存在硬盘的文件中,因此操作文件中的数据是一种很常见的操作,在操作文件时,最常见的就是从文件中读取数据并将数据写入文件,即文件的读/写.针对文件的读/写,JDK专门提供了两个类,分别是FileInputStream和FileOutputStream,它们分别包含了一套所有输入与输出需要使用的方法,可以完成最基本的读取与写出功能.
下面实现字节流对文件数据的读取.
FileInputStream fileInputStream=new FileInputStream("C:\\Users\\Administrator\\Desktop\\flow.txt");
int i =0;
while (true)
{
i=fileInputStream.read();
if(i==-1)
{
break;
}
System.out.println(i);
}
fileInputStream.close();
}
72
101
108
108
111
在上述文件中,创建的字节流FileInputStream通过read()方法读取txt文件中的数据并输出,从运行结果可以看出结果都是数字,通常情况下读取文件输出结果为字符,之所以输出数字是因为在硬盘上存储的文件都是以字节的形式存在的,在txt文件中,字符"Hello"各占一个字节.因此,最终在控制台显示的结果就是文件txt中的五个字节对应的十进制数.
与FileInputStream对应的是FileOutputStream,专门用于把数据写入文件中,下面演示如何把数据写入文件.
FileOutputStream output=new FileOutputStream("test.txt");
String string ="[email protected]";
byte [] arr=string.getBytes();
for (int i=0;i<arr.length;i++)
{
output.write(arr[i]);
}
output.close();
System.out.println("新建文件完成");
程序运行结束后,会在项目src中生成一个新的文本文件test.txt,并将数据写如文件.需要注意的时,如果是通过FileOutputStream向一个已经存在的文件中写入数据,那么该文件中的数据首先会被清空,再写入新的数据.若希望在已存在的文件内容后面增加新的内容,只需要在字节输出流的末尾追加一个参数值True即可.FileOutputStream output=new FileOutputStream("test.txt",true);
由于I/O流在进行数据读/写操作时会出现异常,为了代码的简洁,在上面的程序中使用了throws关键字将异常抛出.然而一旦遇到I/O异常,I/O流的close()方法将无法得到执行,流对象所占用的系统内存将无法释放,因此为了保证I/O流的close()方法必须执行,通常将关闭流的操作写在finally代码块中.
FileOutputStream output=null;
try {
output=new FileOutputStream("test.txt");
String string ="[email protected]";
byte [] arr=string.getBytes();
for (int i=0;i<arr.length;i++)
{
output.write(arr[i]);
}
}
catch (IOException e) {
e.printStackTrace();}
finally {
if(output!=null)
{
output.close();
}
}
文件的复制
在应用程序中,通常情况下输入流和输出流都是同时使用的.例如,文件的复制就需要通过输入流读取文件中的数据,通过输出流将数据写入文件.下面通过案例演示如何进行文件的复制操作.
public class test {
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("C:\\Users\\Administrator\\Desktop\\source\\test.txt");
FileOutputStream outputStream=new FileOutputStream("C:\\Users\\Administrator\\Desktop\\target\\test.txt");
int length;
long starttime=System.currentTimeMillis();
while ((length=inputStream.read())!=-1)
{
System.out.println(length);
outputStream.write(length);
}
long endtime=System.currentTimeMillis();
System.out.println("time="+(endtime-starttime)+"ms");
inputStream.close();
outputStream.close();
}
}
程序运行结束后,刷新并打开target文件夹,发现source文件夹中的文件被成功复制到了target文件夹.
需要注意的时,载定义文件路径时使用了\\,这是因为在Windows中的目录符号为反斜线\,但是反斜线在java中是特殊字符,表示转义符,所以在使用反斜线\时,前边应该再添加一个反斜线,即\\,除此之外,目录符号也可以使用一个正斜线/来表示..
字节流的缓冲区
上节中实现了对文件的复制,但是单个字节的读/写效率非常低,为了提高效率,可以定义一个字符数组作为缓冲区.在复制文件时,可以将读取的单个字节保存到字符数组中,然后将字节数组中的数据一次性写入文件.下面通过使用字节数组来复制文件
public static void main(String[] args) throws IOException {
FileInputStream inputStream=new FileInputStream("C:\\Users\\Administrator\\Desktop\\source\\test.txt");
FileOutputStream outputStream=new FileOutputStream("C:\\Users\\Administrator\\Desktop\\target\\test.txt");
byte buffer[]=new byte[1024];
int length;
long starttime=System.currentTimeMillis();
while ((length=inputStream.read(buffer))!=-1)
{
System.out.print(length);
outputStream.write(buffer, 0, length);;
}
long endtime=System.currentTimeMillis();
System.out.println("time="+(endtime-starttime)+"ms");
inputStream.close();
outputStream.close();
}
通过比较可以看出,复制文件的消耗时间减少了,这说明使用字节数组作为缓冲区读/写文件可以有效的提高程序的执行效率.
字节缓冲流
在进行文件复制时,使用字节流缓冲区可以提高程序的效率,与此同时,还可以使用java.io包中自带缓冲功能的字节缓冲流,它们分别是BufferedInputStream和BufferedOutputStream,这两个流在实例化时,需要接收InputStream 和OutputStream类型的对象作为参数.接下来使用字节缓冲流BufferedInputStream和BufferedOutputStream实现文件的复制/
public static void main(String[] args) throws IOException {
BufferedInputStream bfi=new BufferedInputStream
(new FileInputStream("C:\\Users\\Administrator\\Desktop\\source\\课程表.png"));
BufferedOutputStream bfo=new BufferedOutputStream
(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\target\\课程表.png"));
long starttime=System.currentTimeMillis();
int length;
while ((length=bfi.read())!=-1)
{
bfo.write(length);
}
long endtime=System.currentTimeMillis();
System.out.println("time="+(endtime-starttime)+"ms");
bfi.close();
bfo.close();
}
通过比较可以看出,复制图片所消耗的时间相同,这说明除了使用字节数组作为缓冲区能够提高程序的执行效率以外,使用I/O流自带的字节缓冲流同样能提高执行效率..
字符流
字符流的目标通常是文本文件.Reader和Writer 是java.io包中所有字符流的抽象父类,定义了在I/O流中读/写字符数据的通用API.在Java中,字符采用的是Unicode字符编码,常见的字符输入/输出流是由Reader和Writer抽象类派生出来的,处理数据时以字符为基本单位.
字符流专门处理字符,Reader 和 Writer 作为字符流的顶级父类.
从上图可以看到,字符流的继承关系与字节流的继承关系有些类似,很多子类都是成对的(输入流和输出流)出现的,其中FileReader和FileWriter 用于读/写文件,BufferedReader和BufferedWriter是具有缓冲功能的流,使用它们可以提高读/写效率.
字符流的读/写操作
在程序开发中,经常需要读取文本文件的内容,如果要从文件中直接读取字符,便可以使用字符输入流FileReader ,通过此流可以从关联的文件中读取一个或一组字符.接下来在创建文本文件"read.txt",并在其中输入字符"www.souhu.com",然后创建一个字符输入流FileReader读取文本文件中的内容....
FileReader fr=new FileReader("C:\\Users\\Administrator\\Desktop\\flow.txt");
int i ;
while((i=fr.read())!=-1)
{
System.out.print((char)i);
}
fr.close();
}
在代码中,创建一个FileReader对象并指定"flow.txt"文件,然后通过while循环每次从文件中读取单个字符并输出到控制台上,这样便实现了FileReader读取文本文件字符的操作.在代码中,讲解了如何使用FileReader读取文本文件 中的字符,如果要向文件中写入字符就需要使用FileWriter类.FileWriter是Writer的一个子类,下面代码说如何使用
FileReader fr=new FileReader("C:\\Users\\Administrator\\Desktop\\flow.txt");
FileWriter fw=new FileWriter("C:\\Users\\Administrator\\Desktop\\write.txt");
int i ;
while((i=fr.read())!=-1)
{
fw.write(i);
}
fr.close();
fw.close();
}
程序运行结束后,会在当前目录下生成write.txt文件,打开此文件会看到和输入流相同的内容.
FileWrite同FileOutputStream一样,如果制定的文件不存在,则会先创建文件再写入数据,如果文件存在,则会先清空文件中的内容,再进行写入,若希望在已存在的文件内容之后增加新内容,只需要在字符输出流的末尾追加一个参数值true即可.
字符缓冲流
从之前的学习中我们可以看出.字节缓冲流可以有效地提高读/写数据的效率,而字符流同样提供了带缓冲区的输入流与输出流,分别是BufferedReader和BufferedWriter,其中BufferedReader是用于对字符输入流提供缓冲区,BufferedWriter用于对字符输出流提供缓冲区,需要注意的是,在BufferedReader中有一个重要的方法readLine(),该方法用于一次读取一行文本...String类型
BufferedReader br=new BufferedReader
(new FileReader("C:\\Users\\Administrator\\Desktop\\flow.txt"));
BufferedWriter bw=new BufferedWriter
(new FileWriter("C:\\Users\\Administrator\\Desktop\\write.txt"));
String string =null;
while((string=br.readLine())!=null)//每次读取一行文本,判断是否到文件末尾
{
bw.write(string);
}
br.close();
bw.close();
}
需要注意的是,由于字符缓冲流内部使用了缓冲区,在循环中调用BufferedWriter的write()方法写入字符时,这些字符会写入缓冲区中,当缓冲区填充不下或者调用close()方法时,缓冲区中的字符才会一次性写入目标文件中.因此在循环结束时一定要调用close()方法,否则很容易会导致缓冲区的部分数据无法写入目标文件中...
转换流
在程序开发中,有时候会遇到字节流和字符流之间需要进行转换的可能.在java.io包中提供了两个类可以将字节流转换为字符流,它们分别是InputStreamReader和OutputStreamWriter.
OutputStreamWriter是Writer的子类,能够将一个字节输出流转换成字符输出流,方便直接写入字符,而InputStreamReader是Reader的子类,能够将一个字节输入流转换为字符输入流,方便直接读取字符.下面通过案例学习如何将字节流转换为字符流,为了提高读/写效率,通过字符缓冲流实现转换的操作如下
FileInputStream fi=new FileInputStream("C:\\\\Users\\\\Administrator\\\\Desktop\\\\flow.txt");
InputStreamReader streamReader=new InputStreamReader(fi);
BufferedReader br=new BufferedReader(streamReader);
FileOutputStream fo=new FileOutputStream("C:\\\\Users\\\\Administrator\\\\Desktop\\\\write.txt");
OutputStreamWriter outputStreamWriter=new OutputStreamWriter(fo);
BufferedWriter bw=new BufferedWriter(outputStreamWriter);
String string=null;
while ((string=br.readLine())!=null)
{
bw.write(string);
}
br.close();
bw.close();
}
代码实现了字节流和字符流之间的转换...将字节流转换为字符流,从而实现直接对字符的读/写.需要注意的是,在使用转换流时,只能针对操作文本文件的字节流进行转换,如果字节流操作的是图片或者音频,此时转换为字符流会造成数据丢失
File类
File类提供了目录和文件管理的功能,主要用于文件命名、文件属性查看和文件目录管理、文件夹创建等操作。File类同样位于java.io包中。但是File类不能对文件内容进行读/写操作.
File类的常用方法
File类用于封装一个路径,这个路径可以指向一个文件,也可以指向一个目录,File类提供了针对这些文件或目录的一些常规操作.File类常用的构造方法如下所示.
方法声明 |
功能描述 |
File(String pathname) |
通过将给定路径名字符串转换为抽象路径名创建一个新File实例 |
File (String parent,String child) |
根据parent路径名字符串和child路径名字符串创建一个新File实例 |
File (File parent,String child) |
根据parent抽象路径名和child路径名字符串创建一个新File实例 |
在上表中,如果程序只处理一个目录或文件,并且知道该目录或文件的路径,则使用第一个构造方法更方便;如果程序处理的时一个公共目录中的若干子目录或者文件,那么使用第二或第三个构造方法更方便.
File类中提供了一系列方法,用于操作其内部封装的路径指向的文件或者目录,如创建目录,删除目录,返回目录名称等,File类中的常用方法如下表.
方法声明 |
功能描述 |
boolean exists() |
判断File对象对应的文件或目录是否存在 |
boolean delete() |
删除File对象对应的文件或目录 |
boolean createNewFile() |
当File对象对应的文件不存在时,将创建一个此File对象指定的新文件 |
String getName() |
返回由此抽象路径名表示的文件或目录的名称 |
Stirng getPath() |
将此抽象路径名转换为一个路径名字符串 |
String getParent() |
返回File对象对应目录的父目录(即返回的目录不包含最后一级子目录) |
boolean isDirectory() |
测试此抽象路径名表示的文件是否是一个目录 |
long lastModified() |
返回此抽象路径名表示的文件最后一次被修改的时间 |
long length() |
返回由此抽象路径名表示的文件的长度 |
String []list() |
列出指定目录的全部内容,仅列出名称 |
File[] listFiles() |
返回一个包含了File对象所有子文件和子目录的File数组 |
遍历目录下的文件
在程序开发中,经常需要遍历某个指定目录下的所有文件的名称,下面通过案例演示如何使用File类进行遍历
File file=new File("D:\\");
if (file.isDirectory())
{
String []strings=file.list();
for (String s:strings)
{
System.out.println(s);
}
}
}
在代码中,创建了一个File对象,并在对象中封装了一个路径,通过调用File对象的isDirectory()方法判断该路径是否存在,如果存在则调用list()方法并返回String类型的数组strings,数组中包含该路径下所有文件的文件名.然后循环遍历数组s,依次输出每个文件的文件名到控制台上.
删除文件及目录
在程序开发中,可能会遇到需要删除指定目录下的某个文件或删除整个目录的情况,此时可以通过File对象的删除方法实现.首先在C盘的根目录下创建一个名称为hello文件夹,然后在该文件夹中创建任意数量的Java文件.下面通过案例演示如何删除指定目录下的所有Java文件
File folder=new File("D:\\");
File[] files=folder.listFiles();
for (File file : files)
{
if (file.getName().endsWith(".java"))
{
file.delete();
}
}
运行成功后,观察D盘目录下的所有文件,可以发现该目录下的所有java文件都已经被成功删除.需要注意的是,在使用File对象删除目录时,是从JVM直接删除而不经过回收站,因此文件一旦删除则无法恢复.