1 多线程
1.1 概述
多个执行线路的程序
一个程序开启就会对应一个进程,一个进程中可以有多个执行线程
1.2 进程和线程
进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
单线程:安全性高,效率低
多线程:安全性低,效率高
1.3 线程实现
1.3.1 继承 Thread 类
- 定义一个类继承Thread
- 重写run方法
- 将要执行的代码放入到run方法中
- 创建子类实例
- 调用start()
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
//创建线程实例
MyThread mt = new MyThread();
//修改线程名字
mt.setName("张三");
//启动线程
mt.start();
//创建线程实例
MyThread mt2 = new MyThread();
mt2.setName("老王");
//启动线程
mt2.start();
}
}
1.3.2 实现 Runnable 接口
- 定义一个类去实现runnable接口
- 实现run方法
- 将要执行的写到run方法中
- 创建实现类A的实例
- 创建Thread的实例,将A作为构造参数传递过来
- 调用Thread类的start方法开启线程
public class MyThread2 implements Runnable {
int num;
public MyThread2(int num) {
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//Thread t = Thread.currentThread();
//System.out.println(t.getName() + ":" + i);
//链式编程
System.out.println(Thread.currentThread().getName() + ":" + i + num);
}
}
}
1.4 多线程安全问题解决
1.4.1 同步代码块
synchronized(锁对象){
//需要同步的代码
}
1.4.2 同步方法
修饰符 synchronized 返回值 方法名(){
}
非静态同步方法的锁对象是this
静态同步方法的锁对象是当前类的字节码对象
1.5 线程调度
分时调度模型:平均分配时间片
抢占式调度模型:谁的优先级高,谁抢到的机会就更大,执行的次数就多,如果优先级相同 就随机抽取线程执行
//修改线程优先级
setPriority(int i) //必须是1 - 10 之间 默认是5
//获取线程优先级
getPriority()
1.6 线程控制
//线程休眠 static sleep(long millis)
Thread.sleep(100);// 线程休眠 long 毫秒值
//线程加入 join() 等待当前线程执行结束
m1.start();
try {
m1.join();// 等待当前线程执行结束之后 其他线程才能去抢夺CPU的执行权
} catch (InterruptedException e) {
e.printStackTrace();
}
m2.start();
m3.start();
//线程礼让 static yield() 一定程度让线程执行的更加和谐
// 线程礼让 可以在一定程度上 让线程之间的执行变得和谐
// 但是不能保证绝对的每人一次
Thread.yield();
System.out.println(getName() + ":" + i);
//后台线程 setDaemon(boolean flag) 将当前线程设置后守护线程(三国/坦克大战)
MyThread m1 = new MyThread("张飞");
MyThread m2 = new MyThread("关羽");
Thread.currentThread().setName("刘备");
for (int i = 0; i < 20; i++) {
// static Thread currentThread()
// 返回对当前正在执行的线程对象的引用。
System.out.println(Thread.currentThread().getName() + ":" + i);
}
// 守护线程 主线程结束之后 守护线程也会随之结束
// 但是执行的速度很快 所以有一定的延迟性
m1.setDaemon(true);
m2.setDaemon(true);
m1.start();
m2.start();
//获取当前正在执行的线程对象的引用
static CurrentThread() -> Thread
//获取线程名称
getName() -> String
//设置线程名称
setName(String name)
1.5 匿名内部类用于多线程
new 接口名/类名(){
重写方法
}
//本质是创建了继承该父类或者实现该父接口的子类/子实现类的对象
//继承Thread类
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}.start();
//实现Runnable接口
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
};
new Thread(r).start();
1.6
跟线程安全相关的类
StringBuilder StringBuffer
ArrayList Vector
HashMap Hashtable
Collections.synchronizedList(List list)
Collections.synchronizedMap(Map map)
5.关于异常的注意事项
如果父类方法有异常,子类在重写这个方法的时候,不能比父类的这个方法抛出的异常大
如果父类的方法没有异常,子类在重写这个方法的时候,就不能有异常;
6.同步方法
如果是非静态的话,同步锁是this
如果是静态方法的话,是该类的字节码对象;
7.线程的生命周期
创建 ----> 就绪 -----> 运行-----> 死亡
| |
| |
等待(调用sleep方法)
8.线程间的通信
注意重点: 线程间的通信是通过同步锁来通信,注意需要通信的线程的锁必须一样吧
wait();//使当前线程处于等待状态, 这个只能被notify()或者notifyAll()唤醒
notify();//唤醒这把锁上除本线程以外的其他任意一条线程;
9 . final的最后一个作用:延长数据的生命周期,把数据存储到常量池中,JDK1.8后,自动加final
内部类访问局部变量的时候,局部变量需要被final修饰,延长变量的生命周期.
2 网络编程
目的:为了实现网络上设备之间的信息传递和交互
2.1 网络编程三要素
2.1.1 ip地址
ip地址是计算机在网络中的唯一标识
ipv4:点分十进制 192.168.16.28
ipv6:冒分十六进制
两个常用命令:ipconfig / ping
回环地址: 127.0.0.1 传给自己
广播地址: x.x.x.255
2.1.2 端口号
应用程序再计算机中的唯一标识
范围 0 - 65535 0 - 1024 保留端口号,建议使用一万以上
2.1.3 协议
ip http udp tcp ftp
UDP协议:
- 不需要建立连接
. 速度快
. 不保证接收端一定接收到
. 容量小,一次只能发送64k
TCP协议:
- 需要建立连接
. 速度稍慢
. 可以保证接收端一定可以接收到
. 发送数据容量大小不受限制
2.2 InetAddress 类
static getByName(String name) //可以给主机名/ip地址
String getHostAddress() //返回 IP 地址字符串(以文本表现形式)。
String getHostName() //获取此 IP 地址的主机名。
2.3 使用UDP发送/接收数据
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端socket对象
DatagramSocket ds = new DatagramSocket();
//创建数据并打包
byte[] bys = "hello udp!".getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getLocalHost(), 8888);
//发送数据
ds.send(dp);
//释放资源
ds.close();
}
}
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端socket对象
DatagramSocket ds = new DatagramSocket(8888);
//接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
ds.receive(dp);
//解析数据并输出
byte[] data = dp.getData();
System.out.println(new String(data, 0, dp.getLength()));
//释放资源
ds.close();
}
}
2.4 使用TCP发送/接收数据
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端socket对象
Socket s = new Socket("CHI",9999);
//获取输出流对象
OutputStream os = s.getOutputStream();
发送数据
byte[] bys = "hello tcp!!".getBytes();
os.write(bys);
//释放资源
s.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务端socket对象
ServerSocket ss = new ServerSocket(9999);
//监听(阻塞)
Socket s = ss.accept();
//获取输入流对象
InputStream is = s.getInputStream();
//获取数据
byte[] bys = new byte[1024];
int len;
len = is.read(bys);
//输出数据
System.out.println(new String(bys, 0, len));
//释放资源
s.close();
}
}
2.5 使用TCP进行数据双向传输
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端socket对象
Socket s = new Socket("CHI",9999);
//获取输入流对象,键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入用户名:");
String username = br.readLine();
System.out.println("请输入密码:");
String password = br.readLine();
//获取输出流对象
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
pw.println(username);
pw.println(password);
//获取输入流对象
BufferedReader br2 = new BufferedReader(new InputStreamReader(s.getInputStream()));
System.out.println(br2.readLine());
//释放资源
s.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务端socket对象
ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept();
//获取输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String username = br.readLine();
String password = br.readLine();
//获取输出流对象
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
//判断
if("chipeize".equals(username) && "123456".equals(password)) {
pw.println("验证成功");
}else {
pw.println("验证失败");
}
//释放资源
s.close();
}
}
类加载
类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
-
加载
就是指将class文件读入内存,并为之创建一个Class对象。 任何类被使用时系统都会建立一个Class对象。
-
连接
**验证** 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值static
解析 将类的二进制数据中的符号引用替换为直接引用
-
初始化
就是我们以前讲过的初始化步骤
类初始化时机
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
类加载器
负责将.class文件加载到内存中,并为之生成对应的Class对象
组成
- Bootstrap ClassLoader 根类加载器: 负责Java核心类的加载
- Extension ClassLoader 扩展类加载器:负责JRE的扩展目录中jar包的加载
- System ClassLoader 系统类加载器:在JVM启动时加载来自java命令的class文件
序列化和反序列化
序列化
创建出的对象是存放在内存中 需要把对象持久化到硬盘上 也可以在网络上进行传输 用到java提供的序列化流
从内存到硬盘、在网络中传输
ObjectOutputStream
public static void write() throws IOException {
// TODO Auto-generated method stub
Person p = new Person(1, 23);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C://person.txt"));
oos.writeObject(p);
oos.close();
}
反序列化
硬盘文件中还原回内存、在网络另一端接收到之后还原
ObjectInputStream
public static void read() throws Exception {
// TODO Auto-generated method stub
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C://person.txt"));
Object object = ois.readObject();
if (object instanceof Person) {
Person p = (Person) object;
// System.out.println(p);//
p.m1();
}
ois.close();
}
序列化出现的问题:
在我们修改了类中的成员变量后,发现无法正常反序列化。
问题在于类文件的标记值和曾经写出的标记值不同,将来读取的时候,程序会对比他俩的标记值是否相同,标记值相同才允许反序列化(常量)
解决方案:
//启用Person类的序列化功能 implements Serializable
public class Person implements Serializable {
// 自动生成序列化id值
private static final long serialVersionUID = 2097882983863448016L
//如果不想让某个字段属性参与序列化操作 可以使用一个关键字transient修饰
private Integer name;
// public String name;
private transient int age;
}
}
3 反射
3.1 概述
在运行时,我们可以获取任意一个类的所有方法和属性
在运行时,让我们调用任意一个对象的所有方法和属性
反射的前提:
要获取类的字节码对象(Class对象)
3.2 获取字节码文件
// 此方法来自于Object 对象已经存在的情况下, 可以使用这种方式
对象名.getClass();
// 类名.class这是一个静态的属性, 只要知道类名, 就可以获取
类名.class
// 通过Class类中的静态方法, 指定字符串, 该字符串是类的全类名(包名+类名)/全限定路径名
Class.forName(“com.itheima_01.Student”);
// 此处将会抛出异常都系 ClassNotFoundException 防止传入错误的类名
3.3 获取构造方法
Constructor<?>[] getConstructors() //获取所有的构造方法(public修饰的)
Constructor<?>[] getDeclaredConstructors() //获取所有的构造方法
Constructor<T> getConstructor() //获取无参构造方法-
Constructor<T> getConstructor(Class<?>... parameterTypes) //获取有参构造
Constructor c = clazz.getConstructor(String.class,int.class);
Constructor:
T newInstance(Object... initargs) //创建对象
Object obj = clazz.newInstance(); //通过字节码对象创建对象(只能无参)
public class demo2 {
public static void main(String[] args) throws ReflectiveOperationException {
Class clazz = Class.forName("com.cpz.Student");
//获取全部public修饰的构造方法
Constructor[] arr = clazz.getConstructors();
for (Constructor c : arr) {
System.out.println(c);
}
System.out.println("-------------");
//获取无参构造
Constructor c = clazz.getConstructor();
System.out.println(c);
//创建对象(无参)
Object obj = c.newInstance();
System.out.println(obj);
System.out.println("-------------");
//获取有参构造
Constructor cc = clazz.getConstructor(String.class, int.class);
System.out.println(cc);
//创建对象(有参)
Object obj2 = cc.newInstance("zhangsan", 18);
System.out.println(obj2);
System.out.println("-------------");
//使用字节码文件直接创建对象(无参构造)
Object obj3 = clazz.newInstance();
System.out.println(obj3);
}
}
3.4 获取成员变量
Field[] getFields() --> 返回该类所有(公共)的字段
Field getField(String name) --> 返回指定名称字段
Field[] getDeclaredFields() --> 暴力反射获取所有字段(包括私有)
Field getDeclaredField(String name) --> 暴力反射获取指定名称字段
Field:
Object get(Object obj) --> Field对象调用, 返回传入对象的具体字段
void set(Object obj, Object value) --> Field对象调用
//参数1: 要修改的对象
//参数2: 将此对象的字段修改为什么值.
Class<?> getType() : 返回此对象所表示字段的声明类型的Class对象
public class demo3 {
public static void main(String[] args) throws ReflectiveOperationException {
//获取字节码对象
Class clazz = Class.forName("com.itheima_01.Student");
//根据字节码对象创建对象
Object stu = clazz.newInstance();
// 获取所有public修饰的成员变量
Field[] fs = clazz.getFields();
for (int i = 0; i < fs.length; i++) {
System.out.println(fs[i]);
}
// 暴力获取所有的成员变量
Field[] fs2 = clazz.getDeclaredFields();
for (int i = 0; i < fs2.length; i++) {
System.out.println(fs2[i]);
}
// 根据字段名称获取公共的字段对象
Field f = clazz.getField("age");
System.out.println(f);
// 根据字段名称暴力获取所有字段对象
Field f2 = clazz.getDeclaredField("name");
System.out.println(f2);
System.out.println(stu);
// 通过成员变量对象,修改指定对象为指定的值
f.set(stu, 28);
System.out.println(stu);
// 通过对象获取成员变量的值
Object age = f.get(stu);
}
}
3.5 获取私有变量
public class demo4 {
public static void main(String[] args) throws ReflectiveOperationException {
//获取字节码对象
Class clazz = Class.forName("com.cpz.Student");
//获取学生对象
Object stu = clazz.newInstance();
//获取私有的成员变量
Field f = clazz.getDeclaredField("name");
//设置反射时取消Java的访问检查,暴力访问(让JVM不检查权限)
f.setAccessible(true);
f.set(stu, "lisi");
Object name = f.get(stu);
System.out.println(name);
}
}
3.6 获取成员方法
Method getMethod(String name, Class<?>... parameterTypes) //获取成员方法
// 参数1: 要反射的方法名称
// 参数2: 此方法需要接受的参数类型(注意,传入的都是字节码)
Method:
Object invoke(Object obj, Object... args)
// 参数1: 要由那个对象调用方法
// 参数2: 方法需要的具体实参(实际参数)
public class demo5 {
public static void main(String[] args) throws ReflectiveOperationException {
//获取字节码对象
Class clazz = Class.forName("com.cpz.Student");
//获取学生对象
Object stu = clazz.newInstance();
//无参无返回值
Method m1 = clazz.getMethod("method");
m1.invoke(stu);
//有参无返回值
Method m2 = clazz.getMethod("setName", String.class);
m2.invoke(stu, "lisi");
//无参有返回值
Method m3 = clazz.getMethod("getName");
Object name = m3.invoke(stu);
System.out.println(name);
}
}
//私有成员方法
Class clazz = Class.forName("com.cpz.Student");
Object stu = clazz.newInstance();
Method m = clazz.getDeclaredMethod("method");
m.setAccessible(true);
m.invoke(stu);
4 JavaBean、BeanUtils
4.1 JavaBean
将需要操作的多个属性封装成JavaBean,简单来说就是用于封装数据的
规范:
- 类使用公共进行修饰
. 提供私有修饰的成员变量
. 为成员变量提供公共的 getter 和 setter 方法
. 提供公共无参的构造
. 实现序列号接口
public class Person implements Serializable{
private static final long serialVersionUID = 1049712678750452511L;
private String name;
private int age;
private String gender;
public Person() {
super();
// TODO Auto-generated constructor stub
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]";
}
}
4.2 BeanUtils
Apache commons 提供的一个组件,主要功能就是为了简化 JavaBean 封装数据的操作
static void setProperty(Object bean, String name, Object value)
setProperty 用来给对象中的属性赋值(了解)
参数1: 需要设置属性的对象
参数2: 需要修改的属性名称
参数3: 需要修改的具体元素
static String getProperty(Object bean, String name)
getProperty 用来获取对象中的属性(了解)
参数1: 要获取的javaBean对象
参数2: 对象中的哪个属性
static void populate(Object bean, Map properties)
Populate 用来给对象中的属性赋值(掌握)
参数1: 要设置属性的对象
参数2: 将属性以Map集合的形式传入
Key : 属性的名称
Value: 属性具体的值
public class BeanUtilsDemo {
public static void main(String[] args) throws ReflectiveOperationException {
Person p = new Person();
//给对象中的属性赋值
BeanUtils.setProperty(p, "name", "zhangsan");
BeanUtils.setProperty(p, "age", 18);
BeanUtils.setProperty(p, "gender", "male");
System.out.println(p);
//获取对象中的属性
String name = BeanUtils.getProperty(p, "name");
String age = BeanUtils.getProperty(p, "age");
String gender = BeanUtils.getProperty(p, "gender");
System.out.println(name + "," + age + "," + gender);
//给对象中的属性赋值
Person p2 = new Person();
HashMap<String, Object> hm = new HashMap<>();
hm.put("name", "lisi");
hm.put("age", 20);
hm.put("gender", "female");
BeanUtils.populate(p2, hm);
System.out.println(p2);
}
}
注意: BeanUtils 的 setProperty 和 getProperty 方法底层并不是直接操作成员变量,而是操作和成员变量名有关的 get 和 set 方法。三个方法底层都是通过反射实现,而且操作的是 set 和 get 方法,所以编写 JavaBean 的时候一定要注意格式。
4.3 自定义BeanUtils
public class myBeanUtils {
private myBeanUtils() {
}
public static void setProperty(Object bean, String name, Object value) throws ReflectiveOperationException {
Class clazz = bean.getClass();
Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
f.set(bean, value);
}
public static String getProperty(Object bean, String name) throws ReflectiveOperationException {
Class clazz = bean.getClass();
Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
Object obj = f.get(bean);
return obj.toString();
}
public static void populate(Object bean, Map map) throws ReflectiveOperationException {
Class clazz = bean.getClass();
//获取所有键
Set keys = map.keySet();
//遍历
for (Object key : keys) {
//使用try..catch语句来捕获可能发生的异常:NoSuchFieldException
try {
Object value = map.get(key);
//根据key获取对应的Field对象
Field f = clazz.getDeclaredField(key.toString());
f.setAccessible(true);
f.set(bean, value);
} catch (NoSuchFieldException e) {
//e.printStackTrace();
}
}
}
}
public class myBeanUtilsDemo {
public static void main(String[] args) throws ReflectiveOperationException {
Person p = new Person();
myBeanUtils.setProperty(p, "name", "zhangsan");
myBeanUtils.setProperty(p, "age", 18);
myBeanUtils.setProperty(p, "gender", "male");
System.out.println(p);
String name = myBeanUtils.getProperty(p, "name");
String age = myBeanUtils.getProperty(p, "age");
String gender = myBeanUtils.getProperty(p, "gender");
System.out.println(name + "," + age + "," + gender);
HashMap<String, Object> hm = new HashMap<>();
hm.put("name", "lisi");
hm.put("age", 20);
hm.put("gender", "female");
hm.put("brithday", "2019年6月21日");
myBeanUtils.populate(p, hm);
System.out.println(p);
}
}
5 XML
5.1 概念
XML:eXtensible Markup Language:可扩展标记型语言,主要用于存储数据
xm l语言是具有结构性的标记语言, 可以灵活的存储一对多的数据关系.
文档声明
<?xml version = '1.0' encoding = 'UTF-8' standalone = 'yes' ?>
version 版本号
encoding 文档所使用的字符编码(如果XML文件中有中文出现,那么编码必须包含中文)
standalone 文档是否独立(是否依赖其他文档)
创建xml文件时会自动生成文档声明(默认UTF-8)
元素(标签):开始标签、结束标签
包含标签体:
<age>18</age>
<Student><age>18</age></Student>
不含标签体:
<Student name='zhangsan' age='18'/>
一个标签中可以嵌套若干子标签,但所有标签必须合理的嵌套,不允许有交叉嵌套
一个XML文档必须有且仅有一个根标签,其他标签都是这个根标签的子标签或孙标签
元素(标签)的名称可以包含字母、数字、减号、下划线和英文句点
严格区分大小写、只能以字母或者下划线开头、不能以xml(或XML、Xml等)和数字开头
名称字符之间不能有空格或制表符、名称字符之间不能使用冒号
属性:一个元素可以有多个属性,每个属性都有自己的名称和取值
属性值一定要用引号引起来
属性名称的命名规范与元素的命名规范相同
元素中的属性是不允许重复的
在XML技术中,标签属性所代表的信息也可以被改成用子元素的形式来描述
注释:
<!--这是注释-->
XML声明之前不能有注释
注释不能嵌套
CDATA区:
CDATA是Character Data 的缩写
把标签当作普通文本内容
<![CDATA[
<cpz>www.cpz.com</cpz>
]]>
特殊字符:
特殊字符 | 替代符号(后边加;) |
---|---|
& | & |
< | < |
> | > |
" | " |
’ | &apos |
空格 |   |
<!--itheima也变成了普通文本-->
<url>
<cpz>www.cpz.com</cpz>
</url>
数据多用CDATA区,数据少可以用替代字符
处理指令:
必须以"<?"开头,以"?>"结尾
<!--XML声明-->
<?xml version='1.0' encoding='UTF-8' ?>
<!--xml-stylesheet指令,引入一个CSS文件-->
<?xml-stylesheet type="text/css" href="some.css" ?>
约束
XML技术中,可以编写一个文档来约束一个XML的书写规范,这个文档称之为约束
格式良好的XML:遵循XML语法的XML
有效的XML:遵循约束文档的XML
DTD(Document Type Definition):文档类型定义
作用:约束XML的书写规范
XML文件中引入约束(本地):
<!--书架:根标签 book.dtd:约束路径-->
<!DOCTYPE 书架 SYSTEM "book.dtd" >
DTD约束文档可以在XML文档中直接定义,也可以作为单独的文档进行编写(单独的文档必须以UTF-8编码进行保存)
当引用的DTD文档在公共网络上时
<!DOCTYPE 根元素 PUBLIC "DTD名称" "DTD文档的URL">
在DTD文档中使用ELEMENT关键字来声明一个XML元素
<!ELEMENT 元素名称 使用规则>
<!--使用规则
(#PCDATA):指示元素的主题内容只能是普通的文本
EMPTY:用于指示元素的主体为空,比如<br/>
ANY:用于指示元素的主题内容为任意类型
(子元素):指示元素中包含的子元素
如果子元素用逗号分开,说明必须按照声明顺序去编写XML文档
如果子元素用"|"分开,说明任选其一
用+、*、?来表示元素出现的次数
没有表示必须且只能出现一次
+:表示至少出现一次、一次或多次
*:表示可有可无,零次、一次或多次
?:表示可有可无,有的话只能有一次,零次或一次
-->
在DTD文档中使用ATTLIST关键字来为一个元素声明属性
<!ATTLIST 元素名
属性名1 属性值类型 设置说明
属性名2 属性值类型 设置说明
...
>
<!--
属性值类型:
CDATA:表示属性的取值为普通的文本字符串
ENUMERATED(DTD没有此关键字):表示枚举,只能从枚举列表中任选其一
ID:表示属性的取值不能重复
设置说明:
#REQUIRED:表示该属性必须出现
#IMPLIED:表示该属性可有可无
#FIXED:表示属性的取值为一个固定值 语法:#FIXED"固定值"
直接值:表示属性的取值为该默认值
-->
XML Schema
XML Schema 也是一种用于定义和描述XML文档结构与内容的末世预言,其出现是为了克服DTD的局限性
XML Schema 文件自身就是一个XML文件,但他的扩展名通常为.xsd
XML Schema 文档通常称之为模式文档(约束文档),遵循这个文档书写的xml文件称之为实例文档
和XML文件一样,一个XML Schema文档也必须有一个根结点,但这个根结点的名称为schema
约束文档:
<?xml version='1.0' encoding='UTF-8' ?>
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
//标准的名称空间
targetNamespace='http://www.itheima.com'
//将该schema文档绑定到http://www.itheima.com名称空间
>
<xs:element name='书架' >
<xs:complexType> //复杂类型,有子标签
<xs:sequence maxOccurs='unbounded' >//子标签数量无上限,sequence:必须按照次序
<xs:element name='书' >
<xs:complexType>
<xs:sequence>
<xs:element name='书名' type='xs:string' />
<xs:element name='作者' type='xs:string' />
<xs:element name='售价' type='xs:string' />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
引入XML Schema约束文档
<itheima:书架
xmlns:itheima="http://www.itheima.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itheima.com book.xsd">
</itheima:书架>
XML的解析方式
DOM(Document Object Model):文档对象模型
SAX(Simple API for XML)
XML解析开发包
**JAXP:**是SUN公司推出的解析标准实现。
**Dom4J:**是开源组织推出的解析开发包。(牛,大家都在用,包括SUN公司的一些技术的实现都在用。)
DOM: 将整棵树一口气全部加载到内存当中, 我们可以非常方便的操作任意的标签和属性.
但是, 如果整棵树特别大的时候, 会出现内存溢出的问题
节点: 标签、属性、文本、甚至是换行都称之为节点
SAX: 一个节点一个节点的进行解析(暂不掌握)
Dom4J
常用方法:
Document:
Element getRootElement() :获取根元素对象(根标签)
Element:
List elements() :获取所有的子元素
List elements(String name):根据指定的元素名称来获取相应的所有的子元素
Element element(String name):根据指定的元素名称来获取子元素对象,如果元素名称重复,则获取第一个元素
String elementText(String name) :根据指定的子元素名称,来获取子元素中的文本
String getText() :获取当前元素对象的文本
void setText(String text):设置当前元素对象的文本
Element getParent():获取父元素
void remove(Element element):删除元素
Element addElement(String name):添加元素
String attributeValue(String name):根据指定的属性名称获取其对应的值
public Element addAttribute(String name,String value):根据指定的属性名称和值进行添加或者修改
DocumentHelper:
static Element createElement(String name):创建一个新的与元素对象
public class Dom4JUtils {
private Dom4JUtils() {}
//获取Document对象
public static Document getDocument() throws Exception {
SAXReader reader = new SAXReader();
Document document = reader.read("src/com/itheima_04/city.xml");
return document;
}
//将数据写回XML文件中
//to
public static void write2XML(Document document) throws IOException {
OutputFormat format = OutputFormat.createPrettyPrint();
//format.setEncoding("UTF-8");//默认的编码就是UTF-8
XMLWriter writer = new XMLWriter(new FileOutputStream("src/com/itheima_04/city.xml"), format);
writer.write( document );
}
}
// 得到某个具体的节点内容:打印"郑州"
// 遍历所有元素节点:打印他们的元素名称
// 修改某个元素节点的主体内容:信阳-->安阳
// 向指定元素节点中增加子元素节点:添加一个新城市<City>南阳</City>
// x向指定元素节点上增加同级元素节点:在洛阳前面添加一个<City>三门峡</City>
// 删除指定元素节点:删除元素开封
// 操作XML文件属性:打印State的Name
// 添加属性:State: GDP="99999亿"
<?xml version="1.0" encoding="UTF-8"?>
<State Code="37" Name="河南" description="郑州">
<City>
<Name>郑州</Name>
<Region>高薪区</Region>
</City>
<City>开封</City>
<City>洛阳</City>
<City>信阳</City>
</State>