Java从零开始 第15讲 网络编程,XML和JSON
网络编程
在本讲之前,我们所有编写的程序都是单机运行的,并没有任何需要网络的部分,而现在让我们学习运用网络的编程
在学习代码之前,让我们先学习两个概念,协议(protocol)和套接字(socket)
协议是端到端通信需要遵循的规矩,你不需要具体理解协议的内容,你只需要明白协议是用于约束信息在网络上传输的东西,就像是交警一样
而套接字,是端到端通信的端点,是网络驱动提供给应用程序编程的一种接口,一种标准,一种机制,当我们想要通过网络传输或者接收文件时,我们都是通过套接字来完成的
TCP的网络编程
TCP是一种非常常用的协议,TCP拥有很多机制,能够保证运输的信息全部到达
TCP编程主要使用到两个类,ServerSocket类和Socket类
让我们直接通过实例来学习
// 服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8000);// 4096为端口号
System.out.println("服务器启动了");
Socket socket = ss.accept();// 如果没有连接,会阻塞在此处
System.out.println("服务器被连接了");
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("欢迎连接");// 发送消息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String text = br.readLine();
System.out.println("接收到消息:" + text);//接收消息
ss.close();// 运行结束后关闭
System.out.println("服务器关闭了");
}
}
// 客户端
import java.io.*;
import java.net.Socket;
class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8000);// 127.0.0.1为本机IP地址
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String text = br.readLine();
System.out.println("接收到消息:" + text);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("不客气");
socket.close();// 使用结束后关闭
System.out.println("连接关闭了");
}
}
注意着两段代码要分开运行,先运行服务器端代码,再运行客户端代码
多线程网络编程
在真实的服务器和客户端模型中,肯定不能像上面这样一对一交互,服务器需要面对连接不同的客户端,是一对n的交互,为了能够完成一对多的交互,我们需要引入多线程
让我们同样直接用代码来学习:
// 服务器端
package general;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Network {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8000);// 4096为端口号
System.out.println("服务器启动了");
while (true) {
Socket socket = ss.accept();
new Thread() {
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("欢迎连接");
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
System.out.println("服务器被连接了");
}
//ss.close();// 运行结束后关闭
//System.out.println("服务器关闭了");
}
}
// 客户端
import java.io.*;
import java.net.Socket;
class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8000);// 127.0.0.1为本机IP地址
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String text = br.readLine();
System.out.println("接收到消息:" + text);
socket.close();// 使用结束后关闭
System.out.println("连接关闭了");
}
}
当我们的服务器开始运行时,可以接收到多个客户端的访问而不结束
更多
在网络编程中还有两个常用的类,一个是面向UDP协议的网络编程类DatagramSocket类和InetAddress类
DatagramSocket类和TCP的类在我们编写的部分基本没有差别(也就是应用层),这里不过多介绍
InetAddress类用于将ip地址包装成一个对象,有些时候编程需要传入一个对象时,你可以把ip地址传入其中生成一个对象
InetAddress ip = InetAddress.getByName(“127.0.0.1”);
XML语言
XML(Extensible Markup Language),即可扩展标记语言,是一种用于标记电子文件使其具有结构性的标记语言
通过XML的标记,计算机之间可以处理包含各种的信息比如文章等。它可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言
在我们学习到网络编程阶段时,为了能够传输不受限制的内容,我们需要一种更好的,能够被广泛接受和使用的,不局限于Java的语法去组织数据,这时XML诞生了
在看完上面一段话后你可能还是不能理解,让我们通过例子分别分析一下。
首先让我们关注 什么叫传输不受限制的内容:在进行网络编程传输数据时,我们通常会一次传输多个内容,其中用自己协定的分隔符隔开;不同的程序员很有可能去用不同的分隔符,比如甲用&分隔而乙用$分割,他们直接进行通信时就无法通过传输的数据得到应该得到的信息。而且他们使用的分隔符在正文中不能使用,不然同样会导致错误
再让我们关注 什么叫能被广泛接受和使用的,不局限于Java的:在网络编程的世界中,对语言的多样性是包容的,即你无论用什么语言,C,Java,还是python都能使用套接字进行数据传输。如果你想要传输Java中的对象信息,而接收方使用C来处理接收的数据,那么不难想象,无法处理得到的数据
XML的存在,主要就是为了传输网络数据,存储数据,和配置文件(然而现在传输网络数据和存储文件都通过更优异的JSON来完成)。XML可以通过.xml文件保存,但是也可以通过其他方式存在
XML格式
XML文档开头会有一个声明
<?xml version="1.0" encoding="UTF-8"?>来表明自己是一个XML文档,和表明基本信息
XML通过如下方式记录信息
<标记名称>想要记录的信息</标记名称>
标记不能以字母或标点符号或xml开始,不能包含空格或冒号,且区分大小写
标记可以进行嵌套,但不能重复或交叉
标记之间可以互称为子标记(包含), 父标记 (被包含), 兄弟标记(被同一个父标记包含), 后代标记(多重包含) ,以及祖先标记(多重被包含)
标记中可以描述属性,属性名不允许重复,多个属性之间用空格隔开,属性值被引号包裹
XML可以添加注释,注释不能写在文档声明前,注释通过<!--开始,以-->结束,注释不能嵌套
如何打开.xml文件
首先自己创建一个xml文件,选择打开方式为记事本,按照上面讲的规则自行撰写
上面就是我写出的一个简单的xml文档
直接将xml文件放在IDEA工程文件夹下,你就可以在IDEA中看到你写的xml文件,如果你写的有语法问题,IDEA也会将其标红
Java解析XML方法
Java主要有两种解析方法,SAX和DOM解析,其中DOM解析还延申出了JDOM解析和DOM4J解析。
SAX解析的方式是事件驱动机制,即解析到某些特殊事件时,进行相应的处理。SAX的解析是逐行解析的,即读一行解析一行,读下一行时前一行就释放前一行,十分节省内存。但是SAX只能单向解析,即无法同时访问文档的不同部分,不能解析前一行,不能确定正在解析的部分与前后的关系,且无法修改xml文档的内容
DOM解析是用与平台和语言无关的方式表示XML文档的官方W3C标准,分析该结构通常需要加载整个 文档和内存中建立文档树模型.程序员可以通过操作文档树(即DOM会将文档通过树的数据结构保存数据), 来完成数据的获取,修改,删除等。DOM解析会将整个文档加载完,对内存的消耗比较大(但是xml文档是纯文本文件,基本不会太大),但是可以双向解析,可以在任何时候解析任何部分,可以获取文档与上下的关系,还可以对xml文档进行修改
JDOM解析是DOM解析对Java的特定优化,它大量使用了的Java集合类,而且简化了DOM的API,但是由于是第一个Java特定模型,灵活性较差,性能也不是很好
DOM4J解析(主要使用)是JDOM的一种智能分支,它合并了许多超出基本XML文档表示的功能,包括集成的XPath 支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项, DOM4J是一个非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一 个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML
DOM4J解析XML代码
使用DOM4J解析XML首先需要添加一些资源(dom4j的jar文件,本节中所有的相关jar我都已经打包上传 点我下载),添加方式这里我就不详细阐明了
让我们直接开始代码部分:
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
public class MyDOM4J {
public static void main(String[] args) throws IOException, DocumentException {
FileInputStream fis = new FileInputStream("d://myXml.xml");
SAXReader sr = new SAXReader();// 创建xml读取对象
Document doc = sr.read(fis);// 读取并得到文档对象
Element root = doc.getRootElement();// 获取根节点
List<Element> elementList = root.elements();
for (int i=0; i< elementList.size(); i++){
Element person = elementList.get(i);
// 我的xml中没有写属性,故上述语句返回null
System.out.println(person.attribute("id"));
System.out.println(person.elementText("name"));
System.out.println(person.elementText("length"));
System.out.println("=============================");
}
Element person = root.element("person");// 获取root中的person内容
Element name = person.element("name");// 获取person中的name内容
System.out.println(name.getName());
fis.close();
}
}
运行之后会有很多的warning,这是因为JDK的版本过高,可能出现异常,但是目前并不影响运行和结果
注意,上面的代码仅限于获取本地资源,即已经下载好的资源,如果需要获取网络资源,需要使用不同的代码
让我们先来认识一下这个网页:
http://apis.juhe.cn/mobile/get? phone=手机号码&dtype=xml&key=9f3923e8f87f1ea50ed4ec8c39cc9253
填入你自己的手机号码,用浏览器打开,就会显示一个xml的网页
让我们尝试解析这个xml:
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
public class MyDOM4J {
public static void main(String[] args) throws IOException, DocumentException {
URL url = new URL("http://apis.juhe.cn/mobile/get? phone=18717153103&dtype=xml&key=9f3923e8f87f1ea50ed4ec8c39cc9253");
URLConnection uc = url.openConnection();
InputStream is = uc.getInputStream();
SAXReader sr = new SAXReader();
Document doc = sr.read(is);
}
}
之后的获取节点和遍历操作和本地解析一样,这里就不多写了
DOM4J还支持通过XPATH(路径表达式)解析,即可以通过路径快速的查找一个或一组元素
路径表达式主要有以下几种
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
public class MyDOM4J {
public static void main(String[] args) throws IOException, DocumentException {
FileInputStream fis = new FileInputStream("d://myXml.xml");
SAXReader sr = new SAXReader();
Document doc = sr.read(fis);
List<Node> names = doc.selectNodes("//person//name");
for (int i=0; i< names.size(); i++){
System.out.println(names.get(i).getName());
System.out.println(names.get(i).getText());
}
// 查找多个时推荐
Node n = doc.selectSingleNode("//person//name");
System.out.println(n.getName()+":"+n.getText());
// 查找单个时推荐
}
}
运行结果
name
Mike
name
Jimmy
name:Mike
Process finished with exit code 0
其中//person//name就是查找person下的name,如果想要限定属性查找,可以写成//person[@id=‘001’]//name
Java生成XML
Java生成XML主要有以下几步:
具体的代码我们就不实现了
使用XStream还可以直接将类包装为xml格式:
JSON
JSON( JavaScript Object Notation ),中文为JS对象简谱,是一种轻量级的数据交换格式,JSON的解析效率更高,占用的内存较小,目前已经基本取代了XML的大部分功能
JSON主要的作用是用于表明一个对象,在JSON中对象这样定义:
{
"name":"Mike",
"Length":"180cm"
}
// 在xml中
<person>
<name>Mike</name>
<length>180cm</length>
</person>
且在JSON中,对象中可以含有数组,数组用中括号[]包围,中间的数据用逗号隔开。JSON也支持嵌套,比如在一个对象中包含另一个对象,一个数组中包含一个对象
JSON解析
由于JSON官方没有提供解析工具,JSON解析主要使用两个工具,一个是谷歌的Gson,另一个是阿里的FastJson
Gson中主要只有两个功能
String json = new Gson().toJson(要转换的对象);
// 将对象转化为JSON字符串
类 对象 = new Gson().fromJson(JSON字符串,对指定类.class);
// 将JSON字符串转化为指定类的对象
FastJson也一样
String json=JSON.toJSONString(要转换的对象);
类 对象名=JSON.parseObject(JSON字符串, 指定类.class);
List<类型> list=JSON.parseArray(JSON字符串,类型.class);
// 额外的支持,转为List对象
在两种转化中,无论指定类是什么,数组内容都会默认被存为List类型