Java中的IO

概念

  • I/O是输入和输出的简写,指的是数据在计算机内部和外部设备之间的流动
  • Java中将输入输出抽象为流,类似于水管将两个容器连接起来
  • 将数据从外存读取到内存中的称为输入流,从内存写到外存中的称为输出流

 电脑的三种数据存储方式

  • 外存:硬盘,磁盘,U盘等。存储量大,读取最慢
  • 内存:电脑上的内存条。存储量其次,读取速度其次慢
  • 缓存:CPU。存储量小,读取最快。

分类

  • 基于字节操作的 I/O 接口:InputStream 和 OutputStream

  • 基于字符操作的 I/O 接口:Writer 和 Reader

  • 基于磁盘操作的 I/O 接口:File

  • 基于网络操作的 I/O 接口:Socket  

字节流 

数据流中最小的数据单元 

Java系统自带标准的数据流是java.lang.System:

public final class System {
    //标准输入流(键盘输入流)
    public final static InputStream in = null;
    //标准输出流(显示器输出流)
    public final static PrintStream out = null;
    //标准错误流(输出)
    public final static PrintStream err = null;
}

//它用于打印异常(Exception)或错误(Error)的栈追踪信息到标准错误流(System.err)
public class Throwable implements Serializable {
    //打印异常或错误
    public void printStackTrace() {
        //错误流
        printStackTrace(System.err);
    }
}

输入流与输出流

InputStream节点流ByteArrayInputStream(数组)
FileInputStream(文件)=>SocketInpurStream(网络)
PipedInputStream(管道)
处理流InflaterInputStream(压缩操作)=>ZipInputStream(Zip格式操作)
BufferedInputStream(缓冲操作)
DataInputStream(基本数据类型操作)
ObjectInputStream(序列化操作)
OutputStream节点流ByteArrayOutputStream(数组)
FileOutputStream(文件)=>ScoketOutputStream(网络)
PipedOutputStream(管道)
处理流InflaterOutputStream(压缩操作)=>ZipOutputStream(Zip格式操作)
BufferedOutputStream(缓冲操作)
DataOutputStream(基本数据类型操作)
PrintStream(打印操作)
ObjectInputStream(序列化操作)

字符流 

最小存储单元是字节而不是字符,Java中的字符是Unicode编码,一个字符占用两个字节

因为程序通常操作的数据都是以字符的形式,为了程序操作更加方便提供了直接写字符的IO接口

Reader节点流CharArrayReader(数组)
FileReader(文件)
PipedReader(管道)
StringReader(字符串)
处理流BufferReader(缓冲操作)=>LineNumnerReader(跟踪行号)
InputStreamReader(转化控制)
PushbackReader(从回退的缓冲区读数据)
Writer节点流CharArrayWriter(数组)
FileWriter(文件)
PipedWriter(管道)
StringWriter(字符串)
处理流BufferWriter(缓冲操作)
OutputWriter(转化控制)
PrintWriter(打印操作)

字符与字节的转换

InputStreamReader将字节输入流转为字符输入流

OutputStreamWriter将字符输出流转为字节输出流

编码与解码

编码:字符串与字符 ==> 字节

解码:字节 == >字符串与字符

字符编码和字符集

编码依赖于字符集,一个字符集可以有多个编码实现,就像代码中接口的实现依赖于接口一样

转换流原理:字符流=字节流+编码表

字符编码:enconding是charset encoding的简写,即字符集编码,简称编码

字符集:character set的简写

ASCII字符集ASCII编码
GBK字符集GBK编码
Unicode字符集UTF8编码
UTF16编码
UTF32编码

输入流转化过程

  • InputStreamReader 类是字节到字符的转化桥梁
  • StreamDecoder指的是一个解码操作类,Charset值指的是字符集
  • InputStream到Reader的过程需要制定编码字符集,否则采取默认字符集可能会出现乱码,StreamDecoder则是完成字节到字符的解码的实现类
  • InputStreamReader(InputStream in):创建一个默认字符集字符输入流
  • InputStreamReader(InputStream in, String charsetName):创建一个指定字符集的字符流
public class InputStreamReader extends Reader {

    private final StreamDecoder sd;

    public InputStreamReader(InputStream in) {
        super(in);
        try {
            // ## check lock object
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); 
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }

    public InputStreamReader(InputStream in, String charsetName)
        throws UnsupportedEncodingException
    {
        super(in);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
    }
}

public class IOTest {
    public static void main(String[] args) {
        try {
            InputStreamReader isr1 = new InputStreamReader
                        (new FileInputStream("D:\\IO\\utf8.txt"));
            InputStreamReader isr2 = new InputStreamReader
                        (new FileInputStream("D:\\IO\\utf8.txt"),"UTF-8");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (UnsupportedEncodingException e){
            e.printStackTrace();
        }
    }
}

 输出流转化过程

  • 通过 OutputStreamWriter 类完成字符到字节的编码过程,由StreamEncoder完成编码过程
  • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
  • OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
public class OutputStreamWriter extends Writer {

    private final StreamEncoder se;

    public OutputStreamWriter(OutputStream out, String charsetName)
        throws UnsupportedEncodingException
    {
        super(out);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
    }

    public OutputStreamWriter(OutputStream out) {
        super(out);
        try {
            se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }
}

public class IOTest {
    public static void main(String[] args) {
        try {
            OutputStreamWriter isr1 = new OutputStreamWriter
                        (new FileOutputStream("D:\\IO\\gbk.txt"));
            OutputStreamWriter isr2 = new OutputStreamWriter
                        (new FileOutputStream("D:\\IO\\gbk1.txt") , "GBK");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}

基于磁盘的操作接口

  • 将数据持久化到物理磁盘
  • 数据在磁盘的唯一最小描述就是文件
  • 文件是系统与磁盘驱动器交互的最小单元
  • File类是唯一代表磁盘文件本身的对象,方法有文件是否存在、创建、删除、重命名等
  • File并不代表一个真实存在的文件对象,通过制定路径返回代表这个路径的虚拟对象,可能是一个真实的文件也可能是一个包含多级文件的目录

创建File对象

//通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例

File file = new File("E://test//Test.txt");

//从父路径名字符串和子路径名字符串创建新的 File实例。
File file = new File("E:\\test","Test.txt");

// 创建一个URI对象,指向新文件的位置

URI uri = URI.create("file:///path/to/the/newfile.txt");

// 使用URI创建File对象

File myFile = new File(uri);

public class File
    implements Serializable, Comparable<File>
{

    public File(String pathname) {
        if (pathname == null) {
            throw new NullPointerException();
        }
        this.path = fs.normalize(pathname);
        this.prefixLength = fs.prefixLength(this.path);
    }

    public File(String parent, String child) {
        if (child == null) {
            throw new NullPointerException();
        }
        if (parent != null) {
            if (parent.equals("")) {
                this.path = fs.resolve(fs.getDefaultParent(),
                                       fs.normalize(child));
            } else {
                this.path = fs.resolve(fs.normalize(parent),
                                       fs.normalize(child));
            }
        } else {
            this.path = fs.normalize(child);
        }
        this.prefixLength = fs.prefixLength(this.path);
    }

    public File(URI uri) {

        // Check our many preconditions
        if (!uri.isAbsolute())
            throw new IllegalArgumentException("URI is not absolute");
        if (uri.isOpaque())
            throw new IllegalArgumentException("URI is not hierarchical");
        String scheme = uri.getScheme();
        if ((scheme == null) || !scheme.equalsIgnoreCase("file"))
            throw new IllegalArgumentException("URI scheme is not \"file\"");
        if (uri.getAuthority() != null)
            throw new IllegalArgumentException("URI has an authority component");
        if (uri.getFragment() != null)
            throw new IllegalArgumentException("URI has a fragment component");
        if (uri.getQuery() != null)
            throw new IllegalArgumentException("URI has a query component");
        String p = uri.getPath();
        if (p.equals(""))
            throw new IllegalArgumentException("URI path component is empty");

        // Okay, now initialize
        p = fs.fromURIPath(p);
        if (File.separatorChar != '/')
            p = p.replace('/', File.separatorChar);
        this.path = fs.normalize(p);
        this.prefixLength = fs.prefixLength(this.path);
    }

}

 File常用方法

方法返回值类型作用
getName()String获取文件名称
getParent()String获取文件的父路径(字符串),若无指定父目录,则返回null
getParentFile()File获取文件的父路径(抽象路径名),若无指定父目录,则返回null
canRead()boolean判断文件是否可读
canWrite()boolean判断文件是否可写
canExecute()boolean判断文件是否执行
exists()boolean判断文件是否存在
length()long文件的长度(字节)
getAbsolutePath()String获取文件的绝对路径(字符串)
getAbsoluteFile()File获取文件的绝对路径(抽象路径名)
isFile()boolean判断文件是否为标准文件或是否存在
isDirectory()boolean判断文件是否是目录
isHidden()boolean判断文件是否是隐藏文件
latModified()long返回文件最后修改时间
renameTo()boolean文件重命名

基于网络操作的接口

  • 数据写入互联网中以供其他电脑访问
  • Socket是猫叔计算机之间完成互相通信的一种抽象定义
  • 多数基于TCP/IP的流套接字,它是一种稳定通信协议

TCP协议的三次握手 

  • 发送端=>发送带有SYN标志的数据包=>接收端 (第一次握手)
  • 接收端=>发送带有SYN+ACK标志的数据包=>发送端(第二次握手)
  • 发送端=>发送带有=ACK标志的数据包=>接收端(第三次握手)

SYN:Synchronize Sequence Numners,表示同步序列序号,是TCP/IP建立连接使用的握手信号

ACK:AcKnowledge character,确认字符,表示发送的数据已确认接收无误

完成三次握手后,客户端与服务器应用程序可以开始传送数据 

scoket客户端

public class ClientTest {
    public static void main(String[] args) {
        try {
            //通过IP和端口与服务端连接
            Socket socket=new Socket("127.0.0.1",8080);
            //将字符流转换为字节流,并输出
            BufferedWriter bufferedWriter=new BufferedWriter
                        (new OutputStreamWriter(socket.getOutputStream()));
            String str="Hello,我是客户端!";
            bufferedWriter.write(str);
            bufferedWriter.flush();
            bufferedWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

socket服务端

public class ServerTest {
    public static void main(String[] args) {
        try {
            //初始化服务端socket并绑定8080端口
            ServerSocket serverSocket = new ServerSocket(8080);
            //循环监听客户端请求
            while (true) {
                //等待客户端连接
                Socket socket = serverSocket.accept();
                //将字节流转换成字符流,读取客户端输入的内容
                BufferedReader bufferedReader = new BufferedReader
                        (new InputStreamReader(socket.getInputStream()));
                //读取一行数据
                String str = bufferedReader.readLine();
                //输出打印
                System.out.println("服务端接收到客户端信息:" + str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果

  • 注意,客户端只有与服务端建立三次握手成功之后,才会发送数据,而 TCP/IP 握手过程,底层操作系统已经帮我们实现了!
  • 当连接已经建立成功,服务端和客户端都会拥有一个 Socket 实例,每个 Socket 实例都有一个 InputStream 和 OutputStream
  • 当 Socket 对象创建时,操作系统将会为 InputStream 和 OutputStream 分别分配一定大小的缓冲区,数据的写入和读取都是通过这个缓存区完成的

IO的工作方式

BIO:同步阻塞 IO

  • 一个客户端一个线程
  • 由独立的Acceptor线程负责监听客户端的连接
  • 服务端wilhe(ture)中调用accept方法等待客户端连接
  • 接收一个请求,只能等待当前连接客户端操作完成才能接收下一个请求
  • 可以通过多线程的方式连接
  • 对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发效率和更好的维护性

多线程方式 

public class MoreClientTest {
    public static void main(String[] args) {
        //创建5个线程
        for (int i = 0; i < 5; i++) {
            final int m = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //通过IP和端口与服务端连接
                        Socket socket = new Socket("127.0.0.1", 8080);
                        //将字符流转换为字节流,并输出
                        BufferedWriter bufferedWriter = new BufferedWriter
                                (new OutputStreamWriter(socket.getOutputStream()));
                        String str = "Hello,我是客户端" + m + "号!";
                        bufferedWriter.write(str);
                        bufferedWriter.flush();
                        bufferedWriter.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

public class MoreServerTest {
    public static void main(String[] args) {

        try {
            //初始化服务端socket并绑定8080端口
            ServerSocket serverSocket = new ServerSocket(8080);
            //创建5个线程来监听客户端
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //循环监听客户端请求
                            while (true) {
                                //等待客户端连接
                                Socket socket = serverSocket.accept();
                                //将字节流转换成字符流,读取客户端输入的内容
                                BufferedReader bufferedReader = new BufferedReader
                                    (new InputStreamReader(socket.getInputStream()));
                                //读取一行数据
                                String str = bufferedReader.readLine();
                                //输出打印
                                System.out.println("服务端接收到客户端信息:" + str);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

线程池和任务队列(伪异步BIO)

  • 采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架
  • Java 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理

socket客户端

public class BIOClient {
    public static void main(String[] args) {
        //创建15个线程,模拟15个客户端向服务端发送请求
        for (int i = 0; i < 15; i++) {
            final int m = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Socket socket = null;
                    PrintWriter printWriter = null;
                    BufferedReader bufferedReader = null;
                    try {
                        //通过IP和端口与服务端连接
                        socket = new Socket("127.0.0.1", 8080);
                        //将字符流转换为字节流,并输出
                        printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                        String str = "Hello,我是客户端" + m + "号!";
                        printWriter.println(str);

                        //从输入流程中读取服务端返回的消息,将字节流转化成字符流
                        bufferedReader = new BufferedReader(new InputStreamReader((socket.getInputStream())));
                        //读取内容
                        String result = bufferedReader.readLine();
                        //打印服务端返回的信息
                        System.out.println("客户端发送:" + str + " => 收到服务端返回内容:" + result);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }finally {
                        try {
                            if(bufferedReader!=null){
                                bufferedReader.close();
                            }
                            if(printWriter!=null){
                                printWriter.close();
                            }
                            if(socket!=null){
                                socket.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}

socket服务端

public class BIOServer {
    public static void main(String[] args) {
        try {
            //初始化服务端socket并绑定8080端口
            ServerSocket serverSocket = new ServerSocket(8080);
            //在线程池中创建5个固定大小线程,来处理客户端请求
            ExecutorService executorService = Executors.newFixedThreadPool(5);

            while (true) {
                //等待客户端连接
                Socket socket = serverSocket.accept();
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        BufferedReader bufferedReader = null;
                        PrintWriter printWriter = null;
                        try {
                            //将字节流转换成字符流,读取客户端输入的内容
                            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                            //读取一行数据
                            String str = bufferedReader.readLine();
                            //输出打印
                            System.out.println("服务端接收到客户端信息:" + str);

                            //向服务端返回信息,字符流转换为字节流,并输出
                            printWriter=new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                            printWriter.println("服务端返回消息结果:"+str);
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            try {
                                if(bufferedReader!=null){
                                    bufferedReader.close();
                                }
                                if(printWriter!=null){
                                    printWriter.close();
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

运行结果

  • 客户端数量是 15,服务端使用 java 线程池来处理任务,线程数量为 5 个
  • 服务端不用为每个客户端都创建一个线程,
  • 由于线程池可以设置消息队列的大小和最大线程数资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机
  • 底层仍然是同步阻塞的 BIO 模型,当面对十万甚至百万级连接的时候,采用NIO模型
  • 适用于连接数目比较小且固定的架构,这对服务器资源要求比较高

 

NIO:同步非阻塞 IO

  • 新增了 Channel、Selector、Buffer 等抽象概念,支持面向缓冲、基于通道的 I/O 操作方法。
  • NIO 提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现
  • 对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
  • NIO 引入了 Channel、Buffer 和 Selector 就是想把 IO 传输过程中涉及到的信息具体化
  • 适用于连接数目多且连接比较短(轻操作)的架构,例如聊天服务器

NIO 中的核心概念 

Channel:可以理解为通道

Selector:

  • 可以理解为选择器,可以翻译为多路复用器
  • 用于检查一个或多个 Channel(通道)的状态是否处于连接就绪、接受就绪、可读就绪、可写就绪
  • 可以实现单线程管理多个 channels,也就是可以管理多个网络连接
  • 相比传统方式使用多个线程来管理 IO,Selector 使用了更少的线程就可以处理通道了,并且实现网络高效传输

Buffer:可以理解为数据缓冲流

socket客户端

public class NIOClient {
    public static void main(String[] args) {
        //创建15个线程,模拟15个客户端向服务端发送请求
        for (int i = 0; i < 15; i++) {
            final int m = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Socket socket = null;
                    PrintWriter printWriter = null;
                    BufferedReader bufferedReader = null;
                    try {
                        //通过IP和端口与服务端连接
                        socket = new Socket("127.0.0.1", 8080);
                        //将字符流转换为字节流,并输出
                        printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                        String str = "Hello,我是客户端" + m + "号!";
                        printWriter.println(str);

                        //从输入流程中读取服务端返回的消息,将字节流转化成字符流
                        bufferedReader = new BufferedReader(new InputStreamReader((socket.getInputStream())));
                        //读取内容
                        String result = bufferedReader.readLine();
                        //打印服务端返回的信息
                        System.out.println("客户端发送:" + str + " => 收到服务端返回内容:" + result);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }finally {
                        try {
                            if(bufferedReader!=null){
                                bufferedReader.close();
                            }
                            if(printWriter!=null){
                                printWriter.close();
                            }
                            if(socket!=null){
                                socket.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}

socket服务端 

public class NIOServer {
    public static void main(String[] args) {
        try {
            //打开服务器套接字通道
            ServerSocketChannel ssc=ServerSocketChannel.open();
            //服务器配置为非阻塞
            ssc.configureBlocking(false);
            //进行服务绑定,监听8080端口
            ssc.bind(new InetSocketAddress(8080));

            //通过open()方法找到Selector选择器
            Selector selector=Selector.open();
            //将ssc注册到selector,并让selector监听同道中人的接受事件
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            while (true){
                //查询指定事件已经就绪的通道数量,如果为0就跳过
                int readyChannels=selector.select();
                if (readyChannels==0){
                    continue;
                }
                //通过选择器获取所有的key集合
                Set<SelectionKey> selectedKeys=selector.selectedKeys();
                Iterator<SelectionKey> keyIterator=selectedKeys.iterator();
                while (keyIterator.hasNext()){
                    SelectionKey key=keyIterator.next();
                    //判断状态是否有效
                    if(!key.isValid()){
                        continue;
                    }
                    if(key.isAcceptable()){
                        //通道接受就绪,接受事件下通道只能是服务器套接字通道
                        ServerSocketChannel serverSocketChannel= (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel=serverSocketChannel.accept();
                        clientChannel.configureBlocking(false);
                        //将通道注册到选择器并监听通道中的可读事件
                        clientChannel.register(selector,SelectionKey.OP_READ);
                        System.out.println("接收到NIO客户端连接,地址:"+clientChannel.getRemoteAddress());
                    }
                    else if(key.isReadable()){
                        //通道可读就绪
                        //创建一个容量为1024的字节数组
                        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
                        //获取通道
                        SocketChannel clientChannel= (SocketChannel) key.channel();
                        //从通道中读取数据到缓冲中
                        int numberRead=clientChannel.read(byteBuffer);
                        byteBuffer.flip();
                        //获取缓冲数据
                        String result=new String(byteBuffer.array(),0,numberRead);
                        System.out.print("服务端接收到客户端发送的消息:"+result);
                        //将通道注册到选择器,并监听通道中的可写事件
                        clientChannel.register(selector,SelectionKey.OP_WRITE);

                    }else if(key.isWritable()){
                        //通道可写就绪
                        //创建一个容量为1024的字节数组
                        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
                        SocketChannel clientChannel= (SocketChannel) key.channel();
                        byteBuffer.put("服务端发送server send".getBytes());
                        byteBuffer.flip();
                        clientChannel.write(byteBuffer);
                        //将通道注册到选择器并监听通道中的可读事件
                        clientChannel.register(selector,SelectionKey.OP_READ);
                        //写完关闭通道
                        clientChannel.close();
                    }
                    //该事件已经处理,可以丢弃
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果 

SocketChannel客户端 

public class NIOClient2 {
    public static void main(String[] args) {
        try {
            //写入缓冲buffer
            ByteBuffer writeBuffer= ByteBuffer.allocate(1024);
            //读取缓冲buffer
            ByteBuffer readBuffer=ByteBuffer.allocate(1024);

            //打开socket通道
            SocketChannel sc=SocketChannel.open();
            //设置为非阻塞
            sc.configureBlocking(false);
            //连接服务器地址和端口
            sc.connect(new InetSocketAddress("127.0.0.1",8080));

            //打开选择器
            Selector selector=Selector.open();
            //注册连接服务器socket的冯作,并且让selector监听通道中的连接事件
            sc.register(selector, SelectionKey.OP_CONNECT);

            //查询指定的事件已经就绪的通道数量,如果为0跳过
            while (selector.select()>0){
                //通过选择器获取所有key的集合
                Set<SelectionKey> keys= selector.selectedKeys();
                Iterator<SelectionKey> keyIterator=keys.iterator();
                while (keyIterator.hasNext()){
                    SelectionKey key=keyIterator.next();
                    if(key.isConnectable()){
                        //判断此通道上是否正在进行连接操作
                        if(sc.finishConnect()){
                            //完成连接
                            sc.register(selector,SelectionKey.OP_WRITE);
                            System.out.println("连接目标服务器...");
                        }
                    }
                    else if(key.isWritable()){
                        //写入数据
                        //将缓冲区清空以备下次使用
                        writeBuffer.clear();
                        writeBuffer.put("Hello,我是客户端".getBytes());
                        writeBuffer.flip();
                        sc.write(writeBuffer);

                        //注册到选择器中,并监听读取事件操作
                        sc.register(selector,SelectionKey.OP_READ);
                    }
                    else if(key.isReadable()){
                        //读取数据
                        SocketChannel client= (SocketChannel) key.channel();
                        //将缓冲区清空以备下次使用
                        readBuffer.clear();
                        //从通道中读取数据至缓冲中
                        client.read(readBuffer);
                        readBuffer.flip();
                        String result= Charset.defaultCharset().newDecoder().decode(readBuffer).toString();
                        System.out.println("客户端接收到服务端:"+client.socket().getRemoteSocketAddress()+",返回的信息:"+result);

                        //注册到选择器中,并监听写入事件操作
                        sc.register(selector,SelectionKey.OP_WRITE);
                        //读取完关闭客户端通道
                        client.close();
                    }
                }
                keyIterator.remove();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果 

AIO:异步非阻塞 IO

  • 线程发起IO请求,不需要阻塞,立即返回值
  • 也不需要定时轮询查询结果,异步IO操作之后会回调通知调用方
  • 适用于连接数目多气人连接比较长(重操作)的架构,例如相册服务器
  • 17
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: JavaIO流操作基本流程如下: 1. 创建流对象:通过File类或其他相关类创建输入流或输出流对象。 2. 打开流:使用输入流或输出流对象的open()方法打开流,这样就可以读取或写入数据。 3. 读取或写入数据:使用输入流或输出流对象的read()或write()方法读取或写入数据。 4. 关闭流:使用输入流或输出流对象的close()方法关闭流,释放资源。 需要注意的是,在使用IO流操作时,要遵循“先打开、后关闭”的原则,以确保数据的完整性和流的正确性。同时,在操作过程也需要进行异常处理,以避免出现不必要的错误。 ### 回答2: JavaIO流基本操作流程如下: 1. 打开文件或者建立网络连接:使用File类或者URL类打开文件或者建立网络连接。 2. 创建流对象:根据需要选择输入流(读取数据)或输出流(写入数据),并创建相应的流对象。常见的输入流有FileInputStream、BufferedReader等,常见的输出流有FileOutputStream、BufferedWriter等。 3. 读取或写入数据:使用流对象读取或写入数据。对于输入流,可以通过调用相关方法(如read()、readline()等)逐个字符或逐行读取数据;对于输出流,可以通过调用相应方法(如write()、print()等)逐个字符或逐行写入数据。 4. 关闭流:读取或写入完成后,需要关闭文件或网络连接,以释放资源。可以调用流对象的close()方法来关闭流。 需要注意的是,在处理IO流时,应该始终使用try-catch-finally块,以确保在发生异常时能够正确关闭流。可以把IO操作放在try块,catch块用于捕获异常,并在finally块关闭流。 另外,为了提高IO效率,可以考虑使用缓冲流来进行读写操作。缓冲流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)可以通过缓冲区将数据从源读入到缓冲区,再从缓冲区写入到目标,提高读写的速度。 以上就是JavaIO流基本操作的流程。根据实际需要选择合适的流对象,并遵循打开、读取/写入、关闭的流程,可以实现灵活、高效的IO操作。 ### 回答3: 在JavaIO流是用于处理输入输出操作的工具。下面是JavaIO流的基本操作流程: 1. 创建流对象:通过使用Java的InputStream和OutputStream类来创建流对象。InputStream类用于读取输入流,而OutputStream类用于写入输出流。 2. 打开流:通过使用流对象对应的构造函数和方法来打开输入和输出流。根据具体情况,可以选择文件流、网络流或内存流来打开流。 3. 读取/写入数据:使用流对象提供的读取和写入方法来读取和写入数据。例如,使用InputStream的`int read()`方法来读取一个字节的数据,使用OutputStream的`void write(int b)`方法来写入一个字节的数据。 4. 关闭流:在读取或写入结束后,必须关闭流以释放相关资源。通过调用流对象的`close()`方法来关闭流。 需要注意的是,在处理异常的时候,我们需要对可能出现的`IOException`进行处理。可以使用try-catch语句块来捕获和处理异常。 流程示例: ```java import java.io.*; public class IOExample { public static void main(String[] args) { try { // 1. 创建流对象 FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt"); // 2. 打开流 // 3. 读取/写入数据 int data; while ((data = fis.read()) != -1) { fos.write(data); } // 4. 关闭流 fis.close(); fos.close(); } catch(IOException e) { e.printStackTrace(); } } } ``` 上述示例,我们创建了一个用于将一个文件的内容拷贝到另一个文件的程序。首先,我们创建了一个FileInputStream对象来读取输入文件的内容,然后创建了一个FileOutputStream对象来写入输出文件。接下来,我们通过循环从输入流读取一个字节的数据,并将其写入到输出流,直到读取完所有的数据。最后,我们关闭了流对象来释放资源。 这就是JavaIO流的基本操作流程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值