Java基础篇--IO

目录

前言

Java IO发展之路

Java传统IO

认识各种输入输出流

认识File类

***:什么是绝对路径,什么是相对路径?

***:new File(path)生成的文件去哪了?

认识RandomAccessFile类

FileInputStream和FileOutputStream实现以字节为单位的文件读写

FileReader和FileWriter实现以字符为单位的文件读写

BufferedReader和BufferedWriter缓冲流高效率读写文件

序列化流将对象序列化

利用转换流实现修改文件的编码格式

通过打印流控制sout的输出位置

Java BIO

***:(实战)使用BIO实现cs(client-server)架构通讯

Java NIO

***:(实战)使用NIO实现cs架构通讯


前言

带着问题学java系列博文之java基础篇。从问题出发,学习java知识。


Java IO发展之路

IO(input/output),我们都知道unix世界里、一切皆文件、而文件是什么呢?文件就是一串二进制流而已、不管socket、还是FIFO、管道、终端、对我们来说、一切都是文件、一切都是流、在信息交换的过程中、我们都是对这些流进行数据的收发操作、简称为I/O操作(input and output)。Java的核心库java.io提供了全面的IO接口。包括:文件读写、标准设备输出等。Java中IO是以流为基础进行输入输出的,所有数据被串行化写入输出流,或者从输入流读入。

在JDK1.4推出Java NIO之前,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),这种一请求一应答的通信模型简化了上层的应用开发,但是在性能和可靠性方面却存在着巨大的瓶颈。当并发访问量增大、响应时间延迟增大之后,采用Java BIO开发的服务端软件只有通过硬件的不断扩容来满足高并发和延时,极大地增加了企业的成本,并且随着集群规模的不断膨胀,系统的可维护性也面临巨大的挑战,只能通过采购性能更高的硬件服务器来解决问题,这会导致恶性循环。

JDK1.4推出 NIO 1.0 ,新增java.nio包,提供了很多进行异步I/O开发的API和类库,极大地促进了基于Java的异步非阻塞编程的发展和应用。但是,它依然有不完善的地方,特别是对文件系统的处理能力仍显不足。

JDK1.7推出 NIO 2.0,主要提供了如下三个方面的改进。

  • 提供能够批量获取文件属性的API,这些API具有平台无关性,不与特性的文件系统相耦合,另外它还提供了标准文件系统的SPI,供各个服务商扩展实现;
  • 提供AIO功能,支持基于文件的异步I/O操作和针对网络套接字的异步操作;
  • 完成JSR定义的通道功能,包括对配置和多播数据报的支持等。

 

Java传统IO

Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流.Java的IO模型设计非常优秀,它使用Decorator(装饰者)模式,按功能划分Stream,可以动态装配这些Stream,以便获得需要的功能。

按照处理数据的类型来划分,可以分为字节流(处理数据的单位是字节)和字符流(处理数据的单位是字符,且可以识别编码,根据编码来确定一个字符占用的字节数);按照数据的流向来划分,则可以分为输入流和输入流(这都是以计算机内存为源来区分的,从文件读取到内存,相当于向内存输入,所以是输入流;从内存写到文件,相当于从内存中输出,所以是输出流)。

***小贴士:记住以内存为参照物,读操作相当于进内存,写操作相当于出内存,轻松区分文件读写的输入、输出流

 

认识各种输入输出流

在认识输入输出流之前,我们先学习两个类File和RandomAccessFile,输入输出流无非都是操作文件,当然得先了解一下java定义的文件类型。

认识File类

两个属性:separatorChar(separator就是separatorChar+“”,系统的名称分隔符);pathSeparatorChar(pathSeparator就是pathSeparatorChar+“”,系统的路径分隔符);

四个构造器:

  • File(String pathname):通过给定的路径创建File实例;
  • File(String parent,String child):组合parent和child字串,形成全路径,以此路径创建File实例;
  • File(File parent,String child):取parent的path,再组合child字串,形成全路径,以此路径创建File实例;
  • File(Uri uri):以Uri转化为的全路径创建File实例

***:什么是绝对路径,什么是相对路径?

绝对路径可以简单的理解为文件在系统中的保存位置,我们一看到这个路径,就知道在系统的哪个位置可以找到这个文件。所以绝对路径不依赖任何坐标,它就是文件在系统中的全路径。例如:D:\IdeaProjects\JavaBaseDemo\a.txt

相对路径是相对而言的,随着运行环境的不同,相对坐标也发生着变化,则相对路径也随之变化。相对路径是指以当前程序的运行路径作为父路径,父路径其下面的具体路径就是相对路径。例如:a.txt(程序运行路径是C:\test),则此时的全路径就是:C:\test\a.txt

***:new File(path)生成的文件去哪了?

public class Test {

    public static void main(String[] args){
        System.out.println(test1());
        System.out.println(test2());
        System.out.println(test3());
        System.out.println(test4());
        System.out.println(test5());
    }

    public static String test1(){
        File file = new File("a.txt");
        return file.getAbsolutePath();
    }

    public static String test2(){
        File file = new File("/a.txt");
        return file.getAbsolutePath();
    }

    public static String test3(){
        File file = new File("D:/a.txt");
        return file.getAbsolutePath();
    }

    public static String test4(){
        File file = new File("C:a.txt");
        return file.getAbsolutePath();
    }

    public static String test5(){
        File file = new File("D:a.txt");
        return file.getAbsolutePath();
    }
}

结果输入如下图:

分析:

test1方法中,new File时参数是“a.txt”,系统检测到path路径既不含有名称分隔符,也不含有路径分隔符,所以认为是相对路径,先添加上当前程序运行的路径(如果当前程序是一个jar,那就是jar所在的路径,如果是直接idea运行项目,那就是项目所在的路径),然后再以这个全路径生成File实例,所以输出的绝对路径就是:D:\IdeaProjects\JavaBaseDemo\a.txt

test2方法中,new File时参数是“/a.txt”,系统检测到path路径中含有名称分隔符,但是不含有路径分隔符,则认为是相对于当前盘符下的绝对路径,也就是在path前加上当前程序运行所在的盘符,然后再以这个全路径生成File实例,所以输出的绝对路径就是:D:\a.txt

test3方法中,new File时的参数是“D:/a.txt”,系统检测到path路径中既含有名称分隔符,又含有windows盘符特殊符号“:”,判断已经是绝对路径,直接以path生成File实例,所以输出的就是path:D:\a.txt(这里存在一个转换,是因为“\”这个字符在java中是转义字符)

test4方法中,new File时的参数是“C:a.txt”,系统检测到path路径中含有完整的盘符(不是当前程序运行所在盘),遗漏了一个名称分隔符,因此只需要加上名称分隔符即可(此时默认就是盘符指定盘的根目录下直接创建),所以输出的就是:C:\a.txt( \\就是转义\)

test5方法中,new File时的参数是“D:a.txt”,系统检测到path路径中含有与当前程序运行所在盘相同的盘符,但是没有指定之后的具体路径,则默认采用当前程序运行的路径,组合之后形成全路径,再以此路径生成File实例,所以输出的是:D:\IdeaProjects\JavaBaseDemo\a.txt

 

File类主要方法:

createNewFile():创建文件(如果是文件夹层级,则最后一个目录将作为文件名)

delete():删除文件                                                                                    deleteOnExit():删除文件如果存在的话

exists():文件是否存在                                                                             getAbsolutePath():获取绝对路径

getName():获取文件名称                                                                        getParent():获取父路径

getPath():获取创建File对象时传入的path参数                                        isDirectory():是否是文件夹

isFile():是否是文件                                                                                  length():返回文件字节总数

list():可选参数FilenameFilter,返回当前路径下所有文件名(含文件夹),可以自定义Filter过滤结果

listFiles():可选参数FileFilter或者FilenameFilter,返回当前路径下所有文件(含文件夹),可以自定义Filter过滤结果

mkdir():创建文件夹(前提是上级目录存在)

mkdirs():根据绝对路径创建该路径下所有目录的文件夹,逐层创建,包含文件名这一层也是文件夹

renameTo(File dest):修改文件名(修改当前文件名为dest的文件名)

 

认识RandomAccessFile类

RandomAccessFile是java Io体系中功能最丰富的文件内容访问类。我们只需要选择好读写模式,仅创建一个RandomAccessFile对象,就既可以读取文件内容,又可以向文件中写入内容,而不用像传统IO流,需要创建输入流和输出流两个流。而且RandomAccessFile还支持自定义移动指针,随机读取文件内容,不像传统io流,只能从头或者尾读取或者写入。RandomAccessFile是java4新增的随机访问文件的类,底层是依赖DataInput和DataOutput实现的,支持了channel通道,可以更加快捷的读写文件;且支持选定模式打开文件,共有四种模式,分别如下:

  • r:只读模式打开文件,仅允许读取文件内容;
  • rw:读写模式打开文件,支持读写文件内容;
  • rwd:读写模式打开文件,写操作的内容会立即同步到磁盘的文件里;
  • rws:读写模式打开文件,写操作的内容和元数据都会立即同步到磁盘的文件里

构造器:

RandomAccessFile(File file, String mode)            RandomAccessFile(String name, String mode)

主要方法:

close():关闭随机流                                                                               getChannel():获取通道

read():读取一个字节                                                                            read(byte[] b):读取一数组的字节,可选参数int off,int len

readInt():读取一个int型数据(系列方法,可以读取所有的基本类型)

readLine():读取一整行                                                                        seek():移动指针到指定下标

write(byte[] b):写入字节                                                                      writeInt:写入一个int数据(系列方法,支持所有基本类型)

 

FileInputStream和FileOutputStream实现以字节为单位的文件读写

    private static void testFileInputStream(){
        File file = new File("src\\com\\zst\\javabasedemo\\file\\test.txt");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            int temp;
            while ((temp = fis.read()) != -1){
                System.out.println((char)temp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void testFileOutputStream(){
        File file = new File("src\\com\\zst\\javabasedemo\\file\\test.txt");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file,true);
            fos.write((int) '\n');
            fos.write((int) 'A');
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

可以看到由于是字节流,每次读和写都是以字节为单位进行。当我们要获取文本类数据时,很明显字节流就不是很方便,因为不知道读取出来的内容是什么,写入的时候也需要将文本字串转换成字节数组。

 

FileReader和FileWriter实现以字符为单位的文件读写

    //相较于字节流,字符流可以识别不同编码下的字符所占字节数,从而保证读取到的一定是一个字符(不像字节流中读取一个字节时,中文显示乱码)
    private static void testFileReader(){
        File file = new File("src\\com\\zst\\javabasedemo\\file\\test.txt");
        FileReader fr = null;
        try {
            fr = new FileReader(file);
            int temp;
            while ((temp = fr.read()) != -1)
                System.out.println((char) temp);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void testFileWriter(){
        FileWriter fw = null;
        try {
            fw = new FileWriter("src\\com\\zst\\javabasedemo\\file\\test.txt",true);
            fw.write("\n这是通过字符流写入的内容");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

因为使用的是字符流,所以每次读写都是以字符为单位,对于文本类文件,使用字符流可以便于我们读写内容。注意,字符输入流可以自动识别文件编码,从而保证每次读取字符的实际大小(占多少个字节)。

 

BufferedReader和BufferedWriter缓冲流高效率读写文件

    private static void testBufferedReader(){
        FileReader fr = null;
        BufferedReader br = null;
        try {
            fr = new FileReader("src\\com\\zst\\javabasedemo\\file\\test.txt");
            br = new BufferedReader(fr);
            String temp;
            while ((temp = br.readLine()) != null){
                System.out.println(temp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void testBufferedWriter(){
        FileWriter fw = null;
        BufferedWriter bw = null;
        try {
            fw = new FileWriter("src\\com\\zst\\javabasedemo\\file\\test.txt",true);
            bw = new BufferedWriter(fw);
            //写入一行行分隔符,相当于‘\n’
            bw.newLine();
            bw.write("这是通过缓冲流写入的内容。");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bw.close();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

缓冲流体现了java io设计时采用了装饰者模式,缓冲流只是对底层的输入、输出流进行了包装,创建一个缓冲区,支持单次读/写一个缓冲区的数据,然后再刷新到内存或者写入文件,避免频繁的读写硬盘,提高io效率。创建缓冲流是可以指定缓冲区的大小,如果不指定,则使用系统默认的大小(缓冲字符流,默认缓冲区大小是8192个字符;缓冲字节流,默认缓冲区大小是8192个字节)。其实我们也可以自己创建一个buffer,只使用文件读写流,每次读写一个buffer的内容,这样就和缓冲流是完全一样的。注意:缓冲流的引入,带来了一个新的方法flush(),flush方法的意思是强制刷新缓冲区,将缓冲区的内容写入文件或者读取出来。所以在使用缓冲流的时候,在关闭缓冲流之前,建议先执行一下flush,防止缓冲区中还有内容没有及时读取或写入。

 

序列化流将对象序列化

    //---------------------序列化流-----------------------------
    private static void  testObjectOS(){
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Object.txt"));
            Person person = new Person("张三",19);
            oos.writeObject(person);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void testObjectIS(){
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Object.txt"));
            Person person = (Person) ois.readObject();
            System.out.println(person.toString());
            ois.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

实现Serializable接口的对象,可以利用序列化流实现序列化,写入文件,或者以流的形式传输在网络等。

 

利用转换流实现修改文件的编码格式

    private static void testChangeEncoding(){
        try {
            FileInputStream fis = new FileInputStream("utf-8.txt");
            InputStreamReader isr = new InputStreamReader(fis,"utf-8");//不设置编码,默认使用utf-8
            FileOutputStream fos = new FileOutputStream("GBK.txt");
            OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");
            int len = 0;
            while ((len = isr.read()) != -1){
                osw.write(len);
            }
            isr.close();
            fis.close();
            osw.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

如上例,实现了将UTF-8的文件转换成GBK编码的文件。

 

通过打印流控制sout的输出位置

    //----------------打印流-----------------------
    private static void testPrintStream(){
        try {
            System.out.println("打印在控制台……");
            PrintStream ps = new PrintStream(new FileOutputStream("print.log"));
            ps.println("你好!");
            System.setOut(ps);
            System.out.println("输出到文件中去了……");
            ps.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

sout(System.out.println)打印的内容,系统默认是输出到控制台;我们可以通过打印流来改变输出方向,输出到文件中。

 

Java BIO

java bio主要是针对网络服务器架构(多线程场景)的即时通讯设计的,解决客户端和服务端之间依赖网络数据传输的问题。主要实现步骤如下:

1.服务端新建ServerSocket(port)实例,并一直阻塞监听客户端连接事件(serverSocket.accept());

2.服务端一旦监听到客户端连接(serverSocket.accept()返回了客户端socket),则启动一个线程单独处理该socket的io事件;

3.客户端新建一个Socket(ip,port)实例,与server端建立连接;

4.客户端所在线程阻塞读取服务端发来的数据,或者使用socket的outputstream,向服务端发送数据。

可以看到不管是服务端还是客户端,处理IO事件的时候都是阻塞式的,服务端在监听客户端连接以及从socket读取数据时都是阻塞等待,客户端从socket读取数据也是阻塞等待,所以这套io设计叫做BIO(阻塞式IO)。

从上述步骤可以看到BIO有一个最大的缺点,那就是有多少个客户端连接,服务端就得启多少个线程,并且这些线程大多都是处于阻塞状态。BIO的设计无疑对于服务端并不友好,会极大消耗服务端资源,并且效率还不高!

***:(实战)使用BIO实现cs(client-server)架构通讯

public class BIOServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        ExecutorService service = Executors.newFixedThreadPool(50);
        try {
            server = new ServerSocket(6066);
            System.out.println("server started with port:"+6066);
            while (true){
                Socket socket = server.accept();
                service.execute(new Handler(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (server != null){
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            server = null;
        }
    }

    static class Handler implements Runnable{
        Socket socket = null;

        public Handler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            BufferedReader reader = null;
            PrintWriter writer = null;
            try {
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
                writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8"));
                String readMessage = null;
                while (true){
                    System.out.println("server reading……");
                    if ((readMessage = reader.readLine()) == null) break;
                    System.out.println(readMessage);
                    writer.println("server receive: "+readMessage);
                    writer.flush();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (socket != null) {
                        socket.close();
                    }
                    socket = null;
                    if (reader != null) {
                        reader.close();
                    }
                    reader = null;
                    if (writer != null) {
                        writer.close();
                    }
                    writer = null;
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}


public class BIOClient {
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader reader = null;
        PrintWriter writer = null;
        Scanner s = new Scanner(System.in);
        try {
            socket = new Socket("localhost",6066);
            String message = null;
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
            writer = new PrintWriter(socket.getOutputStream(),true);
            while (true){
                message = s.nextLine();
                if (message.equals("exit")){
                    break;
                }
                writer.println(message);
                writer.flush();
                System.out.println(reader.readLine());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket != null) {
                    socket.close();
                }
                socket = null;
                if (reader != null){
                    reader.close();
                }
                if (writer != null)
                    writer.close();
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

服务端在6066端口启动:server started with 6066,之后就阻塞等待客户端socket连接;

客户端新建socket,尝试与服务端建立连接,一旦连接建立成功,则可以看到服务端马上监听到连接事件,启动一个子线程用于处理这个客户端的IO事件;服务端日志打印:server reading……,然后阻塞等待读取到内容;服务端主线程则继续阻塞监听连接事件;

客户端输入字符,通过socket的outputstream向服务端发送;服务端接收后,对应的子线程打印接收到的字串,然后向客户端回写server receive:收到的字串;

客户端阻塞读取到服务端发来的消息,并打印出来,然后进入下轮循环。

执行结果如下图:

     

 

Java NIO

NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

关于NIO的详细学习可以参考大佬的方志朋的这篇博客:https://blog.csdn.net/forezp/article/details/88414741

***:(实战)使用NIO实现cs架构通讯

服务端实现步骤:

1.通过静态方法ServerSocketChannel.open()得到ServerSocketChannel(区别于BIO,升级为Channel);

2.通过静态方法Selector.open()得到选择器(监听各种事件);

3.配置服务端参数port、是否阻塞等;

4.将服务端channel(serversocketchannel)注册给选择器,事件是连接建立(OP_ACCEPT);则选择器就会监听该通道的连接建立事件;

5.逻辑执行(循环执行,处理选择器监听到的各种事件)

5.1.查看选择器是否监听到事件(超时等待2000ms,等待2s还没有任何事件,则可以处理一点别的事情,之后再尝试查看)

5.2.遍历选择器监听到的事件(2s期间可能已经监听到多个),一一处理,这里主要处理了两类事件:

客户端连接事件(OP_ACCEPT):获取客户端SocketChannel,将客户端channel注册给选择器,事件是数据读取(OP_READ),并设定数据缓冲区;

通道数据读取事件(OP_ACCEPT):从选择器的key中得到channel和buffer,从channel中读取buffer大小的数据;

5.3.事件处理完毕,则从将它从选择器的事件集合中移除。

public class NIOServer {
    public static void main(String[] args) throws IOException {
        //1.得到一个ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2.得到选择器对象Selector
        Selector selector = Selector.open();
        //3.配置
        //端口9999
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //非阻塞
        serverSocketChannel.configureBlocking(false);
        //4.注册给选择器(配置关注的事件)
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //5.逻辑执行
        while (true){
            //5.1监控客户端
            if (selector.select(2000) == 0){
                //nio方式的优势,这里可以执行别的逻辑
                System.out.println("nio持续监控客户端连接同时,做点别的");
                continue;
            }

            //5.2得到selectionkey,判断通道事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                if (key.isAcceptable()){
                    //客户端连接事件
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    //最后一个参数是缓冲区
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (key.isReadable()){
                    //读取客户端数据事件
                    SocketChannel channel = (SocketChannel) key.channel();
                    //获取缓冲区(和上面匹配,就是bytebuffer)
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    channel.read(buffer);
                    System.out.println("客户端发来数据:"+ new String(buffer.array()));
                }

                //5.3手动从集合中移除当前key
                iterator.remove();
            }
        }
    }
}

客户端实现步骤:

1.通过静态方法SocketChannel.open()得到SocketChannel(NIO升级为channel读取数据);

2.配置参数(是否阻塞等);

3.创建InetSocketAddress实例,设定ip和端口;

4.尝试连接服务端(循环执行,连接失败,可以执行一下其他的业务,然后再次尝试连接);

5.发送数据(创建缓冲区,填入数据,通过channel.write()写入channel,向服务端发送)

public class NIOClient {
    public static void main(String[] args) throws IOException {
        //1.得到网络通道
        SocketChannel channel = SocketChannel.open();
        //2.设置阻塞方式(true阻塞,false非阻塞)
        channel.configureBlocking(false);
        //3.提供服务器端ip和端口
        InetSocketAddress address = new InetSocketAddress("localhost",9999);
        //4.连接服务器端
        if (!channel.connect(address)){
            //connect没连接上,则进行重连尝试
            while (!channel.finishConnect()){
                //nio的优势所在,这里一直尝试连接的过程中,可以执行别的逻辑
                System.out.println("Client连接服务端的同时,干点别的");
            }
        }
        //5.得到缓冲区
        ByteBuffer writeBuffer = ByteBuffer.wrap("Hello,Server".getBytes());
        //6.发送数据
        channel.write(writeBuffer);

        //防止服务端异常,这里一直不关闭client程序
        System.in.read();
    }
}

通过实战demo可以看到NIO编程还是比较复杂的,而且不管是服务端还是客户端都是在主线程中处理业务逻辑,且仅有主线程一个线程在处理。显然这肯定是不可以,实际项目中业务逻辑很多、也很复杂,处理业务往往需要一定的耗时。如果全部丢在主线程执行,那肯定会导致主线程阻塞,服务端响应变慢。所以不管是服务端还是客户端,我们都应该将处理选择器的事件放到子线程中去执行,而且应该根据实际业务逻辑,创建合适大小的线程池,提高并发效率。我们可以使用线程池,改写上例demo,将事件处理放到子线程去执行,这样无疑又增加了NIO编程的复杂度。万幸,前辈们已经为我们做好了封装,Netty框架,NIO通讯,支持链式处理器,且将事件处理放到了子线程中去执行。关于netty框架的更多知识点,请移步《Java框架篇--Netty》。


以上系个人理解,如果存在错误,欢迎大家指正。原创不易,转载请注明出处!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值