Java IO流

来源:互联网 发布:js运行机制 编辑:程序博客网 时间:2024/05/16 14:03

一、概述

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

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

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

这里写图片描述 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: Hello2: haha3: Hi4: good meeas5: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();    }}// 执行结果为helloHELLOHiHIgoodGOODend

四、流的总结

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,否则返回falsefile.deleteOnExit(); // 退出时删除,一般用于系统退出时删除临时文件

文件的判断

// 通过文件的目录和文件名创建一个File对象File file = new File("c:" + File.separator + "java", "info.txt");file.canExecute(); // 是否可被执行file.exists(); // 是否存在file.mkdir(); // 创建目录,一层(成功返回true,失败返回false)file.mkdirs(); // 创建多级目录file.isDirectory(); // 是否为目录文件,不存在或者不是目录返回falsefile.isFile(); // 是否为文件,不存在或不是文件返回falsefile.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.javaFileDemo2.javaFileDemo3.javaJavaFileList.javaPrintStreamDemo.javaPropertiesDemo.javaRemoveDir.javaRunCount.javaSequenceDemo.javaSplitFile.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<? extends InputStream> e类型参数,示例代码如下:

import java.io.*;import java.util.*;class SequenceDemo {    public static void main(String[] args) throws IOException {        // Vector 具有获取Enumeration功能        Vector<FileInputStream> v = new Vector<FileInputStream>();        v.add(new FileInputStream("1.txt"));        v.add(new FileInputStream("2.txt"));        v.add(new FileInputStream("3.txt"));        // 获取流枚举        Enumeration<FileInputStream> 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针对字符串操作的流。

3 0