1. File 类

  • 文件分隔符

    1. String pathSeparator = File.pathSeparator;//与系统有关的路径分隔符 windows分号,linux冒号
    2. String separator = File.separator;// 文件名称分隔符,windows 反斜杠\ ,linux正斜杠/
  • 构造文件对象 and 获取文件属性 ```java // 构造一:File(String pathName) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例 File f1 = new File(“D:\JavaLearing\code\testIO\cyt.txt”); System.out.println(f1.length()); //获取文件大小,若不存在,则大小为 0; System.out.println(f1.getPath()); //output:D:\JavaLearing\code\testIO\cyt.txt System.out.println(f1.getAbsolutePath()); //output:D:\JavaLearing\code\testIO\cyt.txt

File f2 = new File(“E:\JavaLearing\code\testIO”); System.out.println(f2.length()); //文件夹没有大小概念,返回为 0

File f3 = new File(“java.txt”);//文件会在当前路径下新建 java.txt System.out.println(f3.getPath()); //output: java.txt System.out.println(f3.getAbsolutePath()); //output: E:\JavaLearing\code\testIO\java.txt

// 构造二:File(String parent, String child) File f4 = new File(“e:\“,”cyt.txt”); System.out.println(f4.getName());//获取的是路径的结尾部分,即最后一个分隔符之后的部分

// 构造三:File(File parent, String child) File parent = new File(“c:\“); File f5 = new File(parent, “cyt.java”);

// 构造四:File(String url)

  1. - 创建与删除文件夹文件
  2. ```java
  3. // 创建一:createNewFile
  4. File f1 = new File("E:\\JavaLearing\\code\\testIO\\1.txt");
  5. try {
  6. boolean b = f1.createNewFile();
  7. System.out.println(b);
  8. } catch (IOException e) {
  9. e.printStackTrace();
  10. }
  11. // 创建二 :mkdir 创建单级文件夹
  12. File f2 = new File("E:\\JavaLearing\\code\\testIO\\hhhhh");
  13. System.out.println(f2.mkdir());
  14. // 创建三:mkdirs 创建多级文件夹
  15. File f3= new File("E:\\JavaLearing\\code\\testIO\\hh\\cuwegui");
  16. System.out.println(f3.mkdirs());
  17. // 删除文件夹
  18. f2.delete();
  • 遍历文件目录 ```java // File 类遍历目录,遍历的是构造方法中给出的目录,如果构造方法中的目录路径不存在或不是一个目录,会抛出空指针异常 File f = new File(“E:\JavaLearing\code\testIO”); // 方式一: String[] list() String[] arr = f.list();

// 方式二: File[] listFiles() File[] files = f.listFiles();

// list 输出的是该路径下子文件的名称, listFiles 输出的是子文件的全路径名称 // eg:testIO.iml , E:\JavaLearing\code\testIO\testIO.iml


- 递归实现文件搜索
   - 实现一:FileFilter
```java
//1. 普通表达
class MyFileFilter implements FileFilter {
     public boolean accept(File pathname) {
         if (pathname.isDirectory()) {
             return true;
         }
           return pathname.getName().toLowerCase().endsWith(".java");
         }
     }
    // 依据过滤器返回的 boolean 值决定是否加入 files 数组,为 true 时才加入
     File[] files = dir.listFiles(new MyFileFilter());
     for (File f : files) {
        if(f.isDirectory()){
           testFileFilter(f);
        }else{
        System.out.println(f);
    }
}
//2. Lambda 表达
File[] files = dir.listFiles(f->f.isDirectory() || f.getName().toLowerCase().endsWith(".java"));
  • 实现二:FilenameFilter ```java //方式一:匿名内部内 File[] files = dir.listFiles(new FilenameFilter(){ public boolean accept(File dir, String name) { return new File(dir,name).isDirectory() || name.toLowerCase().endsWith(“java”); }});

//方式二:Lambda 表达式 File[] files = dir.listFiles((d, name)->new File(d,name).isDirectory() || name.toLowerCase().endsWith(“.java”) ); for (File f : files) { if(f.isDirectory()){ testFileFilter(f); }else{ System.out.println(f); } }

<a name="0is3b"></a>
## 2. 基本 IO 流
![](https://cdn.nlark.com/yuque/0/2019/png/611598/1576299703610-56446926-b0d2-4cdb-a6ca-01d0e171b0c2.png#align=left&display=inline&height=732&originHeight=732&originWidth=934&size=0&status=done&style=none&width=934)

- 注意
   - 字节使用 byte 数组,字符使用 char 数组;
   - 相比 InputStream,writer 是先把数据写入到内存缓冲区中,然后再从缓冲区刷新到内存或文件中 flush();
   - 字符流是字节流的包装,字节流不支持直接写入或读取 Unicode 码元,字符流可直接接受字符串,其内部将字符串转成字节,再写入底层设备;
<a name="HMIcT"></a>
### 2.1 字节流

- 复制文件
```java
// 复制文件
FileInputStream in = new FileInputStream("C:\\Users\\yuting\\Desktop\\1.jpg");
FileOutputStream out = new FileOutputStream("C:\\Users\\yuting\\Desktop\\1.1.jpg");

byte[] bytes = new byte[1024];//缓存读取到的多个字节,数组大小为 1024 的整数倍
int len = 0;//记录已经存取的有效字节个数,结尾读取的为 -1;
while((len = in.read(bytes)) != -1) {
//    out.write(bytes);
    out.write(bytes, 0,len);//从 偏移量 offer 开始写len个字节
    out.write("\r\n".getBytes());//换行
}
out.close();
in.close();
// 开启续写
// FileOutputStream out = new FileOutputStream("E:\\JavaLearing\\code\\testIO\\1.txt",true);

2.2 字符流

  • FileReader

      public static void testFileReader() throws IOException {
          FileReader reader = new FileReader("E:\\JavaLearing\\code\\testIO\\1.txt");
          int len = 0;
          char[] cs = new char[1024];
          while ((len = reader.read(cs)) != -1){
              System.out.print(new String(cs,0,len));
          }
          reader.close();
      }
    
  • FileWriter

    //写数据的时候会把十进制的整数转换为二进制,任意的文本编辑器在打开文件的时候,都会查询编码表,把字节转换为字符表示;
    //写入字节数组,如果写的第一个字节是正数(0~127),则会查询ASCLL码,若为负数,则第一个和第二个字节组成中文显示,查询默认码表GBK;
      public static void testFileWriter() throws IOException {
          FileWriter writer = new FileWriter("E:\\JavaLearing\\code\\testIO\\1.txt",true);
          writer.write(97);//写入单个字符
          writer.flush();//刷新缓冲区,流对象可以继续使用
          writer.write(98);
          char[] cs = {'a','b','c','d','e'};
          writer.write(cs);//写入字符数组
          writer.write("天津\r\n");//换行
          writer.write("just have a good day",1,2);
          writer.close();//close 先刷新缓冲区,然后通知系统释放资源,流对象不可再被使用
      }
    

    2.3 缓冲流

  • 处理字节流

      public static void testBuffer() throws IOException{
          FileInputStream in = new FileInputStream("C:\\Users\\yuting\\Desktop\\1.jpg");
          FileOutputStream out = new FileOutputStream("C:\\Users\\yuting\\Desktop\\1.1.jpg");
          BufferedInputStream bis = new BufferedInputStream(in);
          BufferedOutputStream bos = new BufferedOutputStream(out);
          int len = 0;
          byte[] bytes = new byte[1024];
          while((len = bis.read(bytes)) != -1) {
              bos.write(bytes,0,len);
          }
          bos.flush();
          bos.close();
          bis.close();
      }
    
  • 处理字符流

      public static void testBuffer() throws IOException{
          FileWriter out = new FileWriter("E:\\JavaLearing\\code\\testIO\\1.txt",true);
          BufferedWriter bw = new BufferedWriter(out);
          for (int i = 0; i < 5; i++){
    //            bw.write("cyt\r\n");
              bw.write("hahahhh");
              bw.newLine();//换行
          }
          bw.flush();
          bw.close();
          FileReader in = new FileReader("E:\\JavaLearing\\code\\testIO\\1.txt");
          BufferedReader br = new BufferedReader(in);
          String line = null;
          // 在进行文本复制时需注意,readLine() 读的内容不包括终止符号
          while ((line = br.readLine()) != null){
              System.out.println(line);
          }
      }
    

    2.4 转换流

    clipboard.png

  • InputStreamWriter

      public static void testInPutStreamWriter() throws IOException{
          //文件的编码格式和读取的编码格式要一致,否则会乱码
          InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\JavaLearing\\code\\testIO\\gbk.txt"),"gbk");
          int len = 0;
          while ((len=isr.read()) != -1){
              System.out.print((char) len);
          }
          isr.close();
      }
    
  • OutputStreamWriter

      public static void testOutPutStreamWriter() throws IOException {
    //        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\JavaLearing\\code\\testIO\\UTF_8.txt",true),"utf-8");//不指定默认utf-8编码
          OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\JavaLearing\\code\\testIO\\gbk.txt",true),"gbk"); //会有乱码
          osw.write("陈玉婷");
          osw.flush();
          osw.close();
      }
    

    2.5 序列化与反序列化流

  • 说明

    • 继承 Serializable 的类(Person)中,static 和 transient 修饰的成员变量不能被序列化;
    • 为了防止 InvalidClassException 异常,可以在 实现类中设定 版本号;
      class Person implemnets Serialiable{
      private static final long serialVersionUID = 1L;
      }
      
  • 序列化

      public static void testObjectInputStream() throws IOException, ClassNotFoundException {
          // 如果 对象类是匿名的,则会报错 java.io.InvalidClassException:
          ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\JavaLearing\\code\\testIO\\person.txt"));
          Object o = ois.readObject();
          ois.close();
          System.out.println(o);
    
      }
    
  • 反序列化

      public static void testObjectOutputStream() throws IOException {
          Person p = new Person("cyt",25);
          ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\JavaLearing\\code\\testIO\\person.txt"));
          oos.writeObject(p);//person 类以二进制保存
          oos.close();
      }
    

    2.6 打印流

      public static void main(String[] args) throws FileNotFoundException {
          /**
           * PrintStream 特点:
           *      1)只负责数据的额输出,不负责数据的读取
           *      2)与其他输出流不同,printStream 永远不会输出 IOException
           *      3)有特有的方法 print.println
           *      4)可以输出任意类型的值,注意 write()由码表翻译后输出,println直接输出并换行
           *      5) 可以改变输出语句的目的地(即打印流的流向)**/
          PrintStream ps = new PrintStream("E:\\JavaLearing\\code\\testIO\\print.txt");
          ps.write(97);
          ps.println(97);
          ps.println('a');
          ps.println("hello world");
          ps.println(true);
          System.out.println("在控制台输出的语句");
          System.setOut(ps);
          System.out.println("在打印流的目的地输出");
          ps.close();
          /** person.txt 中的结果
           * a97
           * a
           * hello world
           * true
           * 在打印流的目的地输出
           */
      }
    

    3. 异常处理

      public static void testTryCatch(){
          FileReader reader = null;
          try{
              reader = new FileReader("E:\\JavaLearing\\code\\testIO\\1.txt");
              int len = 0;
              char[] cs = new char[1024];
              while ((len = reader.read(cs)) != -1){
                  System.out.print(new String(cs,0,len));
              }
          }catch (IOException e){
              e.printStackTrace();
          }finally {
              if (reader != null){//不做此判断的话,若不能进行正常的 try 操作,则 reader=null,会报空指针异常
                  try {
                      reader.close();
                  } catch (IOException e){
                      e.printStackTrace();
                  }
              }
          }
          // jdk 1.7 后可以不用写 finally 来关闭流,但是需要在 try 后加括号 ()
          /**
           *  jdk 1.7 支持的异常处理格式:
           *  try(定义流对象1,定义流对象2,...){
           *
           *  }catch(){
           *
           *  }
           */
          /**
           *  jdk 1.9 支持的异常处理格式:
           *  A a = new A();
           *  B b = new B();
           *  try(a,b){
           *
           *  }catch(){
           *
           *  }
           */
      }
    

    4. IO 模型

    4.1. 同步阻塞

    Java IO - 图3

    4.2. 同步非阻塞(论询)

    Java IO - 图4

    4.3. 信号驱动式

    Java IO - 图5

0

4.4. I/O 多路复用【事件驱动】

背景:多线程

  • 使用一个线程来监控多个文件描述符(Socket)的就绪状态
  • 少量线程数,减少内存开销和上下文切换的CPU开销;

    ![](https://cdn.nlark.com/yuque/0/2019/jpeg/611598/1576146869799-594ccca1-e9ee-44eb-9b09-3dd41907ff58.jpeg#align=left&display=inline&height=229&originHeight=272&originWidth=500&size=0&status=done&style=none&width=421)
    
  • select、poll 和 epoll 的区别

    • select

Java IO - 图6

  - 缺点:
     - 每次调用 select ,都需要把fd(文件描述符)集合从用户态拷贝到内核态,这个开销在fd很多时会很大;
     - 每次调用 select 都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
     - select 支持的文件描述符数量太小,默认是1024;
  • poll
    • 与 select 非常相似,只是描述 fd 集合的方式不同,poll 使用 pollfd 结构而不是 select 的 fd_set 结构;
  • epoll
    • 三个函数:
      • epoll_create:创建一个 epoll 句柄;
      • epoll_ctl:注册监听的事件类型;
      • epoll_wait:等待事件的产生;
    • 三点改进:
      • 1)每次注册新的事件到 epoll 句柄中时(在 epoll_ctl 中指定 EPOLL_CTL_ADD ),会把所有的 fd 拷贝进内核,而不是在 epoll_wait 的时候重复拷贝,即 epoll 保证了每个 fd 在整个过程中只会拷贝一次;
      • 2)epoll_ctl 只把 current 挂一遍(这一遍必不可少)并为每个 fd 指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表,epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd;
      • 所支持的 fd 无限制,为最大可以打开文件的数目;
  • 相同点:select,poll,epoll本质上都是同步阻塞 I/O,应用程序需在读写事件就绪后自己负责进行读写;
  • 不同点:
    • 1)select,poll 实现需要自己不断轮询所有 fd 集合,直到设备就绪,期间可能要睡眠和唤醒多次交替,而 epoll 其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪 fd 放入就绪链表中,并唤醒在 epoll_wait 中进入睡眠的进程,虽然都要睡眠和交替,但是 select 和 poll 在“醒着”的时候要遍历整个 fd 集合,而 epoll 在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的 CPU 时间;
    • 2)select,poll 每次调用都要把 fd 集合从用户态往内核态拷贝一次,并且要把 current 往设备等待队列中挂一次,而 epoll 只要一次拷贝,而且把 current 往等待队列上挂也只挂一次(在 epoll_wait 的开始,注意这里的等待队列并不是设备等待队列,只是一个 epoll 内部定义的等待队列);

      4.5. 异步非阻塞

  • 异步 IO :无需应用程序自己读写,回调后数据会直接从内核拷贝到用户空间;

Java IO - 图7

4.6. 异步 IO + 协程

4.7 小结

Java IO - 图8

5. IO 与多线程

5.1 管道流

  • 管道流仅用于多个线程之间传递信息,若用在同一个线程中可能造成死锁;
  • 管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或 connect 函数进行连接;
  • 一对管道流包含一个缓冲区,其默认值为 1024 个字节,若要改变缓冲区大小,可使用带有参数的构造函数;
  • 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞,当缓冲区满时,写操作阻塞;
  • 管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
  • 管道流的读取方法与普通流不同,只有输出流正确 close 时,输出流才能读到 -1 值; ```java public static void testPipedStream() throws IOException {

      //创建一个线程池
      ExecutorService executorService = Executors.newCachedThreadPool();
      try {
          //创建输入和输出管道流
          PipedOutputStream pos = new PipedOutputStream();
          PipedInputStream pis = new PipedInputStream(pos);//必须传入此参数,否则报错: Pipe not connected
          //创建发送线程和接收线程
          Sender sender = new Sender(pos);
          Receiver receiver = new Receiver(pis);
          //提交给线程池运行线程
          executorService.execute(sender);
          executorService.execute(receiver);
      } catch (IOException e) {
          e.printStackTrace();
      }
      //通知线程池,不再接受新的任务,并执行完成当前正在运行的线程后关闭线程池
      executorService.shutdown();
      try{
          //shutdown 后可能正在运行的线程很长时间都运行不完成,设置超时处理
          executorService.awaitTermination(1, TimeUnit.HOURS);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
    
      class Sender extends Thread {
          private PipedOutputStream pos;
          public Sender(PipedOutputStream pos){
              this.pos = pos;
          }
          public void run(){
              String s = "have a good day";
              System.out.println("Sender : " + s);
              byte[] bytes = s.getBytes();
              try {
                  pos.write(bytes,0,bytes.length);
                  pos.close();
                  TimeUnit.SECONDS.sleep(5);
              } catch (IOException | InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
    
      class Receiver extends Thread{
          private PipedInputStream pis;
          public Receiver(PipedInputStream pis){
              this.pis = pis;
          }
          public void run(){
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              int len = 0;
              byte[] buf = new byte[1024];
              try {
                  while (((len = pis.read(buf)) != -1)) {
                      baos.write(buf,0, len);
                  }
                  byte[] results = baos.toByteArray();
                  String s = new String(results,0, results.length);
                  System.out.println("Reciever : " + s);
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
    

    } 输出:

      Sender : have a good day
      Reciever : have a good day
    
<a name="6Fmcw"></a>
### 5.2 Properties 集合

- Java.util.Properties 集合 extends HashMap implements Map;
- Properties 类表示一个持久的属性集,Properties 可保存在流中或从流中加载;
- Properties 集合是一个唯一和 IO 流相结合的集合
   - 可以使用 Properties 集合中的方法 store 把集合中的临时数据持久化写到硬盘中存储;
   - 可以使用 Properties 集合中的方法 load 把硬盘中保存的文件(键值对)读取到集合中使用;
- Properties 是双列集合,key 和 value 默认都是字符串;
```java
    public static void testProperties() throws IOException{
        Properties p = new Properties();
        p.setProperty("a","123");
        p.setProperty("b","456");
        p.setProperty("c","789");
        // 保存
        FileWriter fw = new FileWriter("E:\\JavaLearing\\code\\testIO\\prop.properties");
        p.store(fw, "save data");//若是字节流,会乱码,而且 comment 是中文的话也会乱码
        fw.close();

        // 加载
        p.load(new FileReader("E:\\JavaLearing\\code\\testIO\\prop.properties"));
        Set<String> set = p.stringPropertyNames();
        for (String key: set) {
            String value = p.getProperty(key);
            System.out.println(value);
        }
    }

参考

  1. 深入分析 java IO 的工作机制
  2. 了解 Linux 五种 IO 模型
  3. IO 多路复用原理剖析
  4. 黑马教程之多线程与 IO