IO/NIO

IO流学习

流的分类

  1. 操作数据单位:字节流,字符流
  2. 数据的流向:输入流,输出流
  3. 流的角色:节点流,处理流

流的体系结构

抽象基类节点流或者叫文件流缓冲流(处理流的一种)
InputStreamFileInputStreamBufferedInputStream
outputStreamFileOutPutStreamBufferedOutPutStream
ReaderFileReaderBufferedReader
WriterFileWriterBufferedWriter

1.getPath():

​ 返回的是定义时的路径,可能是相对路径,也可能是绝对路径,这个取决于定义时用的是相对路径还是绝对路径。如果定义时用的是绝对路径,那么使用getPath()返回的结果跟用getAbsolutePath()返回的结果一样

2.getAbsolutePath():

​ 返回的是定义时的路径对应的相对路径,但不会处理“.”和“…”的情况

3.getCanonicalPath():

​ 返回的是规范化的绝对路径,相当于将getAbsolutePath()中的“.”和“…”解析成对应的正确的路径

读取文件的内容输出到控制台

@Test
public void testFileReader()  {
	
   //总结
   // Read()的理解:返回读入的一个字符,如果达到文件末尾,返回-1
   // 异常的处理:为了保证流资源一定可以执行关闭操作,需要使用try-catch-fian
   // 读入的文件一定要存在,否则就会报异常  java.io.FileNotFoundException: File\hello.txt (系统找不到指定的路径。)

   FileReader  fileReader = null;
    
   try {
      // 实例化File类的对象,指明要操作的文件
      File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\hello.txt");
      //提供具体的流
      fileReader = new FileReader(file);
      //数据的读入
      int read = fileReader.read();
      while (read !=-1){
         System.out.print((char)read);
         read = fileReader.read();
      }
   } catch (IOException e) {
      e.printStackTrace();
   } finally {
      try {
          if (fileReader!=null){
             // 一定要关闭
             fileReader.close();
          }
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

使用 char[] cbuf = new char[5]; 读入数据

  @Test
   public void testFileReader1()  {
      FileReader  fileReader = null;
      try {

         // 实例化File类的对象,指明要操作的文件
         File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\hello.txt");

         //提供具体的流
         fileReader = new FileReader(file);
          char[] cbuf = new char[5];
          int len;
          while ((len = fileReader.read(cbuf)) !=-1){
             //取出的方法1
//             for (int i = 0; i < len; i++) {
//                System.out.print(cbuf[i]);
//             }
             // 取出的方法2
             String s = new String(cbuf,0,len);
             System.out.print(s);
          }
      } catch (IOException e) {
         e.printStackTrace();
      } finally {
         if (fileReader !=null){
            try {
               fileReader.close();
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
      }
   }
/**
 * 从内存中写出数据到硬盘的文件里
 */

@Test
public void testFileWriter() throws IOException {

   /**
    *    文件输入流
    *     假如要输入的的文件没有会帮你自动创建,不会报错
    *
    *     new FileWriter 有两个构造方法
    *     FileWriter fileWriter = new FileWriter(file);
    *     FileWriter fileWriter = new FileWriter(file,false);
    *
    *      new FileWriter(file)和FileWriter fileWriter = new FileWriter(file,false);  是一样的意思
    *      上面代表的都是对修改文件的覆盖
    *       new FileWriter(file,true);
    *       意思就是在后面追加内容
    *
    */

   // 1. 提供File类的对象,指明写出到的文件地址
   File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\hello1.txt");
   // 2. 提供具体的流
   FileWriter fileWriter = new FileWriter(file,true);
   // 3. 写出的操作
   fileWriter.write("唐鸿真的牛逼\n");
   fileWriter.write("我觉得很好");
   // 4. 关闭流
   fileWriter.close();

}

结论 :

对于文本文件(.txt, .java , . c , .cpp) 使用字符流处理

对于非文本文件(.jpg, .mp3 , .mp4 , .avi, .doc , .ppt …),使用字节流

/**
 *  对图片进行复制操作
 *
 */

@Test
public void testInpuStream()   {

   FileInputStream InputStream = null;   // 输出流
   FileOutputStream OutputStream = null;  // 输入流
   try {
      // 选择文件
      File file = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\223734-1528036654a425.jpg");
      File file1 = new File("E:\\xuexi\\juc\\src\\main\\java\\com\\tanghong\\juc\\File\\2.jpg");

      //选择流
      InputStream = new FileInputStream(file);
      OutputStream = new FileOutputStream(file1);
      // 执行
      byte[]  ba = new byte[5];
      int len;
      while ((len = InputStream.read(ba)) !=-1){
         OutputStream.write(ba,0,len);
      }
   } catch (IOException e) {
      e.printStackTrace();
   } finally {
      // 关闭流
      try {
          if (InputStream!=null){
             InputStream.close();
          }
      } catch (IOException e) {
         e.printStackTrace();
      }
      try {
         if (InputStream!=null){
            OutputStream.close();
         }
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

实际开发中都是用Buffered 缓冲流

//两个输入流
bis1 = new BufferedInputStream(new FileInputStream(f1));
bis2 = new BufferedInputStream(new FileInputStream(f2));
//缓冲字节输出流(true表示可以在流的后面追加数据,而不是覆盖!!)
bos = new BufferedOutputStream(new FileOutputStream(f,true));

多个服务端和多个客户端的socket通信

如果服务端需要处理很多个客户端的消息通信请求应该如何处理呢,此时我们就需要在服务端引入线程了,也就是说客户端每发起一个请求,服务端就创建一个新的线程来处理客户端的请求,这样就是实现了一个客户端一个线程

/**
 * 目标:实现服务端可以同时接收多个客户端的Socket通信需求
 * 思路: 是服务端每接收一个客户端socket请求对象之后都交给一个独立的线程来处理客户端的数据交互需求
 */


public class Server {

    public static void main(String[] args)  {
        try {
            // 1. 注册端口
            ServerSocket ss = new ServerSocket(9999);
            // 2. 定义一个死循环,负责不断的接收客户端的socket
            while (true){
                Socket accept = ss.accept();
                //3. 创建一个独立的线程来处理与这个客户端的socket通信
                 new ServerThreadRead(accept).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
public class ServerThreadRead extends Thread{
private Socket socket;

public ServerThreadRead(Socket socket) {
    this.socket = socket;
}
@Override
public void run() {
    try {
        // 从socket 对象中得到一个字节输入流
        InputStream is = socket.getInputStream();
        // 使用缓冲字符输入包装字节输入流
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
         String st;
         while ((st = bufferedReader.readLine()) !=null){
             System.out.println(st);
         }
    } catch (IOException e) {
        e.printStackTrace();
    }

}
}
public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1", 9999);
            PrintStream printStream = new PrintStream(socket.getOutputStream());
            Scanner scanner = new Scanner(System.in);
             while (true){
                 System.out.println("打印");
                 String s = scanner.nextLine();
                 printStream.println(s);
                 printStream.flush();
             }
        } catch (IOException e) {
            e.printStackTrace();
        }
}
}

小结

​ 每个Socket接收到,都会创建一个线程,线程的竞争,切换上下文影响性能;

​ 每个线程都会占用栈空间和CPU资源

​ 并不会每个socket都进行IO操作,无意义的线程处理

​ 哭护短的并发访问增加时,服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出,线程创建失败,最终导致进程宕机,从而不能对外提供服务

Java BIO

  • ​ java BIO 就是传统的 java io 编程,其相关的类和接口在java.io

  • ​ BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务端就需要启动一个线程进行处理,

    ​ 如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善

单行发送和单行接收

/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) throws IOException {
        // 1.创建socket 对象请求服务端的链接
        Socket socket = new Socket("127.0.0.1", 9999);
        // 从socket对象中获取一个字节输入流
        OutputStream outputStream = socket.getOutputStream();
        // 把 字节输出流包装成一个打印流
        PrintStream printStream = new PrintStream(outputStream);
        printStream.println("唐鸿你好啊 hello");
        printStream.flush();
    }
}
/**
 *  服务端
 */
public class Server {

    public static void main(String[] args)  {

        BufferedReader br = null;
        try {
            // 定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            // 监听客户端的Socket链接请求
            Socket accept = ss.accept();
            // 从socket管道中得到一个字节输入流对象
            InputStream is = accept.getInputStream();
            // 把字节输入流包装成一个缓冲字符输入流
             br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) !=null){
                System.out.println("服务端接收到"+ msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br !=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

小结:

在以上通信中,服务端会一直等待客户端的消息,如果客户端没有进行消息的发送,服务端将一直进入阻塞状态。

同事服务端是按照行获取消息的,这意味着客户端也必须按照行进行消息的发送,否则服务端将进行等待消息的阻塞状态

多发和多收机制

/**
 *  服务端
 */
public class Server {

    public static void main(String[] args)  {

        BufferedReader br = null;
        try {
            System.out.println("服务端已开启");
            // 定义一个ServerSocket对象进行服务端的端口注册
            ServerSocket ss = new ServerSocket(9999);
            // 监听客户端的Socket链接请求
            Socket accept = ss.accept();
            // 从socket管道中得到一个字节输入流对象
            InputStream is = accept.getInputStream();
            // 把字节输入流包装成一个缓冲字符输入流
             br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) !=null){
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br !=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
**
 * 客户端
 */
public class Client {
    public static void main(String[] args) throws IOException {
        // 1.创建socket 对象请求服务端的链接
        Socket socket = new Socket("127.0.0.1", 9999);
        // 从socket对象中获取一个字节输入流
        OutputStream outputStream = socket.getOutputStream();
        // 把 字节输出流包装成一个打印流
        PrintStream printStream = new PrintStream(outputStream);
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.println("请说");
            String s = sc.nextLine();
            printStream.println(s);
            printStream.flush();
        }
    }
}

java NIO

NIO和BIO比较

  1. ​ BIO以流的方式处理数据,而NIO以块的方式处理数据,块I/O的效率比流I/0高很多
  2. ​ BIO是阻塞的,NIO则是非阻塞的
  3. ​ BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中,或者从缓冲区写入到通道中,Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等) 因此使用单个线程就可以监听多个客户端通道

NIOBIO
面向缓冲区(buffer)面向流(stream)
非阻塞(Non Blocking IO)阻塞IO(Blocking IO)
选择器(Selectors)

NIO 三大核心原理

NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)

Buffer缓冲区

​ 缓冲区本质是一块可以写入数据,然后可以从中读取数据的内存,这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存,相比较直接对数组的操作,Buffer API更加容易操作和管理

Channel(通道)

java NIO的通道类似流,但又有些不同:即可以从通道中读取数据,又可以写数据到通道。但流的(input或Output)读写通畅是单向的。通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步的读取写

Selector 选择器

Selector是一个javaNIO 组件,可以能够检查一个或多个NIO通道,并确定哪些通道已经准备好进行读取或写入。这样,一个单独的线程可以管理多个Channel,从而管理多个网络连接,提供效率

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值