Java IO流

一、概述

Java中与IO相关的类有很多,都集中在java.io中,都是以流的形式操作的,流是有一定的顺序,像一个管道一样,它的本质是传输数据。根据数据类型的不同可以分为字节流和字符流,根据流向的不同可以分为输入流和输出流。

  • 字符流:因为数据有不同的编码,可以对字符进行不同的操作,其本质还是基于字节流,然后再查询相应的码表。一般用于处理纯文本数据。
  • 字节流:可以处理所有类型数据,二进制文件(图片,音频等)。
  • 输入流:读入数据,也就是数据的源,如键盘,磁盘文件,网络文件,内存等。字符流对应的基本输入流为Reader,字节流对应的基本输入流为InputStream
  • 输出流:输出数据,数据的目的地,如控制台,磁盘文件,内存等。字符流对应的基本输出流为Writer,字节流对应的基本输出流为OutputStream

Java 所有IO流的关系如下如:

Java IO流_第1张图片
Java IO流的关系结构

二、字符流

字符流的基本输入输出流是ReaderWriter,两者有一些常用的方法:
Reader的常用读取操作:

  1. int read() 读取单个字符。
  2. int read(char[] cbuf) 将字符读入数组。
  3. abstract int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。

Writer的常用写入操作:

  1. abstract void flush() 刷新该流的缓冲。
  2. void write(char[] cbuf) 写入字符数组。
  3. void write(int c) 写入单个字符。
  4. void write(String str) 写入字符串。
  5. void write(String str, int off, int len) 写入字符串的某一部分。

在使用ReaderWriter进行数据流的读入和写入操作时是不会直接创建ReaderWriter对象的,一般都是使用其子类,如FileReaderFileWriter,示例代码如下:

import java.io.*;
class ReaderDemo {
    public static void main(String[] args) {
        FileReader reader = null;
        FileWriter writer = null;
        try {
            writer = new FileWriter("file.txt");
            // 向流中写入一个char[],内容为['H','e','l','l','o']
            writer.write("Hello".toCharArray());
            writer.flush(); // 刷新内容到磁盘上
        } catch (IOException e) {
            // IO异常在这里处理
        } finally {
            try {
                // 在finally中关闭流,并判断是否为null
                if(writer != null) {
                    writer.close();
                }
            } catch (IOException e) {}
        }

        try{
            reader = new FileReader("file.txt");
            char[] buf = new char[1024];
            int n = 0;
            // 从流中读入一个char[],然会读入的长度,-1表示到达流尾
            while((n = reader.read(buf)) != -1) {
                // 输出到屏幕上
                System.out.println(new String(buf, 0, n));
            }
        } catch (IOException e) {
            // IO异常在这里处理
        } finally {
            try {
                // 在finally中关闭流,并判断是否为null
                if(reader != null) {
                    reader.close();
                }
            } catch (IOException e) {}
        }
    }
}

上面代码有些需要注意的地方,如不管是流的创建,打开,写入,读取,刷新,关闭等操作,一般都会抛出IOException,因为对磁盘进行操作(一般都是)都有可能产生错误,如磁盘满了,文件被占用等等,所以必须对其进行捕获,并处理。流的关闭一般放在try中的finally中,原因是为防止出错后无法及时释放资源。在进行数据的写入时,当执行完write方法后,数据可能不会立即被写入到目的地,这时可以使用flush功能进行立即完成写入功能,也可以在close时自定完成内容的写入。
还有一点关于数据的读入过程,如使用int read()一个字节一个字节的读入,到达流尾时,返回-1,这种方式,需要注意其返回int需要强制转换一下,如int data = reader.read();,那么读到的数据便是(char)data;如果使用int read(char[])方式读取数据,那么就如上面代码示例的读取方式操作即可。

BufferedWriterBufferedReader使用简介

BufferedInputStreamBufferedOutputStream是为提高读取和写入的效率而出现的,当我们读取和写入数据时,可以现在内存中建立一个缓冲区,加快数据流的读写,其使用非常简单,将Reader或者Writer对象做为参数传递给其构造函数即可。功能和类似,示例代码如下:

import java.io.*;
class Demo {
    public static void main(String[] args) {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter("file.txt"));
            // 向流中写入一个char[],内容为['H','e','l','l','o']
            bw.write("Hello".toCharArray());
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            // IO异常在这里处理
        } finally {
            try {
                // 在finally中关闭流,并判断是否为null
                if(bw != null) {
                    bw.close();
                }
            } catch (IOException e) {}
        }
    }
}

其中newLine()是输出一个换行,这种操作是跨平台的,即在Windows下输出\r\n,而在Linux下输出\n

LineNumberReader使用简介

LineNumberReader单从类名上看,大致也知道了类的功能,便是可以输出文本文件的行号。这里行号是从1开始的,也可以使用setLineNumber(int)功能为其设置一个偏移值,如设置100,那么行号就会从101开始输出。示例代码如下:

import java.io.*;
class Demo {
    public static void main(String[] args) throws IOException {
        LineNumberReader reader = new LineNumberReader(new FileReader("file.txt"));
        String line = null;
        // 如果读到文件末尾则返回null
        while((line=reader.readLine()) != null ) {
            // 获取行号并输出
            System.out.println(reader.getLineNumber()+": " + line);
        }
        reader.close();
    }
}
// 执行结果为
1: Hello
2: haha
3: Hi
4: good meeas
5:
6: sa

三、字节流

字符流操作的是纯文本内容,而字节流则是所有二进制文件都可以操作,如图片,视频,当然文件文件也是可以的。与字符流中读出和写入的类型为char型相对应,字节流读出和写入的是byte类型。字节流的两个基本输入输出流为InputStreamOutputStream,其功能与字符流的功能类似。对于文件的操作的流是相应的FileInputStreamFileOutputStream,下面通过一个图片拷贝功能作为字节流的一个示例代码:

import java.io.*;
class Demo {
    public static void main(String[] args) throws IOException {

        FileInputStream fin = new FileInputStream("pic.png");
        FileOutputStream fout = new FileOutputStream("pic2.png");

        // 与Reader不同的是这里使用的是byte类型
        byte[] buf = new byte[1024];
        int n = 0;

        while((n=fin.read(buf)) != -1) {
            fout.write(buf, 0, n);
        }

        // 关闭流
        fin.close();
        fout.close();
    }
}

BufferedInputStreamBufferedOutputStream使用简介

为了提高流操作的效率,这里也用相应的缓冲流,到底使用缓冲流与不使用缓冲流在效率上有多大的差别,可以通过比较得出结果。从下面代码的比较结果可以明显的发现,加入缓冲机制会大大提高程序的运行效率,原因大致解释为,未加入缓冲机制,每次读取read()都会调用系统底层读取磁盘操作,每次读取一个字节,非常耗时;而加入缓冲机制后,系统会一次将很多内容读取到内存,而调用read()时,只需要从内存中返回数据内容即可,大大减少了系统底层访问磁盘的次数,所以速度会加快很多。代码示例如下:

import java.io.*;
class Demo {
    public static void main(String[] args) throws IOException {
        // 未加缓冲机制
        FileInputStream fin = new FileInputStream("movie.avi");
        FileOutputStream fout = new FileOutputStream("movie2.avi");
        int data = 0;
        long time1 = System.currentTimeMillis();

        while((data = fin.read()) != -1) {
            fout.write(data);
        }
        System.out.println("普通:" + (System.currentTimeMillis()-time1) + "毫秒");
        fin.close();
        fout.close();
        // 加上缓冲机制
        BufferedInputStream bfin = 
            new BufferedInputStream(new FileInputStream("movie.avi"));
        BufferedOutputStream bfout = 
            new BufferedOutputStream(new FileOutputStream("movie3.avi"));
        long time2 = System.currentTimeMillis();

        while((data = bfin.read()) != -1) {
            bfout.write(data);
        }
        System.out.println("缓冲:" + (System.currentTimeMillis()-time2) + "毫秒");
        bfin.close();
        bfout.close();
    }
}
// 执行结果为
普通:50052毫秒
缓冲:61毫秒

读取转换流和写入转换流

这里涉及到的两个流是关于字符流和字节流的转换的操作,去两者名称为:InputStreamReaderOutputStreamWriterInputStreamReader是将InputStream(字节流)流作为参数构造出输入字符流。而OutputStreamWriter则是将OutStream(字节流)作为参数构造出输出字符流。如此一来,我们读取键盘(System.in)数据时,便可以使用Reader的功能,也可以使用Writer功能将数据输出到控制台(System.out),示例代码如下:

import java.io.*;
class Demo {
    public static void main(String[] args) throws IOException {
        // 键盘的最常见写法。
        BufferedReader bufr = 
                new BufferedReader(new InputStreamReader(System.in));

        // 使用字符输出方式到控制台
        BufferedWriter bufw = 
                new BufferedWriter(new OutputStreamWriter(System.out));

        String line = null;
        while((line=bufr.readLine()) != null) {
            // 输入end时退出
            if("end".equals(line)) break;
            bufw.write(line.toUpperCase());
            bufw.newLine();
            bufw.flush();
        }
        bufr.close();
        bufw.close();
    }
}
// 执行结果为
hello
HELLO
Hi
HI
good
GOOD
end

四、流的总结

JavaIO单独封装在一个包内,其中不同功能的流数不胜数,使用起来很容易混乱,所以,在使用java io流时需要按照一定的规则,如按照流的流向可以分为两大类,即输入流和输出流,输入流来自数据源,输出流有目的地。常见的源和目的地有如下:

  • 源:键盘录入,磁盘读入,网络文件,内存。
  • 目的地:控制台输出,磁盘文件,内存。

在明确流向后,在看流的数据是否为纯文本类型,若是则优先选用ReaderWriter字符流来操作,若是二进制类型,则使用InputStreamOutputStream字节流来操作。
提高流的效率,可以使用BufferedXXX来包装流。对于编码问题,可以使用InputStreamReaderOutputStreamWriter,其可以指定编码类型,如utf-8或者GBK等。

五、File的使用简介

是将文件和文件夹封装成的对象,方便对文件或者文件夹的属性信息进行操作,也可以作为参数传递。

File常用内容

static String separator为一种夸平台文件路径分隔符。
文件的创建和删除

File file = new File("file.txt");
file.createNewFile(); // 用于创建一个空文件,成功返回true,若已存在,则不会创建并返回false,
file.delete(); // 删除成功返回true,否则返回false
file.deleteOnExit(); // 退出时删除,一般用于系统退出时删除临时文件

文件的判断

// 通过文件的目录和文件名创建一个File对象
File file = new File("c:" + File.separator + "java", "info.txt");
file.canExecute(); // 是否可被执行
file.exists(); // 是否存在
file.mkdir(); // 创建目录,一层(成功返回true,失败返回false)
file.mkdirs(); // 创建多级目录
file.isDirectory(); // 是否为目录文件,不存在或者不是目录返回false
file.isFile(); // 是否为文件,不存在或不是文件返回false
file.isHidden(); // 是否为隐藏文件
file.isAbsolute(); // 是否为据对路径
file.getName(); // 获取文件名
file.getParh(); // 获取文件路径
file.getParent(); // 父目录
file.lastModified(); // 最后修改时间

文件列表

File[] files = File.listRoots();列出有效盘符,如(C:\,D:\等)。获取文件夹内的所有文件,以及自定义指定文件,可以使用FileFilter来过滤文件。若要删除内容不为空的文件夹时,需要递归删除文件夹内的所有内容。示例代码如下:

import java.io.*;
class Demo {
    public static void main(String[] args) {

        // 列出javas\\day20目录下的java文件
        File file = new File("javas\\day20");
        File[] files = file.listFiles(new FileFilter() {
            public boolean accept(File filename) {
                // 以.java结尾的都接受
                return filename.getName().endsWith(".java");
            }
        });

        for(File f : files) {
            System.out.println(f.getName());
        }

        // 删除javas内的所有内容
        delete(new File("javas"));
    }

    /**
     * 递归删除内容不为空的文件夹或文件
     */
    public static void delete(File file) {
        if(file.isDirectory()) {
            File[] files = file.listFiles();
            for(File f : files) {
                if(f.isDirectory()) {
                    delete(f);
                } else {
                    f.delete();
                }
            }
        }
        file.delete();
    }
}
// 执行结果为
FileDemo.java
FileDemo2.java
FileDemo3.java
JavaFileList.java
PrintStreamDemo.java
PropertiesDemo.java
RemoveDir.java
RunCount.java
SequenceDemo.java
SplitFile.java

六、其他IO内容

1、改变标准输入输入流,可以使用System.setIn(InputStream)System.setOut(PrintStream)来设置标准输入输出流,可以将标准输入设置成从键盘读入,或者将标准输出设置成输出到文件等。
2、异常信息输出到文件,在捕获异常后一般使用e.printStackTrace()将异常信息输出,但是这样对用户来说是没有任何意义的,所以可以使用e.printStackTrace(new FileOutputStream("log.txt"))将异常信息输出到文件。
3、Properties简介,可以使用Properties存取配置文件,其内部有相关流操作,即load(InputStream inStream)store(OutputStream out, String comments)用于从本地读取配置文件和将配置文件保存至本地。
4、合并流SequenceInputStream可以将多个流合并成一个整体,有两种构造函数,一是将两个字节流合并成一个流(较为易懂),二是传递一个Enumeration e类型参数,示例代码如下:

import java.io.*;
import java.util.*;
class SequenceDemo {
    public static void main(String[] args) throws IOException {
        // Vector 具有获取Enumeration功能
        Vector v = new Vector();
        v.add(new FileInputStream("1.txt"));
        v.add(new FileInputStream("2.txt"));
        v.add(new FileInputStream("3.txt"));

        // 获取流枚举
        Enumeration en = v.elements();
        // 构造合并流
        SequenceInputStream sin = new SequenceInputStream(en);
        // 将内容都写入fout
        FileOutputStream fout = new FileOutputStream("123.txt");
        byte[] buf = new byte[1024];
        int len = 0;
        while((len=sis.read(buf))!=-1) {
            fos.write(buf,0,len);
        }
        fout.close();
        sin.close();
    }
}

5、对象的序列化,实现Serializable接口,序列化即使将一个对象整个存储到文件中保存,可以实现数据的保存,下次可以将对象从文件中恢复出,只需要此对象实现了Serializable接口即可,注意的两点是,一是为对象添加static long serialVersionUID = 42L;随便指定一个数值,这是这个对象的唯一标识,即有相同标识才可以从文件中恢复出对象;二是在不需要序列化的对象前加上transient关键字。序列化只会序列化堆中的数据,静态数据不会被序列化,transient关键字的也不会。
6、管道流,PipedInputStreamPipedOutputStream,管道流就好像输入输出为一条管道,输入流可以从管道中读取数据,而输出流可以向管道中输入。可以通过两条线程同时操作,当输入流没有内容可以读取时,会柱塞线程等待输入数据。通过connect()来关联相关流。
7、RandomAccessFile用与文件的随机访问,自身具备读写方法,有rrw等打开方式,常用方法如skipBytes(int)跳过一部分内容,seek(index)将文件指针移到指定位置,适用于文件的断点写入,和多线程分段写入等。
8、ByteArrayStream是一种基于数组的流,不用close操作,也不会抛出IOException,其简单来说就是一个数组操作工具。和其类似的有CharArrayInputStream、CharArrayOutputStream针对字符数组操作的流和StringReaderStringWriter针对字符串操作的流。

你可能感兴趣的:(Android,Java)