Java基础增强(多线程、网络编程、类加载机制、反射、JavaBean、XML、Dom4J)

本文深入探讨了多线程编程的基本概念和技术,包括线程的生命周期、线程安全问题及解决方案、线程调度和控制等内容。同时,文章还详细介绍了网络编程的基础,包括网络编程的三要素、UDP和TCP协议的区别、以及如何使用Java进行网络通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 多线程

1.1 概述

​ 多个执行线路的程序

​ 一个程序开启就会对应一个进程,一个进程中可以有多个执行线程

1.2 进程和线程

​ 进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

​ 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

​ 单线程:安全性高,效率低

​ 多线程:安全性低,效率高

1.3 线程实现

1.3.1 继承 Thread 类

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 将要执行的代码放入到run方法中
  4. 创建子类实例
  5. 调用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 接口

  1. 定义一个类去实现runnable接口
  2. 实现run方法
  3. 将要执行的写到run方法中
  4. 创建实现类A的实例
  5. 创建Thread的实例,将A作为构造参数传递过来
  6. 调用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协议:

  1. ​ 不需要建立连接
    . ​ 速度快
    . ​ 不保证接收端一定接收到
    . ​ 容量小,一次只能发送64k

TCP协议:

  1. ​ 需要建立连接
    . ​ 速度稍慢
    . ​ 可以保证接收端一定可以接收到
    . ​ 发送数据容量大小不受限制

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,简单来说就是用于封装数据的

规范:

  1. ​ 类使用公共进行修饰
    . ​ 提供私有修饰的成员变量
    . 为成员变量提供公共的 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>
]]>

特殊字符:

特殊字符替代符号(后边加;)
&&amp
<&lt
>&gt
"&quot
&apos
空格&nbsp
<!--itheima也变成了普通文本-->
<url>
	&lt;cpz&gt;www.cpz.com&lt;/cpz&gt;
</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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值