Java IO网络模型实现解析

目录
  • 前言
  • 正文
    • 一. BIO
    • 二. Non Blocking IO
    • 三. IO多路复用
    • 四. NIO
  • 总结

前言

本篇文章会对Java中的网络IO模型的概念进行解释,并给出具体的Java代码实现,主要涉及如下部分。

  • BIO(同步阻塞IO模型)的概念和Java编程实现;
  • Non Blocking IO(同步非阻塞IO模型)的概念和Java编程实现;
  • IO多路复用的概念;
  • NIONew IO)的概念和Java编程实现。

在开始本篇文章内容之前,有一个简单的关于Socket的知识需要说明:在进行网络通信的时候,需要一对Socket,一个运行于客户端,一个运行于服务端,同时服务端还会有一个服务端Socket,用于监听客户端的连接。下图进行一个简单示意。

那么整个通信流程如下所示。

  • 服务端运行后,会在服务端创建listen-socketlisten-socket会绑定服务端的ipport,然后服务端进入监听状态;
  • 客户端请求服务端时,客户端创建connect-socketconnect-socket描述了其要连接的服务端的listen-socket,然后connect-socketlisten-socket发起连接请求;
  • connect-socketlisten-socket成功连接后(TCP三次握手成功),服务端会为已连接的客户端创建一个代表该客户端的client-socket,用于后续和客户端进行通信;
  • 客户端与服务端通过socket进行网络IO操作,此时就实现了客户端和服务端中的不同进程的通信。

需要知道的就是,在客户端与服务端通信的过程中,出现了三种socket,分别是。

  • listen-socket。是服务端用于监听客户端建立连接的socket
  • connect-socket。是客户端用于连接服务端的socket
  • client-socket。是服务端监听到客户端连接请求后,在服务端生成的与客户端连接的socket

(注:上述中的socket,可以被称为套接字,也可以被称为文件描述符。)

正文

一. BIO

BIO,即同步阻塞IO模型。用户进程调用read时发起IO操作,此时用户进程由用户态转换到内核态,只有在内核态中将IO操作执行完后,才会从内核态切换回用户态,这期间用户进程会一直阻塞。

BIO示意图如下。

简单的BIOJava编程实现如下。

服务端实现

public class BioServer {

    public static void main(String[] args) throws IOException {
        // 创建listen-socket
        ServerSocket listenSocket = new ServerSocket(8080);
        // 进入监听状态,是一个阻塞状态
        // 有客户端连接时从监听状态返回
        // 并创建代表这个客户端的client-socket
        Socket clientSocket = listenSocket.accept();
        // 获取client-socket输入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream()));
        // 读取客户端发送的数据
        // 如果数据没准备好,会进入阻塞状态
        String data = bufferedReader.readLine();
        System.out.println(data);

        // 获取client-socket输出流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(clientSocket.getOutputStream()));
        // 服务端向客户端发送数据
        bufferedWriter.write("来自服务端的返回数据\n");
        // 刷新流
        bufferedWriter.flush();
    }

}

客户端实现

public class BioClient {

    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws IOException {
        // 客户端创建connect-socket
        Socket connectSocket = new Socket(SERVER_IP, SERVER_PORT);
        // 获取connect-socket输出流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(connectSocket.getOutputStream()));
        // 客户端向服务端发送数据
        bufferedWriter.write("来自客户端的请求数据\n");
        // 刷新流
        bufferedWriter.flush();

        // 获取connect-socket输入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(connectSocket.getInputStream()));
        // 读取服务端发送的数据
        String returnData = bufferedReader.readLine();
        System.out.println(returnData);
    }

}

BIO的问题就在于服务端在accept时是阻塞的,并且在主线程中,一次只能accept一个SocketacceptSocket后,读取客户端数据时又是阻塞的。

二. Non Blocking IO

Non Blocking IO,即同步非阻塞IO。是用户进程调用read时,用户进程由用户态转换到内核态后,此时如果没有系统资源数据能够被读取到内核缓冲区中,返回read失败,并从内核态切换回用户态。也就是用户进程发起IO操作后会立即得到一个操作结果。

Non Blocking IO示意图如下所示。

简单的Non Blocking IOJava编程实现如下。

public class NonbioServer {

    public static final List<SocketChannel> clientSocketChannels = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        // 客户端创建listen-socket管道
        // 管道支持非阻塞模式和同时读写
        ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式
        listenSocketChannel.configureBlocking(false);
        // 绑定监听的端口号
        listenSocketChannel.socket().bind(new InetSocketAddress(8080));
        // 在子线程中遍历clientSocketChannels并读取客户端数据
        handleSocketChannels();

        while (true) {
            // 非阻塞方式监听客户端连接
            // 如果无客户端连接则返回空
            // 有客户端连接则创建代表这个客户端的client-socket管道
            SocketChannel clientSocketChannel = listenSocketChannel.accept();
            if (clientSocketChannel != null) {
                // 设置为非阻塞模式
                clientSocketChannel.configureBlocking(false);
                // 添加到clientSocketChannels中
                // 用于子线程遍历并读取客户端数据
                clientSocketChannels.add(clientSocketChannel);
            } else {
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        }
    }

    public static void handleSocketChannels() {
        new Thread(() -> {
            while (true) {
                // 遍历每一个client-socket管道
                Iterator<SocketChannel> iterator = clientSocketChannels.iterator();
                while (iterator.hasNext()) {
                    SocketChannel clientSocketChannel = iterator.next();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int read = 0;
                    try {
                        // 将客户端发送的数据读取到ByteBuffer中
                        // 这一步的操作也是非阻塞的
                        read = clientSocketChannel.read(byteBuffer);
                    } catch (IOException e) {
                        // 移除发生异常的client-socket管道
                        iterator.remove();
                        e.printStackTrace();
                    }
                    if (read == 0) {
                        System.out.println("客户端数据未就绪");
                    } else {
                        System.out.println("客户端数据为:" + new String(byteBuffer.array()));
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        }).start();
    }

}

上述是Non Blocking IO的一个简单服务端的实现,相较于BIO,服务端在accept时是非阻塞的,在读取客户端数据时也是非阻塞的,但是还是存在如下问题。

  • 一次只能accept一个Socket
  • 需要在用户进程中遍历所有的SocketChannel并调用read() 方法获取客户端数据,此时如果客户端数据未准备就绪,那么这一次的read() 操作的开销就是浪费的。

三. IO多路复用

在上述的BIONon Blocking IO中,一次系统调用,只会获取一个IO的状态,而如果采取IO多路复用机制,则可以一次系统调用获取多个IO的状态。

也就是获取多个IO的状态可以复用一次系统调用。

最简单的IO多路复用方式是基于select模型实现,步骤如下。

  • 在用户进程中将需要监控的IO文件描述符(Socket)注册到IO多路复用器中;
  • 执行select操作,此时用户进程由用户态转换到内核态(一次系统调用),然后在内核态中会轮询注册到IO多路复用器中的IO是否准备就绪,并得到所有准备就绪的IO的文件描述符列表,最后返回这些文件描述符列表;
  • 用户进程在select操作返回前会一直阻塞,直至select操作返回,此时用户进程就获得了所有就绪的IO的文件描述符列表;
  • 用户进程获得了就绪的IO的文件描述符列表后,就可以对这些IO进行相应的操作了。

换言之,IO多路复用中,只需要一次系统调用,IO多路复用器就可以告诉用户进程,哪些IO已经准备就绪可以进行操作了,而如果不采用IO多路复用,则需要用户进程自己遍历每个IO并调用accept() 或者read() 方法去判断,且一次accept() 或者read() 方法调用只能判断一个IO

四. NIO

NIO,即New IO。关于NIO,有如下三大组件。

  • channel(管道)。介于buffer(字节缓冲区)和Socket(套接字)之间,用于数据的读写操作;
  • buffer(字节缓冲区)。是用户程序和channel(管道)之间进行读写数据的中间区域;
  • selectorIO多路复用器)。服务端的listen-socketclient-socket,客户端的connect-socket,都可以注册在selector上,注册的时候还需要指定监听的事件,比如为listen-socket指定监听的事件为ACCEPT事件,该事件发生则表示客户端建立了连接,还比如为client-socket指定监听的事件为READ事件,该事件发生则表示客户端发送的数据已经可读。

NIO的代码实现如下所示。

服务端实现

public class NioServer {

    private static Selector selector;

    public static void main(String[] args) {

        try {
            // 开启并得到多路复用器
            selector = Selector.open();
            // 服务端创建listen-socket管道
            ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
            // 设置为非阻塞模式
            listenSocketChannel.configureBlocking(false);
            // 为管道绑定端口
            listenSocketChannel.socket().bind(new InetSocketAddress(8080));
            // 将listen-socket管道注册到多路复用器上,并指定监听ACCEPT事件
            listenSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                // 获取发生的事件,这个操作是阻塞的
                selector.select();
                // 拿到有事件发生的SelectionKey集合
                // SelectionKey表示管道与多路复用器的绑定关系
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍历每个发生的事件,然后判断事件类型
                // 根据事件类型,进行不同的处理
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isAcceptable()) {
                        // 处理客户端连接事件
                        handlerAccept(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        // 处理客户端数据可读事件
                        handlerRead(selectionKey);
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerAccept(SelectionKey selectionKey) {
        // 从事件中获取到listen-socket管道
        ServerSocketChannel listenSocketChannel = (ServerSocketChannel) selectionKey.channel();
        try {
            // 为连接的客户端创建client-socket管道
            SocketChannel clientSocketChannel = listenSocketChannel.accept();
            // 设置为非阻塞模式
            clientSocketChannel.configureBlocking(false);
            // 将client-socket管道注册到多路复用器上,并指定监听READ事件
            clientSocketChannel.register(selector, SelectionKey.OP_READ);
            // 给客户端发送数据
            clientSocketChannel.write(ByteBuffer.wrap("连接已建立\n".getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerRead(SelectionKey selectionKey) {
        // 从事件中获取到client-socket管道
        SocketChannel clientSocketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            // 读取客户端数据
            int read = clientSocketChannel.read(byteBuffer);
            if (read <= 0) {
                // 关闭管道
                clientSocketChannel.close();
                // 从多路复用器移除绑定关系
                selectionKey.cancel();
            } else {
                System.out.println(new String(byteBuffer.array()));
            }
        } catch (IOException e1) {
            try {
                // 关闭管道
                clientSocketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
            // 从多路复用器移除绑定关系
            selectionKey.cancel();
            e1.printStackTrace();
        }
    }

}

客户端实现

public class NioClient {

    private static Selector selector;

    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) {
        try {
            // 开启并得到多路复用器
            selector = Selector.open();
            // 创建connect-socket管道
            SocketChannel connectSocketChannel = SocketChannel.open();
            // 设置为非阻塞模式
            connectSocketChannel.configureBlocking(false);
            // 设置服务端IP和端口
            connectSocketChannel.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT));
            // 将connect-socket管道注册到多路复用器上,并指定监听CONNECT事件
            connectSocketChannel.register(selector, SelectionKey.OP_CONNECT);

            while (true) {
                // 获取发生的事件,这个操作是阻塞的
                selector.select();
                // 拿到有事件发生的SelectionKey集合
                // SelectionKey表示管道与多路复用器的绑定关系
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍历每个发生的事件,然后判断事件类型
                // 根据事件类型,进行不同的处理
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isConnectable()) {
                        // 处理连接建立事件
                        handlerConnect(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        // 处理服务端数据可读事件
                        handlerRead(selectionKey);
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerConnect(SelectionKey selectionKey) throws IOException {
        // 拿到connect-socket管道
        SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
        if (connectSocketChannel.isConnectionPending()) {
            connectSocketChannel.finishConnect();
        }
        // 设置为非阻塞模式
        connectSocketChannel.configureBlocking(false);
        // 将connect-socket管道注册到多路复用器上,并指定监听READ事件
        connectSocketChannel.register(selector, SelectionKey.OP_READ);
        // 向服务端发送数据
        connectSocketChannel.write(ByteBuffer.wrap("客户端发送的数据\n".getBytes()));
    }

    private static void handlerRead(SelectionKey selectionKey) {
        // 拿到connect-socket管道
        SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            // 读取服务端数据
            int read = connectSocketChannel.read(byteBuffer);
            if (read <= 0) {
                // 关闭管道
                connectSocketChannel.close();
                // 从多路复用器移除绑定关系
                selectionKey.cancel();
            } else {
                System.out.println(new String(byteBuffer.array()));
            }
        } catch (IOException e1) {
            try {
                // 关闭管道
                connectSocketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
            // 从多路复用器移除绑定关系
            selectionKey.cancel();
            e1.printStackTrace();
        }
    }

}

总结

本篇文章中所讨论的IO模型,都是同步IO模型,而何谓同步异步,何谓阻塞非阻塞,可以总结如下。

  • 同步异步同步IO表示需要在用户进程中主动的去询问操作系统数据是否准备好,如果没有准备好,还需要持续的询问,直到数据准备好为止;而异步IO则是在用户进程中只需要询问操作系统一次,后续数据准备好后操作系统会主动的将数据给到用户进程;
  • 阻塞非阻塞阻塞IO就是发起一次系统调用后,会一直阻塞直到有结果返回;而非阻塞IO就是发起一次系统调用后,会立即得到一个返回结果。

实际上在Java中是有对异步IOAIO)做支持,但是AIO依赖操作系统的底层实现,而目前LinuxAIO的支持不成熟,所以AIO的使用并不多,像主流的网络应用框架Netty也都没有使用到AIO

以上就是Java IO网络模型实现解析的详细内容,更多关于Java IO网络模型的资料请关注我们其它相关文章!

(0)

相关推荐

  • flutter升级3.7.3报错Unable to find bundled Java version解决

    目录 引言 升级过程很顺利,一跑应用傻眼了,报错! build gradle 指定 compileSdkVersion 33报错 引言 Android studio 是2020 年的版本,有点老,昨天突发想法,升级到了 Android Studio Electric Eel 2022.1. 计划今天和明天写那个 Flutter WebView 优化的文章,这篇是 在 Flutter 中使用 webview_flutter 4.0 | js 交互 的续集.早上起来,发现 Flutter 有新版本了

  • JavaIO字符操作和对象操作示例详解

    目录 字符操作 编码与解码 String 的编码方式 Reader 与 Writer 实现逐行输出文本文件的内容 对象操作 序列化 Serializable transient 字符操作 编码与解码 编码就是把字符转换为字节,而解码是把字节重新组合成字符. 如果编码和解码过程使用不同的编码方式那么就出现了乱码. GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节: UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节: UTF-16be 编码中,中文字符和英文字符都占

  • Java线程池队列PriorityBlockingQueue和SynchronousQueue详解

    目录 正文 PriorityBlockingQueue阻塞优先队列 SynchronousQueue 正文 public enum QueueTypeEnum { ARRAY_BLOCKING_QUEUE(1, "ArrayBlockingQueue"), LINKED_BLOCKING_QUEUE(2, "LinkedBlockingQueue"), DELAY_QUEUE(3, "DelayQueue"), PRIORITY_BLOCKING

  • Java IO篇之Reactor 网络模型的概念

    目录 一.什么是 Reactor 模型: 二.Reactor 模型的分类: 1.单 Reactor 单线程模型: 1.1.处理流程: 1.2.优缺点: 2.单 Reactor 多线程模型: 2.1.处理流程: 2.2.优缺点: 3.主从 Reactor 多线程模型: 3.1.处理流程: 3.2.优缺点: 4.Reactor 优缺点: 一.什么是 Reactor 模型: The reactor design pattern is an event handling pattern for hand

  • Java中StringUtils与CollectionUtils和ObjectUtil概念讲解

    目录 一.解析 概念 StringUtils概念 CollectionUtils概念 ObjectUtil概念 二.区别 三.总结 一.解析 概念 StringUtils概念 StringUtils 方法的操作对象是 Java.lang.String 类型的对象,是 JDK 提供的 String 类型操作方法的补充,并且是 null 安全的(即如果输入参数 String 为 null 则不会抛出 NullPointerException ,而是做了相应处理,例如,如果输入为 null 则返回也是

  • java中Pulsar InterruptedException 异常

    目录 背景 前置排查 Pulsar 源码排查 定位问题 总结 背景 今天收到业务团队反馈线上有个应用往 Pulsar 中发送消息失败了,经过日志查看得知是发送消息时候抛出了 java.lang.InterruptedException 异常. 和业务沟通后得知是在一个 gRPC 接口中触发的消息发送,大约持续了半个小时的异常后便恢复正常了,这是整个问题的背景. 前置排查 拿到该问题后首先排查下是否是共性问题,查看了其他的应用没有发现类似的异常:同时也查看了 Pulsar broker 的监控大盘

  • Java网络编程之IO模型阻塞与非阻塞简要分析

    目录 1.阻塞I/O模型 2.非阻塞I/O模型 1.阻塞I/O模型 阻塞IO模型是常见的IO模型,在读写数据时客户端会发生阻塞.阻塞IO模型的工作流程为: 1.1在用户线程发出IO请求之后,内核会检查数据是否就绪,此时用户线程一直阻塞等待内存数据就绪: 1.2在内存数据就绪后,内核将数据复制到用户线程中,并返回I/O执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据. 典型的阻塞I/O模型的例子为data= socket.read(),如果内核数据没有就绪, Socket线程就会一直阻

  • Java IO流相关知识代码解析

    一.IO流的分类 字符流 Reader InputStreamReader(节点流) BufferedReader(处理流) Writer OutputStreamWriter(节点流) BufferedWriter(处理流) PrintWriter 字节流 InputStream FileInputStream(节点流) BufferedInputStream(处理流) ObjectInputStream(处理流) PrintStream OutputStream FileOutputStre

  • java IO数据操作流、对象序列化、压缩流代码解析

    数据操作流 在io包中,提供了两个与平台无关的数据操作流: 数据输入流(DataInputStream) 数据输出流(DataOutputStream) 通常数据输出流会按一定格式将数据输出,再通过数据输入流按照一定格式将数据读入 DataOutputStream接口定义了一系列的writeXxx()的操作,可以写入各种数据类型的数据. 范例:使用数据操作流写入与读出数据 import java.io.DataOutputStream ; import java.io.File ; import

  • Java IO流和文件操作实现过程解析

    Java.io 包几乎包含了所有操作输入.输出需要的类.所有这些流类代表了输入源和输出目标. Java.io 包中的流支持很多种格式,比如:基本类型.对象.本地化字符集等等. 一个流可以理解为一个数据的序列.输入流表示从一个源读取数据,输出流表示向一个目标写数据. Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中. 控制台输入 BufferedReader br = new BufferedReader(new InputStreamReader(Syste

  • 在java中使用dom4j解析xml(示例代码)

    虽然Java中已经有了Dom和Sax这两种标准解析方式 但其操作起来并不轻松,对于我这么一个初学者来说,其中部分代码是活生生的恶心 为此,伟大的第三方开发组开发出了Jdom和Dom4j等工具 鉴于目前的趋势,我们这里来讲讲Dom4j的基本用法,不涉及递归等复杂操作 Dom4j的用法很多,官网上的示例有那么点儿晦涩,这里就不写了 首先我们需要出创建一个xml文档,然后才能对其解析 xml文档: 复制代码 代码如下: <?xml version="1.0" encoding=&quo

  • JAVA Vector源码解析和示例代码

    第1部分 Vector介绍Vector 是矢量队列,它是JDK1.0版本添加的类.继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口.Vector 继承了AbstractList,实现了List:所以,它是一个队列,支持相关的添加.删除.修改.遍历等功能.Vector 实现了RandmoAccess接口,即提供了随机访问功能.RandmoAccess是java中用来被List实现,为List提供快速访问功能的.在Vector中,我们即可以通过

  • java中使用sax解析xml的解决方法

    在java中,原生解析xml文档的方式有两种,分别是:Dom解析和Sax解析 Dom解析功能强大,可增删改查,操作时会将xml文档以文档对象的方式读取到内存中,因此适用于小文档 Sax解析是从头到尾逐行逐个元素读取内容,修改较为不便,但适用于只读的大文档 本文主要讲解Sax解析,其余放在后面 Sax采用事件驱动的方式解析文档.简单点说,如同在电影院看电影一样,从头到尾看一遍就完了,不能回退(Dom可来来回回读取) 在看电影的过程中,每遇到一个情节,一段泪水,一次擦肩,你都会调动大脑和神经去接收或

  • java中利用Dom4j解析和生成XML文档

    一.前言 dom4j是一套非常优秀的Java开源api,主要用于读写xml文档,具有性能优异.功能强大.和非常方便使用的特点.   另外xml经常用于数据交换的载体,像调用webservice传递的参数,以及数据做同步操作等等,   所以使用dom4j解析xml是非常有必要的. 二.准备条件 dom4j.jar 下载地址:http://sourceforge.net/projects/dom4j/ 三.使用Dom4j实战 1.解析xml文档 实现思路: <1>根据读取的xml路径,传递给SAX

  • 【Java IO流】字节流和字符流的实例讲解

    字节流和字符流 对于文件必然有读和写的操作,读和写就对应了输入和输出流,流又分成字节和字符流. 1.从对文件的操作来讲,有读和写的操作--也就是输入和输出. 2.从流的流向来讲,有输入和输出之分. 3.从流的内容来讲,有字节和字符之分. 这篇文章先后讲解IO流中的字节流和字符流的输入和输出操作. 一.字节流 1)输入和输出流 首先,字节流要进行读和写,也就是输入和输出,所以它有两个抽象的父类InputStream.OutputStream. InputStream抽象了应用程序读取数据的方式,即

  • 在java中使用dom解析xml的示例分析

    dom是个功能强大的解析工具,适用于小文档 为什么这么说呢?因为它会把整篇xml文档装载进内存中,形成一颗文档对象树 总之听起来怪吓人的,不过使用它来读取点小东西相对Sax而言还是挺方便的 至于它的增删操作等,我是不打算写了,在我看教程的时候我就差点被那代码给丑到吐了 也正因为如此,才有后来那些jdom和dom4j等工具的存在-- 不多说,直接上代码 Dom解析示例 复制代码 代码如下: import java.io.File; import javax.xml.parsers.Document

随机推荐