IO与NIO

一 .IO

1.1 流的简单介绍和分类

Java流操作的相关的类和接口:


1810211634.png

Java流类图结构:

io201810211637.png

四个抽象基类分别为:InputStream 、OutputStream 、Reader 、Writer;

流的概念:在Java中将输入输出抽象称为为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称和抽象,即数据在两设备间的传输称为流

Java中IO所采用的设计模式:装饰者模式

分类:

1.按流向不同:输入流,输出流(以程序为主体)

2.按类型不同:字节流,字符流(字符流用于操作文本文件 .txt .java 字节流用于操作非文本文件 .avi .rmvg .jpg .mp3)

3.按角色不同:节点流,处理流

注:若用字节流操作文本文件,会引起乱码和效率低的问题。若用字符流去操作非文本文件,不会报错,但什么也获取不了。

1.2 常见节点流和处理流的使用方法

1.2.1 只使用节点流的复制粘贴:

非文本文件:

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //1.创建 FileInputStream 的实例,同时打开指定文件
            fis = new FileInputStream("1.jpg");
            fos = new FileOutputStream("2.jpg");
            
            byte[] b = new byte[1024];
            int len = 0;
            
            while((len = fis.read(b)) != -1){
                fos.write(b,0,len);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
        }

文本文件:

        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader("1.txt");
            fw = new FileWriter("2.txt");
            
            char[] c = new char[100];
            int len = 0;
            
            while((len = fr.read(c)) != -1){
                fw.write(c, 0, len);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fw != null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if(fr != null){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
1.2.2 带上缓冲流的复制粘贴

非文本文件:

        BufferedOutputStream bos = null;
        BufferedInputStream  bis = null;
        try {
            FileInputStream fis  = new FileInputStream("1.jpg");
            FileOutputStream fos = new FileOutputStream("2.jpg");
            
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            
            byte[] b = new byte[1024];
            int len = 0;
            while((len = bis.read(b)) != -1){
                bos.write(b, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            if(bos != null){
                bos.close();
            }
            if(bis != null){
                bis.close();
            }
        }

文本文件:

        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            FileReader fr = new FileReader("newFile.txt");
            FileWriter fw = new FileWriter("newFile2.txt");
            
            br = new BufferedReader(fr);
            bw = new BufferedWriter(fw);
            
            String str = null;
            
            while( (str = br.readLine()) != null){
                bw.write(str);
                bw.newLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bw != null){
                bw.close();
            }
            if(br != null){
                br.close();
            }
        }

1.3 序列化与反序列化

主要使用对象流进行操作: ObjectInputStream 、ObjectOutputStream

序列化:将内存中的对象以二进制的形式保存在磁盘中

反序列化:将磁盘的对象读取

准备工作: 需要提供一个序列化接口。序列号如果不显示给出, 则会默认根据类信息自动生成一个序列号,一旦类信息发送变动与序列化前不同,对象的反序列化将会抛出异常,所以还是建议 显示给出一个序列号。

关键字: transient 和 static修饰的属性不会被序列化

1.3.1 序列化反序列化多个值

---序列化:

        //3. 创建对象流,包装缓冲流,用于完成序列化
        ObjectOutputStream oos = null;
        try {
            int num = 10;
            boolean flag = false;
            String str = "abcde";
            
            //1.创建节点流,同时打开指定文件
            FileOutputStream fos = new FileOutputStream("./data.dat");
            
            //2.(可选)使用缓冲流包装节点流,用于提高传输效率。
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            
            oos = new ObjectOutputStream(bos);
            
            oos.writeInt(num);
            oos.writeBoolean(flag);
            oos.writeUTF(str);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //5.关闭流
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

---反序列化:

        ObjectInputStream ois = null;
        try {
            FileInputStream fis = new FileInputStream("./data.dat");
            
            ois = new ObjectInputStream(fis);
            //反序列化的顺序务 必和 序列化的顺序保持一致
            int num = ois.readInt();
            boolean flag = ois.readBoolean();
            String str = ois.readUTF();
            
            System.out.println(num);
            System.out.println(flag);
            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
1.3.2 序列化和反序列化多个对象

---准备工作:

public class Person implements Serializable{

    private static final long serialVersionUID = 134628734823487283L;
    
    private String name;
    private int age;
    
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public Person() {}
    
    public int getAge(){
        return age;
    }
    
    public String getName(){
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
    
}

---序列化:

        //Person 务必要实现序列化接口
        Person p1 = new Person("张三",19);
        Person p2 = new Person("李四",20);
        Person p3 = new Person("王五",16);
        
        ObjectOutputStream oos = null;
        try {
            FileOutputStream fos = new FileOutputStream("person.dat");
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            
            oos = new ObjectOutputStream(bos);
            
            oos.writeObject(p1);
            oos.writeObject(p2);
            oos.writeObject(p3);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }   

---反序列化:

ObjectInputStream ois = null;
        try {
            FileInputStream fis = new FileInputStream("person.dat");
            BufferedInputStream bis = new BufferedInputStream(fis);
            ois = new ObjectInputStream(bis);
            
            Person p1 = (Person)ois.readObject();
            Person p2 = (Person)ois.readObject();
            Person p3 = (Person)ois.readObject();
            System.out.println(p1);
            System.out.println(p2);
            System.out.println(p3);
        }  catch (Exception e) {
            e.printStackTrace();
        }  finally {
            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

1.4 转换流

转换流:InputStreamReader & OutStreamWriter

编码:字符串 -> 字节数组

解码:字节数组 -> 字符串

        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            FileInputStream fis = new FileInputStream("hello.txt");
            InputStreamReader isr = new InputStreamReader(fis);
            br = new BufferedReader(isr);
            
            FileWriter fileWriter = new FileWriter("hello1.txt");
            bw = new BufferedWriter(fileWriter);
            String str = null;
            while((str = br.readLine()) != null){
                bw.write(str);
                bw.newLine();
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            
            if(bw != null){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

1.5 随机存取文件类

RandomAccessFile 类支持"随机访问"的方式,程序可以跳到文件的任意地方来读写文件

支持只访问文件的部分内容

可以向已存在的文件后追加内容

RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。

RandomAccessFile 类对象可以自由移动记录指针:

long getFilePointer():获取文件记录指针的位置

void seek(long pos):将文件记录指针定位到pos位置

  • 构造器

public RandomAccessFile(File file,String mode)

public RandomAccessFile(String name,String mode)

  • 创建RandomAccessFile 类实例需要制定一个mode 参数, 该参数指定 RandomAccessFile的访问模式:

r:以只读方式打开

rw:打开以便读取和写入

rwd:打开以便读取和写入;同步文件内容的更新

rws:打开以便读取和写入;同步文件内容和元数据的更新

    /**
     * 在abcdef写入文件 再向abc中间 插入hello
     */
    @Test
    public void test4() throws IOException{
        RandomAccessFile randomAccessFile = new RandomAccessFile("hell.txt", "rw");
        String str = "abcdef";
        
        randomAccessFile.write(str.getBytes());
        
        randomAccessFile.seek(3);
        
        String line = randomAccessFile.readLine();
        
        randomAccessFile.seek(3);
        
        randomAccessFile.write("hello".getBytes());
        randomAccessFile.write(line.getBytes());
        
        randomAccessFile.close();
    }

二. NIO

1、reactor(反应器)模式

使用单线程模拟多线程,提高资源利用率和程序的效率,增加系统吞吐量。下面例子比较形象的说明了什么是反应器模式:

一个老板经营一个饭店,

传统模式 - 来一个客人安排一个服务员招呼,客人很满意;(相当于一个连接一个线程)

后来客人越来越多,需要的服务员越来越多,资源条件不足以再请更多的服务员了,传统模式已经不能满足需求。老板之所以为老板自然有过人之处,老板发现,服务员在为客人服务时,当客人点菜的时候,服务员基本处于等待状态,(阻塞线程,不做事)。

于是乎就让服务员在客人点菜的时候,去为其他客人服务,当客人菜点好后再招呼服务员即可。 --反应器(reactor)模式诞生了

饭店的生意红红火火,几个服务员就足以支撑大量的客流量,老板用有限的资源赚了更多的money~~~~_

通道:类似于流,但是可以异步读写数据(流只能同步读写),通道是双向的,(流是单向的),通道的数据总是要先读到一个buffer 或者 从一个buffer写入,即通道与buffer进行数据交互。

通道类型:

  • FileChannel:从文件中读写数据。
  • DatagramChannel:能通过UDP读写网络中的数据。
  • SocketChannel:能通过TCP读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

- FileChannel比较特殊,它可以与通道进行数据交互, 不能切换到非阻塞模式,套接字通道可以切换到非阻塞模式;

缓冲区 - 本质上是一块可以存储数据的内存,被封装成了buffer对象而已!

缓冲区类型:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

常用方法:

  • allocate() - 分配一块缓冲区

  • put() - 向缓冲区写数据

  • get() - 向缓冲区读数据

  • filp() - 将缓冲区从写模式切换到读模式

  • clear() - 从读模式切换到写模式,不会清空数据,但后续写数据会覆盖原来的数据,即使有部分数据没有读,也会被遗忘;

  • compact() - 从读数据切换到写模式,数据不会被清空,会将所有未读的数据copy到缓冲区头部,后续写数据不会覆盖,而是在这些数据之后写数据

  • mark() - 对position做出标记,配合reset使用

  • reset() - 将position置为标记值

缓冲区的一些属性:

  • capacity - 缓冲区大小,无论是读模式还是写模式,此属性值不会变;
  • position - 写数据时,position表示当前写的位置,每写一个数据,会向下移动一个数据单元,初始为0;最大为capacity - 1,切换到读模式时,position会被置为0,表示当前读的位置
  • limit - 写模式下,limit 相当于capacity 表示最多可以写多少数据,切换到读模式时,limit 等于原先的position,表示最多可以读多少数据。

非直接缓冲区:通过allocate() 方法 分配缓冲区,将缓冲区建立在JVM内存中

直接缓冲区:通过allocateDirect() 方法直接缓冲区 将缓冲区建立在物理内存中

2.1 关于缓冲区各个属性的测试

        String str = "abcde";
        
        //1. 分配一个指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        System.out.println("--------------allocate()----------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024
        
        //2. 利用put存入数据到缓冲区中去
        buf.put(str.getBytes());
        
        System.out.println("----------------put()-------------------");
        System.out.println(buf.position());//5
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024

        
        //3. 切换到读取模式
        buf.flip();
        
        System.out.println("----------------flip()------------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        
        //4. 利用get() 读取缓冲区中的数据
        byte[] dst = new byte[buf.limit()];
        buf.get(dst);
        System.out.println(new String(dst,0,dst.length));
        
        System.out.println("----------------get()------------------");
        System.out.println(buf.position());//5
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        
        //5.可重复读
        buf.rewind();
        
        System.out.println("----------------rewind()------------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        
        //6.clear(): 清空缓冲区, 但是缓冲区的数据依然存在, 但是处于被遗忘的状态
        buf.clear();
        
        System.out.println("----------------clear()-------------------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024

        byte[] newByte = new byte[buf.limit()];
        buf.get(newByte);
        System.out.println(new String(newByte,0,newByte.length));

2.2 关于通道的使用

1.利用通道进行 文件的复制 非直接缓冲区


        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            fis = new FileInputStream("1.jpg");
            fos = new FileOutputStream("2.jpg");

            // ①获取通道
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();

            // ②将通道中的数据存入缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

            // 将通道中的数据存入缓冲区
            while (inChannel.read(byteBuffer) != -1) {
                byteBuffer.flip(); // 切换读取数据的模式
                outChannel.write(byteBuffer);
                byteBuffer.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    

2.通道之间的传输

CREATE_NEW:如果文件不存在就创建,存在就报错

CREATE:如果文件不存在就创建,存在创建(覆盖)


        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = FileChannel.open(Paths.get("hello.txt"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("hello2.txt"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
            
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } catch (Exception e) {
            e.printStackTrace();
        }  finally {
            
            if(inChannel != null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if(outChannel != null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    

3. 使用直接缓冲区完成内存文件的复制

        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("x.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
            
            MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
            
            System.out.println(inMappedBuffer.limit());
            byte[] b = new byte[inMappedBuffer.limit()];;
            inMappedBuffer.get(b);
            outMappedBuffer.put(b);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            
            if(inChannel != null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if(outChannel != null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
        }

2.3 重点 NIO-非阻塞IO

个人认为 NIO 最难的两点 一个是对于选择器和选择键的理解 其次是对于网络通信模型的理解

本章内容以防过长 只讲解 NIO 的使用方法 上述两点参看下回分解

2018-10-28_220243.png

阻塞IO示例:

    //客户端
    @Test
    public void client() throws IOException{
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        while(inChannel.read(buf) != -1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        
        sChannel.shutdownOutput();
        
        //接收服务端的反馈
        int len = 0;
        while((len = sChannel.read(buf)) != -1){
            buf.flip();
            System.out.println(new String(buf.array(), 0, len));
            buf.clear();
        }
        
        inChannel.close();
        sChannel.close();
    }
    
    //服务端
    @Test
    public void server() throws IOException{
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        
        FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        
        ssChannel.bind(new InetSocketAddress(9898));
        
        SocketChannel sChannel = ssChannel.accept();
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        while(sChannel.read(buf) != -1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        
        //发送反馈给客户端
        buf.put("服务端接收数据成功".getBytes());
        buf.flip();
        sChannel.write(buf);
        
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }

非阻塞IO示例-TCP:

//客户端
    @Test
    public void client() throws IOException{
        //1. 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        
        //2. 切换非阻塞模式
        sChannel.configureBlocking(false);
        
        //3. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        //4. 发送数据给服务端
        Scanner scan = new Scanner(System.in);
        
        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + "\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        
        //5. 关闭通道
        sChannel.close();
    }

    //服务端
    @Test
    public void server() throws IOException{
        //1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        
        //2. 切换非阻塞模式
        ssChannel.configureBlocking(false);
        
        //3. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        
        //4. 获取选择器
        Selector selector = Selector.open();
        
        //5. 将通道注册到选择器上, 并且指定“监听接收事件”
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        //6. 轮询式的获取选择器上已经“准备就绪”的事件
        while(selector.select() > 0){
            
            //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            
            while(it.hasNext()){
                //8. 获取准备“就绪”的是事件
                SelectionKey sk = it.next();
                
                //9. 判断具体是什么事件准备就绪
                if(sk.isAcceptable()){
                    //10. 若“接收就绪”,获取客户端连接
                    SocketChannel sChannel = ssChannel.accept();
                    
                    //11. 切换非阻塞模式
                    sChannel.configureBlocking(false);
                    
                    //12. 将该通道注册到选择器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                }else if(sk.isReadable()){
                    //13. 获取当前选择器上“读就绪”状态的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    
                    //14. 读取数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    
                    int len = 0;
                    while((len = sChannel.read(buf)) > 0 ){
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }
                }
                
                //15. 取消选择键 SelectionKey
                it.remove();
            }
        }
    }

非阻塞IO示例-UDP:

    @Test
    public void send() throws IOException{
        DatagramChannel dc = DatagramChannel.open();
        
        dc.configureBlocking(false);
        
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        Scanner scan = new Scanner(System.in);
        
        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + ":\n" + str).getBytes());
            buf.flip();
            dc.send(buf, new InetSocketAddress("127.0.0.1", 9898));
            buf.clear();
        }
        
        dc.close();
    }
    
    @Test
    public void receive() throws IOException{
        DatagramChannel dc = DatagramChannel.open();
        
        dc.configureBlocking(false);
        
        dc.bind(new InetSocketAddress(9898));
        
        Selector selector = Selector.open();
        
        dc.register(selector, SelectionKey.OP_READ);
        
        while(selector.select() > 0){
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            
            while(it.hasNext()){
                SelectionKey sk = it.next();
                
                if(sk.isReadable()){
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    
                    dc.receive(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(), 0, buf.limit()));
                    buf.clear();
                }
            }
            
            it.remove();
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • IO是计算机中Input和Output简称,即输入和输出。 无论是系统、还是语言的设计中IO的设计都是异常复杂的。...
    BobWen阅读 362评论 0 0
  • 问:Java 的 IO 和 NIO 有什么差异? 答:Java 的 IO 主要面向流、阻塞式 IO,而 JDK 1...
    Little丶Jerry阅读 194评论 0 0
  • 不知道是不是年纪的原因,现在看到生离死别总会默默的留下眼泪。 今天听到一句话,世上除了死这件事是一定的,其他的都是...
    蛮干的猪阅读 254评论 0 1
  • 【每日精进打卡第188天】 姓名:张晓雪 公司:淮安市金鸡喜满堂食品有限公司 349期六项精进(南京)乐观二组学员...
    我怕我忘记你_6e63阅读 137评论 0 1
  • 慧友冠源科技&272期六项精进努力二组&广东盛和塾稻牙二组 【日精进打卡第170天】' 【知~学习】 早晨诵读: ...
    杨忠诚阅读 117评论 0 0