JAVA IO

1. File 类的作用?

  1. File类是java.io包下代表与平台无关的文件和目录,通过File可以操作文件和目录(增, 删, 改)

2. 创建File对象,以及调用File对象的方法操作文件或目录?

  1. 创建File对象
    File file = new File("绝对/相对路径");
  2. 调用 File 对象的方法
    • 操作文件的方法: getName(), getPath(), getAbsolutePath(), getParent(), renameTo(File filename), exists(), canWrite(), canRead(),
      isFile(), isDirectory(), isAbsolute(), length(), createNewFile(), delete(), createTempFile(String prefix, String suffix),
      createTempFile(String prefix, String suffix, File Directory), deleteOnExit()
    • 操作目录: mkdir(), String[] list(), File[] listFiles(), static File[] listRoots()

3. 文件过滤器(File的list()方法)例子?

    // 查找 filePath 路径下的以 ".java" 结尾的文件
    import java.io.File;
    public class FilenameFilterTest {
        public static void fileNameFilter(String filePath, String patten){
            File file = new File(filePath);
            String[] nameList = file.list((dir, name) -> name.endsWith(patten)
                || new File(name).isDirectory());
            for (String name : nameList){
                System.out.println(name);
            }
        }
        public static void main(String[] args){
            String filePath = "D:\\File\\Java\\JavaBase\\src\\xyz\\xmcs\\CrazyJava\\IO";
            String patten = ".java";
            fileNameFilter(filePath, patten);
    }

4. 什么是流? 什么是输入? 输出?

  1. 流: 在Java中把不同的输入/输出源抽象为 "流"(Stream), 通过流的方式允许Java程序使用相同的方式来访问不同的输入/输出源
  2. 输入: 指的是从程序外部读取数据
  3. 输出: 指的是向程序外部输出数据
  4. 输入/输出的主题是程序, 对程序来说, 将数据从外部读取到程序, 就是输入, 而将数据从程序输出到外部就是输出

5. 流的分类?

  1. 按照流的流向为:
    • 输入流: 只能从中读取数据, 不能写入数据, 基类为: InputStream 和 Reader
    • 输出流: 只能向其写入数据, 不能读取数据, 基类为: OutputStream 和 Writer
  2. 按照处理的大小分为:
    • 字节流: 处理的8为字节; 字节流字符流的用法一模一样; 基类: InputStream 和 OutputStream
    • 字符流: 处理的16为字符 基类: Reader 和 Writer
  3. 按照流的角色分为:
    • 节点流: 可以从/向特定的IO设备读/写数据的流, 程序直接连接到实际的物流节点
    • 处理流: 包装一个已存在的流, 对其进行封装或连接; 通过使用处理流来包装不同的节点流, 既可以消除不同的节点流的实现差异, 也可以提供更方便的方法来完成输入/输出功能

6. InputStream/Reader 基类,其常用方法, 及例子?

  1. InputStream/Reader 是所有输入流的抽象基类, 本身不能创建实例来执行输入; 但分别有一个用于读取文件的输入流:FileInputStream 和 FileReader, 都是节点流--会直接和指定的文件关联

  2. InputStream/Reader 的常用方法类似(区别只是一个处理字节, 一个处理字符):

    • int read(): 从输入流中读取单个字节/字符, 返回所读取的字节/字符数据
    • int read(byte[]/char[] b): 从输入流中最多读取 b.length个字节/字符的数据, 并将其存储在字节/字符数据b中, 返回实际读取的字节/字符数
    • int read(byte[]/char[] b, int off, int len): 从输入流中最多读取 len 长度的字节/字符数据, 并将其存储在字节/字符数据b中, 放入数据b中时, 并不是从数据的起点来时, 而是从off的位置开始, 返回实际读取的字节/字符数
  3. InputStream/ Reader 移动指针的方法:

    • void mark(int readAheadLimit): 在记录指针当前位置设置一个标记(mark)
    • boolean markSupported(): 判断此输入流是否支持mark() 操作
    • void reset(): 将此流的记录指针重新定位到上一次记录标记(mark)的位置
    • long skip(long n): 记录指针向前移动n个字节/字符
  4. 例子:

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    public class FileInputStreamTest {
        // InputStream 用法
        public static void inputStream(String path) {
            try(FileInputStream fis = new FileInputStream(path)){
                // 创建一个长度为512的字节数组("竹筒")
                byte[] b = new byte[512];
                // 用于保存实际读取的字节数
                int hasRead = 0;
                // 使用循环来重复"取水"过程
                while ((hasRead = fis.read(b)) != -1) {
                    System.out.println(new String(b, 0, hasRead));
                }
            }catch(Exception e){
                e.printStackTrace();}
        }
        // FileReader 用法
        public static void readerTest(String path) {
            try(FileReader fr = new FileReader(path)){
                char[] c = new char[512];
                int hasRead = 0;
                while((hasRead = fr.read(c)) != -1){
                    System.out.println(new String(c, 0, hasRead));
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            String path = "D:\\File\\Java\\ShoppingMall\\mall\\src\\jeffmall\\Mall.java";
            long start = System.currentTimeMillis();
    //        inputStream(path);
            readerTest(path);
            System.out.println(System.currentTimeMillis() - start);
        }
    }

7. OutputStream/Writer 基类,其常用方法, 及例子?

  1. OutputStream/Writer 是所有输出类的基类, 本身不能创建实例, 但是可以通过 FileOutputStream/FileWriter 来创建实例
  2. OutputStream/Writer 常用方法:
    • void write(int c): 将指定字节/字符输出到输出流中, c既可以是字节, 也可以是字符
    • void write(byte[]/char[] buf): 将字节/字符数组中的数据输出到指定的输出流中
    • void write(byte[]/char[] buf, int off, int len): 将字节/字符数组中从off位置开始, 长度为len的字符输出到指定输出流中
  3. Writer 特有的方法:
    • void write(String str): 将字符串str里包含的字符输出到指定输出流中
    • void write(String str, int off, int len): 将 str 字符串里从 off 位置开始, 长度为 len 的字符输出到指定输出流中
  4. 例子:
    import java.io.*;
    public class FileOutputStreamTest {
        // FileOutputStream 实例
        public static void fileOutPutStream(String sourcePath, String newPath) {
            try (
                    FileInputStream fis = new FileInputStream(sourcePath);
                    FileOutputStream fos = new FileOutputStream(newPath)) {
                byte[]  bbuf = new byte[512];
                int hasRead = 0;
                while ((hasRead = fis.read(bbuf)) != -1){
                    fos.write(bbuf, 0, hasRead);
                    }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // FileWriter 实例
        public static void writer(String sourcePath, String newPath) {
            try (
                    FileReader fr = new FileReader(sourcePath);
                    FileWriter fw = new FileWriter(newPath)){
                char[] bbuf = new char[256];
                int hasRead = 0;
                while((hasRead = fr.read(bbuf)) != -1){
                    fw.write(bbuf, 0, hasRead);
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            long start = System.currentTimeMillis();
            String sourcePath = "C:\\Users\\Desktop\\Login.html";
            String newPath = "C:\\Users\\Desktop\\login02.txt";
            fileOutPutStream(sourcePath, newPath);
    //        writer(sourcePath, newPath);
            System.out.println(System.currentTimeMillis() - start);
        }
    }

8. 处理流(或叫装饰流,外层流)?

  1. 使用处理流的优势就是可以隐藏底层设备节点流的差异, 并对外提供更加方便的输入/输出方法
  2. 使用处理流来包装节点流, 程序通过处理流来执行输入/输出功能, 让节点流与底层的I/O设备,文件交互
  3. 处理流也就是装饰流, 它需要直接以物理IO节点作为构造器的参数

9. 常用的处理流?

  1. 常用的底层输入流:

    • ByteArrayInputStream - 输入字节数组
    • StringBufferInputStream - 输入字符串
    • FileInputStream - 从文件输入,即打开一个文件
    • PipedInputStream - 管道输入流,构造器接受一个管道输出流
    • FileReader - 读取文件
    • StringReader - 读取字符串
    • CharArrayReader - 读取字符数组
    • PipedReader - 读取管道数据
  2. 常用的输入处理流:

    • DataInputStream: 从流中读取基本类型数据
    • BufferedInputStream: 采用缓冲区方式读写
    • LineNumberInputStream: 来跟踪输入流中的行号
    • BufferedReader - 通用的带缓冲读取字符
    • LineNumberReader - 带有行号API的读取
  3. 常用的底层输出流:

    • ByteArrayOutputStream: 字节数组输出,实际上是在内存中创建一个缓冲区输出
    • FileOutputStream: 输出到文件
    • PipedOutputStream: 管道输出流,构造器接受一个管道输入流
    • FileWriter - 写文件
    • StringWriter - 写字符串
    • CharArrayWriter - 写字符数组
    • PipedWriter - 向管道中写入字符
  4. 常用的输出处理流:

    • DataOutputStream - 向流中写入基本类型数据
    • PrintStream - 产生格式化输出,有print()和println()两个方法
    • BufferedOutputStream - 带缓冲区的写操作,带有flush()方法用于清空缓冲区并实际执行写操作
    • PrintWriter - 格式化输出,带有print()和println()方法

10. 常用的处理流的例子?

    import java.io.*;
    //
    // 从ASCII编码的文件 test.txt 中读出所有字符
    DataInputStream in = new DataInputStream(new FileInputStream("test.txt"));
    //
    // 从一个二进制文件 data.out 中采用缓冲的方式读取基本类型数据,已经知道这个文件按照顺序存放了一个int类型,一个long类型,四个ASCII字符
    DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.out")));
    //
    // 想从一个文本文件 story.txt 中按行读取字符
    BufferedReader in = new BufferedReader(new FileReader("story.txt"));
    //
    // 向一个文件 test.data 中带缓冲写字符串,写完之后自动换行
    PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("test.data")));

11. 转换流?

  1. 转换流就是用于将字节流转换为字符流
  2. 常用的类:
    • InputStreamReader: 将字节输入流转换成字符输入流
    • OutputStreamWriter: 将字节输出流转换成字符输出流
  3. 例子:
    import java.io.*;
    public class KeyinTest {
        public static void inputStreamReader(){
            try (
                    // 将 System.in对象转换为 InputStreamReader 对象
                    InputStreamReader reader = new InputStreamReader(System.in);
                    // 将普通的 Reader 包装成 BufferedReader
                    BufferedReader br = new BufferedReader(reader)) {
                String line = null;
                // 采用循环方式逐行读取
                while ((line = br.readLine()) != null) {
                    // 如果读取的字符串是 "exit", 则退出程序
                    if (line.equals("exit")) {
                        System.exit(1);
                    }
                    // 打印读取的内容
                    System.out.println("检测到输入的内容是: " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static void outputStreamWriter(String path) {
            try(
                BufferedReader br = new BufferedReader(new FileReader(path));
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))){
                String data = "";
                while ((data = br.readLine()) != null) {
                    bw.write(data);
                    bw.newLine();
                }
            }catch(Exception e) {e.printStackTrace();}
        }
        public static void main(String[] args) {
            String path = "C:\\Users\\Desktop\\Login.html";
    //        inputStreamReader();
            outputStreamWriter(path);
        }
    }

12. 重定向标准输入/输出及方法?

  • ava的标准输入/输出分别通过System.in和System.out来代表, 默认情况下他们分别代表键盘和显示器, 当程序 通过 System.in 来获取输入时, 实际上是从键盘读取输入, 当程序视图通过System.out 执行输出时, 程序总是 输出到屏幕; 重定向就是改变默认的输入输出情况, 例如输入重定向到从文本读取, 输出重定向为write到文本...

  • System 类里面提供下面三个重定向的标准输入/输出的方法:

    • static void setErr(PrintStream err): 重定向 "标准" 错误输出流
    • static void setIn(InputStream in): 重定向 "标准" 输入流
    • static void setOut(PrintStream out): 重定向 "标准" 输出流
  • 重定向标准输入从文本读取:

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.util.Scanner;
    public class RedirectIn {
        public static void redirectIn(String path) {
            try (FileInputStream fis = new FileInputStream(path)){
                // 将标准输入重定向到 fis 输入流
                System.setIn(fis);
                // 使用 System.in 创建 Scanner 对象, 用于获标准输入
                Scanner sc = new Scanner(System.in);
                // 增加下面一行只把回车作为分隔符
                sc.useDelimiter("\n");
                // 判断是否还有下一个输入项
                while(sc.hasNext()){
                    // 输出输入项
                    System.out.println("键盘输入的内容是: " + sc.next());
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            String path = "C:\\Users\\Desktop\\Login.html";
            redirectIn(path);
        }
    }
  • 重定向标准输出到写入文本, 而不是显示在屏幕
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.PrintStream;
    public class RedirectOut {
        public static void redirectOut(String path, String str) {
            try (
                    // 一次性创建 PrintStream 输出流
                    PrintStream ps = new PrintStream(new FileOutputStream(path))
            ) {
                // 将标准输出重定向到ps输出流
                System.setOut(ps);
                // 向标准输出输出一个字符串
                System.out.println(str);
                // 向标准输出输出一个对象
                System.out.println(new RedirectOut());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            String str = "这是要写入的String字符串";
            String path = "C:\\Users\\Desktop\\out.txt";
            redirectOut(path, str);
        }
    }

13. RandomAccessFile(任意访问文件)?

  1. RandomAccessFile 既可以读取文件内容, 也可以向文件输出数据, 因为它支持自由定位文件记录指针, 所以可以跳转到文件的任意位置读写数据
  2. RandomAccessFile 对象包含了一个记录指针, 用于标识当前读写处的位置, 当程序新创建一个 RandomAccessFile 对象时, 该对象的文件记录指针位于文件头(也就是0处), 当读/写了n个字节后, 文件记录指针将会向后移动n个字节
  3. RandomAccessFile 允许自由定位文件记录指针, RandomAccessFile 可以不从开始的地方输出, 因此RandomAccessFile 可以向已存在的文件后追加内容; 如果程序需要向已存在的文件后追加内容, 则应该使用 RandomAccessFile
  4. RandomAccessFile 操作文件记录指针的方法:
    • long getFilePointer(): 返回文件记录指针的当前位置
    • void seek(long pos): 将文件记录指针定位到pos位置
  5. RandomAccessFile 既可以读取文件, 也可以写, 所以它既包含了完全类似于 InputStream 的三个 read() 方法, 其用法和 InputStream 的三个 read() 方法完全一样; 也包含了完全类似于 OutputStream 的三个 write() 方法 其用法和 OutputStream 的三个 write() 方法完全一样; 另外 RandomAccessFile 还包含了一些列 readXxx() 和 writeXxx() 方法来完成输入, 输出
  6. 创建 RandomAccessFile 对象时还需要指定一个 mode 参数, 该参数指定 RandomAccessFile 的访问模式
    • "r": 只读方式打开指定文件, 如果试图对该 RandomAccessFile 执行写入方法, 将抛出 IOException 异常
    • "rw": 以读, 写方式打开指定文件, 如果文件尚不存在, 则尝试创建文件
    • "rws": 以读, 写方式打开指定文件, 相对于 "rw" 模式, 还要求对文件的内容或元数据的每个更新都同步写入到 底层存储设备
    • "rwd": 以读, 写方式打开指定文件, 相对于 "rw" 模式, 还要求对文件内容的每个更新都同步写入到底层存储设备

14. 在文本的指定位置插入数据

    // 在文件的指定位置插入文本
    // 实现方法是: 找到要插入的文件位置pos, 创建临时文件, 读取pos到文件尾的数据, 输出到临时文件中
    //            接着移动文件指针到pos位置, 插入指定的数据, 在读取临时文件的内容, 写入到文件中
    import java.io.*;
    //
    public class InsertContent {
        /**
         * @param fileName  需要追加的文件的名字, 绝对路径
         * @param pos   需要追加的位置
         * @param insertContent  需要追加的内容
         * @throws IOException  可能存在文件打开异常
         */
        public static void insert(String fileName, long pos,
              String insertContent) throws IOException {
            // 通过 File 的 createTempFile() 方法创建临时文件
            File tmp = File.createTempFile("tmp", null);
            // 通过 File 实例调用 deleteOnExit() 方法实现退出Java虚拟机时, 删除刚才创建的临时文件
            tmp.deleteOnExit();
            try (
                RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
                // 使用临时文件来保存插入点之后的数据
                FileOutputStream tmpOut = new FileOutputStream(tmp);
                FileInputStream tmpIn = new FileInputStream(tmp)) {
                raf.seek(pos);
                // -------------下面代码将插入点后的内容读入临时文件保存--------------
                byte[] bbuf = new byte[64];
                // 用于保存实际读取的字节数
                int hasRead = 0;
                // 使用循环方式读取插入点后的数据
                while ((hasRead = raf.read(bbuf)) != -1) {
                    // 将读取的数据写入临时文件
                    tmpOut.write(bbuf, 0, hasRead);
                }
                // -------------下面代码用于插入内容--------------
                // 把文件记录指针重新定位到pos位置
                raf.seek(pos);
                // 追加需要插入的内容
                raf.write(insertContent.getBytes());
                // 追加临时文件中的内容
                while ((hasRead = tmpIn.read(bbuf)) > 0) {
                    raf.write(bbuf, 0, hasRead);
                }
            }
        }
        public static void main(String[] args) throws IOException {
            insert("D:\\File\\Java\\JavaBase\\src\\xyz\\xmcs\\Main.java",
                19, "// --- 这是插入的内容 --- // \r\n");
        }
    }

15. NIO Java的新 IO 概述?

  1. 新IO和传统的IO具有相同的目的, 都是用于进行输入/输出, 但新IO使用了不同的方式来处理输入/输出, 新IO采用了 内存映射文件的方式来处理输入/输出, 新IO将文件或文件的一段区域映射到内存中, 这样就可以像访问内存一样来 访问文件了, 通过这种方式来进行输入/输出比传统的输入/输出要快
  2. Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象, Channel 是对传统的输入/输出系统的模拟, 在新IO系统 中所有的数据都需要通过通道传输; Channel 与传统的 InputStream, OutputStream最大的区别在于它提供了一个 map() 方法, 通过该 map() 方法可以直接将 "一块数据" 映射到内存中; 传统的输入/输出是面向流的处理, 新IO 则是面向块的处理
  3. Buffer 可以被理解成一个容器, 他的本质是一个数组, 发送到 Channel 中的所有对象都必须首先放到Buffer中, 而 从Channel 中读取的数据也必须先放到Buffer中; 此处的Buffer类似于前面介绍的 "竹筒", 但该Buffer既可以像 "竹筒" 那样一次次去Channel中取水, 也允许使用Channel直接将文件的某块数据映射成Buffer

16. Buffer?

  1. Buffer的内部结构就是一个数组, 它可以保存多个类型相同的数据; Buffer是一个抽象类, 最常用的子类是 ByteBuffer, 它可以在底层字节数组上进行 get/set 操作; 除此之外还有其他基本数据类型(boolean除外): CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer
  2. XxxBuffer抽象类没有构造器, 只能通过下面的方法来获取Buffer对象:
    • static XxxBuffer allocate(int capacity): 创建一个容量为 capacity 的 XxxBuffer 对象
  3. Buffer 的三个重要概念: 容量(capacity), 界限(limit), 位置(position)
    • 容量(capacity): 缓冲区的容量(capacity)表示该Buffer的最大数据容量, 即最多可以存储多少数据, 缓冲区的容量 不可为负值, 创建后不能改变
    • 界限(limit): 第一个不应该被读出或者写入的缓冲区位置索引, 也就是说, 位于limit后的数据既不可读, 也不可写
    • 位置(position): 用于指明下一个可以被读出的或者写入的缓冲区位置索引(类似于IO流中的记录指针), 当使用Buffer 从 Channel 中读取数据时, position的值恰好等于已经读到了多少数据, 当刚刚新建了一个Buffer对象时, 其 position为0; 如果从 Channel 中读取了2个数据到Buffer时, 则 position为2, 指向Buffer中的第2个(第一个位置 索引为0)位置

17. 使用Buffer的流程?

  1. Buffer的主要功能就是装入数据, 然后输出数据, 开始时Buffer的position为0, limit为capacity, 程序可通过put()方法 向Buffer中放入一些数据(或者从Channel中获取一些数据), 每放入一些数据, Buffer的position相应的向后移动一些位置 当Buffer装入数据结束后, 调用Buffer的flip()方法, 该方法将limit设置为position所在位置, 并将position设为0, 这 就使得Buffer的读写指针又移到了开始位置, 也就是说, Buffer调用flip()方法之后, Buffer为输出数据做好了准备; 当 Buffer输出数据结束后, Buffer调用clear()方法, clear()方法不是清空Buffer的数据, 它仅仅将position置为0, 将 limit置为capacity, 这样为再次向Buffer中装入数据做好准备

18. Buffer 常用的方法?

  • int capacity(): 返回Buffer的capacity大小
  • boolean hasRemaining(): 判断当前位置(position)和界限(limit)之间是否还有元素可供处理
  • int limit(): 返回Buffer的界限(limit)的位置
  • Buffer limit(int newLt): 重新设置界限(limit)的值, 并返回一个具有新的limit的缓冲区对象
  • Buffer mark(): 设置Buffer的mark位置, 它只能在0和位置(position)之间做mark
  • int position(): 返回Buffer中的position值
  • Buffer position(int newPos): 设置Buffer的position, 并返回position被修改后的Buffer对象
  • int remaining(): 返回当前位置和界限之间的元素个数
  • Buffer reset(): 将位置(position)转到mark所在的位置
  • Buffer rewind(): 将位置(position)设置为0, 取消设置的mark
  • flip(): (将limit设置为position, 再将position设为0)为从Buffer中取出数据做好准备
  • clear(): (将position设为0, 将limit设置为capacity)为再次向Buffer中装入数据做好准备
  • put(): 向Buffer中放入数据
  • get(): 从Buffer中取出数据

19. Buffer的例子?

    import java.nio.CharBuffer;
    public class BufferTest {
        public static void main(String[] args) {
            // 通过 Buffer 的 allocate(int capacity) 方法来创建容量为capacity的Buffer对象
            CharBuffer buff = CharBuffer.allocate(8);
            System.out.println("capacity = " + buff.capacity());
            System.out.println("limit = " + buff.limit());
            System.out.println("position = " + buff.position());
            // 通过 put() 方法放入元素
            buff.put('a');
            buff.put('b');
            buff.put('c');
            // 放入元素后, position 的位置也会随之往后移
            System.out.println("加入三个元素后, position = " + buff.position());
            // 调用flip()方法, 将limit移动到position, 将position位置移动到0, 为读取数据做好准备
            buff.flip();
            System.out.println("执行flip()方法后, limit = " + buff.limit());
            System.out.println("执行flip()方法后, position = " + buff.position());
            // 取出第一个元素
            System.out.println("取出第一个元素(position = 0): " + buff.get());
            System.out.println("取出第一个元素后, position = " + buff.position());
            // 调用clear()方法将position移动到0, 将limit移动到capacity的位置, 为装入数据做好准备
            buff.clear();
            System.out.println("执行clear()后, limit = " + buff.limit());
            System.out.println("执行clear()方法后, position = " + buff.position());
            System.out.println("执行clear()后, 缓冲区内容并没有被清除: "
                    + "第三个元素为: " + buff.get(2));
            System.out.println("执行绝对读取后, position = " + buff.position());
        }
    }

20. Channel?

  1. Channel接口可以直接将指定文件的部分或全部直接映射成Buffer
  2. 程序不能直接访问 Channel中的数据, 包括读取, 写入都不行, Channel 只能和 Buffer 交互; 如果要从 Channel 中 取得数据, 必须先用 Buffer 从 Channel 中取出一些数据, 然后让程序从Buffer中取出这些数据; 如果要将程序中的 数据写入 Channel, 也要先让程序将数据放入 Buffer 中, 程序再将 Buffer 里的数据写入 Channel 中
  3. Channel 接口的实现类:
    • Pipe.SinkChannel, Pipe.SourceChannel: 这两个实现类用于支持线程之间通信的管道 Channel
    • ServerSocketChannel, SocketChannel: 是用于支持TCP网络通信的Channel
    • DatagramChannel: 是用于支持UDP网络通信的 Channel
    • FileChannel: 是用于文件访问的 Channel
  4. 所有的 Channel 通过传统的节点 InputStream, OutputStream 的 getChannel() 方法来返回
  5. Channel 中最常用的三类方法是 map(), read(), write()
    • map() 用于将 Channel 对应的部分或全部数据映射成ByteBuffer
    • read() 用于从Buffer中读取数据
    • write() 向Buffer中写入数据
  6. map() 方法的方法签名为: MappedByteBuffer map(FileChannel.MapMode mode, long position, long size): 第一个 参数执行映射时的模式, 分别有只读, 读写模式; 第二个参数, 第三个参数用于控制将 Channel 的那些数据映射成ByteBuffer
  7. 例子:
    // Channel 通过 "少拿勤跑" 的方式映射数据
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.channels.FileChannel;
    import java.nio.charset.Charset;
    import java.nio.charset.CharsetDecoder;
    //
    public class ReadFile {
        public static void main(String[] args) throws IOException {
            try (
                // 创建文件输入流
                FileInputStream fis = new FileInputStream(
                    "D:\\File\\Java\\JavaBase\\src\\xyz\\xmcs\\CrazyJava\\IO\\ReadFile.java");
                // 通过文件输入流创建一个FileChannel对象
                FileChannel fc = fis.getChannel()) {
                // 定义一个ByteBuffer对象, 用于重复取水
                ByteBuffer bbuff = ByteBuffer.allocate(256);
                // 将 FileChannel 中的数据放入 ByteBuffer 中
                while (fc.read(bbuff) != -1) {
                    // 锁定Buffer的空白区
                    bbuff.flip();
                    // 创建 Charset 对象
                    Charset charset = Charset.forName("UTF-8");
                    // 创建解码器 (CharsetDecoder) 对象
                    CharsetDecoder decoder = charset.newDecoder();
                    // 将 ByteBuffer 的内容转码
                    CharBuffer cbuff = decoder.decode(bbuff);
                    System.out.print(cbuff);
                    // 将 Buffer 初始化, 为下一次读取数据做好准备
                    bbuff.clear();
                }
            }
        }
    }

21. Path ?

  1. Path 接口代表一个平台无关的平台操作
  2. 例子
    // Path 接口的功能和方法
    import java.nio.file.Path;
    import java.nio.file.Paths;
    //
    public class PathTest {
        public static void main(String[] args) throws Exception {
            // 以当前路径来创建Path对象
            Path path = Paths.get(".");
            System.out.println("path 里包含的路径数量: " + path.getNameCount());
            // 获取path对应的绝对路径
            Path absolutePath = path.toAbsolutePath();
            System.out.println(absolutePath);
            // 获取绝对路径的根路径
            System.out.println("absolutePath 的根路径: " + absolutePath.getRoot());
            // 获取绝对路径所包含的路径数量
            System.out.println("absolutePath 里包含的路径数量: " + absolutePath.getNameCount());
            System.out.println(absolutePath.getName(0));
            // 以多个String来构建Path对象
            Path path2 = Paths.get("D:", "Books", "Java");
            System.out.println(path2);
        }
    }

22. Files ?

  1. Files 包含了大量静态的工具来操作文件
  2. 例子
    // Files 操作文件工具类
    import java.io.FileOutputStream;
    import java.nio.charset.Charset;
    import java.nio.file.FileStore;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.ArrayList;
    import java.util.List;
    //
    public class FilesTest {
        public static void main(String[] args) throws Exception {
            String FileTestPath = "D:\\File\\Java\\JavaBase\\src\\xyz\\xmcs\\CrazyJava\\IO\\FilesTest.java";
            // 复制文件
            Files.copy(Paths.get(FileTestPath), new FileOutputStream("a.txt"));
            // 判断 FilesTest.java 文件是否为隐藏文件
            System.out.println("FilesTest.java 是否为隐藏文件: "
                + Files.isHidden(Paths.get(FileTestPath)));
            // 一次性读取 FilesTest.j所有行
            List lines = Files.readAllLines(Paths.get(FileTestPath), Charset.forName("UTf-8"));
            System.out.println(lines);
            // 判断指定文件的大小
            System.out.println("FilesTest.Java 的大小为: "
                + Files.size(Paths.get(FileTestPath)));
            List poem = new ArrayList<>();
            poem.add("水晶潭底银鱼跃");
            poem.add("清风徐来碧竿横");
            // 直接将多个字符串内容写入指定文件中
            Files.write(Paths.get("poem.txt"), poem, Charset.forName("UTF-8"));
            // 使用Java8新增的 Stream API 列出当前目录下所有文件和子目录
            Files.list(Paths.get(".")).forEach(path -> System.out.println(path));
            // 使用Java8 新增的API读取文件内容
            Files.lines(Paths.get(FileTestPath), Charset.forName("UTF-8")).forEach(line -> System.out.println(line));
            FileStore cStore = Files.getFileStore(Paths.get("C:"));
            // 判断 C盘的总空间, 可用空间
            System.out.println("C盘的共有空间: " + cStore.getTotalSpace());
            System.out.println("C盘的可用空间: " + cStore.getUsableSpace());
        }
    }

你可能感兴趣的:(JAVA IO)