一、多线程的常用方法及API
java.lang.Thread 类中提供了大量的相关的方法:
new Thread();
new Thread(name);
new Thread(Runnable,name);
new Thread(Runnable)
常用方法:
getId() :获取线程的唯一标识
getName() :获取线程名
getPriority():获取线程的优先级: 优先级从1-10 , min-priority:1。 max-priority:10。norm- priority:5 。注意说明优先级高的获取到cpu资源的概率越大,并不是一定会优先执行完成。
currentThread():获取当前线程的对象引用
getState():获取线程的状态 (这是返回线程状态的枚举, NEW:未启动,RUNABLE:可运行 BLOCK:阻塞状态, WAITING:等待状态TIMED-WAITIN: 等待另一个线程执行完)
interrupt():中断这个线程
isInterrupted(): 返回boolean 测试当前线程是否中断
isAlive():该线程是否处于活动状态
isDaemon():判断该线程是否是守护线程
setDaemon():设置该线程是否是守护线程
join() :合并线程,使它变为单线程
sleep(ms) :让当前线程休眠 ,休眠时间到了,自动唤醒
yield(): 让出cpu资源,使当前线程处理可运行状态(可运行状态也随时可以获取cpu资源)
System.out.println("当前主线程:"+Thread.currentThread().getName());
System.out.println("主线程id:"+Thread.currentThread().getId());
//设置主线程的线程级别
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
System.out.println("主线程的线程级别:"+Thread.currentThread().getPriority());
// 创建线程对象
MyThread my = new MyThread();
//设置线程名
my.setName("线程A");
//设置优先级
my.setPriority(10);
my.start();
MyThread my1 = new MyThread();
my1.setName("线程B");
//设置优先级 线程A 获取到资源的概率 大于线程B (大概率线程A优先执行完)
my1.setPriority(1);
//新生态
System.out.println("线程"+my1.getName()+"状态-----"+my1.getState());
my1.start();
//可运行状态(就绪)
System.out.println("线程"+my1.getName()+"状态-----"+my1.getState());
for(int i = 0;i<100;i++){
System.out.println("主线程------"+i);
}
守护线程
线程类型分为两种 一种是用户线程一种是守护线程,用户线程是执行某一个任务的独立代码 ,守护线程是用于守护用户线程的线程, 它的特点是 当用户线程执行完毕后守护现在自动结束,当用户线程没有执行完, 守护线程也不会停止
操作系统中有守护进程 ,用于操作系统的运行,只有关机进程自动结束,这里守护线程和守护进程类似。
//创建线程对象
DaemonThread daemonThread = new DaemonThread();
//设置该线程为守护线程 守护的是与它并行的线程类 ,当主线程或其他线程执行完毕,守护线程自动结束
// daemonThread.setDaemon(true);
System.out.println("是否是守护线程:"+daemonThread.isDaemon());
daemonThread.start();
for(int i=0;i<100;i++){
System.out.println("主线程i------"+i);
活动的线程总数: Thread.activeCount()
}
线程中断
关于终止线程
线程中止就是当线程运行时由于满足特定的条件需要停止运行,此时我们需要考虑如何安全的中止线程这里中止线程提供几个方法
方法1 : 打标记中断法
线程运行1000,当程序达到500时,中止程序
public class ThreadEnd extends Thread {
@Override
public void run() {
boolean isOver=false;
for(int i = 0 ;i<1000;i++){
if(i>=500){
isOver= true;
return ;
}
System.out.println("线程结果i-----------"+i);
}
System.out.println("正常结束");
}
public static void main(String[] args) {
ThreadEnd th = new ThreadEnd();
th.start();
}
}
方法2: 异常中断法
interrupt() :给线程打一个中断标记,不会立马中断
interrupted() : 检测线程是否中断,并清除中断标记,返回boolean ,如果线程打标记了,就返回true
isInterrupted() : 检测线程是否中断,但不清除中断标记, 返回boolean
注意用法: interrupted() : 它所处于的位置,对应于它作用的位置 ,通过线程类名调用interrupt() 和 isInterrupted() : 使用线程对象调用。
public class Thread1 extends Thread {
@Override
public void run() {
int i =0;
while(true){
System.out.println("线程--------------"+i);
//判断当前线程是否有中断标记 ,但是不清除中断标记
if(this.isInterrupted()){
// 通过抛出异常或 break
System.out.println("当前线程打中断标记,可以停止了");
break;
}
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 th = new Thread1();
th.start();
// 休眠一会儿
Thread.sleep(2000);
//给th打中断标记
System.out.println("打标记");
th.interrupt(); //给th打标记
}
Thread.currentThread().interrupt();
System.out.println("判断线程是否打标记(不清除标记)"+ Thread.currentThread().isInterrupted());
System.out.println("判断当前线程是否打标记 (清除标记):"+ Thread.interrupted()); // 静态方法
join用法
join的用法: 合并当前线程 ,使其变为单线程 ,哪个线程调用join方法,就立即将该线程剩下的部分执行完成,再执行其他线程
sleep用法
sleep的用法: 用于休闲当前线程 ,休眠时间结束后自动唤醒继续执行,如果同时有多个线程执行 ,如果线程没有同步的情况下,相互休眠不影响,资源被公用
yield用法
yield的用法 : 出让cpu, 让当先线程变为可运行状态 ,并也可以继续抢占cpu资源
二、线程同步
同步的解决办法:
1、将需要操作公共资源的代码增加 “同步锁” (也叫互斥锁)
语法:
//对象一定是需要同步的线程共享的
synchronized(对象锁){
代码块
}
注意这里的对象锁必须满足 两个线程是同一个对象(同一把锁)
public void run() {
System.out.println("开始取钱了");
// 增加互斥锁,协同步伐 ,这个锁必须是公有的对象
synchronized(account) {
//先判断账户余额是否足够
if (account.getMoney() >= 1000) {
System.out.println(Thread.currentThread().getName() + "可以取钱");
System.out.println(Thread.currentThread().getName() + "正在取钱");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新账户余额
account.setMoney(account.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() +
"取到了钱,卡里余额还剩:" + account.getMoney());
} else {
System.out.println("抱歉,卡里余额不足,不能取1000元");
}
}
// 以上代码需要将操作通过资源的代码块 增加同步关键字
}
2、 同步方法:在方法的返回值前面增加 “synchronize” , 此时的锁代表的是当前this对象
public synchronized void get(){
//先判断账户余额是否足够
if (account.getMoney() >= 1000) {
System.out.println(Thread.currentThread().getName() + "可以取钱");
System.out.println(Thread.currentThread().getName() + "正在取钱");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新账户余额
account.setMoney(account.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() +
"取到了钱,卡里余额还剩:" + account.getMoney());
} else {
System.out.println("抱歉,卡里余额不足,不能取1000元");
}
}
三、线程死锁
线程死锁的产生
线程同步可以帮助我们解决多个线程操作同一个资源而导致数据不安全的问题,但线程同步也有可能产生隐患,假如一个线程中出现多个锁对象时,可能出现锁使用不当,导致锁与锁之前相互等待对方释放资源,从而形成一种 “相互等待”的僵局,这就是线程死锁。 例如哲学家吃饭
模拟线程死锁
public class DeadThread implements Runnable {
Object obj1 = new Object();
Object obj2 = new Object();
@Override
public void run() {
// 模拟线程死锁
// 如果当前线程为线程A 先拿到obj1锁 ,等待obj2锁资源
// 如果当前线程为线程B 先拿到obj2锁 ,等待obj1锁的资源
if(Thread.currentThread().getName().equals("线程A")){
synchronized (obj1){
System.out.println(Thread.currentThread().getName()+"拿到了obj1的锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在等待obj2锁。。。。。");
synchronized (obj2){
System.out.println("我已经拿到了obj2锁。。。。");
}
}
}else{
//线程B
synchronized (obj2){
System.out.println(Thread.currentThread().getName()+"拿到了obj2的锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在等待obj1锁。。。。");
synchronized (obj1){
System.out.println("我已经拿到了obj1锁,我和开心");
}
}
}
}
}
public static void main(String[] args) {
DeadThread dead = new DeadThread();
Thread th1 = new Thread(dead,"线程A");
Thread th2 = new Thread(dead,"线程B");
th1.start();
th2.start();
}
之所以会发生死锁,因为对象锁直接没有良好的“沟通”,导致互相获取对方的锁 ,进入等待中 ,可以通过线程类的几个方法 解决线程之间通信问题
四、线程通信的几个方法
wait() : 让当前线程处于等待中,会释放对象锁 ,但是不会自动唤醒,需要其他线程唤醒
notify() : 唤醒等待中的一个线程
notifyAll: 唤醒所有等待中的线程
他们都属性Object的方法,需要相同的对象 ,使用时 通过Object的对象调用
注意: 以上方法的调用必须满足两个条件: a、他们必须在同步代码块中执行, b、调用该方法的对象是锁对象
五、线程的生产者和消费者模式
生成/消费的产品(数据): Mobile (手机编号)
生成者线程类: Provider : 无限制的生成手机
消费者线程类:Customer : 无限制的消费手机
共享存储区: Storage ( push 、pop) 存储手机的对象数组
测试类
public class Mobile {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Mobile(int id) {
this.id = id;
}
}
存储区
package com.j2008.provider_customer;
//存储区,它是生产者和消费者共享的空间 ( 生产者和消费者将该对象作为公有锁)
public class Storage {
// 定义存储手机的对象数据
Mobile [] mobiles = new Mobile[10];
int index=0; // 个数
static int n=1000;
// 存放手机
public synchronized void push(Mobile mobile){
//考虑容器上限已满,必须等待
while(index == mobiles.length){
System.out.println("容器已满,需等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//通知消费者取消费 ,将其他线程全部唤醒
this.notifyAll();
mobiles[index]=mobile;
index++;
}
// 取出手机 1 2 3 4
// 1 2 3
// index--;
// mobile[index] =null
// return 取出的手机对象
public synchronized Mobile pop(){
Mobile m = null;
// 判断index是否小于0
while(index<=0){
//等待
System.out.println("容器中没有手机,需要等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
m = mobiles[index];
//将容器中的这个位置 设置为空
mobiles[index]=null;
// 通知生产者去生产
this.notifyAll();
return m;
}
public synchronized int getSize(){
return index;
}
}
生产者
package com.j2008.provider_customer;
public class Provider implements Runnable {
//共享存储区
Storage storage =null;
public Provider(Storage storage){
this.storage = storage;
}
@Override
public void run() {
//手机编号
int n=1000;
//一直生产
while(true){
Mobile m = new Mobile(n);
storage.push(m);
System.out.println(Thread.currentThread().getName()+
"生产了一部手机,其编号:"+m.getId()+" 其库存:"+storage.getSize());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
n++;
}
}
}
消费者
package com.j2008.provider_customer;
public class Customer implements Runnable {
Storage storage=null;
public Customer(Storage storage){
this.storage = storage;
}
@Override
public void run() {
while(true){
Mobile mobile = storage.pop();
System.out.println(Thread.currentThread().getName()+
"消费了一部手机,编号》》"+mobile.getId()+" 库存:"+storage.getSize());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类
package com.j2008.provider_customer;
public class TestProviderCustomer {
public static void main(String[] args) {
//创建公有的存储空间
Storage storage = new Storage();
Provider provider1 = new Provider(storage);
Provider provider2 = new Provider(storage);
Provider provider3 = new Provider(storage);
Thread th1 = new Thread(provider1,"张飞");
Thread th2 = new Thread(provider2,"刘备");
Thread th3 = new Thread(provider3,"关羽");
th1.start();
th2.start();
th3.start();
//消费者上场
Customer customer1 = new Customer(storage);
Customer customer2 = new Customer(storage);
Customer customer3 = new Customer(storage);
Thread th4 = new Thread(customer1,"消费者1");
Thread th5 = new Thread(customer2,"消费者2");
Thread th6 = new Thread(customer3,"消费者3");
th4.start();
th5.start();
th6.start();
}
}
六、线程池
1、定义
用于创建和管理线程的容器就是线程池 (Thread Pool) ,在线程池中的线程执行完任务后不会立马进入销毁状态,而是重置到线程池中变为“空闲线程” 。 有利于避免频繁创建线程消耗资源,提供线程复用率,有限管理该线程。
2、使用线程池的原因:
在多线程环境下,对于不断创建和销毁效率非常消耗系统资源,对于多线程之间的切换存在线程安全问题, 这是使用统一的管理类管理一些线程是比较好的解决办法
3、线程的运行机制:
- 在线程池模式下,任务是提交给线程池,由线程池根据当前空闲线程进行分配任务,如果没有空闲线程,由管理类创建线程或者进入任务等待队列中。
- 一个线程同时只能执行一个任务,但多个任务可以同时提交给这个线程池。
线程池的常用类 (ExecutedService)
参数1: corePoolSize:核心线程数
参数2:maximumPoolSize :最大线程数
参数3: keepAliveTime : 线程活动时长
参数4: 对于参数3的单位
1、可缓存的线程池 newCacheThreadPool(n);如果线程池中没有空闲线程,则创建新线程并放入线程池中,无上限线程数,如果有空闲线程则直接使用该线程
public static void main(String[] args) {
// 创建可缓存线程
ExecutorService service =Executors.newCachedThreadPool();
// 创建10个线程
int n=0;
for(int i = 0 ;i < 10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override //你们内部类
public void run() {
//任务 System.out.println(Thread.currentThread().getName()+"---"+n);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//由于以上的线程 每次执行完成只需要500毫秒 ,会释放线程对象到线程池中
// 1000毫秒后创建的线程 就会复用上一次的线程对象
}
//关闭线程池
service.shutdown();
}
}
2、可重用的固定线程池: newFixedThreadPool(n) ,线程数量固定,如果没有空闲线程,则存放无界队列中等待
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(3);
// 连续创建10个线程, 由于只有3个线程,所有线程只能等待
// 2秒后再执行3个
for(int i =0;i<10;i++){
service.execute(new Runnable() {
@Override
public void run() { System.out.println(Thread.currentThread().getName()+"正在执行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 结果: 每2秒执行3个线程
}
3、 固定长度的可执行定时任务的线程池 newScheduledThreadPool , 类似于定时器线程
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟1秒后每3秒执行一次");
}
}, 1, 3, TimeUnit.SECONDS);
4、单线程的线程池 newSingleThreadExecutor : 线程池中只有一个线程数,所有的任务都通过该线程执行,它可以保证所有的任务是FIFO模式, 满足队列结构
七、计算机网络基础
1、计算机网络定义
把分布在不同区域的计算机与专门的外部设备通过通信线路连接成复杂的网络系统, 众多计算机之间可以方便的互相传输信息,数据共享。
2、计算机网络主要功能:
资源共享
信息传输与集中处理
均衡负荷与分布处理
综合信息服务
计算机之间需要数据传输,离不开网络通信协议,网络通信协议就是 双方在传输数据时的约定
3、网络通信协议定义:
计算机在数据传输时的通用标准 。约定了他们的传输速率,传输代码、代码结构,出错控制等标准。
根据国际约束的协议分为网络通信协议的七层协议,按照实际应用也可分为四层协议
七层从下往上 : 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
四层协议从下往上: 物理+数据链路层 、网络层(IP层)、传输层、应用层
由于网络传输本身是比较复杂的过程,Java 对每一层进行封装,对每一层提供对应的API ,使我们在进行网络传输时不需要跟踪底层协议,只需对每一层提供的API掌握,同时数据传输的过程和 文件流一样操作,从而简化该过程。 所以网络传输会使用流、多线程的概念。
八、Java的网络编程
Java的网络编程包 java.net.*
网络层: 掌握网络IP 和 端口号 java.net.InetAddress \Inet4Address\Inet6Address
IP的定义: IP(Internet Protocol) 互联网协议, 在全球互联网范围内每一个IP地址表示一台独立的计算机(广域网内) ,IPv4由四个段组成,每一个段的数都从0-255
通过一个IP地址可以路由到唯一的计算机。
端口号(PORT): 一台计算机上可运行多个应用程序,通过端口号区分该计算机的指定应用程序, 同一个计算机下,不同应用程序,端口号一定不同。 端口号的范围(0-65535)
常用端口号 :
端口号 | 对应服务 | 端口号 | 对应服务 |
---|---|---|---|
7 | Echo服务器 | 80(http)/443(https) | 浏览器默认端口 |
21 | ftp | 8080 | Tomcat默认端口 |
23 | telnet远程连接 | 3306 | MySql |
25 | SMTP(邮件服务) | 110 | POP3 |
2425 | 内网通端口/飞秋 | 1521 | Oracle |
每一个通信程序都有一个端口号
IP层的类:
java.net. InetAddress
常用方法:
getLocalHost() : 获取本地IP对象
getHostAddress():获取IP地址
getHostName():获取计算机名
getAddress():获取ip地址的数组表现形式
static getByName(参数) :通过ip地址或计算机名 返回一个InetAddress对象
//确定主机名称的IP地址。
// 获取本机 LocalHost 表示本机ip
InetAddress inet = InetAddress.getLocalHost();
System.out.println("主机地址:"+inet.getHostAddress());
System.out.println("主机名:"+inet.getHostName());
System.out.println("主机名IP数组:"+
Arrays.toString( inet.getAddress()));
// 也可以通过ip地址获取InetAddress对象
InetAddress inet2= InetAddress.getByName("DESKTOP-346CK63");
//获取ip地址
System.out.println(inet2.getHostAddress());
InetAddress inet3 = InetAddress.getByName("192.168.7.189");
//获取计算机名
System.out.println(inet3.getHostName());
InetAddress inet4 = InetAddress.getByName("192.168.7.103");
System.out.println("对方的计算机名:"+ inet4.getHostName());
URL类:
URL全称 统一资源定位符,用于访问互联网上的资源地址,也称为 网址,
完成的URL地址包括以下部分
协议名://ip地址:端口号/文件路径/文件名
例如: http://47.100.182.246:8080/robot/
协议名: http 、https、ftp协议
URL : url的路由对象
URLConnection : url的连接对象, 可获取输入流
常用方法:
getConnect() : 获取连接对象的内容
URL url = new URL("https://www.baidu.com/");
// 获取根据ip + 端口 + 协议 + 文件组成URL
// URL url2 = new URL("http","47.100.182.246",8080,"/robot");
//获取连接 (与该地址的连接)
URLConnection conn = url.openConnection();
//获取这里的资源,首先需要获取输入流 ,下载远程资源相当于IO流操作
InputStream is = conn.getInputStream();
// 包装成一个字符流
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String str=null;
while((str =br.readLine()) !=null){
System.out.println(str);
}
br.close();
传输层(TCP/UDP)
传输层用于数据的传输,在数据传输过程中根据数据的可靠性可分为两类,
1、基于TCP的传输
TCP特点: 两台计算机之间建立可靠连接,基于Socket的通道一旦建立,则数据可通过字节流的方式传输到另一方, 安全的可靠协议 ,
2、基于UDP的传输
UDP特点: 它面向无连接的协议,不保证数据的可靠性,传播速度比较快
TCP和UDP的区别
TCP协议 | UDP协议 | |
---|---|---|
是否面向连接 | 面向连接 | 面向无连接 |
可靠性 | 可靠 | 不可靠 |
应用场合 | 适用于数据量较大的文件 | 数据量较少 |
速度 | 较慢 | 较快 |
在Java中如何实现TCP传输
核心类: java.net.ServerSocket : 用于接收数据的套接字
java.net.Socket :用于发送数据的套接字
套接字是两台计算机建立通信连接的端点 ,在Java对应Socket类
a、实现数据的单行发送和接收
以“客户端”发送数据给“服务端” : 客户端是发送方, 服务端是接收方
服务端(接收方)
//定义数据的接收方
ServerSocket server = new ServerSocket(8888);
// 监听返回套接字的 对象 线程会阻塞在这里,只要有人发送消息,就获取消息对应的端点Socket
Socket socket = server.accept();
System.out.println(socket.getInetAddress().getHostAddress()+"发送了消息");
// 获取接收对象的输入流
InputStream is = socket.getInputStream();
byte [] b = new byte[100];
// 实际读取的长度
int len = is.read(b);
String result = new String(b,0,len);
System.out.println(
socket.getInetAddress().getHostAddress()+"发送的数据:"+result);
客户端(发送方)
public static void main(String[] args) throws IOException {
// 客户端用于发送数据
//创建Socket对象 并根据ip地址和端口号8888
Socket socket = new Socket(InetAddress.getByName("192.168.7.189"),8888);
System.out.println("请输入你要发送的内容:");
Scanner sc = new Scanner(System.in);
String msg = sc.next();
OutputStream os = socket.getOutputStream();
os.write(msg.getBytes());
System.out.println("发送成功。");
}
九、UDP通信
UDP是面向无连接的通信协议, 它负责将数据打包后传输,数据以数据报文的形式存在DatagramPacket中, 不能确保对方是否一定能收到,它发送的速度较快。
java支持UDP编程的核心类
1、java.net.DatagramSocket类 :用于发送和接收包裹
2、java.net.DatagramPacket类: 存取数据包裹
1、点对点单次发送接收
发送方:
public static void main(String[] args) throws IOException {
// 1、先发送
DatagramSocket ds = new DatagramSocket();
System.out.println("请输入你要发送的数据 ");
Scanner sc = new Scanner(System.in);
String info= sc.nextLine();
// 将数据打包
DatagramPacket dp = new DatagramPacket(info.getBytes(),
info.getBytes().length, InetAddress.getLocalHost(),8883);
// 发送
ds.send(dp);
}
=============================================================
接收方:
public static void main(String[] args) throws IOException {
// 定义接收方
// 1 、创建接收数据对象
DatagramSocket ds = new DatagramSocket(8882);
byte [] b = new byte[1024];
// 2、创建用于接收的数据包对象
DatagramPacket dp = new DatagramPacket(b ,b.length);
// 3、接收数据报包
ds.receive(dp);
// 4、接收后数据存放在b数组中 ,转成字符串 ,以接收的实际长度转成字符串
String msg = new String(b,0,dp.getLength());
System.out.println("接收来自【"+dp.getAddress().getHostAddress()
+"】的数据:"+msg);
//5、关闭
ds.close();
}
}
2、点对点 一个来回的接收和发送
public static void main(String[] args) throws IOException {
// 1、先发送
DatagramSocket ds = new DatagramSocket();
System.out.println("请输入你要发送的数据 ");
Scanner sc = new Scanner(System.in);
String info= sc.nextLine();
// 将数据打包
DatagramPacket dp = new DatagramPacket(info.getBytes(),
info.getBytes().length, InetAddress.getLocalHost(),8883);
// 发送
ds.send(dp);
// 2、再接收 定义新的接收数据包
byte [] b = new byte[1024];
DatagramPacket dp2 = new DatagramPacket(b,b.length);
ds.receive(dp2);
//接收的数据
System.out.println("接收服务端回复的数据:"+new String(b,0,dp2.getLength()));
===================================================================
接收
public static void main(String[] args) throws IOException {
// 1 先接收数据
DatagramSocket ds = new DatagramSocket(8883);
// 需要定义接收的数组
byte [] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b,b.length);
//2 接收
ds.receive(dp);
System.out.println("接收到来自客户端的数据:"+new String(b,0,b.length));
// 2、再回复数据(发送出去)
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要回复的消息:");
String info = sc.nextLine();
DatagramPacket dp2 = new DatagramPacket(info.getBytes(),
info.getBytes().length,
dp.getAddress(), // 对方的ip对象
dp.getPort() // 对方的端口,都在接收的包裹对象上
);
ds.send(dp2);
}
}
十、数据库概述
数据库相关定义
数据库: 存放数据的仓库,数据库中包含了数据和数据库对象, 例如表(table) ,视图(view),存储过程(procedure) 和触发器等都称为数据对象
在这个数据库体系中包含如下几个概念
1、数据(Data): 数据是描述事物的符号记录,是数据库的基本对象 ,例如 图像,视频,音频,记录信息 。
数据处理: 顾名思义就是对数据进行处理,包括对数据存储、计算、加工、检索的过程
2、数据库(DataBase): 数据库可分为为 关系型数据库(MySql ,Oracle、DB2 等)和非关系型数据库 (Redis ,MongoDB )
存储数据的仓库, 长期存储在计算机内有组织,有结构的大量的可共享的数据集合。
特点:
- 永久存储
- 有组织
- 可共享
3、数据库管理系统(DataBaseManager System)
它是介于用户和操作系统之间的管理数据的软件, 它可以科学有效的组织和存储数据 。
它的作用:
数据定义功能
数据的组织和存储
数据的操纵功能
数据库的运行管理
数据库的建立和维护
数据库管理系统通常由DBA(数据库管理员 DataBase Administrator)管理
4、数据库系统(DataBase System)
把以数据应用为基础的系统统称为数据库系统,
它包含 数据库、计算机硬件,数据库管理系统和相关软件和管理人员
数据库软件介绍
Oracle : 甲骨文公司产品 (收费)
MySql:被甲骨文收购(免费,开源)
SQL Server: 微软的数据库
DB2: IBM公司
数据建模
概念模型定义: 为了解决特定的问题,将人们所关心的某些方面,建立以数据字段为基础的 模型 , 面向人,按用户的观点对数据和信息建立模型 ,这里通常使用 ER模型(Entity -Ralation)
数据模型包括: 层次模型、 网状模型、关系模型(重点)
关系模型:
关系: 将一个没有重复行重复列的二维表称为一个关系, 二维表也叫关系表
元组: 在关系表中的 一条记录就是元素, 每一条记录对应一个元组
字段(属性): 一个关系中的 每一列的列头称为字段(属性) ,每一个属性都有一个属性名, 其属性的值称为属性值
域: 每一个属性的取值范围, 例如 性别属性 它的域就是男,女, 每一个属性的值都必须来自域的值
主键: 在一个关系表中, 用于唯一标识的,没有特定业务意义的可以定义为主键,例如 表的ID
外键: 来自主外键表的关系中(主表和子表) ,在子表中与主表关联的字段。 例如 班级表 和 学生表
主表: 在这个关系中 表示1的一方 是主表, 多的一方式子表 班级表是主表,学生表是子表
例如 学生和科目表的关系: 学生是主表,科目是子表 。
数据库的三大范式
三大范式的作用: 用于在设计数据库表时,尽量减少数据表设计的字段冗余,和增强表的设计的合理性 ,在实际开发中 必须准寻第一,二范式,可以适当的不准寻第三范式。
- 第一范式: 在一个关系表中所有的属性都是不可分割的独立单元。
- 第二范式: 在满足第一范式的基础上, 每一个字段除了主键以外 都与该表主体直接相关
例如学生表中 可以定义 学生姓名,学生年龄,学生生日,不能定义学生的班级。
因为学生的班级不是与学生直接相关的 ,而是建立班级表关联学生表。 - 第三范式: 在满足第二范式的基础上 , 除了主键以外的列 都不传递依赖于主键列,则满足第三范式
在实际开发中 第三范式作为可选规则
学生id 学生姓名 学生年龄 外键班级id 班级名称(传递依赖) ,
传递依赖的字段会增加数据表字段冗余 ,当修改“班级名称时”, 不仅要修改 班级表的班级名称,还要修改 学生表的班级名称,这个不合理的, 但有时候为了提高查询效率(减少表的关联),也可以不满足第三范式(增加冗余字段)。
安装卸载数据库
数据库版本 : MySQL5.0 、MySQL5.5、MySQL5.7 、MySQL8.0 (推荐)
在 我的电脑-》管理-》 管理进程服务 -》 MySQL80服务
mysql的默认管理员 root 密码自定义
mysql的默认端口号: 3306
连接mysql的方式
1、通过命令行连接
cmd进入到mysql的安装目录(C:\Program Files\MySQL\MySQL Server 8.0)的bin目录下
: mysql -hIP地址 -u用户名 -p秒
远程连接: mysql -h47.100.182.246 -uroot -p123456
本地连接时 ip可以写 localhost 或者 127.0.0.1
2、 可以采用第三方客户端连接, 可以通过命令行访问
mysql的核心配置文件: my.ini 8.0以前在安装目录下,8.0以后在数据文件的目录下
C:\ProgramData\MySQL\MySQL Server 8.0
数据库的基本命令
1、创建用户
create user 用户名 identified by ‘密码’
给用户重命名
rename user 用户名 to 新用户名
创建用户之后 需要给用户赋予权限
2、给用户赋予权限 (grant )
grant select on 数据库名.* on 用户名
– 给用户名赋予查询 该数据库的所有对象的权限
撤销权限:
revoke select on 数据库名.* from 用户名
– 给用户撤销 查询该数据库的权限
3、 删除用户 drop user 用户名
4、创建数据库:
create database 数据库名
使用数据库
use 数据库名
当使用指定数据库时,后面所有操作都是针对该数据库的
5、 显示所有数据库
show databases ;
显示数据库下所有的表
show tables;
设置编码格式 (没有乱码则不需要修改)
set character_set_results=’GBK’;
desc 表名: 显示该表的结构
导入数据
SQL语言
SQL(Structure Query Language)结构化查询语言,用于操作主流数据库,例如MySQL ,ORACLE,DB2等, 方便快捷的检索和管理数据库 。
SQL语言分类
1、数据定义语言(DDL Data Definition Language) : 用于定义数据库表结构,视图,用户等 对象, create 、alter 、drop
2、数据操纵语言(DML Data Manipulation Language); 用于操作数据和检索数据 ,包含 insert 、 delete 、 update 、 select
3、数据控制语言(DCL Data Controller Language): 给用户赋予权限 (grant) ,撤销权限 (revoke)
4、事务控制语言(TCL Data Transaction Language ):commit (提交事务)、rollback (回滚事务)
数据查询和检索 select
数据库操作中最常用的语句
基础语法:
select 字段名1 , 字段名2 ,… from 表名 [ where 条件赛选] ;
-- 注释 查询表记录
-- 查询单列
select sid from t_student ;
-- 查询多列 查询指定sql 快捷键 选中sql ctr+shift+r
select sid,sname from t_student;
-- 查询所有列
select * from t_student;
select * from t_dept;
select * from t_man;
-- 去掉重复列 distinct (效率较低)
-- 查询所有学生的年龄
select distinct(sage) from t_student;
-- 使用数学表达式
-- 将每个学生的分数 增加20分 并重命名列(as关键字)
select sscore as 分数 , sscore + 20 as '增加20分后' from t_student;
-- 注意 在数学计算过程中,如果数据为空,会影响计算结果
-- 可以使用 判空处理 ifnull 如果字段为空 就表示0
select sscore as 分数 , ifnull(sscore ,0) + 20 as 加20后 from
t_student;
-- 给字段起别名时,如果出现特殊字符,可以使用“”
-- mysql中“” 和 ‘’的区别
select sname as '姓名' ,sscore as "分 数" from t_student ;
-- 所有男生信息 where 字段名 ='值' mysql推荐用''
select * from t_student where ssex="男";
-- 连接操作符 ,可以将多个字段连接到一个字段中显示
concat(字段1,字段2)
select concat(concat(sname,'的'),SSCORE) as '学生的分数' from t_student ;
SELECT * FROM T_STUDENT;