多线程并发时,多个线程同时操作同一个内存区域(变量),可能会导致的结果不一致的问题;所谓线程安全,指的是在多线程并发操作的时候能保证共享的变量数据一致
线程并发时需要保证线程安全,需要满足三大条件:
对于一条线程执行来说要保证,要么都成功,要么都失败;对于原子性操作可以通过加锁的方式实现;Synchronized和ReentrantLock保证线程的原子性
多条线程操作同一个变量时,需要保证其中一条线程对变量的修改,对其他线程也是可见的
对于每一条线程的执行,内部应该是有序的:
代码顺序执行
锁的释放应该在获得之后
变量读取操作,在修改操作之后
synchronized的关键字使用包含三种方式:
被synchronized鎖住的区域称之为临界区
由于线程并发,会出现共享变量的情况,如果使用同步锁,对象会被锁住,如果存在多个线程争抢资源时陷入僵局(多个线程在等待被对方线程占有的资源释放时陷入的无限等待状态),这种情况称之为死锁。死锁无法解决,只能尽量避免
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vK8pImrR-1595769636704)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200720\笔记\assets\1595215030827.png)]
public class DeathLock implements Runnable{
/**
* 使用静态修饰的原因是希望意向两个对象永远只存在一个实例(不论有多少Runnable对象)
*/
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private int flag;
public DeathLock(int flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag == 1) {
synchronized (obj1) {
System.out.println("线程1锁住了obj1");
synchronized (obj2) {
System.out.println("线程1锁住了obj2,结束。。。。");
}
}
}else if(flag == 2){
synchronized (obj2) {
System.out.println("线程2锁住了obj2");
synchronized (obj1) {
System.out.println("线程2锁住了obj1,结束。。。。");
}
}
}
}
//HashMap ConcurrentHashMap
public static void main(String[] args) {
DeathLock d1 = new DeathLock(1);
DeathLock d2 = new DeathLock(2);
new Thread(d1).start();
new Thread(d2).start();
}
}
从JDK1.5之后新增的并发编程包(java.util.concurrent)中新增了一个新的锁机制:Lock;Lock是一个锁接口,常见的实现类:java.util.concurrent.ReentrantLock(可重入锁);提供了跟synchronized相同的功能,也可以对于的对象实现加锁,ReentrantLock粒度控制方面比synchronized更细,同时支持公平锁和非公平锁(默认),synchronized只支持非公平锁;使用方式:
public class SaleTickets implements Runnable {
private static int t = 100;
private static boolean isOver;
/**创建所对象*/
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (!isOver) {
//获得锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "买到票-->" + t--);
if (t <= 0) {
isOver = true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
}
public static void main(String[] args) {
// 创建Runnable对象
SaleTickets st = new SaleTickets();
Thread t1 = new Thread(st, "客户A");
Thread t2 = new Thread(st, "客户B");
Thread t3 = new Thread(st, "客户C");
t1.start();
t2.start();
t3.start();
}
}
注意事项:
- 锁的获取位置一般在try语句块之前
- 锁的释放一般放到finally语句块中
ReentrantLock和Synchronized的区别:
- ReentrantLock是一个实现类,后者是一个关键字
- ReentracntLock需要手动获取锁以及释放锁(粒度控制更细);后者自动加锁,临界区(synchronized锁住的区域)的代码执行完之后自动释放
- ReentrantLock支持公平锁以及非公平锁;sychronized只支持非公平锁
- synchronized一般用于少量代码同步,ReentrantLock一般用于大量的同步代码块
在前面的线程并发中,对于原子性的解决方案使用synchronized或lock实现同步;但是对于数据的可见性来说,我们还需要另外处理,关于可见性,比如:
public class VolatileDemo implements Runnable{
private int count;
private boolean isOver;//false
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName());
while(!isOver) {
count++;
}
System.out.println(Thread.currentThread().getName()+"count--->"+count);
}
public static void main(String[] args) throws InterruptedException {
VolatileDemo vd = new VolatileDemo();
Thread t1 = new Thread(vd,"t1");
t1.start();
Thread t2 = new Thread(vd,"t2");
t2.start();
Thread.sleep(3000);
vd.isOver = true;
}
}
对以上程序的分析,两条子线程启动3秒之后,由于主线程修改过了isOver的变量值,因此预期的结果因该是两条子线程,t1,t2应该会正常结束;但是实际情况是,并没有,效果如下:
线程t1 线程t2
说明主线程对于以上变量的修改,并未立即同步给其他线程;
原因是因为,多线程程序中,jvm为每一条线程单独开辟了一块临时缓存区,该缓存区用于存储主存储器中存储的全局变量的副本;因此在对其中一条线程修改该变量是,该结果并不会立即同步到其他线程,因此会导致在其他线程中不能立即看到该变量的修改(不满足可见性)
所以,如果需要将一条线程修改的变量结果立即同步给其他线程,需要将该变量定义为
volatile
,因此,对于以上的代码应该修改为:private volatile boolean isOver;//false
volatile和synchronized的区别:
synchronized保证的是线程执行的原子性,volatile只能保证变量的可见性不能保证原子性
一句话总结volatile关键字的作用:实时的将全局变量的更新立即同步给其他线程
线程运行时若需要限时等待,则可以通过sleep()方法设置休眠时间,但是对于一些不定时的等待需求sleep则无法实现;对于这种需求,java.lang.Object类中提供了用于实现等待和唤醒的方法:wait和notify;
在使用wait和notify的时候一定要求确保当前线程持有对象的监视器(对象锁)
public class WaitDemo implements Runnable {
private int count;
private volatile boolean isOver = false;
@Override
public void run() {
while (!isOver) {
String tname = Thread.currentThread().getName();
synchronized (this) {
try {
count++;
System.out.println(tname + "---count-->" + count);
if (count <= 300000 && (tname.equals("t1") || tname.equals("t2") || tname.equals("t3"))) {
// 调用wait以及notify时需要当前线程对象持有目标对象的对象监视器(对象锁)
wait();
} else {
if (count == 1500000) {
// 唤醒一条线程(随机)
this.notify();
// 唤醒在该对象上的所有线程
//notifyAll();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
WaitDemo wd = new WaitDemo();
Thread t1 = new Thread(wd, "t1");
t1.start();
Thread t2 = new Thread(wd, "t2");
t2.start();
Thread t3 = new Thread(wd, "t3");
t3.start();
Thread t4 = new Thread(wd, "t4");
t4.start();
}
}
wait()和sleep()区别?
- sleep是来自Thread类中的一个方法;wait是属于Object类的方法
- sleep是限时等待,在等待的时限到达时自动恢复;而wait是无限等待,必须要等到其他线程调用该对象上的notify方法(或notifyAll)才能继续执行
- sleep使用时不需要持有对象锁,而wait使用时必须确保当前线程持有该对象的对象锁,否则会有异常(java.lang.IllegalMonitorStateException)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZVDLm2GI-1595769636708)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200720\笔记\assets\1595236615810.png)]
package lesson37.homework;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Math implements Runnable {
private volatile static int t = 0;
private volatile boolean isOver;
private static RandomAccessFile raf;
@Override
public void run() {
while (!isOver) {
synchronized (this) {
try {
++t;
raf.seek(0);
raf.write(t);
// System.out.println("当前文件指针位置.....................: "+raf.getFilePointer());
System.out.println(Thread.currentThread().getName() + " 取到号码..." + t);
Thread.sleep(1000);
if (t >= 500) {
isOver = true;
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
try {
raf = new RandomAccessFile("E:\\测试文件夹\\MB.txt", "rw");
t = raf.read();
System.out.println("取号的起始位置---->" + (t + 1));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Math mt = new Math();
Thread t1 = new Thread(mt, "患者路人甲");
Thread t2 = new Thread(mt, "患者炮灰乙");
t1.start();
t2.start();
}
}
将分布在不同地理区域的计算机,通过一些外部网络设备以及内部的网络协议,连接成一个网络系统;通俗的理解为2台或以上的计算机协同工作,例如:文件传输,数据传输。计算机网络的核心目的是实现:信息共享
根据网络规模以及用途分为:
根据网络的工作模式分为:
计算机网络之间实现通信需要两种设备支持:
网络协议就是计算器网络设备相互之间通信的标准(暗号),在互联网标准组织的推动下,将网络协议分为两种:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BjAzGkvW-1595769747942)(assets\1595299573815.png)]
传输控制协议,是一个安全可靠的互联网协议,需要通信的主机之间需要先建立正确的链接,才能够进行通信,并且改协议能够保证数据传输稳定性(必须的保证信息发送到一台主机,由该主机确认之后才能发送下一条信息),另外该协议也能保证数据传输的有序性(先发送的信息一定先到达)。一般基于C/S架构,存在服务器客户端模式。
应用领域: 语音通话,视频会议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hjXIN6Ps-1595769747948)(assets\1595300606777.png)]
User Diagram Protocol(用户数据报协议),是一个不安全的网络协议,不需要双方之间建立联系,也不保证信息传输的有序性(有可能后发消息先到),传输效率比TCP/IP更高.没有专门服务器和客户端,只有发送端和接收端
应用领域: 邮件发送,广播、飞秋
以上是互联网数据传输底层协议,目前大多数时候是直接使用的基于这两个协议的应用层协议
Http/Https、Ftp、Smtp、Pop3
Internet Protocol(因特网协议),主机之间通信的唯一标识,每台计算机都已个唯一的ip地址;ip又划分为IPv4和IPv6
1字节=8个二进制位
4字节=32位
16字节=128位
ip地址需要确保在同一个网络中不可重复,一旦重复则会出现:ip冲突
ip地址通常分为5类:
A. (1.0.0.0 到127.0.0.0)
B. (128.1.0.0–191.254.0.0)
C. (192.0.1.0–223.255.254.0) 民用**
D. (224.0.0.0到239.255.255.255) 广播
E.(240.0.0.0到255.255.255.254,255.255.255.255)
端口(port)是主机中应用程序对外的沟通的唯一标识;ip是主机的标识,端是应用的标识;因此如果需要准确的寻找到主机中的应用,则需要同时提供ip和端口。
端口分为TCP/IP、UDP
取值范围从 0 ~ 65535之间;但是由于0~1023之间的端口密集绑定了一些已有的服务,所以应该避免在创建自定义服务时使用这些端口;自定义的服务建议选择范围:
唯一的对ip地址简化记忆一种名称;例如:
#顶级域名
www.baidu.com
www.softeem.top
#二级域名
task.softeem.top
#三级
demo.task.softeem.top
域名后缀:
商用: .com .cn .net 个人组织: .org 教育机构: .edu 政府 .gov=
关于本机地址:
ip:127.0.0.1
域名:localhost
IndetAddress是位于java.net包中提供的用于表示ip地址和主机的类,常用方法:
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip);
//根据主机名称获取包含了该主机的名称和地址的对象
System.out.println(InetAddress.getByName("DESKTOP-UM5AJP5"));
System.out.println(InetAddress.getByName("192.168.7.194"));
//获取InetAddress对象表示的ip地址
String addr = ip.getHostAddress();
System.out.println(addr);
String name = ip.getHostName();
System.out.println(name);
System.out.println(ip.getCanonicalHostName());
System.out.println(InetAddress.getLoopbackAddress());
byte[] byt = ip.getAddress();
for (byte b : byt) {
//-128 127 192/168
System.out.println(b);
}
}
}
Socket(套接字),实际上就是由IP地址跟端口号的结合,通过Socket对象可以实现两台主机之间的通信;Socket分为服务端Socket(java.net.ServerSocket),以及客户端Socket(java.net.Socket)
package Chatroom.client;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import javax.swing.filechooser.FileSystemView;
import Chatroom.comm.Tools;
/**
* 消息接收线程
* @author 法海打印
*
*/
public class MsgReceiver extends Thread{
private Socket s;
public MsgReceiver(Socket s) {
this.s = s;
}
@Override
public void run() {
BufferedReader br=null;
try {
//获取socket中的字节输入流
InputStream is = s.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
br=new BufferedReader(isr);
String msg=null;
//循环读取
while ((msg=br.readLine())!=null) {
Tools.tips(msg);
//往文件写聊天记录
Tools.writefile(msg,s);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (br!=null)
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package Chatroom.client;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import javax.tools.Tool;
import Chatroom.comm.Tools;
/**
* 聊天服务的客户端
*
* @author 法海打印
*
*/
public class SQClient {
/** 远程服务器的ip地址 */
private String ip;
/** 远程服务端应用的端口 */
private int port;
public SQClient(String ip, int port) {
super();
this.ip = ip;
this.port = port;
}
public void startChat() {
try {
Socket s = new Socket(ip, port);
//启动消息接收的线程
MsgReceiver mr = new MsgReceiver(s);
//设置消息接收线程为守护线程
mr.setDaemon(true);
mr.start();
// 获取文本扫描对象
Scanner sc = new Scanner(System.in);
OutputStream os = s.getOutputStream();
String msg="";
while (!"quit".equals(msg)) {
msg = sc.nextLine();
Tools.sendMsg(os, msg);
}
System.out.println("结束聊天...已经退出聊天室");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SQClient client = new SQClient("192.168.7.174", 8090);
client.startChat();
}
}
package Chatroom.comm;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import javax.swing.filechooser.FileSystemView;
public class Tools {
/**封装消息提示*/
public static void tips(String msg) {
System.out.println(msg);
}
/**封装ip转换*/
public static String ip(Socket s) {
return s.getInetAddress().getHostAddress();
}
/**封装主机名*/
public static String host(Socket s) {
return s.getInetAddress().getHostName();
}
/**根据提供的毫秒时间转换为固定格式时间*/
public static String time(long t) {
//根据提供的时间毫秒数构建日期对象
SimpleDateFormat sdf=new SimpleDateFormat("[HH时mm分ss秒]");
Date d=new Date(t);
return sdf.format(d);
}
public static String time2() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
String time=fmt.format(now);
return time;
}
/**通过输出流发送文本消息*/
public static void sendMsg(OutputStream os, String msg) {
PrintWriter pw = new PrintWriter(os);
pw.println(msg);
pw.flush();
}
/**根据提供的毫秒时间转换为固定格式时间*/
public static String time3() {
//根据提供的时间毫秒数构建日期对象
SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd");
long l = System.currentTimeMillis();
return sdf.format(l);
}
public static void writefile(String msg,Socket s) {
String fname = Tools.time3() + ".txt";
// 获取与本机系统有关的文件系统预览
FileSystemView fsv = FileSystemView.getFileSystemView();
// 获取桌面目录
File descktop = fsv.getHomeDirectory();
// 根据获取的桌面目录以及文件名组合为一个新的file对象
String hostname=host(s);
File f=new File(descktop,hostname);
f.mkdir();
File target = new File(f, fname);
try {
FileOutputStream fos = new FileOutputStream(target, true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter brw = new BufferedWriter(osw);
try {
brw.newLine();
brw.write(msg);
brw.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
package Chatroom.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.time.LocalTime;
import java.util.Iterator;
import javax.tools.Tool;
import Chatroom.comm.Tools;
/**
* 消息接收和转发线程
* @author 法海打印
*
*/
public class MsgSever extends Thread{
private Socket s;
public MsgSever(Socket s) {
this.s=s;
}
@Override
public void run() {
//读取消息
try {
//读取接收到的来自Socket的消息
//获取socekt的输入流
InputStream is=s.getInputStream();
//把字节流转换为字符流
InputStreamReader isr=new InputStreamReader(is);
//将低级流转换为缓冲流
BufferedReader br=new BufferedReader(isr);
//临时变量,用于接收每一次读取的文本消息
String msg=null;
while((msg=br.readLine())!=null){
//遍历每一个Socket对象
for (Socket c : SQSever.clients) {
//格式化消息内容 (时间+发送人主机名+消息内容)
//String content=Tools.time(System.currentTimeMillis())+"---"+Tools.host(s)+":"+msg;
String content="["+Tools.time2()+"---"+Tools.host(s)+"]: "+msg;
Tools.sendMsg(c.getOutputStream(), content);
}
}
} catch (IOException e) {
//e.printStackTrace();
Tools.tips("客户端断开连接: "+Tools.ip(s));
//从集合中移除该对象
SQSever.clients.remove(s);
}
}
}
package Chatroom.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import Chatroom.comm.Tools;
/**
* 软帝内通网
* @author 法海打印
*
*/
public class SQSever {
/**对外提供服务端口号*/
private int port;
/**记录所有产生的Socket对象*/
public volatile static List<Socket> clients=new ArrayList<Socket>();
public SQSever(int port) {
super();
this.port = port;
}
public void startSever() {
try {
//创建服务
ServerSocket server=new ServerSocket(port);
Tools.tips("服务已开启,等待连接......");
//循环监听
while (true) {
//监听客户端连接
Socket s = server.accept();
//将获取的socket存储到集合中
clients.add(s);
Tools.tips("客户端已连接: "+Tools.ip(s));
//启动聊天服务线程
new TalkingServer(s).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//启动服务
new SQSever(8090).startSever();
}
}
package Chatroom.server;
import java.io.IOException;
import java.net.Socket;
import Chatroom.comm.Tools;
/**
* 聊天服务线程
* @author 法海打印
*
*/
public class TalkingServer extends Thread {
private Socket s;
public TalkingServer(Socket s) {
this.s=s;
}
@Override
public void run() {
try {
//发送欢迎消息给客户端
Tools.sendMsg(s.getOutputStream(), "欢迎使用 [雷霆嘎巴] 聊天室,祝您ZBC愉快...");
//启动消息接收和转发线程
new MsgSever(s).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP/IP协议是一种面向流的全双工通信协议,有着服务器客户端的概念,必须保证通信双方建立稳定安全的连接才能够进行数据传输;UDP协议不是一种基于稳定连接的协议,是一种面向数据报包的通信协议,不需要通信双方建立稳定的连接,也没有所谓服务端和客户的概念,数据报包在传输的时候不保证一定及时到达,也不能保证数据报包的到达顺序,但是UDP协议传输效率要远高于TCP/IP,比如直播,视频会议。
DatagramSocket类是一个基于UDP通信协议的套接字,使用该类可以实现一个无连接的通信通道;需要实现在该通道通信,我们还需要另一个类的辅助:DatagramPacket(数据报包),该类用于将数据打包成数据报包,然后通过DatagramSocket完成数据报包的发送(send)与接收(receive)
/**
* 发送方
* @author mrchai
*
*/
public class Sender {
public static void main(String[] args) throws IOException {
//创建一个数据报的网络通道(创建物流公司)
DatagramSocket ds = new DatagramSocket();
//准备需要传输的数据(货物)
String msg = "天王盖地虎,小鸡炖蘑菇!!!";
//将需要发送的数据打包成数据报包(打包货物)
DatagramPacket packet = new DatagramPacket(
msg.getBytes(), //需要被发送的数据的字节数组
msg.getBytes().length, //发送的数据长度(字节数组长度)
InetAddress.getByName("localhost"), //接收方的ip
1025 //接收方的端口
);
//发送数据报包(投递包裹)
ds.send(packet);
//关闭通道
ds.close();
}
}
/**
* 接收方
* @author mrchai
*
*/
public class Receiver {
public static void main(String[] args) throws IOException {
//创建一个数据报的网络通道,绑定到指定端口
DatagramSocket ds = new DatagramSocket(1025);
//声明字节数组,用于存储接收的数据
byte[] b = new byte[1024];
//准备数据报包(空包)
DatagramPacket packet = new DatagramPacket(b, b.length);
//通过循环可以不断接收发送到当前地址和端口的数据报包
while(true) {
//接收数据到数据报包中
ds.receive(packet);
//接受到的数据(字节数组)实际长度
int len = packet.getLength();
//将字节数组转换为字符串
String s = new String(b, 0, len);
//获取发送方的ip地址
String ip = packet.getAddress().getHostAddress();
System.out.println("收到来自【"+ip+"】消息:"+s);
}
}
}
飞鸽传书(小日本开发),国内一名开发者基于飞鸽传书协议开发国内的飞鸽(飞秋),如果需要向飞秋发送文本信息,需要使用如下消息格式:
版本号:时间戳:发送人昵称:发送人主机地址:命令字:消息主体
例如:
1:1000:葫芦小金刚:localhost:32:我的大刀已饥渴难耐!!!!
public class MyFeiQ {
public static void main(String[] args) throws IOException {
//准备需要发送的消息
//版本号:时间戳:发送人昵称:发送人主机地址:命令字:消息主体
String msg = "1:1000:葫芦小金刚:localhost:32:我的大刀已饥渴难耐!!!!";
//准备网络通道
DatagramSocket ds = new DatagramSocket();
//将要传输的数据打包为数据报包
DatagramPacket packet = new DatagramPacket(
msg.getBytes(),
msg.getBytes().length,
InetAddress.getByName("192.168.7.141"),
2425);
//发送
ds.send(packet);
}
}
在通用的以太网(Ethernet)构架下,计算机于计算机之间的数据交换都是通过交换机来完成的。如果一份数据需要被传送给多个接收者,在使用TCP/IP连接的情况下,数据发送者需要向交换机发送N 个同样的拷贝,而交换机则负责将这N 个拷 贝分发给所有的接收者;
在使用UDP 数据广播的情况下,数据发送者只需要向交换机发送一个拷贝,交换机负责将这个信息制作N 个拷贝发送给所有的机器。在这种情况下,使用TCP/IP连接会大大的增加网络的负担。在一个普通局域网络中,可以认为由于网络状况较差而造成数据丢失的可能性比较小,而利用UDP 数据广播进行 数据交换能够大幅度减轻网络的负担
java.net包中提供了用于实现UDP数据广播的Socket类:java.net.MulticastSocket,该类从DatagramSocket继承而来,可以实现广播消息的发送:
/**
* 广播发送方
* @author mrchai
*
*/
public class BoradcastSender {
public static void main(String[] args) throws IOException {
//创建广播通道
MulticastSocket ms = new MulticastSocket();
//获取广播地址(D类地址)
InetAddress addr = InetAddress.getByName("228.5.6.7");
//将通道加入组播地址
ms.joinGroup(addr);
String msg = "通知:今晚8点半,本市科技馆将会有大型的歌舞表演,请为市民过来围观!!!";
//将消息打包成数据报包
DatagramPacket dp = new DatagramPacket(
msg.getBytes(),
msg.getBytes().length,
addr,
5555);
//发送
ms.send(dp);
ms.close();
}
}
/**
*广播接收方
* @author mrchai
*
*/
public class BroadcastReceiver {
public static void main(String[] args) throws IOException {
//创建广播通道,并绑定端口5555
MulticastSocket ms = new MulticastSocket(5555);
//获取广播地址(D类地址)
InetAddress addr = InetAddress.getByName("228.5.6.7");
//将通道加入组播地址
ms.joinGroup(addr);
//声明字节缓冲区
byte[] b = new byte[1024];
//基于字节缓冲区构建数据报包(空包)
DatagramPacket dp = new DatagramPacket(b, b.length);
System.out.println("准备接收通知...");
while(true) {
//接收数据到数据报包中
ms.receive(dp);
//获取读取到的数据报长度
int len = dp.getLength();
String ip = dp.getAddress().getHostAddress();
String s = new String(b,0,len);
System.out.println("接收到来自"+ip+"的通知消息:"+s);
}
}
}
URL(统一资源定位器),一般用于表示一个网络地址(本地网,外部网络),通过该地址可以定位到网络中的资源。URL的案例:
http://www.softeem.com:80/sales/home.html?page=1
ftp://119.231.8.19
jdbc:mysql://192.168.0.1:3306/test?user=root&password=123456
一个URL地址通常由以下几个部分构成:
public class URLDemo {
public static void main(String[] args) throws MalformedURLException {
//根据提供的url地址创建一个URL对象
URL url = new URL("http://www.softeem.com:80/doc/index.html?offset=5&limit=10");
//获取当前地址所表示的协议
String protocol = url.getProtocol();
System.out.println(protocol);
//获取主机地址
String host = url.getHost();
System.out.println(host);
//获取端口号
int port = url.getPort();
System.out.println(port);
//获取该URL请求的所有内容包括查询路径
String file = url.getFile();
System.out.println(file);
//获取URL的请求路径部分(不含查询路径)
String path = url.getPath();
System.out.println(path);
//获取查询部分
String query = url.getQuery();
System.out.println(query);
String str = "https://www.kuaidi100.com/query?type=shentong&postid=773046451247625&temp=0.3552732183562499&phone=";
str = "http://music.softeem.top/list";
}
}
Http(Hyper Text Transfer Protocol)超文本传输协议(not only text);是一个基于TCP/IP的应用层协议,一般用于WWW服务器进行数据传输的一种协议,http是一种请求响应协议,即由客户端通过http协议发起请求,由http服务器提供响应;http是一种无状态的短链接协议。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XEWuO9mR-1595770025473)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200723\笔记\assets\1595484616291.png)]
请求原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VWiP3Y0-1595770025475)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200723\笔记\assets\1595484692873.png)]
由于HTTP协议是基于TCP/IP的,因此客户端和服务端之间通信都是面向字节流通信机制
一个http请求过程通常由两个部分组成:
请求体 | 是否必选 |
---|---|
GET/POST [URL路径] HTTP/[HTTP版本] | 是 |
Host: [URL主机] | 是 |
User-Agent: [请求类型唯一标识] | 否 |
Accept: [设置服务器返回的数据类型] | 否 |
Accept-Language:[设置服务器返回的语言] | 否 |
Accept-Encoding: [设置服务器返回的压缩编码] | 否 |
Accept-Charset: [设置服务器返回的文字编码] | 否 |
\r\n\r\n | 是 |
Post内容 | 否 |
例如:
POST /book/xiaoshuo/a.txt HTTP/1.1
User-Agent: PostmanRuntime/7.25.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 140b3f01-da8a-40dd-bed2-7855ebce5ac0
Host: 192.168.7.141:8090
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 0
- 请求头中常见的请求方式有哪一些?
POST/GET/DELETE/PUT/HEAD
- post和get的区别?
- post一般用于大量数据提交的情况下,比如表单提交,文件上传等(提交)
- get一般用于获取服务端数据的时候(获取)
- get也可以用于数据提交,但是提交的内容是通过请求地址栏拼接,但是地址栏长度限制关系,get不能提交大量数据,最多不超过1024字节
- post提交的数据一般是在请求头封装,而不是直接在地址栏显示
回复体 | 是否必须返回 |
---|---|
HTTP/[HTTP版本] [HTTP状态码] | 是 |
Date: [服务器时间] | 否 |
Server: [服务器类型] | 否 |
Content-Type: [返回的数据流内容类型] | 否 |
Content-Length:[返回的数据流内容长度] | 否 |
Content-Encoding:[返回的数据流压缩编码] | 否 |
Accept-Charset: [设置服务器返回的文字编码] | 否 |
\r\n | 是 |
回复体内容 | 否 |
示例响应头
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Thu, 13 Jul 2000 05:46:53 GMT
Content-Length: 2291
Content-Type: text/html
Cache-control: private
\r\n
……
http响应头中比较值得关注的是响应的状态码。
- 1XX 消息
- 2XX 响应成功
- 200 服务端响应成功
- 3XX 重定向,请求被重新定向到其他目标
- 4XX 来自客户端的请求错误
- 404 Not Found 请求的资源不存在
- 405 请求和响应的方式不一致(客户端发送get请求,但是服务端使用post接收)
- 5XX 服务器错误
- 500 服务器内部错误(程序异常)
在项目根目录下存放一个web项目(包含样式文件,脚本文件,图片,html等文件)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qWGiFHeT-1595770025477)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200723\笔记\assets\1595494618570.png)]
注意:该目录与src同级别
代码实现
/**
* 模拟http服务器的实现原理
* @author mrchai
*
*/
public class MyHttpServer extends Thread{
private Socket s;
public MyHttpServer(Socket s) {
super();
this.s = s;
}
@Override
public void run() {
PrintStream out = null;
InputStream is = null;
try {
//获取基于socket的输出流
out = new PrintStream(s.getOutputStream());
//获取socket的输入流
Scanner sc = new Scanner(s.getInputStream());
String line = sc.nextLine();
//截取客户端请求的资源地址(相对地址)
String source = line.split(" ")[1].substring(1);
System.out.println("请求的资源:"+source);
File file = new File(source);
//判断请求的资源是否存在
if(!file.exists()) {
out.println("HTTP/1.1 404 NOT FOUND");
out.println();
out.flush();
return;
}
//发送响应头
out.println("HTTP/1.1 200 OK");
out.println("Content-Type: */*");
out.println();
//发送请求的内容
is = new FileInputStream(file);
byte[] b = new byte[1024];
int len = 0;
while((len = is.read(b)) != -1) {
out.write(b, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(out != null)out.close();
if(is != null)is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(9090);
while(true) {
Socket s = server.accept();
new MyHttpServer(s).start();
}
}
}
浏览器请求:
http://127.0.0.1:9090/easyblog/index.html
HttpURLConnection用于获取一个http连接,是一个http客户端工具类,HttpURLConnection从URLConnection继承而来,通过该类可以读取服务端响应的数据(文本,视频,音频,图片等资源)。
public class HttpConnDemo {
public String loadData(String url) {
//Callable&FutureTask
return null;
}
//练习:将以下地址表示的音乐文件下载到本地
//http://music.softeem.top/musics/1592383934192.mp3
public static void main(String[] args) {
// new Thread() {
// public void run() {
// };
// }.start();
//使用线程启动网络请求
new Thread(()->{
try {
// String path = "http://192.168.7.141:9090/easyblog/index.html";
String path = "http://music.softeem.top/list";
//根据提供的url地址创建一个url对象
URL url = new URL(path);
//打开连接(强转为HttpURLConnection)
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
//打开连接
// conn.connect();
//设置请求方式(不设置默认使用GET)
conn.setRequestMethod("GET");
//获取服务端的响应码
int code = conn.getResponseCode();
//避免魔法值
if(code == HttpURLConnection.HTTP_OK) {
//从连接中获取输入流
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"utf-8");
BufferedReader br = new BufferedReader(isr);
String str = "";
while((str = br.readLine()) != null) {
System.out.println(str);
}
br.close();
}
//断开连接
conn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}) .start();
}
}
/**
* 文件下载器
* @author mrchai
*/
public class FileDownloader {
public void download(String url,File dir) throws MalformedURLException, FileNotFoundException {
URL urlObj = new URL(url);
//获取文件名称
String path = urlObj.getPath(); // musics/1592383934192.mp3
int index = path.lastIndexOf("/");// 6
String fname = path.substring(index+1); // 1592383934192.mp3
//根据文件名组合目录获取本地文件的输出流
OutputStream os = new FileOutputStream(new File(dir,fname));
System.out.println("开始下载...");
//启动线程读写
new Thread(()-> {
HttpURLConnection conn = null;
InputStream is = null;
try {
//打开连接
conn = (HttpURLConnection)urlObj.openConnection();
conn.setRequestMethod("GET");
//获取响应状态码
int stateCode = conn.getResponseCode();
if(stateCode == HttpURLConnection.HTTP_OK) {
//获取连接中输入流
is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
while((len = is.read(b)) != -1) {
os.write(b, 0, len);
}
System.out.println("下载完成!!!");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(os != null)os.close();
if(is != null)is.close();
if(conn != null)conn.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) throws MalformedURLException, FileNotFoundException {
// String url = "http://music.softeem.top/musics/1592383934192.mp3";
// String url = "http://192.168.7.141:9090/easyblog/mp3/WeAreYoung.mp3";
String url="https://vdept.bdstatic.com/366e32794a7965496b53655453614276/4b5a314d42746252/dff065116b75d4ef9129d7746e9ec38095b1d32cfaae43e648f2d7f1c3ee084e52394855eb32d106c6775955cf6abb2def3c701150f1ed387791e632bf442002.mp4?auth_key=1595566524-0-0-8e9d876380f34a9366e5b88aea39c832";
new FileDownloader().download(url,new File("d:/tempfile"));
}
}
对于大多数请求,主要是获取服务端文本数据,因此封装以下工具类用户获取网络资源:
/**
* HTTP工具类,可以通过该工具类轻松访问在线API地址
* 并获取响应的数据
* @author mrchai
*/
public class HttpUtils {
static class CallRemoteData implements Callable<String>{
private String url;
public CallRemoteData(String url) {
this.url = url;
}
@Override
public String call() throws Exception {
//根据提供的url地址构建一个URL对象
URL urlConn = new URL(url);
//打开连接
HttpURLConnection conn = (HttpURLConnection)urlConn.openConnection();
//设置请求方式
conn.setRequestMethod("GET");
//获取响应的状态码
int code = conn.getResponseCode();
if(code == HttpURLConnection.HTTP_NOT_FOUND) {
throw new Exception("请求的资源不存在!");
}
if(code == HttpURLConnection.HTTP_INTERNAL_ERROR) {
throw new Exception("服务器内部错误");
}
if(code != HttpURLConnection.HTTP_OK) {
throw new Exception("未知错误!");
}
BufferedReader br = null;
try {
//获取连接的输入流
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"utf-8");
br = new BufferedReader(isr);
//创建一个可变长字符串
StringBuffer sbf = new StringBuffer();
String str = "";
while((str = br.readLine()) != null) {
sbf.append(str).append("\r\n");
}
return sbf.toString();
}finally{
if(br != null)br.close();
//断开连接
conn.disconnect();
}
}
}
/**
* 根据提供的url地址字符串获取服务端响应回来的字符串数据
* @param url
* @return
*/
public static String getData(String url) {
try {
//创建Callable对象
Callable<String> call = new CallRemoteData(url);
FutureTask<String> task = new FutureTask<>(call); //Runnable
//创建并启动线程
new Thread(task).start();
//获取执行结果并返回
return task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String url = "http://music.softeem.top/list";
String json = HttpUtils.getData(url);
System.out.println(json);
}
}
JSON(JavaScript Object Notation)是一种与语言平台无关的数据交换格式,有着比XML更为轻量级的数据表现形式,是目前大多数互联网应用程序之间的数据交换格式的首选。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bRLId3rQ-1595770061251)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200724\笔记\assets\1595586405537.png)]
JSON数据格式从结构上分为三种类型:
标量即一个具体的值:数值,字符串,布尔等;如:10、softeem、true 都是标量
也称之为序列,序列即一系列数据(类型一致)的集合,数组使用“[]”包裹,内部元素之间使用“,”隔开,比如:数值数组,字符串数组,复杂的对象数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJldPD87-1595770061251)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200724\笔记\assets\1595586817289.png)]
[2,4,6,8,0]
["kobe","curry","wade"]
[
{
"name":"黑龙江",
"citys":["哈尔滨","大庆"]
},
{
"name":"台湾",
"citys":["台北","高雄"]
}
]
也称之为映射(Map),所谓映射即一个对象,包含在一对”{}“之间,内部的元素以键值对为结构组织,键和值之间使用“:”分隔,元素之间以“,”隔开
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OU3v9LIf-1595770061253)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200724\笔记\assets\1595586809283.png)]
{
"id":10,
"name":"softeem",
"type":"normal",
"flag":true,
"langs":["java","c++","php"],
"group":{
id:1,
gname:"vip1"
}
}
JSON数据格式支持的值类型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NCwP2FVi-1595770061257)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200724\笔记\assets\1595586789341.png)]
json作为一种数据交换格式,就避免不了与Java之间的相互转换,因此对于Json字符串和Java之间的转换,目前有一些开源的可选方案:
fastJSON是由阿里巴巴开源的JSON库,号称全世界最快的json插件,常用方法:
导入插件包:fastjson-1.2.5.jar
准备实体类
public class User {
private int id;
private String username;
private String password;
private boolean vip;
//构造器
//setter/getter
//toString
}
测试类:
public class TestFastJSON {
public static void main(String[] args) {
User u = new User();
u.setId(10);
u.setUsername("softeem");
u.setPassword("123456");
u.setVip(true);
//1.将Java对象转换为json字符串
String json =JSON.toJSONString(u);
System.out.println(json);
//2.将json字符串转化为Java对象
json = "{\"id\":20,\"password\":\"666\",\"username\":\"admin\",\"vip\":false}";
u = JSON.parseObject(json, User.class);
System.out.println(u);
//3.如何将集合转换为json字符串
List<User> list = new ArrayList<>();
list.add(new User(1, "softeem", "123456", true));
list.add(new User(2, "james", "000123", false));
list.add(new User(3, "kobe", "888123", false));
list.add(new User(4, "面筋哥", "666123", true));
json = JSON.toJSONString(list);
System.out.println(json);
//4.如何将一个json数组转换为一个Java集合
list = JSON.parseArray(json,User.class);
System.out.println(list);
//5.若解析的json字符串找不到对应的Java类时?
String json2 = "{\"sid\":10,\"name\":\"孙悟空\"}";
//将json字符串解析为Java对象;JSONObject实际上就是一个Map集合
JSONObject obj = JSON.parseObject(json2);
int sid = obj.getInteger("sid");
String name = obj.getString("name");
System.out.println(sid+"---"+name);
}
}
gson是由Google官方开源的一个优秀json插件,使用方式与fastJSON存在细微差异,功能相近,常用方法如下:
使用方式:
导入依赖包:gson-2.2.4.jar
测试类
public class TestGson {
public static void main(String[] args) {
User u = new User(1, "softeem", "888888", true);
//创建Gson对象
Gson gson = new Gson();
//1.将Java对象转换为json字符串
String json =gson.toJson(u);
System.out.println(json);
//2.json字符串转换为java对象
User user = gson.fromJson(json, User.class);
System.out.println(user);
//3.将Java集合转换为json字符串
List<User> list = new ArrayList<>();
list.add(new User(1, "softeem", "123456", true));
list.add(new User(2, "james", "000123", false));
list.add(new User(3, "kobe", "888123", false));
list.add(new User(4, "面筋哥", "666123", true));
json = gson.toJson(list);
System.out.println(json);
//4.将json字符串表示的数组转换为Java集合
list = gson.fromJson(json, new TypeToken<List<User>>() {
}.getType());
System.out.println(list);
}
}
API(Application Programing Interface),应用程序接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vMh7EJ9o-1595770061258)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200724\笔记\assets\1595587577451.png)]
public class MyRobot {
/** 接口的key */
private static final String API_KEY = "396358761f98df50b020580d85a11713";
/** 接口的secret */
private static final String API_SECRET = "kjk6n2ut9i4t";
/** API地址 */
private static final String API = "http://i.itpk.cn/api.php";
public void talking(String msg) {
String url = API + "?api_key=" + API_KEY + "&api_secret=" + API_SECRET + "&question=" + msg;
String answer = HttpUtils.getData(url);
System.out.println("机器人说:"+answer);
}
public static void main(String[] args) {
MyRobot r = new MyRobot();
Scanner sc = new Scanner(System.in);
while(true) {
r.talking(sc.nextLine());
}
}
}
访问以下地址:
http://music.softeem.top/list
将获取的数据缓存到本地文件,要求按照以下格式显示:
歌曲id 歌曲名 专辑 演唱者 大小 上传时间 存储地址
1 mojito mojito 周杰伦 4.5MB 2020-07-09 10:22:21
2 。。。。 。。。 。。 。。 。。。 。。
3
package Homework;
public class MusicDemo {
private String ablum;
private String artist;
private long id;
private String name;
private String path;
private long size;
private String style;
private long uptime;
public MusicDemo() {
super();
}
public MusicDemo(String ablum, String artist, long id, String name, String path, long size, String style, long uptime) {
super();
this.ablum = ablum;
this.artist = artist;
this.id = id;
this.name = name;
this.path = path;
this.size = size;
this.style = style;
this.uptime = uptime;
}
public String getAblum() {
return ablum;
}
public void setAblum(String value) {
this.ablum = value;
}
public String getArtist() {
return artist;
}
public void setArtist(String value) {
this.artist = value;
}
public long getID() {
return id;
}
public void setID(long value) {
this.id = value;
}
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getPath() {
return path;
}
public void setPath(String value) {
this.path = value;
}
public long getSize() {
return size;
}
public void setSize(long value) {
this.size = value;
}
public String getStyle() {
return style;
}
public void setStyle(String value) {
this.style = value;
}
public long getUptime() {
return uptime;
}
public void setUptime(long value) {
this.uptime = value;
}
@Override
public String toString() {
return "MusicDemo [ablum=" + ablum + ", artist=" + artist + ", id=" + id + ", name=" + name + ", path=" + path
+ ", size=" + size + ", style=" + style + ", uptime=" + uptime + "]";
}
}
package Homework;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.JSON;
import httpDemo.HttpUtils;
import json.demo.User;
public class Test {
static List<MusicDemo> list;
public void m1() throws FileNotFoundException {
File f = new File("E:\\测试文件夹\\musiclist.txt");
OutputStream os=new FileOutputStream(f,true);
OutputStreamWriter ow=new OutputStreamWriter(os);
BufferedWriter bw=new BufferedWriter(ow);
try {
bw.write("歌曲id\t歌曲名\t专辑\t演唱者\t大小\t上传时间\t存储地址");
bw.write("\n");
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
list = new ArrayList<MusicDemo>();
String data = HttpUtils.getData("http://music.softeem.top/list");
String[] split = data.replace("[", "").replace("}]", "").split("},");
for (String s : split) {
s = s + "}";
MusicDemo m = JSON.parseObject(s, MusicDemo.class);
list.add(m);
System.out.println(m);
}
for (MusicDemo m : list) {
m2(m);
}
}
public void m2(MusicDemo m) throws FileNotFoundException {
File f = new File("E:\\测试文件夹\\musiclist.txt");
OutputStream os=new FileOutputStream(f,true);
OutputStreamWriter ow=new OutputStreamWriter(os);
BufferedWriter bw=new BufferedWriter(ow);
Date date=new Date(m.getUptime());
SimpleDateFormat df=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
String time=df.format(date);
try {
bw.write(m.getID()+"\t");
bw.write(m.getAblum()+"\t");
bw.write(m.getName()+"\t");
bw.write(m.getArtist()+"\t");
bw.write(m.getSize()/1024/1024+"\t");
//bw.write(m.getStyle()+"\t");
bw.write(time+"\t");
bw.write(m.getPath()+"\t");
bw.write("\n");
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws FileNotFoundException {
Test t = new Test();
t.m1();
}
}
注解(Annotation),是jdk5之后新增一项技术,可以通过在Java类,方法,属性等元素上加上注解,实现一些特定功能:编译检查,丰富文档化注释的内容,实现项目特定程序配置。注解只需要少量代码的存在即可;注释即解释;注解通常不会影响程序的正常逻辑,只是一种标记,Java中的注解通常是给编译器进行识别的
/**
* 这是算数运算工具类
* @author mrchai
* @since jdk8
* @see java.lang.Math
* @version v1.0
*/
public class ArithmaticDemo {
/**
* 相加运算
* @param a 数值a
* @param b 数值b
* @return 返回相加运算结果
*/
public int add(int a,int b) {
return a + b;
}
/**
* 相除运算
* @param a 被除数
* @param b 除数
* @return 返回相除的结果
* @throws ArithmeticException 当除数为0是抛出该异常
*/
public int divide(int a,int b) throws ArithmeticException{
if(b == 0 ) {
throw new ArithmeticException("除数不能为0");
}
return a/b;
}
}
通过一些特定的注解在接口,方法上用于编译期间进行检查,比如:@Override
,@FunctionalInteface
//检查当前接口是否是函数式接口
@FunctionalInterface
public interface Flyer {
void fly();
}
//检查当前方法是否是重写的
@Override
public String toString() {
// TODO Auto-generated method stub
return super.toString();
}
比如,后期框架学习中有很多地方需要进行配置,可以通过注解实现
@SpringBootApplication
@EnableTransactionManagement
@ServletComponentScan
@MapperScan("com.softeem.easymall.dao")
public class TmallSpringBootApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(TmallSpringBootApplication.class,args);
}
}
jdk中包含一些内置的注解类型:
@Deprecated
public class DeathDemo {
@SuppressWarnings("unused")
@Deprecated
private String msg;
@Deprecated
public void printMsg(String msg) {
System.out.println(msg);
}
//压制程序中的警告
@SuppressWarnings({
"rawtypes", "unchecked","unused"})
public static void main(String[] args) {
DeathDemo d = new DeathDemo();
d.msg = "111";
d.printMsg("222");
Date da = new Date();
List list = new ArrayList();
list.add(10);
}
}
java中除了包含一些内置注解以外,还允许开发者自定义注解来解决程序配置问题,如果需要使用自定义注解,只需要使用@interface
创建即可:
public @interface MyAnno {
}
注意:
自定义注解实际上就是一个接口,该接口从java.lang.annotation.Annotation继承而来
public @interface MyAnno {
String value();
int[] types() default {
1,2,3};
//如果注解的属性存在默认值,则使用时可不设置该属性
boolean[] flag() default false;
double[] nums() default 3.14;
Vip[] vip() default Vip.VIP1;
SubAnno[] sub() default @SubAnno;
//注解中的属性不支持自定义的类型
// Group group();
}
注意事项:注解中不支持自定义的数据类型
如果注解中只有一个属性(或者其他属性都有默认值),且该属性的名称叫“value”时,在使用该注解的时候只需要设置具体值就可以了,不需要设置属性名:
@MyAnno("softeem") public class User { }
对应的注解
public @interface MyAnno { String value(); }
Java中除了提供内置的注解和自定义注解之外,另外还提供了一些元注解:
用于色湖之注解的被保留范围,包含三个枚举值:
@Target注解用于限定注解的使用范围(在什么地方可以使用该注解)
源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
其中ElementType(是一个枚举类)用于限定范围,源码如下:
public enum ElementType { /**类,接口,枚举*/ TYPE, /**字段上 */ FIELD, /** 方法*/ METHOD, /** 参数*/ PARAMETER, /** 构造器*/ CONSTRUCTOR, /** 局部变量 */ LOCAL_VARIABLE, /** 注解 */ ANNOTATION_TYPE, /** 包 */ PACKAGE, /** * 泛型 * @since 1.8 */ TYPE_PARAMETER, /** * 使用泛型时 * @since 1.8 */ TYPE_USE }
应用如下:
@Target({
ElementType.TYPE,
ElementType.FIELD,
ElementType.METHOD,
ElementType.PARAMETER,
ElementType.LOCAL_VARIABLE,
ElementType.CONSTRUCTOR})
public @interface MyAnno {
String value();
}
以上的代码表示,该注解可以用于:
- 类型上(类,接口,枚举)
- 字段上(全局变量)
- 方法上
- 方法的参数上
- 局部变量上
- 构造器上
如果不限定范围,默认全局(任何地方可以加)
用于设置该注解是否能够被保留到文档中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnjFh8JS-1595770172773)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200725\笔记\assets\1595646837029.png)]
@FuntionalInterface中有如下声明
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {
}
用于标记当前注解在使用到某个类上时,如果有子类继承该类,则注解也会默认作用子类上
package Udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class MyFeiQ {
public static void main(String[] args) throws IOException {
//准备需要发送的消息
//版本号:时间戳:发送人名称:发送人主机地址:命令字:消息
String msg="1:1000:giao桑:110:32:救救我.......";
//准备网络通道
DatagramSocket ds=new DatagramSocket();
while (true) {
//将要传输的数据打包为数据报包
DatagramPacket packet=new DatagramPacket(msg.getBytes(),
msg.getBytes().length,
InetAddress.getByName("192.168.7.106"),
2425);
//发送
ds.send(packet);
}
}
}