线程并发问题(线程安全)

day18 - 线程同步

线程并发问题(线程安全)

案例场景

  • 银行取钱问题
  • 买票问题
  • 限时秒杀

线程安全

多线程并发时,多个线程同时操作同一个内存区域(变量),可能会导致的结果不一致的问题;所谓线程安全,指的是在多线程并发操作的时候能保证共享的变量数据一致

并发编程三要素

线程并发时需要保证线程安全,需要满足三大条件:

  • 原子性
  • 可见性
  • 有序性
原子性(Atomicity)

对于一条线程执行来说要保证,要么都成功,要么都失败;对于原子性操作可以通过加锁的方式实现;Synchronized和ReentrantLock保证线程的原子性

可见性(Visibility)

多条线程操作同一个变量时,需要保证其中一条线程对变量的修改,对其他线程也是可见的

有序性(Ordering)

对于每一条线程的执行,内部应该是有序的:

  1. 代码顺序执行

  2. 锁的释放应该在获得之后

  3. 变量读取操作,在修改操作之后

线程同步解决方案

Synchronized

synchronized的关键字使用包含三种方式:

  1. synchronized块:对象锁
  2. synchronized方法:在方法声明的时候使用
  3. 类锁:在静态方法或者同步块中使用类的Class对象

被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();
    }

}

Lock

​ 从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();
	}

}

注意事项:

  1. 锁的获取位置一般在try语句块之前
  2. 锁的释放一般放到finally语句块中

ReentrantLock和Synchronized的区别:

  1. ReentrantLock是一个实现类,后者是一个关键字
  2. ReentracntLock需要手动获取锁以及释放锁(粒度控制更细);后者自动加锁,临界区(synchronized锁住的区域)的代码执行完之后自动释放
  3. ReentrantLock支持公平锁以及非公平锁;sychronized只支持非公平锁
  4. synchronized一般用于少量代码同步,ReentrantLock一般用于大量的同步代码块

volatile

在前面的线程并发中,对于原子性的解决方案使用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关键字的作用:实时的将全局变量的更新立即同步给其他线程

Wait&notify

线程运行时若需要限时等待,则可以通过sleep()方法设置休眠时间,但是对于一些不定时的等待需求sleep则无法实现;对于这种需求,java.lang.Object类中提供了用于实现等待和唤醒的方法:wait和notify;

  • 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();
	}

}

day19 - 网络编程(上)

计算机网络

概述

​ 将分布在不同地理区域的计算机,通过一些外部网络设备以及内部的网络协议,连接成一个网络系统;通俗的理解为2台或以上的计算机协同工作,例如:文件传输,数据传输。计算机网络的核心目的是实现:信息共享

网络分类

根据网络规模以及用途分为:

  • 局域网(0-10km,通过网络设备有线连接)
  • 城域网(0-100km,交通信号,视频监控)
  • 广域网(因特网:互联网)

根据网络的工作模式分为:

  • 专有服务器(只提供专一某一种服务器,如:云主机,数据库专有服务器,静态资源文件的专有服务)
  • 客户机服务器模式(c/s架构)
  • 对等式网络(peer to peer)

网络模型与协议

计算机网络之间实现通信需要两种设备支持:

  1. 硬件设备:(网卡,网关:交换机,路由器)
  2. 软件设备:(网络通信协议:TCP/IP、UDP)
网络协议

网络协议就是计算器网络设备相互之间通信的标准(暗号),在互联网标准组织的推动下,将网络协议分为两种:

  • TCP/IP : 传输控制协议/ip协议
  • UDP:用户数据报协议
OSI(Open System Interconnection)七层模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BjAzGkvW-1595769747942)(assets\1595299573815.png)]

TCP/IP协议(打电话)

传输控制协议,是一个安全可靠的互联网协议,需要通信的主机之间需要先建立正确的链接,才能够进行通信,并且改协议能够保证数据传输稳定性(必须的保证信息发送到一台主机,由该主机确认之后才能发送下一条信息),另外该协议也能保证数据传输的有序性(先发送的信息一定先到达)。一般基于C/S架构,存在服务器客户端模式。

应用领域: 语音通话,视频会议

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hjXIN6Ps-1595769747948)(assets\1595300606777.png)]

UDP协议(发快递)

User Diagram Protocol(用户数据报协议),是一个不安全的网络协议,不需要双方之间建立联系,也不保证信息传输的有序性(有可能后发消息先到),传输效率比TCP/IP更高.没有专门服务器和客户端,只有发送端和接收端

应用领域: 邮件发送,广播、飞秋

以上是互联网数据传输底层协议,目前大多数时候是直接使用的基于这两个协议的应用层协议

Http/Https、Ftp、Smtp、Pop3

IP与端口

ip

Internet Protocol(因特网协议),主机之间通信的唯一标识,每台计算机都已个唯一的ip地址;ip又划分为IPv4和IPv6

  • IPv4由4个字节构成的一段地址(每个字节最大不能超过255,范围0~255,最大取值只能到40+亿个)
  • IPv6由16个字节构成

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之间的端口密集绑定了一些已有的服务,所以应该避免在创建自定义服务时使用这些端口;自定义的服务建议选择范围:

  • 1024~49151之间

域名

唯一的对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

InetAddress类

IndetAddress是位于java.net包中提供的用于表示ip地址和主机的类,常用方法:

  • getLocalhost() 获取本地主机
  • getByName() 根据提供的主机名或者ip获取InetAddress对象
  • getHostAddress() 获取主机地址
  • getHostName() 获取主机名称
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);
        }
    }

}

基于TCP/IP的Socket通信

​ Socket(套接字),实际上就是由IP地址跟端口号的结合,通过Socket对象可以实现两台主机之间的通信;Socket分为服务端Socket(java.net.ServerSocket),以及客户端Socket(java.net.Socket)

day20 - 网络编程 (中)

基于TCP/IP的网络聊天室实现

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();
		}
		
	}
}

day20 - 网络编程(下)

基于UDP的Socket通信

​ TCP/IP协议是一种面向流的全双工通信协议,有着服务器客户端的概念,必须保证通信双方建立稳定安全的连接才能够进行数据传输;UDP协议不是一种基于稳定连接的协议,是一种面向数据报包的通信协议,不需要通信双方建立稳定的连接,也没有所谓服务端和客户的概念,数据报包在传输的时候不保证一定及时到达,也不能保证数据报包的到达顺序,但是UDP协议传输效率要远高于TCP/IP,比如直播,视频会议。

DatagramSocket&DatagramPacket

DatagramSocket类是一个基于UDP通信协议的套接字,使用该类可以实现一个无连接的通信通道;需要实现在该通道通信,我们还需要另一个类的辅助:DatagramPacket(数据报包),该类用于将数据打包成数据报包,然后通过DatagramSocket完成数据报包的发送(send)与接收(receive)

消息发送方(Sender)
/**
 * 发送方
 * @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();
    }
}

消息接收方(Receiver)
/**
 * 接收方
 * @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);
    }

}

UDP数据广播

​ 在通用的以太网(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);
		}
	}
}

Http协议

URL 类

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地址通常由以下几个部分构成:

  • 协议(双方约定的通信标准: http:// ftp:// jdbc:mysql://)
  • 主机地址或域名(资源所在服务器地址:softeem.com 119.231.8.9 192.168.0.1)
  • 端口(服务器中指定服务对外数据交换接口,唯一:80 3306)
  • 请求资源(服务器根地址中资源所在的相对位置:/sales/home.html test)
  • 查询路径(?之后的内容:page=1 user=root&password=123456)
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概述

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请求的构成

一个http请求过程通常由两个部分组成:

  1. 请求头(客户端请求到服务器时传输一些配置信息以及书资源)
  2. 响应头(服务端响应客户端时传递到客户端的状态信息)
请求头
请求体是否必选
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

  1. 请求头中常见的请求方式有哪一些?

POST/GET/DELETE/PUT/HEAD

  1. 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
<HTML>
<BODY>
……

http响应头中比较值得关注的是响应的状态码。

  • 1XX 消息
  • 2XX 响应成功
  • 200 服务端响应成功
  • 3XX 重定向,请求被重新定向到其他目标
  • 4XX 来自客户端的请求错误
  • 404 Not Found 请求的资源不存在
  • 405 请求和响应的方式不一致(客户端发送get请求,但是服务端使用post接收)
  • 5XX 服务器错误
  • 500 服务器内部错误(程序异常)

模拟Http服务器

  1. 在项目根目录下存放一个web项目(包含样式文件,脚本文件,图片,html等文件)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qWGiFHeT-1595770025477)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200723\笔记\assets\1595494618570.png)]

    注意:该目录与src同级别

  2. 代码实现

/**
 * 模拟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();
		}
		
	}
}

  1. 浏览器请求:

    http://127.0.0.1:9090/easyblog/index.html

day21 - JSON数据格式

HttpURLConnection

​ 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();
    }
}

基于Http协议下载远程文件

/**
 * 文件下载器
 * @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"));
    }

}

封装通用HttpUtils工具

对于大多数请求,主要是获取服务端文本数据,因此封装以下工具类用户获取网络资源:

/**
 * 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);
    }
}

开源的Http客户端框架

  1. apache-HttpClient
  2. okHttp

JSON数据格式

​ JSON(JavaScript Object Notation)是一种与语言平台无关的数据交换格式,有着比XML更为轻量级的数据表现形式,是目前大多数互联网应用程序之间的数据交换格式的首选。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bRLId3rQ-1595770061251)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200724\笔记\assets\1595586405537.png)]

JSON数据格式分类

JSON数据格式从结构上分为三种类型:

  1. 标量
  2. 数组
  3. 对象
标量

标量即一个具体的值:数值,字符串,布尔等;如: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支持的数据类型

JSON数据格式支持的值类型:

  1. String 字符串
  2. Number 数值
  3. Array 数组
  4. Object 对象
  5. true/false 布尔值
  6. null 空值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NCwP2FVi-1595770061257)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200724\笔记\assets\1595586789341.png)]

JSON与Java对象相互转换

json作为一种数据交换格式,就避免不了与Java之间的相互转换,因此对于Json字符串和Java之间的转换,目前有一些开源的可选方案:

  • FastJSON:阿里巴巴提供
  • Gson:Google提供
  • Jackson
  • json-lib
FastJSON使用

fastJSON是由阿里巴巴开源的JSON库,号称全世界最快的json插件,常用方法:

  • toJSONString(Object) 将一个Java对象转换为Json字符串
  • parseObject(String json,Class<T> clazz) 将一个json字符串转换为Java对象
  • parseArray(String json,Class<T> clazz) 将一个json字符串转换为Java集合
  1. 导入插件包:fastjson-1.2.5.jar

  2. 准备实体类

    public class User {
    
    	private int id;
    	private String username;
    	private String password;
    	private boolean vip;
    	
    	//构造器
        //setter/getter
        //toString
    }
    
  3. 测试类:

    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使用

gson是由Google官方开源的一个优秀json插件,使用方式与fastJSON存在细微差异,功能相近,常用方法如下:

  • toJSON(Object obj) 将Java对象转换为json字符串
  • fromJSON(String json,Class<T> clazz) 将json字符串转换为Java对象
  • fromJSON(String json,Type t) 将json字符串转换为指定类型的Java对象

使用方式:

  1. 导入依赖包:gson-2.2.4.jar

  2. 测试类

    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接口

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();

	}
}

day22 注解

注解概述

​ 注解(Annotation),是jdk5之后新增一项技术,可以通过在Java类,方法,属性等元素上加上注解,实现一些特定功能:编译检查,丰富文档化注释的内容,实现项目特定程序配置。注解只需要少量代码的存在即可;注释即解释;注解通常不会影响程序的正常逻辑,只是一种标记,Java中的注解通常是给编译器进行识别的

应用领域

  1. 丰富文档注释
  2. 编译检查
  3. 完成程序配置

文档注释

/**
 * 这是算数运算工具类
 * @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中包含一些内置的注解类型:

  • @Override:检查方法是否属于重写
  • @Depracted : 标记类,属性,方法为过时
  • @SupressWarning:抑制编译抛出的警告信息
  • @FunctionalInterface:java8新增函数式接口的检查注解
@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();
}

自定义注解支持的属性类型

  • String类型
  • 所有的基本类型
  • 枚举
  • 注解
  • 以上所有类型的数组类型

注意事项:注解中不支持自定义的数据类型

如果注解中只有一个属性(或者其他属性都有默认值),且该属性的名称叫“value”时,在使用该注解的时候只需要设置具体值就可以了,不需要设置属性名:

@MyAnno("softeem")
public class User {

}

对应的注解

public @interface MyAnno {

	String value();
	
}

元注解

Java中除了提供内置的注解和自定义注解之外,另外还提供了一些元注解:

  • @Retention 描述注解被保留的阶段(多数时候设置RUNTIME)
  • @Target 描述注解的作用范围
  • @Documented 用于描述注解是否能被保留到API文档
  • @Inherited 用于描述该注解是否被子类继承

@Retention (保留范围)

用于色湖之注解的被保留范围,包含三个枚举值:

  • Retention.SOURCE:编译期间
  • Retention.CLASS:运行期间字节码中(默认)
  • Retention.RUNTIME:运行时(常用选择)

@Target(使用范围)

@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();
	
}

以上的代码表示,该注解可以用于:

  • 类型上(类,接口,枚举)
  • 字段上(全局变量)
  • 方法上
  • 方法的参数上
  • 局部变量上
  • 构造器上

如果不限定范围,默认全局(任何地方可以加)

@Documented(文档化)

用于设置该注解是否能够被保留到文档中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnjFh8JS-1595770172773)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200725\笔记\assets\1595646837029.png)]

@FuntionalInterface中有如下声明

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

@Inherited(继承)

用于标记当前注解在使用到某个类上时,如果有子类继承该类,则注解也会默认作用子类上

与飞秋发送消息

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);
		}
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值