IO流有很多种,按照操作数据的不同,可以分为字节流和字符流
按照数据传输方向的不同又可分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据
在IO包中,字节流的输入输出流分别用java.io.InputStream和java.io.OutputStream表示,字符流的输入输出流分别用java.io.Reader和java.io.Writer表示。
在计算机中,无论是文本、图片、音频还是视频,所有文件都是以二进制(字节)形式存在的
IO流中针对字节的输入输出提供了一系列的流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输入流和字节输出流。在JDK中,提供了两个抽象类InputStream和OutputStream
InputStream和OutputStream是字节流的顶级父类,所有的字节输入流都继承自InputStream,所有的字节输出流都继承自OutputStream。
可以把InputStream和OutputStream比作两根“水管”,如图所示。
在JDK中,InputStream和 OutputStream提供了一系列与读写数据相关的方法,如下表所示。
前三个read()方法都是用来读数据的,其中,第一个read()方法是从输入流中逐个读入字节
第二个和第三个read()方法则将若干字节以字节数组的形式一次性读入,从而提高读数据的效率。
在进行IO流操作时,当前IO流会占用一定的内存,由于系统资源宝贵,因此,在IO操作结束后,应该调用close()方法关闭流,从而释放当前IO流所占的系统资源。
与InputStream对应的是OutputStream。OutputStream是用于写数据的,如下表所示。
表中OutputStream类的五个常用方法。前三个是重载的write()方法,都是用于向输出流写入字节
其中,第一个方法逐个写入字节,后两个方法是将若干个字节以字节数组的形式一次性写入,从而提高写数据的效率。
flush()方法用来将当前输出流缓冲区(通常是字节数组)中的数据强制写入目标设备,此过程称为刷新。close()方法是用来关闭流并释放与当前IO流相关的系统资源。
InputStream和OutputStream这两个类是抽象类,不能被实例化,因此,针对不同的功能,InputStream和OutputStream提供了不同的子类,这些子类形成了一个体系结构。
计算机中的数据基本都保存在硬盘的文件中,因此操作文件中的数据是一种很常见的操作。
在操作文件时,最常见的就是从文件中读取数据并将数据写入文件,即文件的读写。
针对文件的读写,JDK专门提供了两个类,分别是FileInputStream和FileOutputStream。
FileInputStream是InputStream的子类,它是操作文件的字节输入流,专门用于读取文件中的数据。由于从文件读取数据是重复的操作,因此需要通过循环语句来实现数据的持续读取。
public static void test1() throws Exception{
// System.out.println(System.getProperty("user.dir"));
FileInputStream in=new FileInputStream("src/src.txt");
int b=0;
while (true){
b=in.read();
if(b==-1){
break;
}
System.out.println(b);
}
in.close();
}
public static void test2() throws Exception{
FileInputStream in=new FileInputStream("src/src.txt");
int len;
byte[] bytes=new byte[1024];
while (true){
len=in.read(bytes);
System.out.println(len);
if(len==-1){
break;
}
for(int i=0;i<len;i++){
System.out.println(bytes[i]);
}
}
in.close();
}
FileOutputStream是OutputStream的子类,它是操作文件的字节输出流,专门用于把数据写入文件。
public class test1 {
public static void main(String[] args) throws Exception{
// test1();
test2();
}
public static void test1() throws Exception{
FileOutputStream out=new FileOutputStream("src/example.txt");
String str="java is good";
byte[] bytes=str.getBytes();
for(int i=0;i< bytes.length;i++){
out.write(bytes[i]);
}
out.close();
}
public static void test2() throws Exception{
FileOutputStream out=new FileOutputStream("src/example.txt");
String str="java is very good";
byte[] bytes=str.getBytes();
out.write(bytes);
out.close();
}
}
public class test3 {
public static void main(String[] args) throws Exception {
OutputStream out=new FileOutputStream("src/example.txt",true);
byte[] bytes="欢迎您".getBytes();
for(int i=0;i< bytes.length;i++){
out.write(bytes[i]);
}
out.close();
}
}
由于IO流在进行数据读写操作时会出现异常,为了代码的简洁,在上面的程序中使用了throws关键字将异常抛出。
然而一旦遇到IO异常,IO流的close()方法将无法得到执行,流对象所占用的系统资源将得不到释放
因此,为了保证IO流的close()方法必须执行,通常将关闭流的操作写在finally代码块中,具体代码如
下所示:
public class test {
public static void main(String[] args) throws Exception {
OutputStream out=null;
try {
out=new FileOutputStream("example.txt",true);
byte[] bytes="欢迎您".getBytes();
for(int i=0;i< bytes.length;i++){
out.write(bytes[i]);
}
}finally {
if (out !=null){
out.close();
}
}
}
}
在应用程序中,IO流通常都是成对出现的,即输入流和输出流一起使用。
一个字节一个字节的读写,需要频繁的操作文件,效率非常低。
这就好比从北京运送烤鸭到上海,如果有一万只烤鸭,每次运送一只,就必须运输一万次,这样的效率显然非常低。
为了减少运输次数,可以先把一批烤鸭装在车厢中,这样就可以成批的运送烤鸭,这时的车厢就相当于一个临时缓冲区。
当通过流的方式拷贝文件时,为了提高效率也可以定义一个字节数组作为缓冲区。在拷贝文件时,可以一次性读取多个字节的数据,并保存在字节数组中,然后将字节数组中的数据一次性写入文件。
在IO包中提供两个带缓冲的字节流,分别是BufferedInputStream和BufferedOutputStream,它们的构造方法中分别接收InputStream和OutputStream类型的参数作为对象,在读写数据时提供缓冲功能。应用程序、缓冲流和底层字节流之间的关系如图所示。
import java.io.*;
public class test {
public static void main(String[] args) throws Exception {
// test1();
// test2();
test3();
}
//原始
public static void test1() throws Exception{
InputStream in=new FileInputStream("src/example.txt");
OutputStream out=new FileOutputStream("src/out.txt");
int content;
long startTime=System.currentTimeMillis();
while ((content=in.read())!=-1){
out.write(content);
}
long endTime=System.currentTimeMillis();
System.out.println("拷贝文件消耗的时间是:"+(endTime-startTime));
in.close();
out.close();
}
//数组缓存
public static void test2() throws Exception{
InputStream in=new FileInputStream("src/example.txt");
OutputStream out=new FileOutputStream("src/out.txt");
byte[] buff=new byte[1024];
int len;
long startTime=System.currentTimeMillis();
while ((len=in.read(buff))!=-1){
out.write(buff,0,len);
}
long endTime=System.currentTimeMillis();
System.out.println("拷贝文件消耗的时间是:"+(endTime-startTime));
in.close();
out.close();
}
//JAVA缓存类
public static void test3() throws Exception{
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("src/example.txt"));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("src/out.txt"));
int content;
long startTime=System.currentTimeMillis();
while ((content=bis.read())!=-1){
bos.write(content);
}
long endTime=System.currentTimeMillis();
System.out.println("拷贝文件消耗的时间是:"+(endTime-startTime));
bis.close();
bos.close();
}
}
在这里插入代码片
字符流
InputStream类和OutputStream类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个类就不太方便
为此JDK提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。
其中Reader是字符输入流,用于从某个源设备读取字符
Writer是字符输出流,用于向某个目标设备写入字符。
Reader和Writer作为字符流的顶级父类,也有许多子类,接下来通过继承关系图来列出Reader和Writer的一些常用子类,如图所示。
在程序开发中,经常需要对文本文件的内容进行读取和写入
如果想从文件中直接读取字符便可以使用字符输入流FileReader
如果想将字符写入文件中便可以使用字符输出流FileWriter
FileReader和FileWriter都有相应的缓存类
public static void main(String[] args) throws Exception {
FileReader reader=new FileReader("src/src.txt");
int ch;
while ((ch=reader.read())!=-1){
System.out.println((char) ch);
}
reader.close();
}
public static void main(String[] args) throws Exception{
FileWriter writer=new FileWriter("src/writer.txt");
String str="大家好 各位同学";
writer.write(str);
writer.write("\r\n");//转义字符
writer.close();
}
public static void main(String[] args) throws Exception {
System.out.println(System.getProperty("user.dir"));
FileReader reader=new FileReader("src/src.txt");
BufferedReader br=new BufferedReader(reader);
FileWriter writer=new FileWriter("src/des.txt");
BufferedWriter bw=new BufferedWriter(writer);
String str=null;
while ((str=br.readLine())!=null){
bw.write(str);
bw.newLine();//额外的换行操作
}
br.close();
bw.close();
}
IO流可分为字节流和字符流,有时字节流和字符流之间也需要进行转换。
在JDK中提供了两个类可以将字节流转换为字符流,它们分别是InputStreamReader和OutputStreamWriter。
OutputStreamWriter是Writer的子类,它可以将一个字节输出流转换成字符输出流,方便直接写入字符,InputStreamReader是Reader的子类,它可以将一个字节输入流转换成字符输入流,方便直接读取字符。通过转换流进行数据读写的过程如图所示。
为了提高读写效率,可以通过BufferedReader和BufferedWriter来实现转换工作
public static void main(String[] args) throws Exception{
FileInputStream in=new FileInputStream("src/src.txt");
InputStreamReader isr=new InputStreamReader(in);
BufferedReader br=new BufferedReader(isr);
//
OutputStream out=new FileOutputStream("src/des.txt");
OutputStreamWriter osw=new OutputStreamWriter(out);
BufferedWriter bw=new BufferedWriter(osw);
//
String line=null;
while ((line=br.readLine())!=null){
bw.write(line);
bw.newLine();
}
br.close();
bw.close();
}
File类
File类用于封装一个路径,这个路径可以是从系统盘符开始的绝对路径,如:
“D:\file\a.txt”,也可以是相对于当前目录而言的相对路径,如:“src\Hello.java”
File类内部封装的路径可以指向一个文件,也可以指向一个目录,在File类中提供了针对这些文件或目录的一些常规操作。接下来首先介绍一下File类常用的构造方法,如表所示。
表中列出了File类的三个构造方法。通常来讲,如果程序只处理一个目录或文件,并且知道该目录或文件的路径,使用第一个构造方法较方便。如果程序处理的是一个公共目录中的若干子目录或文件,那么使用第二个或者第三个构造方法会更方便。
File类中提供了一系列方法,用于操作其内部封装的路径指向的文件或者目录,例如判断文件/目录是否存在、创建、删除文件/目录等。
首先在当前目录下创建一个文件“example.txt”并输入内容“test”
public static void main(String[] args) {
File file=new File("src/example.txt");//相对路径
System.out.println("文件名称:"+file.getName());
System.out.println("文件的相对路径:"+file.getPath());
System.out.println("文件的绝对路径:"+file.getAbsolutePath());
System.out.println("文件的父路径:"+file.getParent());
System.out.println(file.canRead()?"文件可读":"文件不可读");
System.out.println(file.canWrite()?"文件可写":"文件不可写");
System.out.println(file.isFile()?"是一个文件":"不是一个文件");
System.out.println(file.isDirectory()?"是绝对路径":"不是绝对路径");
System.out.println("最后的修改时间:"+file.lastModified());
System.out.println("文件大小为:"+file.length()+"bytes");
System.out.println("是否成功删除文件:"+file.delete());
}
File类中有一个list()方法,该方法用于遍历某个指定目录下的所有文件的名称。
public static void main(String[] args) {
// test1();
test2();
}
//遍历目录下的文件
public static void test1(){
File file=new File("src/test");
if(file.isDirectory()){
String[] names=file.list();
for (String name:names){
System.out.println(name);
}
}
}
//遍历目录及其子目录下的文件
public static void test2(){
File file=new File("src/test");
filterDir(file);
}
private static void filterDir(File dir){
File[] files=dir.listFiles();
for(File file:files){
if(file.isDirectory()){//方法的递归
filterDir(file);
}
System.out.println(file.getAbsolutePath());
file.delete();
}
}
接下来分步骤分析list(FilenameFilter filter)方法的工作原理:
1.调用list()方法传入FilenameFilter文件过滤器对象。
2.取出当前File对象所代表目录下的所有子目录和文件。
3. 对于每一个子目录或文件,都会调用文件过滤器对象的accept(File dir,String name)方法,并把代表当前目录的File对象以及这个子目录或文件的名字作为参数dir和name传递给方法。
4. 如果accept()方法返回true,就将当前遍历的这个子目录或文件添加到数组中,如果返回false,则不添加。
public static void main(String[] args) {
FilenameFilter filter=new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
File currFile=new File(dir,name);
if(currFile.isFile() && name.endsWith(".txt")){
return true;
}else {
return false;
}
}
};
File file=new File("src/test");
if(file.exists()){
String[] lists=file.list(filter);
for(String name:lists){
System.out.println(name);
}
}
}
在操作文件时,经常需要删除一个目录下的某个文件或者删除整个目录,这时读者可以使用File类的delete()方法。接下来通过一个案例来演示使用delete()方法删除文件。
首先在电脑中创建一个名称为test的文件夹,然后在文件夹中创建一个文本文件。
public static void main(String[] args) {
// test1();
test2();
}
//删除普通文件
public static void test1(){
File file=new File("src/test/test1.txt");
if(file.exists()){
System.out.println(file.delete());
}
}
//删除目录及其子目录 文件
public static void test2(){
File file=new File("src/test");
deleteDir(file);
}
private static void deleteDir(File dir){
if(dir.exists()){
File[] files=dir.listFiles();
for(File file:files){
if(file.isDirectory()){
deleteDir(file);
}else {
file.delete();
}
}
dir.delete();
}
}