一、多线程
1.1、程序、线程与进程
(1)程序、线程与进程的认识
- 程序:是为完成特定的任务、用某种语言编写的一组指令的集合,即指一段静态(没有运行)的代码,静态的对象;
- 线程:是进程中的单个顺序控制流,是一条执行路径;单线程:一个进程如果只有一条执行路径,则称为单线程程序;多线程:一个进程如果有多条执行路径,则称为多线程程序
进程:是正在运行的程序;是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源(独立性);进程是的实质是程序的一次执行过程,进程是动态产生,动态消亡(动态性);任何进程都可以同其他进程一起并发执行(并发性)。
(2)多线程的认识
多线程的特性
重写run方法 。Thread的子类对象数为线程数
线程的优先级
- 线程调度:两种调度方式
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
- Java使用的是抢占式调度模型
- 随机性:假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
| 方法名 | 功能说明 |
| —- | —- |
| public void setName
(); | 设置线程名字的方法 | | public void getName(); | 获取线程名字的方法 | | public static void sleep(long t) | 让线程休眠指定的时间,单位为毫秒 | | public final void setPriority(int newPriority) | 设置线程的优先级
(默认的优先级值为5 设置线程优先级的方法的参数取值范围为:1~10) | | public final int getPriority(); | 获取线程的优先级- | | void start() | 使此线程开始执行,Java虚拟机会调用run方法() | | void join() | 等待这个线程死亡 | | void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 | | static Thread currentThread() | 返回当前线程对象 | | void yield() | 释放 当前的cpu执行权 |
- 线程调度:两种调度方式
守护线程
- 理解:普通线程好比是国家,守护线程就是军队,一个国家灭亡了,那军队也没有存在的意义了,换句话说就,当普通线程结束之后 ,守护线程它不会全部执行完,也会随普通的线程一起结束,但不会立刻结束。
- 实现步骤
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
- 代码演示
```java
/
创建线程的方式一:继承Thread类
/
public class MyThread extends Thread {
@Override
public void run() {
} }for(int i=0; i<100; i++) {
System.out.println(i);
}
/ 通过创建自定义类的对象的方式创建线程 几个对象就有几个线程 / public class MyThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread();
// my1.run(); // my2.run();
//void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
my1.start();
my2.start();
}
}
/ 给线程取名的案例 / public class MyThread extends Thread { public MyThread() {} public MyThread(String name) { super(name); }
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
} public class MyThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread();
//void setName(String name):将此线程的名称更改为等于参数 name
my1.setName("高铁");
my2.setName("飞机");
//Thread(String name)
MyThread my1 = new MyThread("高铁");
MyThread my2 = new MyThread("飞机");
my1.start();
my2.start();
//static Thread currentThread() 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());
}
}
/ 线程优先级的设置 / public class ThreadPriority extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + “:” + i); } } } public class ThreadPriorityDemo { public static void main(String[] args) { ThreadPriority tp1 = new ThreadPriority(); ThreadPriority tp2 = new ThreadPriority(); ThreadPriority tp3 = new ThreadPriority();
tp1.setName("高铁");
tp2.setName("飞机");
tp3.setName("汽车");
//public final int getPriority():返回此线程的优先级
System.out.println(tp1.getPriority()); //5
System.out.println(tp2.getPriority()); //5
System.out.println(tp3.getPriority()); //5
//public final void setPriority(int newPriority):更改此线程的优先级
// tp1.setPriority(10000); //IllegalArgumentException System.out.println(Thread.MAX_PRIORITY); //10 System.out.println(Thread.MIN_PRIORITY); //1 System.out.println(Thread.NORM_PRIORITY); //5
//设置正确的优先级
tp1.setPriority(5);
tp2.setPriority(10);
tp3.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
/ 线程控制的案例 / sleep演示: public class ThreadSleep extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + “:” + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadSleepDemo { public static void main(String[] args) { ThreadSleep ts1 = new ThreadSleep(); ThreadSleep ts2 = new ThreadSleep(); ThreadSleep ts3 = new ThreadSleep();
ts1.setName("曹操");
ts2.setName("刘备");
ts3.setName("孙权");
ts1.start();
ts2.start();
ts3.start();
}
}
Join演示: public class ThreadJoin extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + “:” + i); } } } public class ThreadJoinDemo { public static void main(String[] args) { ThreadJoin tj1 = new ThreadJoin(); ThreadJoin tj2 = new ThreadJoin(); ThreadJoin tj3 = new ThreadJoin();
tj1.setName("康熙");
tj2.setName("四阿哥");
tj3.setName("八阿哥");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}
Daemon演示: public class ThreadDaemon extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + “:” + i); } } } public class ThreadDaemonDemo { public static void main(String[] args) { ThreadDaemon td1 = new ThreadDaemon(); ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
//设置主线程为刘备
Thread.currentThread().setName("刘备");
//设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
for(int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
- 两个小问题
- 为什么要重写run()方法?因为run()是用来封装被线程执行的代码
- run()方法和start()方法的区别?
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():启动线程;然后由JVM调用此线程的run()方法
<a name="01ac8412657fc3b6de9709109ca83b98"></a>
### (2)自定义类实现Runnable接口
- 重写run方法,自定义类的对象是作为Thread线程类的参数进行操作,Thread对象数作为线程数。
- Thread构造方法
| 方法名 | 功能说明 |
| --- | --- |
| Thread(Runnable target) | 分配一个新的Thread对象 |
| Thread(Runnable target, String name) | 分配一个新的Thread对象 |
- 实现步骤
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
- 代码演示
```java
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
//Thread(Runnable target, String name)
Thread t1 = new Thread(my,"高铁");
Thread t2 = new Thread(my,"飞机");
//启动线程
t1.start();
t2.start();
}
}
(3)自定义类实现 Callable和 Future接口和两种线程实现方法的对比
- 重写call()方法,有返回值,自定义类对象作为Future的实现类对象FutureTask对象的参数,而FutureTask对象作为Thread对象的参数; 获取重写call()方法的返回值的方法——-public
get(); - 多线程的实现方案有两种
- 继承Thread类
- 实现Runnable接口
- 相比继承Thread类,实现Runnable接口的好处
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
(4)线程安全
线程安全问题之买票案例
- 案例需求
- 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
- 实现步骤
- 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
- 在SellTicket类中重写run()方法实现卖票,代码步骤如下
- 判断票数大于0,就卖票,并告知是哪个窗口卖的
- 卖了票之后,总票数要减1
- 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
- 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
- 创建SellTicket类的对象
- 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
- 启动线程
代码实现
public class SellTicket implements Runnable {
private int tickets = 100;
//在SellTicket类中重写run()方法实现卖票,代码步骤如下
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//创建SellTicket类的对象
SellTicket st = new SellTicket();
//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
- 案例需求
执行结果
卖票案例中出现的问题
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
- 如何解决多线程安全问题呢?
- 基本思想:让程序没有安全问题的环境
- 怎么实现呢?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- Java提供了同步代码块的方式来解决
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁,只允许一个对象(或线程)进入该同步代码块中
- 同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
代码演示 ```java public class SellTicket implements Runnable { private int tickets = 100; private Object obj = new Object();
@Override public void run() {
while (true) {
//tickets = 100;
//t1,t2,t3
//假设t1抢到了CPU的执行权
//假设t2抢到了CPU的执行权
synchronized (obj) {
//t1进来后,就会把这段代码给锁起来
if (tickets > 0) {
try {
Thread.sleep(100);
//t1休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--; //tickets = 99;
}
}
//t1出来了,这段代码的锁就被释放了
}
} }
public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
<a name="6a1fb9a3c1d40a3e99637e58b281fb60"></a>
### (6)同步方法解决线程安全问题
- 同步方法:就是把synchronized关键字加到方法上
- 同步方法的格式
```java
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
- 同步方法的锁对象是什么呢? 答:this
静态同步方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步静态方法的锁对象是什么呢? 答: 类名.class
代码演示 ```java public class SellTicket implements Runnable { private static int tickets = 100; private int x = 0;
@Override public void run() {
while (true) {
sellTicket();
}
} // 同步方法 // private synchronized void sellTicket() { // if (tickets > 0) { // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // System.out.println(Thread.currentThread().getName() + “正在出售第” + tickets + “张票”); // tickets—; // } // }
// 静态同步方法 private static synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + “正在出售第” + tickets + “张票”); tickets—; } } }
public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
<a name="39e26f3db489813798a64f3d6bcf068b"></a>
### (7)锁对象
- 锁对象的概述:因为Lock是接口,不能被实例化,所以采用实现类ReentrantLock来实例化
- ReentrantLock构造方法
| 方法名 | 功能说明 |
| --- | --- |
| ReentrantLock() | 创建一个ReentrantLock的实例 |
- 加锁解锁方法
| 方法名 | 功能说明 |
| --- | --- |
| void lock() | 获得锁(上锁) |
| void unlock() | 释放锁(解锁) |
| Boolean tryLock(); | 判断锁是否时开的,是开的就返回true ,不是就返回false(该方法可以解决死锁的问题) |
![死锁的问题.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613123838973-2bbcaf00-b594-4da5-83e2-b90a78d37e5b.png#height=842&id=iZK8z&margin=%5Bobject%20Object%5D&name=%E6%AD%BB%E9%94%81%E7%9A%84%E9%97%AE%E9%A2%98.png&originHeight=842&originWidth=1172&originalType=binary&size=450197&status=done&style=none&width=1172)
- 代码演示
```java
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} finally {
lock.unlock();
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
(8)线程安全的类
- StringBuffer
- 线程安全,可变的字符序列
- 从版本JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步
- Vector
- 从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector
Hashtable
生产者与消费者案例的概述
- 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。所谓生产者消费者问题,实际上主要是包含了两类线程:
- 一类是生产者线程用于生产数据
- 生产者的步骤:1、判断共享数据区域中有没有数据,如果有就等待,如果没有就生产数据;2、把数据放到共享数据区域;3、叫醒等待的消费者。
- 一类是消费者线程用于消费数据
- 消费者执行的步骤:1、判断共享数据区域(桌子)有没有数据;2、如果没有就等待;3、如果共享数据区域中有数据,就执行操控共享数的据;4、执行完共享的数据后,共享数据减一,叫醒等待的生产者
- 一类是生产者线程用于生产数据
- 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为,消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
- 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。所谓生产者消费者问题,实际上主要是包含了两类线程:
Object类的等待和唤醒方法 | 方法名 | 功能说明 | | —- | —- | | void wait() | 导致当前线程等待(无限制等待),直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 | | void notify() | 唤醒正在等待对象监视器的单个线程 | | void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
实现步骤:
- 生产者消费者案例中包含的类
- 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
- 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
- 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
- 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
- ①创建奶箱对象,这是共享数据区域
- 创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
- 对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
- ④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
- ⑤启动线程
- 生产者消费者案例中包含的类
代码实现
版本一
/*
盒子类(共享数据区域)
*/
public class Box {
//定义一个成员变量,表示第x瓶奶
private int milk;
//定义一个成员变量,表示奶箱的状态
private boolean state = false;
//提供存储牛奶和获取牛奶的操作
public synchronized void put(int milk) {
//如果有牛奶,等待消费
if(state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有牛奶,就生产牛奶
this.milk = milk;
System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
//生产完毕之后,修改奶箱状态
state = true;
//唤醒其他等待的线程
notifyAll();
}
public synchronized void get() {
//如果没有牛奶,等待生产者生产(消费者等待)
if(!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有牛奶,就消费牛奶
System.out.println("用户拿到第" + this.milk + "瓶奶");
//消费完毕之后,修改奶箱状态
state = false;
//唤醒其他等待的线程
notifyAll();
}
}
/*
生产者线程类
*/
public class Producer implements Runnable {
private Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for(int i=1; i<=30; i++) {
b.put(i);
}
}
}
/*
消费者线程类
*/
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
/*
测试类
*/
public class BoxDemo {
public static void main(String[] args) {
//创建奶箱对象,这是共享数据区域
Box b = new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer p = new Producer(b);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c = new Customer(b);
//创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//启动线程
t1.start();
t2.start();
}
}
版本二 ```java / 共享数据区域类的核心代码 / //定义一个标记;true表示桌子上有汉堡,此时允许吃货线程执行,false表示桌子上没有汉堡,此时允许厨师线程执行 private boolean flag; //汉堡的总数量 private int count; //锁对象 private final Object lock = new Object();
/ 厨师类(吃货类与厨师大同小异) / //成员变量Desk private Desk desk; //构造方法 public Cooker(Desk desk){ this.desk = desk; } //重写的run方法 public void run(){ while(true){ synchronized(){ if(desk.getCount==0){ break; }else{ if(!desk.isFlag){ //生产 System.out.println(“厨师正在生产汉堡包”); desk.isFlag=false; desk.getLock.notifyAll(); desk.getCount—; }else{ //有就等待 try{ desk.getLock.wait(); }catch (InterruptedException e){
}
}
}
}
}
}
/ 测试类 / //创建桌子对象 Desk desk = new Desk();
Foodie f = new Foodie(desk); Cooker c = new Cooker(desk);
f.start(); c.start();
<a name="cd9d1d63221aba973983d5eb6c2aacb1"></a>
### (10)阻塞队列
- 阻塞列队的体系结构组成![阻塞队列.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613130607554-a93e134c-3cca-4d11-99bd-e4c0be68f78e.png#height=277&id=lLugE&margin=%5Bobject%20Object%5D&name=%E9%98%BB%E5%A1%9E%E9%98%9F%E5%88%97.png&originHeight=277&originWidth=1160&originalType=binary&size=58395&status=done&style=none&width=1160)
- 阻塞列队实现等待唤醒机制。
- BlockingQueue的核心方法 :
- E put(E e); 向阻塞列队添加元素,添加数据不成功则等待
- E take(E e):拿取阻塞列队的元素,拿取数据不成功则等待
- 常见的BlockingQueue:
- ArrayBlockingQueue:底层是数组,有界;构造方法:ArrayBlockingQueue(int cupacity); :参数为指定的容量
- LinkedBlockingQueue:底层是链表,无界,但不是真正的无界,最大为int的最大值
<a name="08c5b6a8a11f9e0ca5eb6e2c16865330"></a>
### (11)线程的状态
- 线程状态
- 新建状态(new):创建线程对象
- 就绪状态(runnable):调用start方法
- 阻塞状态(blocked):无法获取锁对象
- 等待状态(waiting):遇到wait方法
- 计时等待(tined_waiting):遇到sleep方法
- 结束状态(terminated):全部代码运行完毕
<a name="9b52b23db1e013c34589e1032d6993ad"></a>
### (12)线程池
- 默认线程池的创建
- Executors类中调用静态newCachedThreadPool()方法,它有返回值:ExecutorService。
- ExecutorService类中控制线程池的方法:
- Submit(Runnable runnable): 提交线程任务
- Shutdown(),终结线程池任务![默认线程池的创建及使用.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613133067895-4d941dd2-8d73-43d9-9520-495bbaa6999a.png#height=583&id=miHvl&margin=%5Bobject%20Object%5D&name=%E9%BB%98%E8%AE%A4%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%88%9B%E5%BB%BA%E5%8F%8A%E4%BD%BF%E7%94%A8.png&originHeight=583&originWidth=1162&originalType=binary&size=369641&status=done&style=none&width=1162)
- 有指定的最大容量的线程池
- Executors类中调用静态newFixedThreadPool(int nThreads)方法,参数是指定线程的容量。它有返回值:也是ExecutorService。
- 自定义线程池的创建和使用
- ThreadPoolExecutor tpe = new ThreadPoolExecutor(七个参数:①核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略)
- 参数一:核心线程数量 :不能小于0;
- 参数二:最大线程数量::不能小于等于0,最大线程数量>=核心线程数量
- 参数三:空闲线程最大存活时间:不能小于0;
- 参数四:时间单位
- 参数五:任务列队:不能为null
- 参数六:创建线程工厂:不能为null;
- 参数七:不能为null:![参数详解.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613133567000-dc28829a-1a47-48a1-b6ce-02edd6f27672.png#height=143&id=gzwQ6&margin=%5Bobject%20Object%5D&name=%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3.png&originHeight=253&originWidth=1178&originalType=binary&size=216296&status=done&style=none&width=664)
- 最后一个拒绝策略相当于是交给别的线程执行
<a name="59b41cb579d1d7ef8deb4ed22d7539ea"></a>
### (13)volatile关键字
- 堆内存(成员变量才会进堆内存)中共享数据不透明的产生的原因
- 堆内存是唯一的,每个线程都有一个自己的线程栈,
- 每一个线程在使用堆内存里面的变量是,都会先拷贝一份到变量副本中。
- 在线程中,每一次使用时是从变量的副本中获取的。
- volatile关键字的作用
- 修饰共享数据(成员变量)使共享数据(成员变量)透明化:public static volatile int date;
- 利用synchronizde使数据透明化
- 女孩线程类
![共享数据透明化女孩.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613136292638-cef5916f-bd70-438e-b600-0adf6d8ca4b4.png#height=696&id=ZNpqu&margin=%5Bobject%20Object%5D&name=%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE%E9%80%8F%E6%98%8E%E5%8C%96%E5%A5%B3%E5%AD%A9.png&originHeight=696&originWidth=1184&originalType=binary&size=261929&status=done&style=none&width=1184)
- 男孩线程类
![共享数据透明化.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613136339304-0a59a7eb-40ba-4f15-b18a-68454a75f43d.png#height=452&id=SrkgP&margin=%5Bobject%20Object%5D&name=%E5%85%B1%E4%BA%AB%E6%95%B0%E6%8D%AE%E9%80%8F%E6%98%8E%E5%8C%96.png&originHeight=452&originWidth=662&originalType=binary&size=139825&status=done&style=none&width=662)
- 同步代码块让共享数据透明化的原理
- 线程获得锁
- 就会清空变量副本
- 然后将最新的共享数据拷贝到变量副本中
- 执行代码
- 将修改之后的变量副本中的值赋值给堆内存中的共享数据
- 最后释放锁
<a name="9e8457dae559b319ff367c50c424a914"></a>
### (14)原子性
- 原子性的概述
- 多个步骤一起执行一起结束,执行的时候每个环节(步骤)且不被外界打断。多个步骤是不可分割的整体。
- 变量++不是原子性的操作
- synchronized同步代码块可以解决多线程的原子性的问题。
- volatile关键字:
- 只能保证线程每次在使用共享数据时是最新值,但是不能保证原子性
- 原子类:AtomicInteger类
- AtomicInteger类中inrementAndGet()方法的内存图解
- AtomicInteger类中的inrementAndGet()方法的源码解析
<a name="10ec52ee7720e0500a3c22885a8789ce"></a>
### (15)悲观锁与乐观锁
- synchronizde与inrementAndGet方法(CAS)的区别(悲观锁与乐观锁)
<a name="81bd318bfafee831aa17fb514f8a02c7"></a>
### (16)并发工具类
- Hashtable
- Hashtable采取悲观锁synchronized的形式保证数据的安全性;只要有线程访问,会将整张表全锁起来,所以Hashtable的效率低下
- ConcurrentHashMap
- 它实现了Map接口;
![并发工具类接口的实现.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613212908643-60247cd1-7c57-4853-8848-fac2e154c378.png#height=364&id=lxalp&margin=%5Bobject%20Object%5D&name=%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB%E6%8E%A5%E5%8F%A3%E7%9A%84%E5%AE%9E%E7%8E%B0.png&originHeight=364&originWidth=1148&originalType=binary&size=86721&status=done&style=none&width=1148)
- ConcurrentHashMap集合在jdk1.7版本的创建对象及添加数据的底层原理图解
![并发工具类的图解.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613213045985-dbf9e2df-40b1-4b0c-b6e8-d34803b6cd14.png#height=300&id=tLZRV&margin=%5Bobject%20Object%5D&name=%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB%E7%9A%84%E5%9B%BE%E8%A7%A3.png&originHeight=472&originWidth=1173&originalType=binary&size=267394&status=done&style=none&width=746)
- ConcurrentHashMap集合在jdk1.8版本的创建对象及添加数据的底层原理图解
![并发工具类的内存图解.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613213446687-7100d839-de71-43a8-9092-b16ce8654732.png#height=733&id=A5MH0&margin=%5Bobject%20Object%5D&name=%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB%E7%9A%84%E5%86%85%E5%AD%98%E5%9B%BE%E8%A7%A3.png&originHeight=733&originWidth=1135&originalType=binary&size=155643&status=done&style=none&width=1135)
- 原理总结:
- 如果使用空参构造 创建ConcurrentHashMap对象,则什么事情都不做在第一次添加元素的时候创建哈希表
- 计算当前元素 应存入的索引(根据元素的哈希值计算)
- 如果该索引位置为null,则利用CAS算法,将本结点添加到数组中
- 如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址值,挂在它下面,变成链表
- 当链表长度大于等于8时,自动转为红黑树
- 以链表或红黑树的头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性
<a name="d7ba0f903b179b02c985949374606a9e"></a>
# 二、网络编程
<a name="f62bf5e2f0c1f42471ecb0cf2bad71d2"></a>
## 2.1、网络编程的认识及三要素简述
(1)网络编程的概述
- 计算机网络
- 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
- 网络编程:在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
(2)网络编程的三要素
- IP地址
- 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
- 端口
- 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识
- 协议
- 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
<a name="90596d3d440e56d551c7c5586c72f045"></a>
## 2.2、三要素详解
<a name="30775c40ef7947be3fdd85cba5fa4b8d"></a>
### (1)IP地址的详解
- IP地址:是网络中设备的唯一标识
- IP地址分为两大类
- IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每 个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
- IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8 组十六进制数,这样就解决了网络地址资源数量不够的问题
- DOS常用命令:
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
- 特殊IP地址:127.0.0.1(localhost):是回送地址,可以代表本机地址,一般用来测试使用
<a name="3b1e7d8e44670dc916ac197989db065a"></a>
### (2)InetAddress类的认识与使用
- InetAddress:此类表示Internet协议(IP)地址
- 常用的方法
| 方法名 | 功能说明 |
| --- | --- |
| static InetAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
| String getHostName() | 获取此IP地址的主机名 |
| String getHostAddress() | 返回文本显示中的IP地址字符串 |
- 代码示例
```java
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//InetAddress address = InetAddress.getByName("itheima");
InetAddress address = InetAddress.getByName("192.168.1.66");
//public String getHostName():获取此IP地址的主机名
String name = address.getHostName();
//public String getHostAddress():返回文本显示中的IP地址字符串
String ip = address.getHostAddress();
System.out.println("主机名:" + name);
System.out.println("IP地址:" + ip);
}
}
(3)端口和协议
- 端口:设备上应用程序的唯一标识
- 端口号
- 用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
- 协议:计算机网络中,连接和通信的规则被称为网络通信协议
- UDP协议
- 用户数据报协议(User Datagram Protocol)
- UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
- 由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
- 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太 大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
- TCP协议
- 传输控制协议 (Transmission Control Protocol)
- TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
- 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。第一次握手,客户端向服务器端发出连接请求,等待服务器确认;第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求;第三次握手,客户端再次向服务器端发送确认信息,确认连接
- 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性, TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
- 四次挥手:第一次挥手,客户端主动发送Fin+Ack报文,并置发送序号为X;第二次挥手,服务器端被动的发送Ack报文,并置发送序号为Z,再确认序号为X+1;第三次挥手,服务器端主动发送Fin+Ack报文,并置发送序号为Y,再确认序号为X;第四次挥手,客户端主动发送ack报文,并置发送序号为X,再确认序号为Y
2.3、UDP通信程序
(1)UDP发送数据
- Java中的UDP通信
- UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发 送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
- Java提供了DatagramSocket类作为基于UDP协议的Socket
构造方法 | 方法名 | 功能说明 | | —- | —- | | DatagramSocket() | 创建数据报套接字并将其绑定到本机地址上的任何可用端口 | | DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建数据包,发送长度为len的数据包到指定主机的指定端口 |
相关方法 | 方法名 | 功能说明 | | —- | —- | | void send(DatagramPacket p) | 发送数据报包 | | void close() | 关闭数据报套接字 | | void receive(DatagramPacket p) | 从此套接字接受数据报包 |
发送数据的步骤
- 创建发送端的Socket对象(DatagramSocket)
- 创建数据,并把数据打包
- 调用DatagramSocket对象的方法发送数据
- 关闭发送端
- 代码演示
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象(DatagramSocket)
// DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket ds = new DatagramSocket();
//创建数据,并把数据打包
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。
byte[] bys = "hello,udp,我来了".getBytes();
DatagramPacket dp = new
DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),10086);
//调用DatagramSocket对象的方法发送数据
//void send(DatagramPacket p) 从此套接字发送数据报包
ds.send(dp);
//关闭发送端
//void close() 关闭此数据报套接字
ds.close();
}
}
(2)UDP接收数据
- 接收数据的步骤
- 创建接收端的Socket对象(DatagramSocket)
- 创建一个数据包,用于接收数据
- 调用DatagramSocket对象的方法接收数据
- 解析数据包,并把数据在控制台显示
- 关闭接收端
构造方法 | 方法名 | 功能说明 | | —- | —- | | DatagramPacket(byte[] buf, int len) | 创建一个DatagramPacket用于接收长度为len的数据包 |
相关的方法 | 方法名 | 功能说明 | | —- | —- | | byte[] getData() | 返回数据缓冲区 | | int getLength() | 返回要发送的数据的长度或接收的数据的长度 |
示例代码
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(12345);
//创建一个数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp);
//解析数据包,并把数据在控制台显示
System.out.println("数据是:" + new String(dp.getData(), 0,
dp.getLength()));
}
}
(3)UDP通信程序练习
- 案例需求
- UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
- UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
代码实现
/*
UDP发送数据:
数据来自于键盘录入,直到输入的数据是886,发送数据结束
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
//键盘录入数据
Scanner sc = new Scanner(System.in);
while (true) {
String s = sc.nextLine();
//输入的数据是886,发送数据结束
if ("886".equals(s)) {
break;
}
//创建数据,并把数据打包
byte[] bys = s.getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("192.168.1.66"), 12345);
//调用DatagramSocket对象的方法发送数据
ds.send(dp);
}
//关闭发送端
ds.close();
}
}
/*
UDP接收数据:
因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(12345);
while (true) {
//创建一个数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp);
//解析数据包,并把数据在控制台显示
System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
}
//关闭接收端
// ds.close();
}
}
(4)UDP的三种通信方式
单播:单播用于两个主机之间的端对端通信
- 组播:组播用于对一组特定的主机进行通信
- 广播:广播用于一个主机对整个局域网上所有主机上的数据通信
(5)UDP组播的实现
- 实现步骤
- 发送端
- 创建发送端的Socket对象(DatagramSocket)
- 创建数据,并把数据打包(DatagramPacket)
- 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里 是发给组播地址)
- 释放资源
- 接收端
- 创建接收端Socket对象(MulticastSocket)
- 创建一个箱子,用于接收数据
- 把当前计算机绑定一个组播地址
- 将数据接收到箱子中
- 解析数据包,并打印数据
- 释放资源
- 发送端
- 代码实现
```java
// 发送端
public class ClinetDemo {
public static void main(String[] args) throws IOException {
} }// 1. 创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
String s = "hello 组播";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10000;
// 2. 创建数据,并把数据打包(DatagramPacket)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,
这里是发给组播地址)
ds.send(dp);
// 4. 释放资源
ds.close();
// 接收端 public class ServerDemo { public static void main(String[] args) throws IOException { // 1. 创建接收端Socket对象(MulticastSocket) MulticastSocket ms = new MulticastSocket(10000); // 2. 创建一个箱子,用于接收数据 DatagramPacket dp = new DatagramPacket(new byte[1024],1024); // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中. ms.joinGroup(InetAddress.getByName(“224.0.1.0”)); // 4. 将数据接收到箱子中 ms.receive(dp); // 5. 解析数据包,并打印数据 byte[] data = dp.getData(); int length = dp.getLength(); System.out.println(new String(data,0,length)); // 6. 释放资源 ms.close(); } }
- 发送端(发送程序)
- 接收端(接收程序)
<a name="1874f4275eb280157515525dca55f66b"></a>
### (6)UDP广播的实现
- 实现步骤
- 发送端
- 1. 创建发送端Socket对象(DatagramSocket)
- 2. 创建存储数据的箱子,将广播地址封装进去
- 3. 发送数据
- 4. 释放资源
- 接收端
- 1. 创建接收端的Socket对象(DatagramSocket)
- 2. 创建一个数据包,用于接收数据
- 3. 调用DatagramSocket对象的方法接收数据
- 4. 解析数据包,并把数据在控制台显示
- 5. 关闭接收端
- 代码实现
```java
// 发送端
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 1. 创建发送端Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
// 2. 创建存储数据的箱子,将广播地址封装进去
String s = "广播 hello";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("255.255.255.255");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 发送数据
ds.send(dp);
// 4. 释放资源
ds.close();
}
}
// 接收端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1. 创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(10000);
// 2. 创建一个数据包,用于接收数据
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
// 3. 调用DatagramSocket对象的方法接收数据
ds.receive(dp);
// 4. 解析数据包,并把数据在控制台显示
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
// 5. 关闭接收端
ds.close();
}
}
2.4、TCP通信程序
(1)TCP通信程序的基本认识及功能介绍(发送端)
- Java中的TCP通信
- Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过 Socket产生IO流来进行网络通信。
- Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
构造方法 | 方法名 | 功能说明 | | —- | —- | | Socket(InetAddress address,int port) | 创建流套接字并将其连接到指定IP指定端口号 | | Socket(String host, int port) | 创建流套接字并将其连接到指定主机上的指定端口号 |
常用方法 | 方法名 | 功能说明 | | —- | —- | | InputStream getInputStream() | 返回此套接字的输入流 | | OutputStream getOutputStream() | 返回此套接字的输出流 |
示例代码
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象(Socket)
//Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
Socket s = new Socket("127.0.0.1",10000);
//获取输出流,写数据
//OutputStream getOutputStream() 返回此套接字的输出流
OutputStream os = s.getOutputStream();
os.write("hello,tcp,我来了".getBytes());
//释放资源
s.close();
}
}
(2)TCP通信程序的基本认识及功能介绍(接收端)
构造方法 | 方法名 | 功能说明 | | —- | —- | | ServletSocket(int port) | 创建绑定到指定端口的服务器套接字 |
相关的方法 | 方法名 | 功能说明 | | —- | —- | | Socket accept() | 监听要连接到此的套接字并接受它 |
注意事项
- accept方法是阻塞的,作用就是等待客户端连接
- 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
- 3.针对客户端来讲,是往外写的,所以是输出流;针对服务器来讲,是往里读的,所以是输入流
- 4.read方法也是阻塞的
- 客户端在关流的时候,还多了一个往服务器写结束标记的动作
- 最后一步断开连接,通过四次挥手协议保证连接终止
(3)三次握手和四次挥手
- 三次握手
- 四次挥手
示例代码
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象(ServerSocket)
//ServerSocket(int port) 创建绑定到指定端口的服务器套接字
ServerSocket ss = new ServerSocket(10000);
//Socket accept() 侦听要连接到此套接字并接受它
Socket s = ss.accept();
//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys,0,len);
System.out.println("数据是:" + data);
//释放资源
s.close();
ss.close();
}
}
(4)TCP程序练习
案例需求
- 客户端:发送数据,接受服务器反馈
- 服务器:收到消息后给出反馈
- 案例分析
- 客户端创建对象,使用输出流输出数据
- 服务端创建对象,使用输入流接受数据
- 服务端使用输出流给出反馈数据
- 客户端使用输入流接受反馈数据
- 代码实现
```java
// 客户端
public class ClientDemo {
public static void main(String[] args) throws IOException {
} }Socket socket = new Socket("127.0.0.1",10000);
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
// os.close();如果在这里关流,会导致整个socket都无法使用
socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
BufferedReader br = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}
br.close();
os.close();
socket.close();
// 服务器 public class ServerDemo { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10000); Socket accept = ss.accept(); InputStream is = accept.getInputStream(); int b; while((b = is.read())!=‐1){ System.out.println((char) b); } System.out.println(“看看我执行了吗?”); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream())); bw.write(“你谁啊?”); bw.newLine(); bw.flush(); bw.close(); is.close(); accept.close(); ss.close(); } }
![TCP发送端.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613263546976-a165dc57-3a24-4cf2-927e-a7d1285d7a35.png#height=442&id=leW48&margin=%5Bobject%20Object%5D&name=TCP%E5%8F%91%E9%80%81%E7%AB%AF.png&originHeight=442&originWidth=1166&originalType=binary&size=358546&status=done&style=none&width=1166)![TCP接收端.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613263992051-07d9fed4-220a-469c-bc89-3d15aa288561.png#height=556&id=s9gel&margin=%5Bobject%20Object%5D&name=TCP%E6%8E%A5%E6%94%B6%E7%AB%AF.png&originHeight=556&originWidth=1162&originalType=binary&size=431977&status=done&style=none&width=1162)
- 文件传输的案例(上面两张图也是本案例)
- 案例需求
- 客户端:数据来自于本地文件,接收服务器反馈
- 服务器:接收到的数据写入本地文件,给出反馈
- 案例分析
- 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
- 创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束 后。使用输出流给客户端反馈信息
- 客户端接受服务端的回馈信息
- 相关的方法
| 方法名 | 功能说明 |
| --- | --- |
| void shutdownInput() | 将此套接字的输入流放置在“流的末尾” |
| void shutdownOutput() | 禁止用此套接字的输出流 |
- 代码实现
```java
// 客户端
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10000);
//是本地的流,用来读取本地文件的.
BufferedInputStream bis = new BufferedInputStream(new
FileInputStream("socketmodule\\ClientDir\\1.jpg"));
//写到服务器 ‐‐‐ 网络中的流
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
int b;
while((b = bis.read())!=‐1){
bos.write(b);//通过网络写到服务器中
}
bos.flush();
//给服务器一个结束标记,告诉服务器文件已经传输完毕
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) !=null){
System.out.println(line);
}
bis.close();
socket.close();
}
}
// 服务器
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
//网络中的流,从客户端读取数据的
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//本地的IO流,把数据写到本地中,实现永久化存储
BufferedOutputStream bos = new BufferedOutputStream(new
FileOutputStream("socketmodule\\ServerDir\\copy.jpg"));
int b;
while((b = bis.read()) !=‐1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new
OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
bos.close();
accept.close();
ss.close();
}
}
三、类加载器与反射
3.1、类加载器的认识与使用
(1)类加载的认识
- 作用:负责将.class文件(存储的物理文件)加载在到内存中
- 类加载的过程
- 类加载时机(什么时候加载类到内存中)
- 创建类的实例(对象)
- 调用类的类方法(static修饰的静态方法)
- 访问类或者接口的类变量(static修饰的静态成员变量),或者为该类变量(static修饰的静态成员变量)赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
- 类加载过程
- . 加载
- 通过包名 + 类名,获取这个类,准备用流进行传输
- 再将这个类加载到内存中
- 加载完毕创建一个class(字节码)对象
- . 加载
- 类加载时机(什么时候加载类到内存中)
- 链接
- 验证:确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全 (文件中的信息是否符合虚拟机规范有没有安全隐患)
- 准备:负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值 (初始化静态变量)
- 解析;将类的二进制数据流中的符号引用替换为直接引用 (本类中如果用到了其他类,此时就需要找到对应的类)
- 初始化:根据程序员通过程序制定的主观计划去初始化类变量和其他资源 (静态变量赋值以及初始化其他资源)
- 总结:当一个类被使用的时候,才会加载到内存;类加载的过程: 加载、验证、准备、解析、初始化
(2)类加载的分类
- 分类
- Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父null
- Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
- System class loader:系统类加载器,负责加载用户类路径上所指定的类库
- 类加载器的继承关系
- System的父加载器为Platform
- Platform的父加载器为Bootstrap
代码演示
public class ClassLoaderDemo1 {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//获取系统类加载器的父加载器 ‐‐‐ 平台类加载器
ClassLoader classLoader1 = systemClassLoader.getParent();
//获取平台类加载器的父加载器 ‐‐‐ 启动类加载器
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println("系统类加载器" + systemClassLoader);
System.out.println("平台类加载器" + classLoader1);
System.out.println("启动类加载器" + classLoader2);
}
}
(3)双亲委派模型
介绍:
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执 行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加 载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器 才会尝试自己去加载,这就是双亲委派模式
(4)ClassLoader 中的两个方法
常用的两个方法 | 方法名 | 功能说明 | | —- | —- | | public static ClassLoader getSystemClassLoader() | 获取系统类加载器 | | public InputStream getResourceAsStream(String name) | 加载某一个资源文件 |
示例代码
public class ClassLoaderDemo2 {
public static void main(String[] args) throws IOException {
//static ClassLoader getSystemClassLoader() 获取系统类加载器
//InputStream getResourceAsStream(String name) 加载某一个资源文件
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//利用加载器去加载一个指定的文件
//参数:文件的路径(放在src的根目录下,默认去那里加载)
//返回值:字节流。
InputStream is = systemClassLoader.getResourceAsStream("prop.properties");
Properties prop = new Properties();
prop.load(is);
System.out.println(prop);
is.close();
}
}
3.2、反射机制
(1)反射的认识
- 反射机制
- 类名.class属性
- 对象名.getClass()方法
- Class.forName(全类名)方法
- 示例代码
```java
/
三种方式获取自定义类的字节码对象
/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
} public String getName() {this.name = name;
this.age = age;
} public void setName(String name) {return name;
} public int getAge() {this.name = name;
} public void setAge(int age) {return age;
} public void study(){this.age = age;
} @Override public String toString() {System.out.println("学生在学习");
} }return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
public class ReflectDemo1 { public static void main(String[] args) throws ClassNotFoundException { //1.Class类中的静态方法forName(“全类名”) //全类名:包名 + 类名 Class clazz = Class.forName(“com.itheima.myreflect2.Student”); System.out.println(clazz); //2.通过class属性来获取 Class clazz2 = Student.class; System.out.println(clazz2); //3.利用对象的getClass方法来获取class对象 //getClass方法是定义在Object类中. Student s = new Student(); Class clazz3 = s.getClass(); System.out.println(clazz3); System.out.println(clazz == clazz2); System.out.println(clazz2 == clazz3); } }
<a name="3d21a03e9f518bc58c362b804de896e1"></a>
### (2)反射获取构造方法并使用
- Class类获取构造方法对象的方法
- 常用方法
| 方法名 | 功能说明 |
| --- | --- |
| Constructor[] getConstructors() | 返回所有公共构造方法对象的数 组 |
| Constructor[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
| Constructor getConstructor(Class... parameterTypes) | 返回单个公共构造方法对象 |
| Constructor getDeclaredConstructor(Class... parameterTypes) | 返回单个构造方法对象 |
- 示例代码
```java
public class Student {
private String name;
private int age;
//私有的有参构造方法
private Student(String name) {
System.out.println("name的值为:" + name);
System.out.println("private...Student...有参构造方法");
}
//公共的无参构造方法
public Student() {
System.out.println("public...Student...无参构造方法");
}
//公共的有参构造方法
public Student(String name, int age) {
System.out.println("name的值为:" + name + "age的值为:" + age);
System.out.println("public...Student...有参构造方法");
}
}
/*
测试类
*/
public class ReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException {
//method1();
//method2();
//method3();
//method4();
}
private static void method4() throws ClassNotFoundException, NoSuchMethodException {
// Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):
// 返回单个构造方法对象
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
Constructor constructor = clazz.getDeclaredConstructor(String.class);
System.out.println(constructor);
}
private static void method3() throws ClassNotFoundException, NoSuchMethodException {
// Constructor<T> getConstructor(Class<?>... parameterTypes):
// 返回单个公共构造方法对象
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//小括号中,一定要跟构造方法的形参保持一致.
Constructor constructor1 = clazz.getConstructor();
System.out.println(constructor1);
Constructor constructor2 = clazz.getConstructor(String.class, int.class);
System.out.println(constructor2);
//因为Student类中,没有只有一个int的构造,所以这里会报错.
Constructor constructor3 = clazz.getConstructor(int.class);
System.out.println(constructor3);
}
private static void method2() throws ClassNotFoundException {
// Constructor<?>[] getDeclaredConstructors():
// 返回所有构造方法对象的数组
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
private static void method1() throws ClassNotFoundException {
// Constructor<?>[] getConstructors():
// 返回所有公共构造方法对象的数组
//1.获取Class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
}
(3)Constructor类用于创建对象的方法
常用方法 | 方法名 | 功能说明 | | —- | —- | | T newInstance(Object…initargs) | 根据指定的构造方法创建对象 | | setAccessible(boolean flag) | 设置为true,表示取消访问检查 |
示例代码
// Student类同上一个示例,这里就不在重复提供了
public class ReflectDemo2 {
public static void main(String[] args) throws ClassNotFoundException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
//T newInstance(Object... initargs):根据指定的构造方法创建对象
//method1();
//method2();
//method3();
//method4();
}
private static void method4() throws Exception {
//获取一个私有的构造方法并创建对象
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.获取一个私有化的构造方法.
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//被private修饰的成员,不能直接使用的
//如果用反射强行获取并使用,需要临时取消访问检查
constructor.setAccessible(true);
//3.直接创建对象
Student student = (Student) constructor.newInstance("zhangsan");
System.out.println(student);
}
private static void method3() throws Exception {
//简写格式
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.在Class类中,有一个newInstance方法,可以利用空参直接创建一个对象
Student student = (Student) clazz.newInstance();//这个方法现在已经过时了,了解一下
System.out.println(student);
}
private static void method2() throws Exception {
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.获取构造方法对象
Constructor constructor = clazz.getConstructor();
//3.利用空参来创建Student的对象
Student student = (Student) constructor.newInstance();
System.out.println(student);
}
private static void method1() throws Exception {
//1.获取class对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.获取构造方法对象
Constructor constructor = clazz.getConstructor(String.class, int.class);
//3.利用newInstance创建Student的对象
Student student = (Student) constructor.newInstance("zhangsan", 23);
System.out.println(student);
}
}
小结:
- 获取class对象的三种方式:三种方式: Class.forName(“全类名”), 类名.class, 对象名.getClass()
- 获取该类的父类:getSuperclass(); 获取带泛型的父类:public Type getGenericSuperClass(); 可以将Type强转成ParameteriedType类型的,通过该对象中的public Type[] getActualTypeArauments() 方法获取泛型参数的类型
- 获取里面的构造方法对象:getConstructor (Class… parameterTypes) getDeclaredConstructor (Class… parameterTypes)
- 如果是public的构造方法,直接创建对象:newInstance(Object… initargs)
- 如果是非public的,需要临时取消检查,然后再创建对象:setAccessible(boolean) 暴力反射
(4)反射获取成员变量并使用
Class类获取成员变量对象的方法
常用方法 | 方法名 | 功能说明 | | —- | —- | | Field[] getFields() | 返回所有公共成员变量对象的数组 | | Field[] getDeclaredFields() | 返回所有成员变量对象的数组 | | Field getField(String name) | 返回单个公共成员变量对象 | | Field getDeclaredField(String name) | 返回单个成员变量对象 |
示例代码 ```java public class Student { public String name; public int age; public String gender; private int money = 300; @Override public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", money=" + money +
'}';
} }
public class ReflectDemo1 { public static void main(String[] args) throws Exception { // method1(); //method2(); //method3(); //method4(); }
private static void method4() throws Exception {
// Field getDeclaredField(String name):返回单个成员变量对象
//1.获取class对象
Class clazz = Class.forName("com.xiaoha.myreflect4.Student");
//2.获取money成员变量
Field field = clazz.getDeclaredField("money");
//3.打印一下
System.out.println(field);
}
private static void method3() throws ClassNotFoundException, NoSuchFieldException {
// Field getField(String name):返回单个公共成员变量对象
//想要获取的成员变量必须是真实存在的
//且必须是public修饰的.
//1.获取class对象
Class clazz = Class.forName("com.xiaoha.myreflect4.Student");
//2.获取name这个成员变量
//Field field = clazz.getField("name");
//Field field = clazz.getField("name1");
Field field = clazz.getField("money");
//3.打印一下
System.out.println(field);
}
private static void method2() throws ClassNotFoundException {
// Field[] getDeclaredFields():返回所有成员变量对象的数组
//1.获取class对象
Class clazz = Class.forName("com.xiaoha.myreflect4.Student");
//2.获取所有的Field对象
Field[] fields = clazz.getDeclaredFields();
//3.遍历
for (Field field : fields) {
System.out.println(field);
}
}
private static void method1() throws ClassNotFoundException {
// Field[] getFields():返回所有公共成员变量对象的数组
//1.获取class对象
Class clazz = Class.forName("com.xiaoha.myreflect4.Student");
//2.获取Field对象.
Field[] fields = clazz.getFields();
//3.遍历
for (Field field : fields) {
System.out.println(field);
}
}
}
![反射获取变量的方法.png](https://cdn.nlark.com/yuque/0/2021/png/12492094/1613366709286-8c97f75c-3919-424b-a650-60a301c286c5.png#height=421&id=TueLa&margin=%5Bobject%20Object%5D&name=%E5%8F%8D%E5%B0%84%E8%8E%B7%E5%8F%96%E5%8F%98%E9%87%8F%E7%9A%84%E6%96%B9%E6%B3%95.png&originHeight=421&originWidth=1164&originalType=binary&size=360577&status=done&style=none&width=1164)
<a name="42d780b4a0b0e66d363cba5b4cdaa6e7"></a>
### (5)Field类用于给成员变量赋值的方法
- 常用的方法
| 方法名 | 功能说明 |
| --- | --- |
| void set(Object obj, Object value) | 赋值 |
| Object get(Object obj) | 获取值 |
- 示例代码
```java
// Student类同上一个示例,这里就不在重复提供了
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
// Object get(Object obj) 返回由该 Field表示的字段在指定对象上的值。
//method1();
//method2();
}
private static void method2() throws Exception {
//1.获取class对象
Class clazz = Class.forName("com.xiaoha.myreflect4.Student");
//2.获取成员变量Field的对象
Field field = clazz.getDeclaredField("money");
//3.取消一下访问检查
field.setAccessible(true);
//4.调用get方法来获取值
//4.1创建一个对象
Student student = (Student) clazz.newInstance();
//4.2获取指定对象的money的值
Object o = field.get(student);
//5.打印一下
System.out.println(o);
}
private static void method1() throws Exception {
// void set(Object obj, Object value):给obj对象的成员变量赋值为value
//1.获取class对象
Class clazz = Class.forName("com.xiaoha.myreflect4.Student");
//2.获取name这个Field对象
Field field = clazz.getField("name");
//3.利用set方法进行赋值.
//3.1先创建一个Student对象
Student student = (Student) clazz.newInstance();
//3.2有了对象才可以给指定对象进行赋值
field.set(student,"zhangsan");
System.out.println(student);
}
}
(6)反射获取成员方法并使用
- Class类获取成员方法对象的方法
常用的方法 | 方法名 | 功能说明 | | —- | —- | | Method[] getMethods() | 返回所有公共成员方法对象的数组,包 括继承的 | | Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括 继承的 | | Method getMethod(String name, Class… parameterTypes) | 返回单个公共成员方法对象(参数一为方法名,参数二为形参的类型:类型.class) | | Method getDeclaredMethod(String name, Class… parameterTypes) | 返回单个成员方法对象 |
示例代码 ```java public class Student { //私有的,无参无返回值 private void show() {
System.out.println("私有的show方法,无参无返回值");
} //公共的,无参无返回值 public void function1() {
System.out.println("function1方法,无参无返回值");
} //公共的,有参无返回值 public void function2(String name) {
System.out.println("function2方法,有参无返回值,参数为" + name);
} //公共的,无参有返回值 public String function3() {
System.out.println("function3方法,无参有返回值");
return “aaa”; } //公共的,有参有返回值 public String function4(String name) {
System.out.println("function4方法,有参有返回值,参数为" + name);
return “aaa”; } //公共的,有参有返回值 public String function4(String name) {
System.out.println("function4方法,有参有返回值,参数为" + name);
return “aaa”; } }
public class ReflectDemo1 { public static void main(String[] args) throws Exception { //method1(); //method2(); //method3(); //method4(); //method5(); } private static void method5() throws ClassNotFoundException, NoSuchMethodException { // Method getDeclaredMethod(String name, Class<?>… parameterTypes): // 返回单个成员方法对象 //1.获取class对象 Class clazz = Class.forName(“com.xiaoha.myreflect5.Student”); //2.获取一个成员方法show Method method = clazz.getDeclaredMethod(“show”); //3.打印一下 System.out.println(method); } private static void method4() throws ClassNotFoundException, NoSuchMethodException { //1.获取class对象 Class clazz = Class.forName(“com.xiaoha.myreflect5.Student”); //2.获取一个有形参的方法function2 Method method = clazz.getMethod(“function2”, String.class); //3.打印一下 System.out.println(method); } private static void method3() throws ClassNotFoundException, NoSuchMethodException { // Method getMethod(String name, Class<?>… parameterTypes) : // 返回单个公共成员方法对象 //1.获取class对象 Class clazz = Class.forName(“com.xiaoha.myreflect5.Student”); //2.获取成员方法function1 Method method1 = clazz.getMethod(“function1”); //3.打印一下 System.out.println(method1); } private static void method2() throws ClassNotFoundException { // Method[] getDeclaredMethods(): // 返回所有成员方法对象的数组,不包括继承的 //1.获取class对象 Class clazz = Class.forName(“com.xiaoha.myreflect5.Student”); //2.获取Method对象 Method[] methods = clazz.getDeclaredMethods(); //3.遍历一下数组 for (Method method : methods) { System.out.println(method); } } private static void method1() throws ClassNotFoundException { // Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的 //1.获取class对象 Class clazz = Class.forName(“com.xiaoha.myreflect5.Student”); //2.获取成员方法对象 Method[] methods = clazz.getMethods(); //3.遍历 for (Method method : methods) { System.out.println(method); } } }
<a name="4febb397bdd635341c4ed9bf5480c56e"></a>
### (7)Method类用于执行方法的方法
- 常用的方法:Object invoke(Object obj, Object... args) 执行方法
- 参数一: 用obj对象调用该方法
- 参数二: 调用方法的传递的参数(如果没有就不写)
- 返回值: 方法的返回值(如果没有就不写)
- 示例代码
```java
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
// Object invoke(Object obj, Object... args):运行方法
// 参数一:用obj对象调用该方法
// 参数二:调用方法的传递的参数(如果没有就不写)
// 返回值:方法的返回值(如果没有就不写)
//1.获取class对象
Class clazz = Class.forName("com.xiaoha.myreflect5.Student");
//2.获取里面的Method对象 function4
Method method = clazz.getMethod("function4", String.class);
//3.运行function4方法就可以了
//3.1创建一个Student对象,当做方法的调用者
Student student = (Student) clazz.newInstance();
//3.2运行方法
Object result = method.invoke(student, "zhangsan");
//4.打印一下返回值
System.out.println(result);
}
}
四、XML及其文档约束(未完结)
4.1、xml文件的认识与使用
(1)xml文件的认识
xml的概述
- 万维网联盟(W3C):万维网联盟(W3C)创建于1994年,又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。 建立者: Tim Berners-Lee (蒂姆·伯纳斯·李)。 是Web技术领域最具权威和影响力的国际中立性技术标准机构。 到目前为止,W3C已发布了200多项影响深远的Web技术标准及实施指南,
- 如广为业界采用的超文本标记语言HTML(标准通用标记语言下的一个应用)、
- 可扩展标记语言XML(标准通用标记语言下的一个子集)
- 以及帮助残障人士有效获得Web信息的无障碍指南(WCAG)等
- xml:XML的全称为(EXtensible Markup Language),是一种可扩展的标记语言 标记语言: 通过标签来描述数据的一 门语言(标签有时我们也将其称之为元素) 可扩展:标签的名字是可以自定义的,XML文件是由很多标签组成的, 而标签名是可以自定义的
- 作用
- 用于进行存储数据和传输数据
- 作为软件的配置文件
- 作为配置文件的优势
- 万维网联盟(W3C):万维网联盟(W3C)创建于1994年,又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。 建立者: Tim Berners-Lee (蒂姆·伯纳斯·李)。 是Web技术领域最具权威和影响力的国际中立性技术标准机构。 到目前为止,W3C已发布了200多项影响深远的Web技术标准及实施指南,
标签由一对尖括号和合法标识符组成
<student>
标签必须成对出现
<student> </student>
<!-- 前边的是开始标签,后边的是结束标签 -->
特殊的标签可以不成对,但是必须有结束标记
<address/>
标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来
<student id="1"> </student>
标签需要正确的嵌套
<!--这是正确的:--> <student id="1"> <name>张三</name> </student>
<!--这是错误的: --><student id="1"><name>张三</student></name>
(3)xml的语法规则
语法规则
- XML文件的后缀名为:xml
文档声明必须是第一行第一列 :
<?xml version=“1.0” encoding=“UTF-8” standalone=“yes”?>
<!--
version:该属性是必须存在的
encoding:该属性不是必须的
打开当前xml文件的时候应该是使用什么字符编码表(一般取值都是UTF-8)
standalone: 该属性不是必须的,描述XML文件是否依赖其他的xml文件,取值为yes/no
-->
必须存在一个根标签,有且只能有一个
- XML文件中可以定义注释信息
XML文件中可以存在以下特殊字符
< < 小于
> > 大于
& & 和号
' ' 单引号
" " 引号
XML文件中可以存在CDATA区
- 示例代码 ```xml <![CDATA[ …内容… ]]>
<?xml version=”1.0” encoding=”UTF‐8” ?> <!‐‐注释的内容‐‐> <!‐‐本xml文件用来描述多个学生信息‐‐>
<a name="22e8e67d4f4deb592901433d40782892"></a>
### (4)xml的解析
- 概述:xml解析就是从xml中获取到数据
- 常见的解析思想:DOM(Document Object Model)文档对象模型:就是把文档的各个组成部分看做成对应的对象。 会把xml文件全部加载到内存,在内存中形成一个树形结构,再获取对应的值
- 常见的解析工具
- JAXP: SUN公司提供的一套XML的解析的API
- JDOM: 开源组织提供了一套XML的解析的API-jdom
- DOM4J: 开源组织提供了一套XML的解析的API-dom4j,全称:Dom For Java
- pull: 主要应用在Android手机端解析XML
- 解析的准备工作
- 我们可以通过网站:[https://dom4j.github.io/](https://dom4j.github.io/) 去下载dom4j
- 将提供好的dom4j-1.6.1.zip解压,找到里面的dom4j-1.6.1.jar
- 在idea中当前模块下新建一个lib文件夹,将jar包复制到文件夹中
- 选中jar包 -> 右键 -> 选择add as library即可
- 需求
- 解析提供好的xml文件
- 将解析到的数据封装到学生对象中
- 并将学生对象存储到ArrayList集合中
- 遍历集合
- 示例代码
```java
/**
* 利用dom4j解析xml文件
*/
public class XmlParse {
public static void main(String[] args) throws DocumentException {
//1.获取一个解析器对象
SAXReader saxReader = new SAXReader();
//2.利用解析器把xml文件加载到内存中,并返回一个文档对象
Document document = saxReader.read(new File("myxml\\xml\\student.xml"));
//3.获取到根标签
Element rootElement = document.getRootElement();
//4.通过根标签来获取student标签
//elements():可以获取调用者所有的子标签.会把这些子标签放到一个集合中返回.
//elements("标签名"):可以获取调用者所有的指定的子标签,会把这些子标签放到一个集合中并返回
//List list = rootElement.elements();
List<Element> studentElements = rootElement.elements("student");
//System.out.println(list.size());
//用来装学生对象
ArrayList<Student> list = new ArrayList<>();
//5.遍历集合,得到每一个student标签
for (Element element : studentElements) {
//element依次表示每一个student标签
//获取id这个属性
Attribute attribute = element.attribute("id");
//获取id的属性值
String id = attribute.getValue();
//获取name标签
//element("标签名"):获取调用者指定的子标签
Element nameElement = element.element("name");
//获取这个标签的标签体内容
String name = nameElement.getText();
//获取age标签
Element ageElement = element.element("age");
//获取age标签的标签体内容
String age = ageElement.getText();
// System.out.println(id);
// System.out.println(name);
// System.out.println(age);
Student s = new Student(id,name,Integer.parseInt(age));
list.add(s);
}
//遍历操作
for (Student student : list) {
System.out.println(student);
}
}
}
(5)DTD约束
- 什么是约束:用来限定xml文件中可使用的标签以及属性
- 编写DTD约束
- 创建一个文件,这个文件的后缀名为.dtd
- 看xml文件中使用了哪些元素:<!ELEMENT>可以定义元素
- 判断元素是简单元素还是复杂元素:简单元素:没有子元素。 复杂元素:有子元素的元素;
代码实现
<!ELEMENT persons (person)>
<!ELEMENT person (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
引入DTD约束
引入DTD约束的三种方法
引入本地dtd
<!DOCTYPE 根元素名称 SYSTEM ‘DTD文件的路径'>
在xml文件内部引入
<!DOCTYPE 根元素名称 [ dtd文件内容 ]>
引入网络dtd
<!DOCTYPE 根元素的名称 PUBLIC "DTD文件名称" "DTD文档的URL">
代码实现
- 引入本地的dtd ```xml
<?xml version=”1.0” encoding=”UTF‐8” ?> <!DOCTYPE persons SYSTEM ‘persondtd.dtd’>
- 在xml文件内部引入
```xml
<?xml version="1.0" encoding="UTF‐8" ?>
<!DOCTYPE persons [
<!ELEMENT persons (person)>
<!ELEMENT person (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
]>
<persons>
<person>
<name>张三</name>
<age>23</age>
</person>
</persons>
- 引入网络dtd
<?xml version="1.0" encoding="UTF‐8" ?>
<!DOCTYPE persons PUBLIC "dtd文件的名称" "dtd文档的URL">
<persons>
<person>
<name>张三</name>
<age>23</age>
</person>
</persons>
五、枚举类
5.1、枚举类的认识
(1)枚举类的概述
- 为了间接的表示一些固定的值,Java就给我们提供了枚举 是指将变量的值一一列出来,变量的值只限于列举出来的 值的范围内
枚举类的定义格式
public enum s {
枚举项1,枚举项2,枚举项3;
}
//注意: 定义枚举类要用关键字enum
枚举类的特点
- 所有枚举类都是Enum的子类
- 我们可以通过”枚举类名.枚举项名称”去访问指定的枚举项
- 每一个枚举项其实就是该枚举的一个对象
- 枚举也是一个类,也可以去定义成员变量
- 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东 西,这个分号就不能省略。建议不要省略
- 枚举类可以有构造器,但必须是private的,它默认的也是private的。 枚举项的用法比较特殊:枚举(“”);
- 枚举类也可以有抽象方法,但是枚举项必须重写该方法
- 示例代码
```java
public enum Season {
SPRING(“春”){
}, SUMMER(“夏”){//如果枚举类中有抽象方法
//那么在枚举项中必须要全部重写
@Override
public void show() {
System.out.println(this.name);
}
}, AUTUMN(“秋”){@Override
public void show() {
System.out.println(this.name);
}
}, WINTER(“冬”){@Override
public void show() {
System.out.println(this.name);
}
}; public String name; //空参构造 //private Season(){} //有参构造 private Season(String name){@Override
public void show() {
System.out.println(this.name);
}
} //抽象方法 public abstract void show(); }this.name = name;
public class EnumDemo { public static void main(String[] args) { / 1.所有枚举类都是Enum的子类 2.我们可以通过”枚举类名.枚举项名称”去访问指定的枚举项 3.每一个枚举项其实就是该枚举的一个对象 4.枚举也是一个类,也可以去定义成员变量 5.枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的, 但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略 6.枚举类可以有构造器,但必须是private的,它默认的也是private的。 枚举项的用法比较特殊:枚举(“”); 7.枚举类也可以有抽象方法,但是枚举项必须重写该方法 / //第二个特点的演示 //我们可以通过”枚举类名.枚举项名称”去访问指定的枚举项 System.out.println(Season.SPRING); System.out.println(Season.SUMMER); System.out.println(Season.AUTUMN); System.out.println(Season.WINTER); //第三个特点的演示 //每一个枚举项其实就是该枚举的一个对象 Season spring = Season.SPRING; } }
<a name="eecc3b469bcf40cd7d82a74f94b30c9e"></a>
### (2)枚举类的方法
- 常用方法
| 方法名 | 功能说明 |
| --- | --- |
| String name() | 获取枚举项的名称 |
| int ordinal() | 返回枚举项在枚举类中的索引值 |
| int compareTo(E o) | 比较两个枚举项,返回的是索引值的差值 |
| String toString() | 返回枚举常量的名称 |
| static T valueOf(Class type,String name) | 获取指定枚举类中的指定名称的枚举值 |
| values() | 获得所有的枚举项 |
- 示例代码
```java
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER;
}
public class EnumDemo {
public static void main(String[] args) {
// String name() 获取枚举项的名称
String name = Season.SPRING.name();
System.out.println(name);
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
// int ordinal() 返回枚举项在枚举类中的索引值
int index1 = Season.SPRING.ordinal();
int index2 = Season.SUMMER.ordinal();
int index3 = Season.AUTUMN.ordinal();
int index4 = Season.WINTER.ordinal();
System.out.println(index1);
System.out.println(index2);
System.out.println(index3);
System.out.println(index4);
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
// int compareTo(E o) 比较两个枚举项,返回的是索引值的差值
int result = Season.SPRING.compareTo(Season.WINTER);
System.out.println(result);//‐3
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
// String toString() 返回枚举常量的名称
String s = Season.SPRING.toString();
System.out.println(s);
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
// static <T> T valueOf(Class<T> type,String name)
// 获取指定枚举类中的指定名称的枚举值
Season spring = Enum.valueOf(Season.class, "SPRING");
System.out.println(spring);
System.out.println(Season.SPRING == spring);
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
// values() 获得所有的枚举项
Season[] values = Season.values();
for (Season value : values) {
System.out.println(value);
}
}
}
六、注解
6.1、注解的认识
(1)注解的概念
- 概念:
- 对我们的程序进行标注和解释
- 注解和注释的区别
- 注释: 给程序员看的
- 注解: 给编译器看的
- 使用注解进行配置的优势
- 代码更加简洁,方便
- java提供的注解:
- @Override 描述子类重写父类的方法
- @Deprecated 描述方法过时
- @SuppressWanrnings 压制警告
(2)自定义注解
格式
public @interface 注解名称 {
public 属性类型 属性名() default 默认值 ;
}
- 注:注解类实际上是一个接口,里面的属性是抽象方法
- 特别介绍:isAnnotationPresent(Class<? >c) :此方法参数为自定义注解的类对象,表示为该测试类或方法上是否有该注解;则返回true ,没有则返回false
- 属性类型
- 基本数据类型
- String
- Class
- 注解
- 枚举
- 以上类型的一维数组
- 代码示例 ```java public @interface Anno2 { } / 枚举类 / public enum Season { SPRING,SUMMER,AUTUMN,WINTER; }
public @interface Anno1 { //定义一个基本类型的属性;默认值是23 int a () default 23; //定义一个String类型的属性;默认值是“itheima” public String name() default “itheima”; //定义一个Class类型的属性;默认值是Anno2.Class public Class clazz() default Anno2.class; //定义一个注解类型的属性;默认值是@Anno2 public Anno2 anno() default @Anno2; //定义一个枚举类型的属性; public Season season() default Season.SPRING; //以上类型的一维数组 //int数组 public int[] arr() default {1,2,3,4,5};
//枚举数组
public Season[] seasons() default {Season.SPRING,Season.SUMMER};
//value。后期我们在使用注解的时候,如果我们只需要给注解的value属性赋值。
//那么value就可以省略
public String value();
}
//在使用注解的时候如果注解里面的属性没有指定默认值。 //那么我们就需要手动给出注解属性的设置值。 //@Anno1(name = “itheima”) @Anno1(“abc”) public class AnnoDemo { }
- 注意:如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
- 自定义注解案例
- 需求:自定义一个注解@Test,用于指定类的方法上,如果某一个类的方法上使用了该注解,就执行该方法
- 实现步骤:
- 自定义一个注解Test,并在类中的某几个方法上加上注解
- 在测试类中,获取注解所在的类的Class对象
- 获取类中所有的方法对象
- 遍历每一个方法对象,判断是否有对应的注解
- 代码实现
```java
/*
自定义的注解类
*/
//表示Test这个注解的存活时间
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Test {
}
/*
使用自定义注解的类
*/
public class UseTest {
//没有使用Test注解
public void show(){
System.out.println("UseTest....show....");
}
//使用Test注解
@Test
public void method(){
System.out.println("UseTest....method....");
}
//使用Test注解
@Test
public void function(){
System.out.println("UseTest....function....");
}
}
/*测试类*/
public class AnnoDemo {
public static void main(String[] args) throws Exception {
//1.通过反射获取UseTest类的字节码文件对象
Class clazz = Class.forName("com.itheima.myanno3.UseTest");
//创建对象
UseTest useTest = (UseTest) clazz.newInstance();
//2.通过反射获取这个类里面所有的方法对象
Method[] methods = clazz.getDeclaredMethods();
//3.遍历数组,得到每一个方法对象
for (Method method : methods) {
//method依次表示每一个方法对象。
//isAnnotationPresent(Class<? extends Annotation> annotationClass)
//判断当前方法上是否有指定的注解。
//参数:注解的字节码文件对象
//返回值:布尔结果。 true 存在 false 不存在
if(method.isAnnotationPresent(Test.class)){
method.invoke(useTest);
}
}
}
}
6.2、元注解
(1)元注解的认识
- 元注解的概述:元注解就是描述注解的注解
常用的元注解 | 元注解名 | 功能说明 | | —- | —- | | @Target | 指定了注解能在哪里使用 | | @Retention | 可以理解为保留时间(生命周期)注解的存活时间 | | @Inherited | 表示修饰的自定义注解可以被子类继承 | | @Documented | 表示该自定义注解,会出现在API文档里面。 |
示例代码 ```java @Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD}) //指定注解使用的位置(成员变 量,类,方法) @Retention(RetentionPolicy.RUNTIME) //指定该注解的存活时间 //@Inherited //指定该注解可以被继承 public @interface Anno { }
@Anno public class Person { }
public class Student extends Person { public void show(){ System.out.println(“student…….show……….”); } }
public class StudentDemo { public static void main(String[] args) throws ClassNotFoundException { //获取到Student类的字节码文件对象 Class clazz = Class.forName(“com.itheima.myanno4.Student”); //获取注解。 boolean result = clazz.isAnnotationPresent(Anno.class); System.out.println(result); } }
<a name="PAY6S"></a>
# 七、动态代理以及java1.8新特性
<a name="TIyJf"></a>
## 7.1 动态代理的认识
<a name="vaYWx"></a>
### (1)什么是动态代理?
- 被代理对象是一个接口,代理对象同时也要实现该接口
- 代码演示
```xml
/*
接口类
*/
package com.xiaoha.day16_advanced;
/**
* 人类接口
*
* @author HausenLee
* @date 2021/05/20
*/
public interface Human {
public abstract String belief();
public abstract void eat();
}
/*
被代理对象类
*/
package com.xiaoha.day16_advanced;
/**
* 超人类实现人类接口
*
* @author HausenLee
* @date 2021/05/20
*/
public class SuperMan implements Human{
@Override
public String belief() {
return "I believe I can fly......";
}
@Override
public void eat() {
System.out.println("I can eat,too.......");
}
}
/*
动态代理工厂类
*/
package com.xiaoha.day16_advanced;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理工厂类
*
* @author HausenLee
* @date 2021/05/20
*/
public class ProxyFactory {
public static Object proxyFactory(Object object){
Class<?>[] interfaces = object.getClass().getInterfaces();
return Proxy.newProxyInstance(object.getClass().getClassLoader(),interfaces,new InvocationHandler(){
/**
*
* @param proxy 代理类对象
* @param method 代理对象的方法对象
* @param args 方法的参数
* @return 方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object,args);
return result;
}
});
}
}
/*
演示类
*/
package com.xiaoha.day16_advanced;
import javax.swing.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
/**
* 动态代理演示类
*
* @author HausenLee
* @date 2021/05/20
*/
public class ProxyDemo {
public static void main(String[]args){
SuperMan sm = new SuperMan();
//必须墙砖为代理对象与被代理对象的公共接口
//Human o = (Human)ProxyFactory.proxyFactory(sm);
//System.out.println(o.belief());
ArrayList<String> list = new ArrayList<>();
list.add("abc");
list.add("123");
list.add("def");
List lsits = (List)ProxyFactory.proxyFactory(list);
list.add("de");
}
}