【IO/NIO】Java IO/NIO

来源:互联网 发布:数控车床编程自学视频 编辑:程序博客网 时间:2024/05/16 15:15

在Java程序中,对于数据的输入/输出以”流”(Stream)方式进行
java.io 包定义了多个流类型:
  1) 按数据流方向分为 – 输入流和输出流
  2) 按数据单位分为 – 字节流和字符流
  3) 按功能分为 – 节点流和处理流

所有流类型均位于java.io包内,分别继承以下四种抽象流类型:
      字节流      字符流
输入流   InputStream   Reader
输出流  OutputStream   Writer

注:输入输出流,都是站在【程序】的角度上来说,从文件读数据这叫输入流,往文件写数据叫输出流

一、字节 I/O 操作(InputStream和OutputStream)相关类图

这里写图片描述

1) InputStream是一个抽象类,核心方法是read()、read(byte b[])、read(byte b[], int off, int len),从不同的数据源读取数据。
这些数据源有:
 ① 字节数组。 
 ② String对象。 
 ③ 文件。
 ④ “管道”,一端输入,另一端输出。
 ⑤ 一个由其他种类的流组成的序列。 
 ⑥ 其他数据源,如Internet等。
每一种数据源都对应相应的InputStream子类:
 ByteArrayInputStream:缓冲区,处理字节数组,允许将内存的缓冲区当作InuputStream。
 StringBufferInputStream:将String转换成 InputStream,底层使用StringBuffer实现。
 FileInputStream:从文件中读取信息。
 PipedInputStream:从管道读数据,最为多线程中的数据源。
 SequenceInputStream:将多个流对象转换成一个InputStream。
 FilterInputStream:“装饰器”类的基类(这里是装饰器模式),为其它InputStream类提供功能:
   DataInputStream:用于读取基本类型数据。
   BufferedInputStream:使用缓冲区,防止每次读取时都得进行实际写操作。
   LineNumberInputStream:跟踪输入流中的行号,getLineNumber()、setLineNumber(int)。
   PushbackInputStream:具有能弹出一个字节的缓冲区,可以将读到的最后一个字符回退。

2) OutputStream是一个抽象类,核心方法是write(int b)、write(byte b[])、write(byte b[], int off, int len),决定输出的目标:字节数组、文件或管道。
 ByteArrayOutputStream:将数据写入一个byte数组缓冲区。
 FileOutputStream:将数据写入文件。
 PipedOutputStream:指定多线程数据的目的地,向与其它线程共用的管道中写入数据,用于多线程之间任务的通信。
 FilterOutputStream:“装饰器”类,提供特定的输出流:
   DataOutputStream:与DataInputStream搭配使用,用于写入基本类型数据。
   BufferedOutputStream:使用缓冲区,避免每次发送数据时都进行实际的写操作,可使用 flush() 清空缓冲区。
   PrintStream:产生格式化输出。

二、字符 I/O 操作(Reader和Writer)相关类图

这里写图片描述

三、基于磁盘的I/O操作(File类)
  File类既能代表一个特定文件的名称,又能代表一个目录下的一组文件的名称。
  
【1】文件、目录的创建和删除

public class FileOperation {    public static void main(String[] args) {        // 使用 File.separator 系统默认名称分隔符,Windows和Linux文件路径分割符不同        String fileName = "C:" + File.separator + "hello.txt";         File file = new File(fileName);        // 创建一个新文件        try {            // 指定的文件不存在且成功创建返回 true;文件已存在返回 false            file.createNewFile();        } catch (IOException e) {            e.printStackTrace();        }        // 删除文件        if(file.exists()){            // 成功删除文件或目录时返回 true            file.delete();        }        // 创建一个文件夹        String dirName = "C:" + File.separator + "hello";        File dirFile = new File(dirName);        dirFile.mkdir();        dirFile.isDirectory(); // 判断是否为目录    }}

【2】查看目录列表

public class FileOperation {    public static void main(String[] args) {        File path = new File("C:\\");        String[] list;        // 查看目录所有内容        list = path.list();        // 查看目录指定内容(以 .txt 结尾的)        final String regStr = ".+.txt";        // 使用一个匿名的目录过滤器内部类,通过正则表达式验证过滤,list()可以回调accept()方法        list = path.list(new FilenameFilter() {            private Pattern pattern = Pattern.compile(regStr);             @Override            public boolean accept(File dir, String name) {                return pattern.matcher(name).matches();            }        });        for(String dirItem : list){            System.out.println(dirItem);        }    }}

四、I/O流典型使用方式
  尽管可以通过不同的方式组合 I/O 流类,但我们可能也就只用到其中的几种组合。
  
【1】缓冲输入文件(读取)

public class BufferedInputFile {    public static String read(String fileName) throws Exception{        BufferedReader br = new BufferedReader(new FileReader(fileName));        String str;        StringBuffer sb = new StringBuffer();        while((str = br.readLine()) != null){            sb.append(str + "\n");        }        br.close();        return sb.toString();    }    public static void main(String[] args) throws Exception {        System.out.println(read("src/com/test/BufferedInputFile.java"));    }}

【2】从内存输入(读取)

public class MemoryInput {    public static void main(String[] args) throws Exception {        StringReader in = new StringReader(BufferedInputFile.read("src/com/test/MemoryInput.java"));        int c;        while((c = in.read()) != -1){            System.out.println((char)c);        }    }}

【3】基本文件输出

/** * FileWriter向文件写入数据,使用BufferWriter将其封装缓冲输出,为了格式化装饰成 PrintWriter * */public class BasicFileOutput {    static String file = "BasicFileOutput.out";    public static void main(String[] args) throws Exception {        BufferedReader br = new BufferedReader(new StringReader(BufferedInputFile.read("src/com/test/BasicFileOutput.java")));        // PrintWriter有个构造函数,内部实现了BufferedWriter缓冲        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));        //可以简写为 PrintWriter out = new PrintWriter(file);        int lineCount = 1;        String s;        while((s = br.readLine()) != null){            out.println(lineCount++ + ": " + s);        }        out.close();        System.out.println(BufferedInputFile.read(file));    }}

【4】存储和恢复数据

/** * 存储和恢复数据,使用 DataOutputStream写入数据,并用 DataInputStream 恢复数据 * 这些流可以是任何形式,以文件为例,对读写都进行缓冲处理 */public class StoringAndRecoveringData {    public static void main(String[] args) throws Exception {        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(                new FileOutputStream("C:" + File.separator + "Data.txt")));        out.writeDouble(3.14159265);        out.writeUTF("That was PI");        out.writeDouble(1.41413);        out.writeUTF("Square root of 2");        out.close();        DataInputStream in = new DataInputStream(new BufferedInputStream(                new FileInputStream("C:" + File.separator + "Data.txt")));        System.out.println(in.readDouble());        System.out.println(in.readUTF());        System.out.println(in.readDouble());        System.out.println(in.readUTF());        in.close();    }}

【5】标准 I/O
  Java提供了System.in、System.out和System.err,System.in是一个没有被包装过未经加工的 InputStream

public class Echo {    public static void main(String[] args) throws IOException {        // 将System.in包装成BufferedReader,使用readLine()一次读取一行        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        String str;        while((str = br.readLine()) != null && str.length() != 0){            System.out.println(str);        }        // 将System.out转换成PrintWriter        PrintWriter out = new PrintWriter(System.out, true); // 第二个参数 true 开启自动清空功能        out.println("Hello, world");        // 标准 I/O 重定向   操作的是字节流        // System类提供了一些简单的静态方法,允许对标准输入、输出和错误I/O流进行重定向        // System.setIn(InputStream)  System.setOut(PrintStream)   System.setErr(PrintStream)        PrintStream console = System.out; // System.out对象的引用        BufferedInputStream in = new BufferedInputStream(new FileInputStream("src/com/test/Echo.java"));        PrintStream _out = new PrintStream(new BufferedOutputStream(new FileOutputStream("test.out")));        System.setIn(in);        System.setOut(_out);        System.setErr(_out);        str = null;        while((str = br.readLine()) != null && str.length() != 0){            System.out.println(str); // 标准输出将重定向到另一个文件        }        _out.close();        System.setOut(console); // 恢复了System.out对象的引用    }}

五、Java NIO
  JDK1.4 的java.nio.* 包中引入了新的java I/O类库,目的在于提高速度。与传统的 IO 相比主要有以下区别:
 ① Java IO是面向流的,Java NIO是面向缓冲区(块)的
 ② Java IO各种流是阻塞的,当一线程调用read()或write()时,该线程被阻塞,直到有数据被读取或写入;Java NIO是非阻塞的,一线程从某通道请求读取数据,若目前没有数据可用,则不保持线程阻塞,直到有数据可供读取,该线程可继续做其他的事情,在写入的时候也是,不等待它完全写入,线程也可以同时去做其他的事情
  Java NIO 由以下3个核心部分组成:Channel(通道)、Buffer(缓冲区)、Selector(选择器)
  
(1)Channel(通道):Java NIO的通道类似流,但又有些不同:
 ① 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
 ② 通道可以异步地读写。
 ③ 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入

在Java NIO中最重要的通道的实现:
 ① FileChannel:从文件中读写数据
 ② DatagramChannel:能通过UDP读写网络中的数据
 ③ SocketChannel:能通过TCP读写网络中的数据
 ④ ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel

这里写图片描述

(2)Buffer(缓冲区):用于和NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入到通道中的

这里写图片描述

使用Buffer读写数据一般遵循以下四个步骤:
 ① 写入数据到Buffer
 ② 调用flip()方法
 ③ 从Buffer中读取数据
 ④ 调用clear()方法或者compact()方法
  向Buffer写入数据,当要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,读取之前写入到buffer的所有数据。一旦数据读取完毕,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据,任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
  
(2.1)Buffer的capacity、position和limit
 ① capacity:不管Buffer处于读模式还是写模式,其返回缓冲区的容量
 ② position:初始值为0,最大值为capacity-1 。写数据到Buffer时,表示当前位置,没插入一条数据,向前移动一个单元;读取数据时,从某个位置读取数据,当Buffer切换为读模式时,position会被重置为0,读取一条数据时,会移动到下一个可读的位置。
 ③ limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
 Buffer的分配,要想获得一个Buffer对象首先要进行分配,如分配48字节capacity的ByteBuffer:ByteBuffer buf = ByteBuffer.allocate(48),分配一个可存储1024个字符的CharBuffer:CharBuffer buf = CharBuffer.allocate(1024);

向Buffer写数据,两种方式:
 ① 从Channel写到Buffer
  int bytesRead = inChannel.read(buf); //read into buffer
 ② 通过Buffer的put()方法写到Buffer里
  buf.put(127);
 
从Buffer中读数据,两种方式:
 ① 从Buffer读取数据到Channel
  int bytesWritten = inChannel.write(buf);
 ② 使用get()方法从Buffer中读取数据
  byte aByte = buf.get();

rewind()方法
Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
  
mark()与reset()方法
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。

buffer.mark();//call buffer.get() a couple of times, e.g. during parsing. buffer.reset();  //set position back to mark.

【1】FileChannel
  用于读取、写入、映射和操作文件的通道,在现版本中,可从现有的 FileInputStream、FileOutputStream 或 RandomAccessFile 对象获得文件通道,方法是调用该对象的 getChannel 方法,这会返回一个连接到相同底层文件的文件通道。

public class GetFileChannel {    public static void main(String[] args) throws Exception {        FileChannel fileChannel = new FileOutputStream("data.txt").getChannel();        // 将 byte 数组包装到缓冲区中,并将数据写入通道(文件)        fileChannel.write(ByteBuffer.wrap("FileOutputStream--FileChannel Test".getBytes()));         fileChannel.close(); // 关闭此通道        // 向文件末尾增加数据  rw读写  r只读        fileChannel = new RandomAccessFile("data.txt", "rw").getChannel();        // 调用position()方法获取FileChannel的当前位置,调用position(long pos)方法设置FileChannel的当前位置        // FileChannel实例的size()方法将返回该实例所关联文件的大小        fileChannel.position(fileChannel.size()); //  设置此通道的文件位置,末尾        fileChannel.write(ByteBuffer.wrap("RandomAccessFile--FileChannel Test".getBytes()));        fileChannel.close();        // FileChannel.force(boolean metaData)方法将通道里尚未写入磁盘的数据强制写到磁盘上        // true 表示是否同时将文件元数据(权限信息等)写到磁盘上        // 读取文件        fileChannel = new FileInputStream("data.txt").getChannel();        ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配一个缓冲对象        fileChannel.read(buffer); // 将字节序列从此通道读入给定的缓冲区,返回读取的字节数        buffer.flip(); // 切换到读模式,让别人做好读取字节的准备        while(buffer.hasRemaining()){            System.out.print((char)buffer.get());        }       }}

上述代码中,在读取通道数据并输出时,使用(char)buffer.get()每次读取一个字节,然后强制转换为char类型(这种方法显然比较原始),如果不强制转换,将会是乱码,这是因为ByteBuffer 存储的是普通的字节,为了把它们转换成字符,有两种方式可以解决:① 在输入它们的时候对其进行编码;② 从缓冲器输出时对它们进行解码。
  从api文档中可以看到CharBuffer有一个toString()方法可以返回缓冲器包含的所有的字符,而ByteBuffer可以调用asCharBuffer()方法作为 char 类型缓冲区

public static void main(String[] args) throws Exception {    final int BSIZE = 1024;    FileChannel fileChannel = new RandomAccessFile("data.txt", "rw").getChannel();    fileChannel.write(ByteBuffer.wrap("some text".getBytes()));    fileChannel.close();    ByteBuffer buffer = ByteBuffer.allocate(BSIZE);    fileChannel = new FileInputStream("data.txt").getChannel();    fileChannel.read(buffer);    buffer.flip();    // 在这里直接调用asCharBuffer()显示不行输出乱码    System.out.println(buffer.asCharBuffer());    buffer.rewind(); // 将position设回0,可以重读Buffer中的所有数据    /** 1) 在输出之前进行解码 Decoding**/    // 获取系统编码,使用java.nio.charset.Charset进行解码    // decode(ByteBuffer bb) 返回一个CharBuffer对象           String encoding = System.getProperty("file.encoding");    System.out.println("使用 " + encoding +" 解码输出:" + Charset.forName(encoding).decode(buffer));    /** 2) 在写入通道之前进行编码,这样输出才有意义 **/    fileChannel = new FileOutputStream("data1.txt").getChannel();    // 使用 UTF-8 写入    fileChannel.write(ByteBuffer.wrap("some text2".getBytes("UTF-16BE")));    fileChannel.close();    // 读取    fileChannel = new FileInputStream("data1.txt").getChannel();    buffer.clear();    fileChannel.read(buffer);    buffer.flip();    System.out.println(buffer.asCharBuffer());// 正常输出    /** 3) 在写入通道使用buffer.asCharBuffer() **/    fileChannel = new FileOutputStream("data3.txt").getChannel();    buffer = ByteBuffer.allocate(24);    buffer.asCharBuffer().put("some text3");    fileChannel.write(buffer);    fileChannel.close();    // 读取    fileChannel = new FileInputStream("data3.txt").getChannel();    buffer.clear();    fileChannel.read(buffer);    buffer.flip();    System.out.println(buffer.asCharBuffer());// 正常输出}

Java IO学习总结:
http://www.cnblogs.com/rollenholt/archive/2011/09/11/2173787.html
http://blog.csdn.net/zhangerqing/article/details/8466532
http://www.importnew.com/17735.html
http://blog.csdn.net/maritimesun/article/details/7973603
http://ifeve.com/java-nio-vs-io/

原创粉丝点击