Java学习记录五(多线程、网络编程、Lambda表达式和接口组成更新)
Java学习记录五(多线程、网络编程、Lambda表达式和接口组成更新)
- Java
- 25.多线程
- 25.1实现多线程
- 25.1.1进程
- 25.1.2线程
- 25.1.3多线程的实现
- 25.1.4设置和获取线程名称
- 25.1.5线程调度
- 25.1.6线程控制
- 25.1.7线程生命周期
- 25.1.8多线程的实现方式
- 25.2线程同步
- 25.2.1线程同步案例卖票
- 线程同步案例卖票的思考:
- 线程同步案例卖票的解决:
- 同步代码块
- 25.2.2同步方法
- 25.2.3线程安全的类
- 25.2.4Lock锁
- 25.3生产者消费者
- 25.3.1生产者消费者模式概述
- 25.3.2生产者消费者案例
- 26.网络编程
- 26.1网络编程入门
- 26.1.1网络编程概述
- 26.1.2网络编程三要素
- 26.1.3IP地址
- 26.1.4InetAddress的使用
- 26.1.5端口
- 26.1.6协议
- 26.2UDP通信程序
- 26.2.1UDP通信原理
- 26.2.2UDP发送数据
- 26.2.3UDP接收数据
- 26.2.4 UDP通信程序练习
- 26.3TCP通信程序
- 26.3.1TCP通信原理
- 26.3.2TCP发送数据
- 26.3.3TCP接收数据
- 26.3.4TCP通信程序练习
- 练习1
- 练习2
- 练习3
- 练习4
- 练习5
- 练习6
- 27.Lambda表达式
- 27.1函数式编程思想概述
- 27.2体验Lambda表达式
- 27.3Lambda表达式的标准格式
- 27.4Lambda表达式的练习
- 练习1:
- 练习2:
- 练习3
- 27.5Lambda表达式的省略模式
- 27.6Lambda表达式的注意事项
- 27.7Lambda表达式和匿名内部类的区别
- 28.接口组成更新
- 28.1接口组成更新概述
- 28.2接口中默认方法
- 28.3接口中静态方法
- 28.4接口中私有方法
- 参考资料
Java
25.多线程
25.1实现多线程
25.1.1进程
进程:是正在运行的程序
- 是系统进行资源分配和调用的独立单位
- 每一个进程都有它自己的内存空间和系统资源
25.1.2线程
线程:是进程中的单个顺序控制流,是一条执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序
- 多线程:一个进程如果有多条执行路径,则称为多线程程序
举例
- 记事本程序
- 扫雷程序
25.1.3多线程的实现
方式方式1:继承Thread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
两个小问题:
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码run)方法和start)方法的区别?run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
package Target001;public class MyThread extends Thread{@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println(i);}}
}
package Target001;
/*方式1:继承Thread类- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程*/
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();}
}
25.1.4设置和获取线程名称
Thread类中设置和获取线程名称的方法
- void setName(String name):将此线程的名称更改为等于参数name
- String getName():返回此线程的名称
- 通过构造方法也可以设置线程名称
如何获取main()方法所在的线程名称?
- public static Thread currentThread():返回对当前正在执行的线程对象的引用
package Target002;public class MyThread extends Thread{public MyThread(String name) {super(name);}public MyThread(){}@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println(getName()+":"+i);}}
}
/* private String name;
public Thread() {this(null, null, "Thread-" + nextThreadNum(), 0);
}public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {this(group, target, name, stackSize, null, true);
}private Thread(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {this.name = name;
}public final synchronized void setName(String name) {checkAccess();this.name = name;
}public final String getName() {return name;
}
private static int threadInitNumber;//0,1,2private static synchronized int nextThreadNum() {return threadInitNumber++;//0,1,...
}*/
package Target002;/*
Thread类中设置和获取线程名称的方法- void setName(Stringname):将此线程的名称更改为等于参数name
- String getName():返回此线程的名称*/
public class MyThreadDemo {public static void main(String[] args) {// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
//
// //- void setName(Stringname):将此线程的名称更改为等于参数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());}
}
25.1.5线程调度
线程有两种调度模型
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
Java使用的是抢占式调度模型
假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
Thread类中设置和获取线程优先级的方法
public final int getPriority():返回此线程的优先级
public final void setPriority(int newPriority):更改此线程的优先级
线程默认优先级是5;线程优先级的范围是:1-10
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
package Target003;public class ThreadPriority extends Thread{public ThreadPriority(String name) {super(name);}public ThreadPriority(){}@Overridepublic void run() {for(int i=0;i<100;i++){System.out.println(getName()+":"+i);}}
}
package Target003;
/*
Thread类中设置和获取线程优先级的方法
- public final int getPriority()):返回此线程的优先级
- public final void setPriority(int newPriority):更改此线程的优先级*/
public class ThreadPriorityDemo {public static void main(String[] args) {ThreadPriority tp1 = new ThreadPriority("利拉德");ThreadPriority tp2 = new ThreadPriority("莱昂纳地");ThreadPriority tp3 = new ThreadPriority("杜兰特");// tp1.start();//5
// tp2.start();//5
// tp3.start();//5//public final int getPriority()):返回此线程的优先级System.out.println(tp1.getPriority());System.out.println(tp2.getPriority());System.out.println(tp3.getPriority());//public final void setPriority(int newPriority):更改此线程的优先级
// tp1.setPriority(10000);//IllegalArgumentException
// System.out.println(Thread.MAX_PRIORITY);
// System.out.println(Thread.MIN_PRIORITY);
// System.out.println(Thread.NORM_PRIORITY);//设置正确优先级tp1.setPriority(10);tp2.setPriority(5);tp3.setPriority(1);//优先级高仅仅代表获取CPU时间片几率高,并不是每次都在最前面tp1.start();tp2.start();tp3.start();}
}
25.1.6线程控制
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() | 等待这个线程死亡 |
void setDaemon(boolean on) |
将此线程标记为守护线程,当运行的线程都是守护线程时 Java虚拟机将退出 |
package Target004;
/*
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出*/
public class TheadDaemonDemo {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);for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + i);}}
}
package Target004;public class ThreadDaemon extends Thread{@Overridepublic void run() {for( int i=0;i<100;i++){System.out.println(getName()+i);}}
}
package Target004;public class ThreadJoin extends Thread{@Overridepublic void run() {for( int i=0;i<100;i++){System.out.println(getName()+i);}}
}
package Target004;
/*
void join():等待这个线程死亡*/
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();}
}
package Target004;public class ThreadSleep extends Thread{@Overridepublic void run() {for( int i=0;i<100;i++){System.out.println(getName()+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
package Target004;
/*
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数*/
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();}
}
25.1.7线程生命周期
25.1.8多线程的实现方式
方式2:实现Runnable接口
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
多线程的实现方案有两种
- 继承Thread类
- 实现Runnable接口
相比继承Thread类,实现Runnable接口的好处
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离
较好的体现了面向对象的设计思想
package Target005;public class MyRunnable implements Runnable{//好处是将来可以有它自己的父类,把Runnable看成同一个资源由多个线程去使用@Overridepublic void run() {for(int i= 0;i<100;i++){System.out.println(Thread.currentThread().getName()+i);//实现的是Runnable接口,没有继承Thread类,不能实现getName()方法}}
}
package Target005;
/*
方式2:实现Runnable接口- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程*/
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 t1 = new Thread(my,"利拉德");//可看作同一个资源由多个线程来使用Thread t2 = new Thread(my,"杜兰特");//启动线程t1.start();t2.start();}
}
25.2线程同步
25.2.1线程同步案例卖票
需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路:
定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
在SellTicket类中重写run)方法实现卖票,代码步骤如下
A:判断票数大于0,就卖票,并告知是哪个窗口卖的
B:卖了票之后,总票数要减1
C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
A:创建SellTicket类的对象
B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
C:启动线程
线程同步案例卖票的思考:
package Target006;
/*1.定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;*/
public class SellTicket implements Runnable{private int tickets = 100;@Overridepublic void run() {//相同的票出现了多次
// while(true)
// //tickets = 100
// //t1 t2 t3
// //假设t1线程抢到CPU的执行权
// if (tickets > 0){// Thread.sleep(100);
// //t1线程休息100毫秒
// //t2线程抢到了CPU的执行权,t2线程就开始执行。执行到这里的时候,t2线程休息100毫秒
// //t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
// try {// } catch (InterruptedException e) {// e.printStackTrace();
// }
// //假设线程按照顺序醒过来
// //t1抢到CPU的执行权,在控制台输出:窗口1正在出售100张票
// System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
// //这个时候tickets还没有--,t2抢到CPU的执行权,在控制台输出:窗口2正在出售100张票
// //t3抢到CPU的执行权,在控制台输出:窗口3正在出售100张票
// tickets--;
// //如果这三个线程还是按照顺序来,这里就执行了3次--的操作,最终票就变成了97
// }//出现了负数的票while(true)//tickets = 100//t1 t2 t3//假设t1线程抢到CPU的执行权if (tickets > 0){//t1线程休息100毫秒//t2线程抢到了CPU的执行权,t2线程就开始执行。执行到这里的时候,t2线程休息100毫秒//t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//假设线程按照顺序醒过来//t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票//假设t1继续拥有CPU的执行权,就会执行tickets--;操作 ticket = 0//t2抢到了CPU的执行权,在控制台输出:窗口正在出售第0张票//假设t2继续拥有CPU的执行权,就会执行tickets--;操作 ticket = -1//t3(随便谁)抢到了CPU的执行权,在控制台输出:窗口正在出售第-1张票//假设t2继续拥有CPU的执行权,就会执行tickets--;操作 ticket = -2(看不到)System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");tickets--;//如果这三个线程还是按照顺序来,这里就执行了3次--的操作,最终票就变成了97}}
}
package Target006;
/*需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路:
1.定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
2.在SellTicket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:卖了票之后,总票数要减1C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
3.定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下A:创建SellTicket类的对象B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class SellTicketDemo {public static void main(String[] args) {//A:创建SellTicket类的对象SellTicket st = new SellTicket();//B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");//C:启动线程t1.start();t2.start();t3.start();}
}
卖票出现了问题
- 相同的票出现了多次
- 出现了负数的票
问题原因:
- 线程执行的随机性导致的
线程同步案例卖票的解决:
为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
如何解决多线程安全问题呢?
- 基本思想:让程序没有安全问题的环境
怎么实现呢?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- Java提供了同步代码块的方式来解决
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized(任意对象){
多条语句操作共享数据的代码
}synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
package Target007;
/*1.定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;*/
public class SellTicket implements Runnable{private int tickets = 100;private Object obj = new Object();//这样三个线程,一把锁@Overridepublic void run() {while(true)//tickets = 100//t1,t2,t3//假设t1抢到了CPU的执行权//假设t2抢到了CPU的执行权synchronized (obj) {//如果不加private Object三个线程,三把锁//t1进来后,就会被这段代码块锁起来,下一次是t2if (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出来了,这段代码的锁就被释放了}
}
package Target007;/*需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路:
1.定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
2.在SellTicket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:卖了票之后,总票数要减1C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
3.定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下A:创建SellTicket类的对象B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class SellTicketDemo {public static void main(String[] args) {//A:创建SellTicket类的对象SellTicket st = new SellTicket();//B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");//C:启动线程t1.start();t2.start();t3.start();}
}
25.2.2同步方法
本节的内容主要是:通过 if(x%2==0) 故意将程序一分为二,让“同步代码块”与“同步方法块” 两种同步方法进行比较
目的是对比,从而推导出“同步方法块” 的“锁”隐藏内容 究竟是什么,只有当If( x%2 ) 两边的锁一致时,程序才不会出现安全问题。这节课,老师的操作可谓是精彩无比!
同步方法:就是把synchronized关键字加到方法上
- 格式:
修饰符synchronized返回值类型方法名(方法参数){}
同步方法的锁对象是什么呢?
- this
同步静态方法:就是把synchronized关键字加到静态方法上
- 格式:
修饰符static synchronized返回值类型方法名(方法参数){}
同步静态方法的锁对象是什么呢?
- 类名.class
package Target008;
/*1.定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;*/public class SellTicket implements Runnable{// private int tickets = 100;private static int tickets = 100;private Object obj = new Object();//这样三个线程,一把锁private int x = 0;@Overridepublic void run() {while(true){if(x%2 == 0){//synchronized (obj) {// synchronized (this) {synchronized (SellTicket.class) {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}}}else {// synchronized (obj) {//如果不加private Object三个线程,三把锁
// //t1进来后,就会被这段代码块锁起来,下一次是t2
// 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
// }
// }sellTicket();}x++;}}//把同步代码封装成方法
// private void sellTicket() {// synchronized (obj) {// 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
// }
// }
// }//把同步代码封装成方法private static synchronized void sellTicket() {//方法内部有一个对象this代表本类,this不是静态,这时不能使用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}}
}
package Target008;/*需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路:
1.定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
2.在SellTicket类中重写run()方法实现卖票,代码步骤如下A:判断票数大于0,就卖票,并告知是哪个窗口卖的B:卖了票之后,总票数要减1C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
3.定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下A:创建SellTicket类的对象B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/
public class SellTicketDemo {public static void main(String[] args) {//A:创建SellTicket类的对象SellTicket st = new SellTicket();//B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");//C:启动线程t1.start();t2.start();t3.start();}
}
25.2.3线程安全的类
StringBuffer
- 线程安全,可变的字符序列
- 从版本JDK5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它
个更快,因为它不执行同步
Vector
- 从Java 2平台v1.2开始,该类改进了List接口,使其成为JavaCollections Framework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector
Hashtable
- 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值
- 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable
package Target009;import java.util.*;public class ThreadDemo {public static void main(String[] args) {StringBuffer sb = new StringBuffer();//源码方法都是同步方法,所以线程安全StringBuilder sb2 = new StringBuilder();//线程不安全Vector<String> v = new Vector<>();//源码方法都是同步方法,所以线程安全ArrayList<String> array = new ArrayList<>();//线程不安全Hashtable<String,String> ht= new Hashtable<>();HashMap<String,String> hm= new HashMap<>();//static〈T> list<T> synchronizedList (List<T> list)返回由指定列表支持的同步(线程安全)列表//也就是说使用之后变成一个线程安全的集合Collections.synchronizedList(new ArrayList<String>());//源码用同步代码块的方式来保证线程安全}
}
25.2.4Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对寸象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
- void lock():获得锁
- void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
- ReentrantLock():创建一个ReentrantLock的实例
package Target010;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class SellTicket implements Runnable{private int tickets = 100;private Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try{lock.lock();if (tickets > 0) {try {Thread.sleep(100);//t1休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}} finally{lock.unlock();//这样即使上面代码出问题,也会释放锁,出不出问题都会解锁}//lock.unlock();//一旦上面出现问题,这个释放锁的操作就没有释放掉。}}
}package Target010;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();}
}
25.3生产者消费者
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻所谓生产者消费者问题,实际上主要是包含了两类线程:
- 一类是生产者线程用于生产数据
- 一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
- 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
- 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
25.3.1生产者消费者模式概述
为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中Object类的等待和唤醒方法:
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
25.3.2生产者消费者案例
生产者消费者案例中包含的类:
- 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
- 生产者类(Producer):实现Runnable接口,重写run0)方法,调用存储牛奶的操作
- 消费者类(Customer):实现Runnable接口,重写run)方法,调用获取牛奶的操作
- 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
1创建奶箱对象,这是共享数据区域
2创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
3创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
4创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
5启动线程
package Target011;public class Box {//定义一个成员变量,表示第x瓶奶private int milk;//定义一个成员变量,表示奶香的状态private boolean state = false;//提供存储牛奶和获取牛奶的操作public synchronized void put(int milk){//存储牛奶//加了synchronized//如果有牛奶,等待消费if(state == true){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();}
}
package Target011;
/*
生产者消费者案例中包含的类:- 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
- 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
- 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
- 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下创建奶箱对象,这是共享数据区域创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递启动线程*/
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();}
}
package Target011;public class Customer implements Runnable{private Box b;public Customer(Box b) {this.b= b;}@Overridepublic void run() {while (true){b.get();}}
}
package Target011;public class Producer implements Runnable{private Box b;public Producer(Box b) {this.b= b;}@Overridepublic void run() {for(int i = 1;i<=30;i++){b.put(i);}}
}
26.网络编程
26.1网络编程入门
26.1.1网络编程概述
计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
网络编程
- 在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换
26.1.2网络编程三要素
IP地址
- 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而lP地址就是这个标识号。也就是设备的标识
端口
- 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识
协议
- 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
26.1.3IP地址
IP地址:是网络中设备的唯一标识
IP地址分为两大类
- IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个P地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“1100000010101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号"”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址士的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
- IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
常用命令:
- ipconfig:查看本机IP地址
- ping lP地址:检查网络是否连通
特殊IP地址:
- 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
26.1.4InetAddress的使用
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用
InetAddress:此类表示Internet协议(IP)地址
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
package Target012;import java.net.InetAddress;
import java.net.UnknownHostException;/*InetAddress此类表示Internet协议(IP)地址public static InetAddress getByWNome (String host):确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址public String getHostName():获取此IP地址的主机名public String getHostAddress ():返回文本显示中的IP地址字符串*/
public class InetAddressDemo {public static void main(String[] args) throws UnknownHostException {//public static InetAddress getByWNome (String host):确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址InetAddress address = InetAddress.getByName("DESKTOP-VIK5RMM");//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);}
}
26.1.5端口
端口:设备上应用程序的唯一标识
端口号:用两个字节表示的整数,它的取值范围是O65535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
26.1.6协议
协议:计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议
- 用户数据报协议(User Datagram Protocol)
- UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输 - 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
TCP协议
传输控制协议(Transmission Control Protocol)
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
26.2UDP通信程序
26.2.1UDP通信原理
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
Java提供了DatagramSocket类作为基于UDP协议的Socket
26.2.2UDP发送数据
发送数据的步骤
创建发送端的Socket对象(DatagramSocket)
DatagramSocket()
创建数据,并把数据打包
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
调用DatagramSocket对象的方法发送数据
void send(DatagramPacket p)
关闭发送端
void close)
package Target013;import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;/*
UDP发送数据的步骤创建发送端的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(StandardCharsets.UTF_8);
// int length = bys.length;
// InetAddress address = InetAddress.getByName("192.168.1.66");
// int port = 10086;//端口
// DatagramPacket dp = new DatagramPacket(bys,length,address,port);DatagramPacket dp = new DatagramPacket(bys, bys.length,InetAddress.getByName("DESKTOP-VIK5RMM"),10086);//调用DatagramSocket对象的方法发送数据// void send (Datagramacketp)从此套接字发送数据报包ds.send(dp);//关闭发送端// void close ()关闭此数据报套接字ds.close();}
}
26.2.3UDP接收数据
接收数据的步骤
创建接收端的Socket对象(DatagramSocket)
创建一个数据包,用于接收数据
调用DatagramSocket对象的方法接收数据
解析数据包,并把数据在控制台显示
关闭接收端
package Target013;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;/*UDp接收数据的步骤
1:创建接收端的Socket对象(DatagramSocket)
2:创建一个数据包,用于接收数据
3:调用DatagramSocket对象的方法接收数据
4:解析数据包,并把数据在控制台显示
5:关闭接收端*/
public class ReceiveDemo {public static void main(String[] args) throws IOException {//创建接收端的Socket对象(DatagramSocket)//DatagramSocket(int port)构造数据报套接字并将其绑定到本地主机上的指定端口DatagramSocket ds = new DatagramSocket(10086);//创建一个数据包,用于接收数据//DatagramSocket(byte[] buf,int length)构造一个DatagramPacket用于接收长度为length数据包byte[] bys = new byte[1024];//这个字节数组是定义的包裹DatagramPacket dp = new DatagramPacket(bys,bys.length);//调用DatagramSocket对象的方法接收数据ds.receive(dp);//解析数据包,并把数据在控制台显示0 //byte[] getData() 返回数据缓冲区
// byte[] datas = dp.getData();//这个字节数组中是包裹中得到的数据
// //int getLength ()返回要发送的数据的长度或接收到的数据的长度
// int len = dp.getLength();// String dataString = new String(datas,0,len);
// System.out.println("数据是"+dataString);System.out.println("数据是" + new String(dp.getData(),0,dp.getLength()));//关闭接收端ds.close();}
}
26.2.4 UDP通信程序练习
按照下面的要求实现程序
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
package Target014;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;/*
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束*/
public class SendDemo {public static void main(String[] args) throws IOException {//创建发送端的Socket对象(DatagramSocket)DatagramSocket ds = new DatagramSocket();//自己封装键盘录入数据BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String line;while((line = br.readLine())!= null){//输入的数据是886,发送数据结束if("886".equals(line)) break;//创建数据,并把数据打包byte[] bys = line.getBytes();DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("DESKTOP-VIK5RMM"),12345);//调用DatagramSocket对象的方法发送数据ds.send(dp);}//关闭发送端ds.close();}
}
package Target014;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/*
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();//不用关了,因为一直接收数据}
}
26.3TCP通信程序
26.3.1TCP通信原理
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生)O流来进行网络通信
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
26.3.2TCP发送数据
发送数据的步骤
创建客户端的Socket对象(Socket)
Socket(String host, int port)
获取输出流,写数据
OutputStreamgetOutputStream()
释放资源
void close()
package Target015;import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;//TCP是需要和服务器进行三次握手的,所以会报错
public class CilentDemo {public static void main(String[] args) throws IOException {//创建客户端的Socket对象(Socket)//Socket (InetAddress address,int port)创建流套接字并将其连接到指定IP地址的指定端口号
// Socket s = new Socket(InetAddress.getByName("DESKTOP-VIK5RMM"),10000);//Socket (String host, int port)创建流套接字并将其连接到指定主机上的指定端口号Socket s = new Socket("DESKTOP-VIK5RMM",10000);//发送到DESKTOP-VIK5RMM主机10000端口上//获取输出流,写数据//OutputStream getOutputStream ()返回此套接字的输出流OutputStream os = s.getOutputStream();os.write("hello,tcp我来了".getBytes());//释放资源s.close();}
}
26.3.3TCP接收数据
接收数据的步骤
创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
监听客户端连接,返回一个Socket对象
Socket accept()
获取输入流,读数据,并把数据显示在控制台
lnputStream getInputStream)
释放资源
void close()
package Target015;import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;//TCP是需要和服务器进行三次握手的,所以会报错
public class CilentDemo {public static void main(String[] args) throws IOException {//创建客户端的Socket对象(Socket)//Socket (InetAddress address,int port)创建流套接字并将其连接到指定IP地址的指定端口号
// Socket s = new Socket(InetAddress.getByName("DESKTOP-VIK5RMM"),10000);//Socket (String host, int port)创建流套接字并将其连接到指定主机上的指定端口号Socket s = new Socket("DESKTOP-VIK5RMM",10000);//发送到DESKTOP-VIK5RMM主机10000端口上//获取输出流,写数据//OutputStream getOutputStream ()返回此套接字的输出流OutputStream os = s.getOutputStream();os.write("hello,tcp我来了".getBytes());//释放资源s.close();}
}
package Target015;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;/*
接收数据的步骤
1创建服务器端的Socket对象(ServerSocket)
2获取输入流,读数据,并把数据显示在控制台
3释放资源*/
public class SeverDemo {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();}
}
26.3.4TCP通信程序练习
练习1
客户端:发送数据,接收服务器反馈
服务器:接收数据,给出反馈
package Target016;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
客户端:发送数据,接收服务器反馈*/
public class ClientDemo {public static void main(String[] args) throws IOException {//创建客户端的Socket对象(Socket)Socket s = new Socket("DESKTOP-VIK5RMM",10000);//获取输出流,写数据OutputStream os = s.getOutputStream();os.write("hello,tcp我来了".getBytes());//接收服务器反馈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);//释放资源
// is.close();
// os.close();s.close();}
}
//客户端是先写数据,后读数据
//服务器是先读数据,后写数据
package Target016;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*服务器:接收数据,给出反馈*/
public class SeverDemo {public static void main(String[] args) throws IOException {//创建服务器端的socket对象(ServerSocket)ServerSocket ss = new ServerSocket(10000);//监听客户端连接,返回一个socket对象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);//给出反馈OutputStream os = s.getOutputStream();os.write("数据已经收到".getBytes());//释放资源
// s.close();//ss关闭,s自然关闭ss.close();}
}
//客户端是先写数据,后读数据
//服务器是先读数据,后写数据
练习2
- 客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束
- 服务器:接收到的数据在控制台输出
package Target017;import java.io.*;
import java.net.Socket;
/*
客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束*/
public class ClientDemo {public static void main(String[] args) throws IOException {//创建客户端Socket对象Socket s = new Socket("DESKTOP-VIK5RMM",10000);//数据来自于键盘录入,直到输入的数据是886,发送数据结束BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//封装输出流对象BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//字节输出流转换为字符流再包装成BufferedWriterString line;while((line = br.readLine())!=null){if("886".equals(line)) break;//获取输出流对象
// OutputStream os = s.getOutputStream();
// os.write(line.getBytes());bw.write(line);bw.newLine();bw.flush();}//释放资源s.close();}
}
package Target017;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/*
服务器:接收到的数据在控制台输出*/
public class SeverDemo {public static void main(String[] args) throws IOException {//创建服务器Socket对象ServerSocket ss = new ServerSocket(10000);//监听客户端的连接,返回一个对应的Socket对象Socket s = ss.accept();//获取输入流
// InputStream is = s.getInputStream();
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader br = new BufferedReader(isr);BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//字节输入流转换为字符流再包装成Reader,如果是图片就不能这么转String line;while((line = br.readLine())!=null){System.out.println(line);}//释放资源ss.close();}
}
练习3
客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束
服务器:接收到的数据写入文本文件
package Target017;import java.io.*;
import java.net.Socket;
/*
客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束*/
public class ClientDemo {public static void main(String[] args) throws IOException {//创建客户端Socket对象Socket s = new Socket("DESKTOP-VIK5RMM",10000);//数据来自于键盘录入,直到输入的数据是886,发送数据结束BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//封装输出流对象BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//字节输出流转换为字符流再包装成BufferedWriterString line;while((line = br.readLine())!=null){if("886".equals(line)) break;//获取输出流对象
// OutputStream os = s.getOutputStream();
// os.write(line.getBytes());bw.write(line);bw.newLine();bw.flush();}//释放资源s.close();}
}
package Target017;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/*
服务器:接收到的数据在控制台输出*/
public class SeverDemo {public static void main(String[] args) throws IOException {//创建服务器Socket对象ServerSocket ss = new ServerSocket(10000);//监听客户端的连接,返回一个对应的Socket对象Socket s = ss.accept();//获取输入流
// InputStream is = s.getInputStream();
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader br = new BufferedReader(isr);BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//字节输入流转换为字符流再包装成Reader,如果是图片就不能这么转String line;while((line = br.readLine())!=null){System.out.println(line);}//释放资源ss.close();}
}
练习4
客户端:数据来自于文本文件
服务器:接收到的数据写入文本文件
package Target018;import java.io.*;
import java.net.Socket;
/*
客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束*/
public class CilentDemo {public static void main(String[] args) throws IOException {//创建客户端Socket对象Socket s = new Socket("DESKTOP-VIK5RMM",10000);//数据来自于键盘录入,直到输入的数据是886,发送数据结束BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//封装输出流对象BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//字节输出流转换为字符流再包装成BufferedWriterString line;while((line = br.readLine())!=null){if("886".equals(line)) break;//获取输出流对象
// OutputStream os = s.getOutputStream();
// os.write(line.getBytes());bw.write(line);bw.newLine();bw.flush();}//释放资源s.close();}
}
package Target018;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
服务器:接收到的数据写入文本文件*/
public class SeverDemo {public static void main(String[] args) throws IOException {//创建服务器对象ServerSocket ss = new ServerSocket(10000);//监听客户端连接,返回一个新的Socket对象Socket s = ss.accept();//接收数据BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//把数据写入文本文件BufferedWriter bw = new BufferedWriter(new FileWriter("Target\\001.txt"));String line;while((line=br.readLine())!=null){bw.write(line);bw.newLine();bw.flush();}//释放资源ss.close();bw.close();}
}
练习5
- 客户端:数据来自于文本文件,接收服务器反馈
- 服务器:接收到的数据写入文本文件,给出反馈
package Target020;import java.io.*;
import java.net.Socket;
/*
客户端:数据来自于文本文件,接收服务器反馈*/
public class CilentDemo {public static void main(String[] args) throws IOException {Socket s = new Socket("DESKTOP-VIK5RMM",10000);BufferedReader br = new BufferedReader(new FileReader("Target\\Target.iml"));BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line;while((line= br.readLine())!=null){bw.write(line);bw.newLine();bw.flush();}//public void shutdownOutput ()s.shutdownOutput();//接受反馈BufferedReader brCilent = new BufferedReader(new InputStreamReader(s.getInputStream()));String data = brCilent.readLine();//等待读取数据System.out.println("服务器的反馈是"+data);//释放资源br.close();s.close();}
}
package Target020;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
服务器:接收到的数据写入文本文件,给出反馈*/
public class SeverDemo {public static void main(String[] args) throws IOException {//创建服务器Scoket对象ServerSocket ss = new ServerSocket(10000);//监听客户端连接,返回一个对应的Socket对象Socket s = ss.accept();//接收数据BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//把数据写入文件BufferedWriter bw = new BufferedWriter(new FileWriter("Target\\copy.java"));String line;while((line = br.readLine()) != null){//等待读取数据
// if("886".equals(line)) break;bw.write(line);bw.newLine();bw.flush();}//给出反馈BufferedWriter bwSever = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));bwSever.write("上传文件成功");bwSever.newLine();bwSever.flush();//释放资源bw.close();ss.close();}
}
练习6
客户端:数据来自于文本文件,接收服务器反馈
服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程
package Target021;import java.io.*;
import java.net.Socket;
/*
客户端:数据来自于文本文件,接收服务器反馈*/
public class CilentDemo {public static void main(String[] args) throws IOException {Socket s = new Socket("DESKTOP-VIK5RMM",10000);BufferedReader br = new BufferedReader(new FileReader("Target\\Target.iml"));BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line;while((line= br.readLine())!=null){bw.write(line);bw.newLine();bw.flush();}//public void shutdownOutput ()s.shutdownOutput();//接受反馈BufferedReader brCilent = new BufferedReader(new InputStreamReader(s.getInputStream()));String data = brCilent.readLine();//等待读取数据System.out.println("服务器的反馈是"+data);//释放资源br.close();s.close();}
}
package Target021;import java.io.*;
import java.net.Socket;public class ServerThread implements Runnable {private Socket s;public ServerThread(Socket s) {this.s = s;}@Overridepublic void run() {try {BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));int count = 0;File file = new File("Target\\copy["+count+"].java");while(file.exists()){count ++;file = new File("Target\\copy["+count+"].java");}BufferedWriter bw = new BufferedWriter(new FileWriter(file));String line ;while ((line= br.readLine())!=null){bw.write(line);bw.newLine();bw.flush();}//给出反馈BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));bwServer.write("文件上传成功");bwServer.newLine();bwServer.flush();//释放资源s.close();} catch (IOException e) {e.printStackTrace();}}
}
package Target021;import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;public class SeverDemo {public static void main(String[] args) throws IOException {//创建服务器Socket端ServerSocket ss = new ServerSocket(10000);while(true){//监听客户端连接,返回一个对应的Socket对象Socket s = ss.accept();//为每一个客户端开启一个线程new Thread(new ServerThread(s)).start();}//ss.close();因为服务器是要一直开启的}
}
27.Lambda表达式
27.1函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作“
面向对象思想强调“必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而我们要学习的Lambda表达式就是函数式思想的体现
27.2体验Lambda表达式
需求:启动一个线程,在控制台输出一句话:多线程程序启动了
方式1:
- 定义一个类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable的对象作为构造参数传递
- 启动线程
方式2:
- 匿名内部类的方式改进
方式3:
- Lambda表达式的方式改进
package Target022;
/*
需求:启动一个线程,在控制台输出一句话:多线程程序启动了*/
public class LambdaDemo {public static void main(String[] args) {//实现类的方式实现需求
// MyRunnable mr = new MyRunnable();
// Thread thread = new Thread(mr);
// thread.start();//匿名内部类的方式改进
// new Thread(new Runnable() {// @Override
// public void run() {// System.out.println("多线程程序启动了");
// }
// }).start();//不需要补全了//Lambda表达式方式改进new Thread(() -> System.out.println("多线程程序启动了")).start();//别总是忘了加start}
}
package Target022;public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println("多线程程序启动了");}
}
27.3Lambda表达式的标准格式
匿名内部类中重写run()方法的代码分析
- 方法形式参数为空,说明调用方法时不需要传递参数
- 方法返回值类型为void,说明方法执行没有结果返回
- 方法体中的内容,是我们具体要做的事情
Lambda表达式的代码分析
- ()︰里面没有内容,可以看成是方法形式参数为空
- ->:用箭头指向后面要做的事情
- {}:包含一段代码,我们称之为代码块,可以看成是方法体中
组成Lambda表达式的三要素:形式参数,箭头,代码块
Lambda表达式的格式
- 格式:(形式参数)->{代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->:由英文中画线和大于符号组成,固定写法。代表指向动作
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
27.4Lambda表达式的练习
Lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
练习1:
- 定义一个接口(Eatable),里面定义一个抽象方法: void eat();
- 定义一个测试类(EatableDemo),在测试类中提供两个方法
一个方法是: useEatable(Eatable e)
—个方法是主方法,在主方法中调用useEatable方法
package Target023;public interface Eatable {public void eat();
}
package Target023;public class EatableDemo {public static void main(String[] args) {//在主方法中调用useEatbale方法Eatable e = new EatableImpl();useEatable(e);//匿名内部类useEatable(new Eatable() {@Overridepublic void eat() {System.out.println("一天一苹果,医生远离我");}});//Lambda表达式useEatable(() -> {System.out.println("一天一苹果,医生远离我");});}public static void useEatable(Eatable e){e.eat();}
}
package Target023;public class EatableImpl implements Eatable{@Overridepublic void eat() {System.out.println("一天一苹果,医生远离我");}
}
练习2:
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
- 定义一个测试类(FlyableDemo),在测试类中提供两个方法
一个方法是: useFlyable(Flyable f)
一个方法是主方法,在主方法中调用useFlyable方法
package Target024;public interface Flyable {void fly(String s);
}
package Target024;public class FlyableDemo {public static void main(String[] args) {//在主方法中调用Flyable//匿名内部类useFlyable(new Flyable() {@Overridepublic void fly(String s) {System.out.println(s);System.out.println("飞机自驾游");}});//Lambda表达式useFlyable((s) -> {System.out.println(s);System.out.println("飞机自驾游");});}public static void useFlyable(Flyable f){f.fly("风和日丽,晴空万里");}
}
练习3
- 定义一个接口(Addable),里面定义一个抽象方法: intadd(intx,int y);
- 定义一个测试类(AddableDemo),在测试类中提供两个方法
一个方法是: useAddable(Addable a)
一个方法是主方法,在主方法中调用useAddable方法
package Target025;public interface Addable {int add(int x,int y) ; //抽象方法无方法体
}package Target025;public class AddableDemo {public static void main(String[] args) {//在主方法中调用useAddable方法useAddable((int x,int y)->{return x+y;});}private static void useAddable(Addable a){int sum = a.add(10,20);System.out.println(sum);}
}
27.5Lambda表达式的省略模式
省略规则:
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
package Target026;public interface Addable {int add(int x,int y);
}
package Target026;public interface Flyable {void fly(String s);
}
package Target026;
/*
Lambda表达式的省略模式*/
public class LambdaDemo {public static void main(String[] args) {useAddable((int x ,int y) -> {return x+y;});//参数的类型可以省略useAddable((x ,y) -> {return x+y;});//但是有多个参数的情况下,不能只省略一个useFlyable((String s) -> {System.out.println(s);});useFlyable((s) -> {System.out.println(s);});//如果代码块的语句只有一条,可以省略大括号和分号useFlyable(s -> System.out.println(s));//如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉useAddable((x,y) -> x+y);}public static void useAddable(Addable a){int sum = a.add(10,20);System.out.println(sum);}public static void useFlyable(Flyable b){b.fly("风和日丽,晴空万里");}
}
27.6Lambda表达式的注意事项
注意事项:
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口
根据局部变量的赋值
得知Lambda对应的接口:Runnabler =()->System.out.printIn(“Lambda表达式”);
根据调用方法的参数
得知Lambda对应的接口: new Thread()-> System.out.printIn(“Lambda表达式”)).startO;
package Target027;public interface Inter {void show();//void method();Lambda表达式前提有且仅有一个抽象方法
}
package Target027;public class LambdaDemo {public static void main(String[] args) {// useInter(() -> {// System.out.println("好好学习天天向上");
// });//使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法useInter(() -> System.out.println("好好学习天天向上"));new Thread(new Runnable() {@Overridepublic void run() {System.out.println("匿名内部类");}}).start();//把匿名内部类给替代了() -> System.out.println("Lambda表达式");Runnable r = (() -> System.out.println());new Thread(r).start();//必须有上下文环境,才能推导出Lambda对应的接口new Thread(() -> System.out.println("Lambda表达式")).start();}public static void useInter(Inter a){a.show();}
}
27.7Lambda表达式和匿名内部类的区别
package Target028;public abstract class Animal {public abstract void method();
}
package Target028;public interface Inter {void show();//void show2();
}
package Target028;import jdk.swing.interop.SwingInterOpUtils;public class LambdaDemo {public static void main(String[] args) {useInter(new Inter() {@Overridepublic void show() {System.out.println("接口");}});useAnimal(new Animal() {@Overridepublic void method() {System.out.println("抽象类");}});useStudent(new Student() {@Overridepublic void study() {System.out.println("具体类");}});//Lambda// useInter(() -> System.out.println("接口"));//useAnimal(() -> System.out.println("抽象类"));//useStudent(() -> System.out.println("具体类"));//useInter(() -> System.out.println("接口"));// useInter(new Inter() {// @Override
// public void show() {// System.out.println("show");
// }
// public void show2() {// System.out.println("show2");
// }
// });useInter(new Inter() {@Overridepublic void show() {System.out.println("接口");}});}private static void useStudent(Student s ){s.study();}private static void useAnimal(Animal a){a.method();}private static void useInter(Inter i){i.show();}
}
package Target028;public abstract class Student {public abstract void study();
}
所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成###
28.接口组成更新
28.1接口组成更新概述
- 接口的组成
常量
public static final - 抽象方法
public abstract - 默认方法(Java 8)
- 静态方法(Java 8)
- 私有方法(Java 9)
28.2接口中默认方法
接口中默认方法的定义格式:
- 格式: public default返回值类型方法名(参数列表){}
- 范例: public default void show3() { }
package Target029;public interface MyInterface {void show();void show2();//void show3();public default void show3(){//默认方法System.out.println("show3");}
}
package Target029;public class MyInterfaceDemo {public static void main(String[] args) {//按照多态的方式创建对象并使用MyInterface my = new MyInterfaceImplOne();my.show();my.show2();my.show3();}
}
package Target029;public class MyInterfaceImplOne implements MyInterface{@Overridepublic void show() {System.out.println("one show1");}@Overridepublic void show2() {System.out.println("one show2");}@Overridepublic void show3() {System.out.println("one show3");}
}
package Target029;public class MyInterfaceImplTwo implements MyInterface{@Overridepublic void show() {System.out.println("two show1");}@Overridepublic void show2() {System.out.println("two show2");}
}
package Target029;public interface MyInterfaceSon extends MyInterface{void show3(); //但如果更新快,体系就会变得非常庞大
}
28.3接口中静态方法
接口中静态方法的定义格式:
- 格式: public static返回值类型方法名(参数列表){ }
- 范例: public static void show() { }
接口中静态方法的注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
package Target030;public interface Fluable {public static void test(){System.out.println("Flyable中的静态方法执行了");}
}package Target030;public interface Inter {void show();default void method(){System.out.println("Inter中默认方法执行");}
// public static void test(){
// System.out.println("Inter中静态方法执行");
// }static void test(){System.out.println("Inter中静态方法执行");}
}
package Target030;public class InterDemo {public static void main(String[] args) {//按照多态的方式创建对象并使用Inter i = new InterImpl();i.show();i.method();//i.test();Inter.test();//InterImpl.test();Fluable.test();}
}
package Target030;public class InterImpl implements Inter,Fluable{@Overridepublic void show() {System.out.println("show方法执行了");}
}
28.4接口中私有方法
Java 9中新增了带方法体的私有方法,这其实在Java8中就埋下了伏笔: Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性
接口中私有方法的定义格式:
- 格式1: private返回值类型方法名(参数列表){}
- 范例1: private void show({ }
- 格式2: private static返回值类型方法名(参数列表){}
- 范例2: private staticvoid method0{ }
接口中私有方法的注意事项:
- 默认方法可以调用私有的静态方法和非静态方法
- 静态方法只能调用私有的静态方法
package Target031;public interface Inter {default void show1() {System.out.println("show1开始执行");
// System.out.println("初级工程师");
// System.out.println("中级工程师");
// System.out.println("高级工程师");show();System.out.println("show1结束执行");}default void show2() {System.out.println("show2开始执行");
// System.out.println("初级工程师");
// System.out.println("中级工程师");
// System.out.println("高级工程师");show();System.out.println("show2结束执行");}private void show(){System.out.println("初级工程师");System.out.println("中级工程师");System.out.println("高级工程师");}static void method1() {System.out.println("method1开始执行");
// System.out.println("初级工程师");
// System.out.println("中级工程师");
// System.out.println("高级工程师");method();System.out.println("method1结束执行");}static void method2() {System.out.println("method2开始执行");
// System.out.println("初级工程师");
// System.out.println("中级工程师");
// System.out.println("高级工程师");method();System.out.println("method2结束执行");}private static void method(){System.out.println("初级工程师");System.out.println("中级工程师");System.out.println("高级工程师");}
}package Target031;import com.sun.jdi.PathSearchingVirtualMachine;public class InterDemo {public static void main(String[] args) {//按照多态的方式创建对象并使用Inter i = new InterImpl();i.show1();System.out.println("----------");i.show2();System.out.println("----------");Inter.method1();System.out.println("----------");Inter.method2();}
}
package Target031;public class InterImpl implements Inter{}
参考资料
https://www.bilibili.com/video/BV18J411W7cE?p=1
Java学习记录五(多线程、网络编程、Lambda表达式和接口组成更新)相关推荐
- Java笔记 - 黑马程序员_08(Lambda表达式,接口组成更新,方法引用,类加载器,反射)
1. Lambda 1.1 函数式编程思想概述 在数学中,函数就是有输入量.输出量的一套计算方案,也就是"数据做操作" 面向对象思想强调"必须通过对象的形式来做事情&qu ...
- 【JAVA黑马程序员笔记】四 P314到P384(特殊流、多线程编程、网络编程模块、lambda表达式、接口组成更新、方法引用、函数式接口)
P314-315 字节/符打印流 PrintStream ps = new PrintStream("test.txt");//使用字节输出流的方法ps.write(97);// ...
- Java高并发与多线程网络编程
目录 一. 基础 1. 线程介绍 2. 创建并启动线程 3. 函数式接口编程 4. Thread 构造器 5. 守护线程 线程关系 6. join 7. interrupt 8. 优雅的结束线程 9. ...
- 【JavaSE】Lambda表达式、接口组成更新、方法引用
文章目录 1. Lambda表达式 1.1 Lambda表达式初体验 1.2 Lambda表达式的基本格式 1.3 Lambda表达式练习 1.4 Lambda表达式的省略规则 1.5 Lambda表 ...
- Java学习笔记53(网络编程:TCP协议案例)
简易的案例 客户端: package demo;import java.io.IOException; import java.io.InputStream; import java.io.Outpu ...
- JAVA学习 API_day11(属性集, 函数式编程, stream流)
属性集, 函数式编程, stream流 I/O流小结 1. 属性集 2. 函数式编程 3. stream流 I/O流小结 文件复制: BufferedInputStream/BufferedOutpu ...
- 20165230 《Java程序设计》实验五《网络编程与安全》实验报告
20165230 <Java程序设计>实验五<网络编程与安全>实验报告 一.实验报告封面 课程:Java程序设计 班级:1652班 姓名:田坤烨 学号:20165230 成绩: ...
- 疯狂java实验五数据流编程_2017-2018-2 20165209 实验五《网络编程与安全》实验报告...
2017-2018-2 20165209 实验五<网络编程与安全>实验报告 实验报告封面 北京电子科技学院(BESTI) 实 验 报 告 课程:Java程序设计 班级:1652 姓名:陈思 ...
- java的网络编程设计报告_20165230 《Java程序设计》实验五《网络编程与安全》实验报告...
20165230 <Java程序设计>实验五<网络编程与安全>实验报告 一.实验报告封面 课程:Java程序设计 班级:1652班 姓名:田坤烨 学号:20165230 成绩: ...
最新文章
- NameNode中几个关键的数据结构
- Bugku——Web——矛盾
- one-to-many many-to-one 为什么只生成了一张表呢?
- 【CyberSecurityLearning 61】文件上传
- 解决docker push镜像到私有仓库时的报错【http: server gave HTTP response to HTTPS client】
- oracle 计算公式解析,用PL/SQL如何实现公式解析计算
- Java 异常处理学习总结
- 7-36 社交网络图中结点的“重要性”计算 (30 分)(思路加详解)兄弟们PTA乙级题目冲起来
- Python应用实战-Python提升运行速度技巧总结
- mysql group_concat去重_mysql 数据库group_concat函数的一些用法
- 漫画:如何找到链表的倒数第n个结点?
- php表白情话,朋友圈唯美表白短句情话 适合发朋友圈的情话
- 离开HK后的第二篇所感--信心
- 解决中64位Win7系统上PLSQL无法连接ORACLE的方法(PLSQL无法识别ORACLE_HOME的配置)
- 三角计算机器应用试卷,2015-1月信息科技学科期末考试试题
- 【病毒分析】——熊猫烧香 专杀工具C源码
- Matlab2018a破解出现licensing error:-8523
- 关于VS2017配置OpenCV出现无法打开文件“opencv_ml249d.lib”的解决方案
- Android加载超大图片
- 调出cmd输入时的光标