java高级篇

线程

线程概念

单线程:同一个时刻,只允许执行一个线程

多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件

并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发

并行:同一个时刻,多个任务同时执行,多核cpu可以实现并行

线程的基本使用

在java中线程使用有两种方法

继承Thread类,重写run方法

注:如果想要让线程持续执行,需要将run方法中的执行流程用while循环包裹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Cat extends Thread{//Thread类实现Runnble接口的run方法
@Override
public void run(){//重写run方法,写上自己的业务逻辑
//该线程每隔一秒,在控制台输出“喵喵,我是o(=•ェ•=)m“
while(true){
System.out.println("喵喵,我是o(=•ェ•=)m");
try{//这里try-catch是保证该线程在sleep时是否还能感知相应,能够响应终端,不会睡死
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
/*Thread的Runnable实现方法
@Override
public void run(){
if(target != null){
target.run();
}
}
*/

//创建Cat
Cat cat = new Cat();
cat.start();//启动线程

实现Runnable接口,重写run方法

Thread实现类Runnable接口,所以也可以让需要多线程的类实现Runnable来重写run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Dog implements Runnable{ //通过实现Runnable接口
int count = 0;
@Override
public void run(){
while(true){
System.out.println("小狗汪汪叫" + (++count) + Thread.currentThread().getName());
//休眠一秒
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
if(count == 10){
break;
}
}
}
}
Dog dog = new Dog();
//dog.start(); 这里不能调用start
//创建Thread对象,把dog对象(实现RUnnable),放入Thread
Thread thread = new Thread(dog);
thread.start();

实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议实现Runnable来实现多线程

监控线程和执行情况

使用JConsole可以实现查看线程每时每刻的状态

将程序写好之后,让程序运行。在运行的过程中,打开cmd,输入Jconsole.查找当前运行程序的线程名,就可以查看当前程序运行的情况了。

结论:

当main线程启动一个子线程 Thread-0,主线程不会阻塞,会继续执行

主线程和子线程会交替执行

子线程结束不会影响主线程的执行,当主线程退出时,子线程还会继续执行,并没有完全退出,当所有正在运行的线程结束后,整个进程才会结束

通过start方法才会真正的开启线程,底层会调用start0这个方法。然后调用run方法

1
2
3
4
5
6
7
cat.start();//启动线程-> 最终会执行cat的run方法
public synchronized void start(){
start0();
}
//start0是本地方法,是JVM调用,底层是c/c++开发
//真正实现多线程的效果,是start0(),而不是run方法
private native void start0();

start()方法调用start0()方法后,该线程并不一定会立马执行,只是将线程编程了可运行状态,具体什么时候执行,取决于cpu,有cpu统一调度

线程底层涉及操作系统,有底层系统的函数来实现

线程代理设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//线程代理类,模拟了一个极简Thread
class ThreadProxy implements Runnable{ //你可以把Proxy类当作 Thread
private Runnable target = null;//属性,类型Runnable

@Override
public void run(){
if(target != null){
target.run();
}
}

public ThreadProxy(Runable target){
this.target = target;
}

public void start(){
start0();//这个方法是真正实现多线程方法
}
public void start0(){
run();
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
@Override
public void run(){
System.out.println("老虎嗷嗷叫");
}
}
Tiger tiger = new Tiger();//实现了Runnable接口
new ThreadProxy(tiger).start();

控制线程终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class T extends Thread{
private int count = 0;
private boolean l = true;//控制线程的变量
@Override
public void run(){
while(l){
try{
Thread.sleep(50);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("T 运行中..." + (++count));
}
}
public void setL(boolean l){//希望其他线程能够控制该线程能否继续执行
this.l = l;
}
}
public static void main() throws InterruptedException{
T t = new T();
t.start();
//让主方法控制t线程终止可以修改t类中的一个变量
//10秒后,终止t线程
System.out.println("主线程休眠10秒");
Thread.sleep(10*1000);
t.setL(false);
}

线程常用的方法

  • setName() 设置线程名称,使之与参数name相同
  • getName() 返回该线程的名称
  • start() 使线程开始执行,java虚拟机底层调用该线程的start0方法
  • run() 调用线程对象的run方法
  • setPriority() 更改线程的优先级
  • getPriority() 获取线程的优先级
  • sleep() 在指定毫秒数内让当前正在执行的线程休眠(暂停执行)
  • interrupt() 中断线程
  1. start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程

  2. 线程优先级的范围:MAX_PRIORITY 10 MIN_PRIORITY 1 NORM_PRIORITY 5

  3. interrupt中断线程,但没有真正的结束线程,所以一般用于中断正在休眠的线程

  4. sleep:线程的静态方法,使当前线程休眠

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class ThreadMethod1 {
    public static void main(String[] args) throws InterruptedException {
    //测试相关的方法
    T t = new T();
    t.setName("老韩");
    t.setPriority(Thread.MIN_PRIORITY);
    t.start();//启动了子线程

    //主线程打印五个hi,然后中断子线程的休眠
    for (int i = 0; i < 5; i++) {
    Thread.sleep(1000);
    System.out.println("hi" + i);
    }
    t.interrupt();//当执行时,就会中断t线程的休眠
    }
    }

    class T extends Thread { //自定义的一个线程类
    @Override
    public void run() {
    while (true) {
    for (int i = 0; i < 100; i++) {
    //Thread.currentThread().getName()货期当前线程的名称
    System.out.println(Thread.currentThread().getName() + "吃包子~~~" + i);
    }
    try {
    System.out.println(Thread.currentThread().getName() + "休眠中~~~");
    Thread.sleep(20000);
    } catch (InterruptedException e) {
    //InterruptException是捕获到一个中断异常
    System.out.println(Thread.currentThread().getName() + "被interrupt了");
    }
    }
    }
    }
    • yield:线程的礼让,让出cpu,让其他线程来执行,但礼让的时间不确定,所以也不一定礼让成功

    • join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public class ThreadMethod2 {
    public static void main(String[] args) throws InterruptedException {
    X x = new X();
    x.start();
    System.out.println("主线程<小弟>执行");
    for (int i = 1; i <= 20; i++) {
    System.out.println("主线程(小弟)吃的包子数量:" + i);
    Thread.sleep(2000);
    System.out.println("喵喵造~~");
    if(i == 5){//小弟吃了五个包子,让老大先吃完
    System.out.println("主线程(小弟) 让 子线程<老大>先吃");
    //join,线程插队
    //x.join();//这里直接让x线程执行完成
    Thread.yield();//礼让,不一定成功
    System.out.println("子线程<老大> 吃完了 主线程(小弟)接着吃");
    }

    }
    System.out.println("(小弟吃完了包子)");

    }
    }

    class X extends Thread {
    @Override
    public void run() {
    System.out.println("子线程<老大>执行");
    for (int i = 1; i <= 20; i++) {
    System.out.println("子线程<老大>吃的包子数量:" + i);
    try {
    Thread.sleep(2000);
    System.out.println("喵喵造~~");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    System.out.println("<老大>吃完了包子");
    }
    }

用户线程和守护线程

用户线程:也叫工作线程,当线程的任务执行完成或通知方式结束

守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

常见的守护线程:垃圾回收机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyDaemonThread extends Thread{
public void run(){
for(;;){
try{
Thread.sleep(50);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("马蓉和宋喆聊天...");
}
}
}
public static void main(String[] args){
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//只需将子线程设为守护线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for(int i = 1;i <= 10;i++){
System.out.println("宝强在辛苦的工作...");
Thread.sleep(1000);
}
}

线程的七大状态

**1.初始(NEW):**刚被创建,还没运行(未执行线程的start()方法)

**2.就绪状态(READY):**线程在可运行线程池中,但未获得CPU执行权,和RUNNING并称运行

**3.运行中状态(RUNNING):**线程执行并获得CPU执行权,和READY并称运行

**4.阻塞(BLOCK):**等待其他线程释放锁的状态

**5.等待(WAITING):**需要其他线程做出一些约定好的动作,或被唤醒(通知或中断)

**6.超时等待(TIME_WAITING):**和等待的不同点在于可以在指定的时间自行醒来

**7.终止(TERMENATED):**线程已经执行完毕

解读

  • NEW 新建状态
    尚未启动的线程,使用new语句创建的线程处于新建状态(new Thread),仅仅在堆上分配了内存
  • RUNNABLE 运行状态
    在Java虚拟机中执行的线程处于此状态
  • BLOCKED 阻塞状态
    当线程由于缺少响应的资源而导致程序无法继续执行,就会从运行状态进入到阻塞状态比 如:锁资源,IO资源
  • WAITING 等待状态
    当线程调用了wait方法就会进入到WAITING状态,该状态只有notify等操作才能唤醒线程进入下一个状态
  • TIMED_WAITING 睡眠状态
    如果线程执行了sleep(long)/join(long)/wait(long),会触发线程进入到Time_waiting状态,只有到达设定的时间,才会脱离阻塞状态
  • TERMINATED 终止状态
    已退出的线程处于此状态,该线程结束生命周期

JDK明明为我们提供了 6 种状态,为什么标题说 7 大状态呢?实际上我们可以将 RUNNABLE 划分为重新两个状态, ReadyRunning 状态

  • Ready 就绪状态
    当线程创建后,其他线程调用start方法,该线程就进入到就绪状态,JVM就会为创建方法调用栈和程序计数器,处于这个状态的线程位于可运行的池中,等待获取CPU的使用权,其他处于阻塞状态解除阻塞之后也会进入就绪状态
  • Running 运行状态
    处于这个状态的线程占用CPU,执行程序代码,只有处于就绪状态的线程才会有机会转到运行状态

1668134610135

在这里插入图片描述

一个线程的生命周期状态包括:NewRunnableRunningTerminated状态
线程在需要响应资源时,就会进入到阻塞状态,阻塞状态包含WaitingBlockedTime_waiting状态

代码查看线程的所处状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ThreadStateTest {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getState());
t.start();

while (t.getState() != Thread.State.TERMINATED) {
System.out.println(t.getState());
Thread.sleep(500);
}

System.out.println(t.getState());

}
}


class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("hi " + (i + 1));

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

Java 多线程七大状态__线程7状态还是6状态

线程同步

在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性

即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,知道该线程完成操作,其他线程才能对该内存地址进行操作

当多个线程同时拥有一个数据时,并且同时运行,如果没有线程同步,会产生数据错误的情况,出现过载和超额的可能,这在实际开发中是一个巨大的错误,必须解决。

同步具体方法-Synchronized

同步代码块

1
2
3
4
5
6
7
synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步代码;
}
synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String name){
//需要被同步代码;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Sell {
public static void main(String[] args) {
/* SellTicket s1 = new SellTicket();
SellTicket s2 = new SellTicket();
SellTicket s3 = new SellTicket();
s1.start();
s2.start();
s3.start(); */
SellTicket2 s1 = new SellTicket2();
new Thread(s1).start();//第一个线程
new Thread(s1).start();//第二个线程
new Thread(s1).start();//第三个线程

}
}

class SellTicket2 implements Runnable {
private static int ticket = 100;
private boolean loop = true;//控制run方法的变量

Object object = new Object();

//public synchronized static void m1(){}的锁是加在SellTicket2.class
public synchronized static void m1(){}

//如果在静态方法,实现一个同步代码块,使用类名.class作为互斥锁
public static void m2(){
synchronized (SellTicket2.class){
System.out.println("m2");
}
}

//public synchronized void m() 就是一个同步方法 这是锁在this对象 也可以在代码块上写synchronize,同步代码块,互斥锁还是在this对象
public /*synchronized*/ void m() { //同步方法,在同一时刻,只有一个线程能访问

//代码块写法
synchronized (object){
if (ticket <= 0) {
System.out.println("售票结束~~");
loop = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买了一张票数,剩余票数" + (--ticket));
}
}

@Override
public void run() {
while (loop) {
m();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

class SellTicket extends Thread {
private static int ticket = 100;//让多个线程共享 ticket

@Override
public void run() {
do {
System.out.println(Thread.currentThread().getName() + "买了一张票数,剩余票数" + (--ticket));
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (ticket >= 0);
System.out.println("售票结束");
}
}

互斥锁

  1. java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。

  2. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

  3. 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问

  4. 同步的局限性:导致程序的执行效率要降低

  5. 同步方法(非静态的)的锁可以时this,也可以时其他对象(要求是同一个对象)

  6. 同步方法(静态的)的锁为当前类本身

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public synchronized void sell(){ //默认的写法是一个同步方法,锁在this对象,也可以在代码快上写synchronized,同步代码块
    if(ticketNum <= 0){
    System.out.println("售票结束...");
    loop = false;
    return;
    }
    }
    Object object = new Object();
    public void sell(){
    synchronized(object /*this*/){//锁不一定是this对象,只要是同一个对象即可
    if(ticketNum <= 0){
    System.out.println("售票结束");
    loop = false;
    return;l
    }
    }
    }
    //同步方法(静态的)的锁为当前类本身
    //这个静态方法的锁是加在是当前类.class对象上
    publics synchronized static void m1(){

    }

    //如果静态方法中,要实现一个同步代码快,就使用当前类.class作为互斥锁
    public static void m2(){
    synchronized(当前类.class){
    System.out.println("m2");
    }
    }

    实现的步骤:

    需要分析上锁的代码

    选择同步代码快或同步方法

    要求多个线程的锁对象为同一个即可

关于Java互斥锁

线程死锁

死锁的定义

多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

下面我们通过一些实例来说明死锁现象。

先看生活中的一个实例,两个人面对面过独木桥,甲和乙都已经在桥上走了一段距离,即占用了桥的资源,甲如果想通过独木桥的话,乙必须退出桥面让出桥的资源,让甲通过,但是乙不服,为什么让我先退出去,我还想先过去呢,于是就僵持不下,导致谁也过不了桥,这就是死锁。

在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class DeadLock_ {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}


//线程
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;

public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}

@Override
public void run() {

//下面业务逻辑的分析
//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程B 得不到 o1 对象锁,就会Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}

}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}

Java多线程:死锁_写一段多线程 要求产生死锁

释放锁

下面的方式会释放锁:

  • 当前线程的同步方法,同步代码块执行结束
  • 当前线程在同步代码块,同步方法中遇到break,return
  • 当前线程在同步代码块,同步方法中痴线了未处理的error或exception,导致异常结束
  • 当前线程在同步艾玛块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

下面的方式不会释放锁:

  • 线程执行同步代码块或者同步方法时,程序会调用Thread.sleep(),Thread.yield()方法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

提示:应尽量避免使用suspend() resume()来控制线程,方法不在推荐使用

IO流

创建文件

创建文件对象相关构造器和方法

1
2
3
4
new File(String pathName) 根据路径创建一个File对象
new File(File parent,String child) 根据父目录文件 + 子路径创建
new File(String parent,String child) 根据父目录 + 子路径创建

实现类图

1668142608271

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class FileCreate {
public static void main(String[] args) {

}

//方式1 new File(String pathname)
@Test
public void create01() {
String filePath = "e:\\news1.txt";
File file = new File(filePath);

try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}

}
//方式2 new File(File parent,String child) //根据父目录文件+子路径构建
//e:\\news2.txt
@Test
public void create02() {
File parentFile = new File("e:\\");
String fileName = "news2.txt";
//这里的file对象,在java程序中,只是一个对象
//只有执行了createNewFile 方法,才会真正的,在磁盘创建该文件
File file = new File(parentFile, fileName);

try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}

//方式3 new File(String parent,String child) //根据父目录+子路径构建
@Test
public void create03() {
//String parentPath = "e:\\";
String parentPath = "e:\\";
String fileName = "news4.txt";
File file = new File(parentPath, fileName);

try {
file.createNewFile();
System.out.println("创建成功~");
} catch (IOException e) {
e.printStackTrace();
}
}

//下面四个都是抽象类
//
//InputStream
//OutputStream
//Reader //字符输入流
//Writer //字符输出流
}

文件相关的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取文件的信息
@Test
public void info() {
//先创建文件对象
File file = new File("e:\\news1.txt");
//调用相应的方法,得到对应信息
System.out.println("文件名字=" + file.getName());
//getName、getAbsolutePath、getParent、length、exists、isFile、isDirectory
System.out.println("文件绝对路径=" + file.getAbsolutePath());
System.out.println("文件父级目录=" + file.getParent());
System.out.println("文件大小(字节)=" + file.length());
System.out.println("文件是否存在=" + file.exists());//T
System.out.println("是不是一个文件=" + file.isFile());//T
System.out.println("是不是一个目录=" + file.isDirectory());//F
}

目录操作和文件删除

mkdir()创建一级目录,mkdirs()创建多级目录,delete删除空目录或文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Directory_ {
public static void main(String[] args) {
//
}

//判断 d:\\news1.txt 是否存在,如果存在就删除
@Test
public void m1() {
String filePath = "e:\\news1.txt";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println("该文件不存在...");
}
}

//判断 D:\\demo02 是否存在,存在就删除,否则提示不存在
//这里我们需要体会到,在java编程中,目录也被当做文件
@Test
public void m2() {
String filePath = "D:\\demo02";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println("该目录不存在...");
}
}

//判断 D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
@Test
public void m3() {

String directoryPath = "D:\\demo\\a\\b\\c";
File file = new File(directoryPath);
if (file.exists()) {
System.out.println(directoryPath + "存在..");
} else {
if (file.mkdirs()) { //创建一级目录使用mkdir() ,创建多级目录使用mkdirs()
System.out.println(directoryPath + "创建成功..");
} else {
System.out.println(directoryPath + "创建失败...");
}
}
}
}

IO流原理及流的分类

Java IO流原理

I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输。如读/写文件,网络通讯等。

java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行

java.io下提供了各种”流“ 类和接口,用以获取不同种类的数据,并通过方法输入或输出数据

输入input:读取外部数据(磁盘,光盘等存储设备的数据到程序中(内存))

输出output:将程序(内存)数据输出到磁盘,光盘等存储到设备中

输入流是读取的,输出流是写入的

流的分类:

按操作数据单位不同分为:字节流(8bit) 二进制文件,字符流(按字符)文本文件,字符对应字节按照编码来查看

按数据流的流向不同分为:输入流,输出流

按流的角色的不同分为:节点流,处理流/包装流

抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

java的IO流共涉及40多个类,实际上非常规则,都是从如上四个抽象基类派生的。

由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

![](C:\Users\Zero\Downloads\javaIO流 (2).png)

字节输入流常用的类

InputStream常用的子类:

FileInputStream 文件输入流

BufferedInputStream 缓冲字节输入流

ObjectInputStream 对象字节输入流

1668151589178

FileInputStream文件输入流

常用方法

public abstract int read() throws IOException: 一次读取一个字节
返回:下一个数据字节;如果已到达文件末尾,则返回 -1。

public int read(byte[] b) throws IOException:一次读取一个字节数组 (读取实际的字节数) 指定字节数组的长度是:1024或者1024的倍数返回:读入缓冲区的字节总数,如果因为已经到达文件末尾而没有更多的数据,则返回 -1。

b - 存储读取数据的缓冲区

public void close() throws IOException:关闭此文件输入流并释放与此流有关的所有系统资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class FileInputStream_ {
public static void main(String[] args) {

}
/**
* 演示读取文件...
* 单个字节的读取,效率比较低
* -> 使用 read(byte[] b)
*/
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
//如果返回-1 , 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char)readData);//转成char显示
}

} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 使用 read(byte[] b) 读取文件,提高效率
*/
@Test
public void readFile02() {
String filePath = "e:\\hello.txt";
//字节数组
byte[] buf = new byte[8]; //一次读取8个字节.
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
//如果返回-1 , 表示读取完毕
//如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));//显示
}

} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

FileOutputStream文件输出流

常用方法
public void write(int b) throws IOException:
一次写一个字节 b- 要写入的字节。

public void write(byte[] b) throws IOException:
一次写一个字节数组

public void write(byte[] b, int off,int len) throws IOException:
一次写一部分字节数组

public void close()throws IOException
关闭此文件输出流并释放与此流有关的所有系统资源。此文件输出流不能再用于写入字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class FileOutputStream01 {
public static void main(String[] args) {

}

/**
* 演示使用FileOutputStream 将数据写到文件中,
* 如果该文件不存在,则创建该文件
*/
@Test
public void writeFile() {

//创建 FileOutputStream对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//得到 FileOutputStream对象 对象
//老师说明
//1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
//2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
//fileOutputStream.write('H');//
//写入字符串
String str = "hsp,world!";
//str.getBytes() 可以把 字符串-> 字节数组
//fileOutputStream.write(str.getBytes());
/*
write(byte[] b, int off, int len) 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流
*/
fileOutputStream.write(str.getBytes(), 0, 3);

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

FileReader和FileWriter字符输入输出流

FileReader和FileWriter是字符流,即按照字符来操作io

FileReader相关方法:

new FileReader(File/String)

read:每次读取单个字符,返回该字符,如果到文件末尾返回-1

read(char[])批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1

new String(char[]) :将char[]转换为String

new String(char[],off,len) :将char[]的指定部分转换成String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* 单个字符读取文件
*/
@Test
public void readFile01() {
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int data = 0;
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read, 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 字符数组读取文件
*/
@Test
public void readFile02() {
System.out.println("~~~readFile02 ~~~");
String filePath = "e:\\story.txt";
FileReader fileReader = null;

int readLen = 0;
char[] buf = new char[8];
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

FileWriter相关方法:

new FileWriter(File/String):覆盖模式,相当于流的指针在首端

new FileWriter(File/String,true) :追加模式,相当于流的指针在尾端

write(int)写入单个字符

write(char[])写入指定数组

write(char[],off,len)写入指定数组的指定部分

write(string)写入整个字符串

write(string,off,len)写入字符串的指定部分

注意:使用后必须要关闭(close)或刷新(flush),否则写入不到指定的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class FileWriter_ {
public static void main(String[] args) {
String filePath = "e:\\note.txt";
//创建FileWriter对象
FileWriter fileWriter = null;
char[] chars = {'a', 'b', 'c'};
try {
fileWriter = new FileWriter(filePath);//默认是覆盖写入
// 3) write(int):写入单个字符
fileWriter.write('H');
// 4) write(char[]):写入指定数组
fileWriter.write(chars);
// 5) write(char[],off,len):写入指定数组的指定部分
fileWriter.write("韩顺平教育".toCharArray(), 0, 3);
// 6) write(string):写入整个字符串
fileWriter.write("你好北京~");
fileWriter.write("风雨之后,定见彩虹");
// 7) write(string,off,len):写入字符串的指定部分
fileWriter.write("上海天津", 0, 2);
//在数据量大的情况下,可以使用循环操作.
} catch (IOException e) {
e.printStackTrace();
} finally {

//对应FileWriter , 一定要关闭流,或者flush才能真正的把数据写入到文件
//老韩看源码就知道原因.
/*
看看代码
private void writeBytes() throws IOException {
this.bb.flip();
int var1 = this.bb.limit();
int var2 = this.bb.position();
assert var2 <= var1;
int var3 = var2 <= var1 ? var1 - var2 : 0;
if (var3 > 0) {
if (this.ch != null) {
assert this.ch.write(this.bb) == var3 : var3;
} else {
this.out.write(this.bb.array(), this.bb.arrayOffset() + var2, var3);
}
}

this.bb.clear();
}
*/
try {
//fileWriter.flush();
//关闭文件流,等价 flush() + 关闭
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("程序结束...");
}
}

节点流和处理流

节点流可以从一个特定的数据源读写数据,如FileReader,FileWriter;

处理流(也叫包装流)是连接在已存在的流(节点流和处理流)之上,为程序提供更为强大的读写功能,也更加灵活,如BufferedReader,BufferedWriter.

节点流和处理流表格

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter 节点流
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 节点流
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter 节点流
访问字符串 StringReader StringWriter 节点流
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter 处理流
转换流 InputStreamReader OutputStreamWriter 处理流
对象流 ObjectInputStream ObjectOutputStream 处理流
抽象基类 FilterInputStream FilterOutputStream FilterReader FilterWriter 处理流
打印流 PrintStream PrintWriter 处理流
退回输入流 PushbackInputStream PushbackReader 处理流
特殊流 DataInputStream DataOutputStream 处理流

节点流是底层流/低级流,直接和数据源相接

处理流(包装流)包装节点流,即可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。

处理流(也叫包装流)对节点流进行包装,使用修饰器设计模式,不会直接与数据源相连

处理流的好处:

性能的提高:主要以增加缓冲的方式来提高输入输出的效率

操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便

BufferedReader和BufferedWriter

BufferedReader和BufferedWriter属于字符流,是按照字符来读取数据的

关闭时,只需要关闭外层流即可

BufferedReader的演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BufferedReader_ {
public static void main(String[] args) throws Exception {
String filePath = "e:\\a.java";
//创建bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line; //按行读取, 效率高
//说明
//1. bufferedReader.readLine() 是按行读取文件
//2. 当返回null 时,表示文件读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭 节点流
//FileReader。
/*
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();//in 就是我们传入的 new FileReader(filePath), 关闭了.
} finally {
in = null;
cb = null;
}
}
}

*/
bufferedReader.close();
}
}

BufferedWriter的演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BufferedWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\ok.txt";
//创建BufferedWriter
//说明:
//1. new FileWriter(filePath, true) 表示以追加的方式写入
//2. new FileWriter(filePath) , 表示以覆盖的方式写入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello, 韩顺平教育!");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("hello2, 韩顺平教育!");
bufferedWriter.newLine();
bufferedWriter.write("hello3, 韩顺平教育!");
bufferedWriter.newLine();

//说明:关闭外层流即可 , 传入的 new FileWriter(filePath) ,会在底层关闭
bufferedWriter.close();

}
}

通过增强流实现文件拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class BufferedCopy_ {
public static void main(String[] args) {
//老韩说明
//1. BufferedReader 和 BufferedWriter 是安装字符操作
//2. 不要去操作 二进制文件[声音,视频,doc, pdf], 可能造成文件损坏
//BufferedInputStream
//BufferedOutputStream
String srcFilePath = "e:\\a.java";
String destFilePath = "e:\\a2.java";
// String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";
// String destFilePath = "e:\\a2韩顺平.avi";
BufferedReader br = null;
BufferedWriter bw = null;
String line;
try {
br = new BufferedReader(new FileReader(srcFilePath));
bw = new BufferedWriter(new FileWriter(destFilePath));
//说明: readLine 读取一行内容,但是没有换行
while ((line = br.readLine()) != null) {
//每读取一行,就写入
bw.write(line);
//插入一个换行
bw.newLine();
}
System.out.println("拷贝完毕...");

} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流
try {
if(br != null) {
br.close();
}
if(bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

BufferedInputStream和BufferedOutputStream

这两种都是字节流,在初始化时,会创建一个内部缓冲区数组。

这两种流可以增强二进制文件的读写效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
* 思考:字节流可以操作二进制文件,可以操作文本文件吗?当然可以
*/
public class BufferedCopy02 {
public static void main(String[] args) {
// String srcFilePath = "e:\\Koala.jpg";
// String destFilePath = "e:\\hsp.jpg";
// String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";
// String destFilePath = "e:\\hsp.avi";
String srcFilePath = "e:\\a.java";
String destFilePath = "e:\\a3.java";
//创建BufferedOutputStream对象BufferedInputStream对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//因为 FileInputStream 是 InputStream 子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环的读取文件,并写入到 destFilePath
byte[] buff = new byte[1024];
int readLen = 0;
//当返回 -1 时,就表示文件读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}
System.out.println("文件拷贝完毕~~~");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
try {
if(bis != null) {
bis.close();
}
if(bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

对象处理流ObjectInputStream和ObjectOutputStream

看一个需求:

将int num = 100 这个int数据保存到文件中,注意不是100数字,而是int 100 并且,能够从文件中直接恢复int 100

将Dog dog = new Dog() 这个对象保存到文件中,并且能够从文件中恢复

上面的需求就是能够将基本数据类型或者对象进行序列化和反序列化操作

序列化和反序列化

序列化就是在保存数据时保存数据的值和数据类型

反序列化就是在恢复数据时恢复数据的值和数据类型

需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:Serializable(这是一个标记接口,没有任何方法),Externalizable(该接口有方法需要实现,但是我们一般实现Serializable)

ObjectInputStream和ObjectOutputStream也是包装流(增强流),并且可以传输可序列化的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//完成对数据的序列化
public class ObjectOutStream_ {
public static void main(String[] args) throws Exception {
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("韩顺平教育");//String
//保存一个dog对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}
//如果需要序列化某个类的对象,实现Serializable
class Dog implements Serializable{
private String name;
private int age;
private String nation;
private String color;
public Dog(String name,int age,String nation,String color){
this.name = name;
this.age = age;
this.nation = nation;
this.color = color;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//完成数据的反序列化
public class ObjectInputStream_ {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//指定反序列化的文件
String filePath = "e:\\data.dat";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
//读取
//老师解读
//1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
//2. 否则会出现异常
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
//dog 的编译类型是 Object , dog 的运行类型是 Dog
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object -> Dog
//这里是特别重要的细节:
//1. 如果我们希望调用Dog的方法, 需要向下转型
//2. 需要我们将Dog类的定义,放在到可以引用的位置
Dog dog2 = (Dog)dog;
System.out.println(dog2.getName()); //旺财..
//关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
ois.close();
}
}

注意:在执行序列化和反序列化中,如果需要序列化的类有所更改,如添加方法或增加字段,需要重新序列化和反序列化一次,否则会报错。

  1. 读写顺序要一致
  2. 要求实现序列化或反序列化对象,实现Seralizable
  3. 序列化的类中建议添加SerialVesionUID,为了提高版本的兼容性,当进行序列化时,如果序列化的类添加字段属性方法,添加序列版本号的类不需要重新序列化一次,会认为是一次版本更新
  4. 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
  5. 序列化对象时,要求里面属性的类型也需要实现序列化接口
  6. 序列化具备可继承性,也就是说如果某类已经实现了序列化,则他的所有子类也已经默认实现了序列化

标准输入输出流

介绍:

类型 默认设备
System.in 标准输入 InputStream 键盘
System.out 标准输出 PrintStream 显示器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InputAndOutput {
public static void main(String[] args) {
//System 类 的 public final static InputStream in = null;
// System.in 编译类型 InputStream
// System.in 运行类型 BufferedInputStream
// 表示的是标准输入 键盘
System.out.println(System.in.getClass());
//老韩解读
//1. System.out 类 的 public final static PrintStream out = null;
//2. 编译类型 PrintStream
//3. 运行类型 PrintStream
//4. 表示标准输出 显示器
System.out.println(System.out.getClass());
System.out.println("hello, 韩顺平教育~");
Scanner scanner = new Scanner(System.in);
System.out.println("输入内容");
String next = scanner.next();
System.out.println("next=" + next);
}
}

转换流InputStreamReader和OutputStreamWriter

把一种字节流转为字符流

一个文件乱码问题,引出思考:(transformation)

在默认情况下,读取文件是按照utf-8来读取的

如果文件不是utf-8编码,读取会报错

在转换流可以在读取中将文件按照指定编码来读取文件,使其不会产生乱码的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CodeQuestion {
public static void main(String[] args) throws IOException {
//读取e:\\a.txt 文件到程序
//思路
//1. 创建字符输入流 BufferedReader [处理流]
//2. 使用 BufferedReader 对象读取a.txt
//3. 默认情况下,读取文件是按照 utf-8 编码
String filePath = "e:\\a.txt";
BufferedReader br = new BufferedReader(new FileReader(filePath));
String s = br.readLine();
System.out.println("读取到的内容: " + s);
br.close();
//InputStreamReader
//OutputStreamWriter
}
}

inputStreamReader和outputStreamWriter的构造器:

1
2
3
inputStreamReader(InputStream,Charset) //后面可以指定字符编码
outputStreamWriter(OutputStream,Charset)//后面可以指定字符编码

InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)

OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @author 韩顺平
* @version 1.0
* 演示使用 InputStreamReader 转换流解决中文乱码问题
* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
*/
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
//解读
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把 InputStreamReader 传入 BufferedReader
//BufferedReader br = new BufferedReader(isr);
//将2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk"));
//4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
//5. 关闭外层流
br.close();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author 韩顺平
* @version 1.0
* 演示 OutputStreamWriter 使用
* 把FileOutputStream 字节流,转成字符流 OutputStreamWriter
* 指定处理的编码 gbk/utf-8/utf8
*/
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\hsp.txt";
String charSet = "utf-8";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
osw.write("hi, 韩顺平教育");
osw.close();
System.out.println("按照 " + charSet + " 保存文件成功~");
}
}

打印流PrintStream和PrintWriter

打印流只有输出流,没有输入流

printStream的继承图

1668479748037

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* @author 韩顺平
* @version 1.0
* 演示PrintStream (字节打印流/输出流)
*/
public class PrintStream_ {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
/*
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
*/
out.print("john, hello");
//因为print底层使用的是write , 所以我们可以直接调用write进行打印/输出
out.write("韩顺平,你好".getBytes());
out.close();
//我们可以去修改打印流输出的位置/设备
//1. 输出修改成到 "e:\\f1.txt"
//2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
//3. public static void setOut(PrintStream out) {
// checkIO();
// setOut0(out); // native 方法,修改了out
// }
System.setOut(new PrintStream("e:\\f1.txt"));
System.out.println("hello, 韩顺平教育~");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author 韩顺平
* @version 1.0
* 演示 PrintWriter 使用方式
*/
public class PrintWriter_ {
public static void main(String[] args) throws IOException {
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
printWriter.print("hi, 北京你好~~~~");
printWriter.close();//flush + 关闭流, 才会将数据写入到文件..
}
}

Properties类

看一个需求

如下配置配置文件:mysql.properties

ip=192.168.0.12

user=root

pwd=1111

请问如何读取ip,user,password的值

使用配置文件可以降低程序的解耦性,将一些程序在启动前的部署配置写到其他文件中。在启动程序自动读取文件,即方便更改而且简单明了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取mysql.properties 文件,并得到ip, user 和 pwd
BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) { //循环读取
String[] split = line.split("=");
//如果我们要求指定的ip值
if("ip".equals(split[0])) {
System.out.println(split[0] + "值是: " + split[1]);
}
}
br.close();
}
}

properties文件:注意不需要再=前后加空格,放置在程序的根目录src下,就可以读取到properties

1
2
3
4
ip=192.168.0.34
user=root
pwd=1111

  • 获取value Properties.get(key)**
  • 删除 Properties.remove(key)
  • 修改 Properties.put(key,value)
  • 读取文件 Properties.load(InputStream)
  • 获取属性 Properties.getProperty()
  • 设置属性 Properties.setProperty()
  • 将数据显示到指定设备 Properties.load()
  • 将properties保存到idea中,有中文会存储为unicode Properties.store()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用Properties 类来读取mysql.properties 文件
//1. 创建Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3. 把k-v显示控制台
properties.list(System.out);
//4. 根据key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Properties03 {
public static void main(String[] args) throws IOException {
//使用Properties 类来创建 配置文件, 修改配置文件内容
Properties properties = new Properties();
//创建
//1.如果该文件没有key 就是创建
//2.如果该文件有key ,就是修改
/*
Properties 父类是 Hashtable , 底层就是Hashtable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//如果key 存在,就替换
return old;
}
}

addEntry(hash, key, value, index);//如果是新k, 就addEntry
return null;
}
*/
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode码值
properties.setProperty("pwd", "888888");
//将k-v 存储文件中即可
//null是文件头的注释
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
System.out.println("保存配置文件成功~");
}
}

网络编程

ip地址

概念:用于唯一标识网络中的每台计算机

  1. 查看ip地址:ipconfig
  2. ip地址的表示形式:点分十进制:xx.xx.xx.xx :4个字节(32位)表示
  3. 每一个十进制数的范围:0~255
  4. ip地址的组成=网络地址+主机地址,比如:192.168.13.1
  5. IPV6是互联网工程任务组设计的用于替代IPV4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子上编上一个地址,IPV6使用128位表示地址,16个字节,是IPV4的四位
  6. 由于IPV4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPV6的使用不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍

ipv4地址分类

类型 范围 形态
A 0.0.0.0 到 127.255.255.255 1 7位网络号 24位主机号
B 128.0.0.0 到 191.255.255.255 10 14位网络号 16位主机号
C 192.0.0.0 到 223.255.255.255 110 21位网络号 8位主机号
D 224.0.0.0 到 239.255.255.255 1110 28位多播组号
E 240.0.0.0 到 247.255.255.255 11110 27位留待后用

特殊的:127.0.0.1表示本机地址

域名

www.baidu.com

好处:为了方便记忆,解决记ip地址的困难

概念:将ip地址映射成域名

端口号

概念:用于标识计算机上某个特定的网络程序

表示形式:以整数形式,范围065535 [2个字节表示端口 02^16-1]

0~1024已经被占用,比如ssh 22 ftp 21 smtp 25 http 80

常见的网络程序端口号:

tomcat:8080

mysql:3306

oracle:1521

sqlserver:1433

网络通信协议

协议tcp/ip

TCP/IP(Transmission Control Protocol/Internet Protocol)的简写

中文译名为传输控制协议/因特网互联协议,又叫网络通信协议,这个协议是Internet最基本的协议,Internet国际互联网络的基础,简单的说,就是有网络层的ip协议和传输层的TCP协议组成的

简单来说就是网络世界中程序或服务器互相交流的一种语言(统一的要求或规定,大家都能懂),这就是一种协议。

1668505822949

网络通信协议表格

OSI模型 TCP/IP模型 TCP/IP模型各层对应协议
应用层 应用层 http,ftp,telnet,dns
表示层 应用层 http,ftp,telnet,dns
会话层 应用层 http,ftp,telnet,dns
传输层 传输层 tcp,udp
网络层 网络层 ip,icmp,arp
数据链路层 物理+数据链路层 link
物理层 物理+数据链路层 link

TCP和UDP

TCP协议:传输控制协议

  1. 使用TCP协议前,须先建立TCP链接,形成传输数据通道
  2. 传输前,采用三次握手方式,是可靠的
  3. TCP协议进行通信的两个应用进程:客户端,服务端
  4. 在连接中可以进行大数据量的传输
  5. 传输完毕需释放已建立的连接,效率低

UDP协议:

  1. 将数据,源,目的封装成数据包,不需要建立连接
  2. 每个数据报的大小限制在64k内
  3. 因无需连接,故是不可靠的
  4. 发送数据结束时无需释放资源(因为不是面向连接的),速度快

InetAddresss类

  • 获取本地InetAddress对象getLocalHost
  • 根据指定主机名/域名获取ip地址对象getByName
  • 获取InetAddress对象的主机名getHostName
  • 获取InetAddress对象的地址getHostAddress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class API_ {
public static void main(String[] args) throws UnknownHostException {
//1. 获取本机的InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);//DESKTOP-S4MP84S/192.168.12.1

//2. 根据指定主机名 获取 InetAddress对象
InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S");
System.out.println("host1=" + host1);//DESKTOP-S4MP84S/192.168.12.1

//3. 根据域名返回 InetAddress对象, 比如 www.baidu.com 对应
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2=" + host2);//www.baidu.com / 110.242.68.4

//4. 通过 InetAddress 对象,获取对应的地址
String hostAddress = host2.getHostAddress();//IP 110.242.68.4
System.out.println("host2 对应的ip = " + hostAddress);//110.242.68.4

//5. 通过 InetAddress 对象,获取对应的主机名/或者的域名
String hostName = host2.getHostName();
System.out.println("host2对应的主机名/域名=" + hostName); // www.baidu.com
}
}

Socket套接字

基于客户端和服务端的网络通信

底层使用的是TCP/IP协议

应用场景距离:客户端发送数据,服务端接收并显示

基于Socket的TCP编程和UDP编程

当我们需要通讯时(读写数据)

1
2
3
4
socket.getOutputStream() 

socket.getInputStream()

  1. Socket是开发网络应用程序被广泛采用,以至于成为事实上的标准。
  2. 通信的两端都要有Socket,是两台机器间通信的关键
  3. 网络通信其实就是Socket间的通信
  4. Socket允许程序包网络连接当成一个流,数据在两个Socket之间通过IO传输
  5. 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端

服务端和客户端通讯过程的简单展示

Server Client
ServerSocket(int port) Socket(InetAddress address,int port)
Socket.accept() Socket.getOutputStream()
Socket.getOutputStream() Socket.getInputStream()
Socket.getInputStream() Socket.close()
Socket.close()

完成一个需求:

  • 编写一个服务器端,和一个客户端
  • 服务器端在9999端口监听
  • 客户端连接到服务器端,发送hello,server,然后退出
  • 服务器接收到客户端发送的消息,输出,并退出

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.hspedu.socket;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author 韩顺平
* @version 1.0
* 服务端
*/
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的9999端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接..");
//2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回Socket对象,程序继续

Socket socket = serverSocket.accept();

System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根据读取到的实际长度,显示内容.
}
//5.关闭流和socket
inputStream.close();
socket.close();
serverSocket.close();//关闭

}
}


客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.hspedu.socket;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
* @author 韩顺平
* @version 1.0
* 客户端,发送 "hello, server" 给服务端
*/
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回=" + socket.getClass());
//2. 连接上后,生成Socket, 通过socket.getOutputStream()
// 得到 和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道
outputStream.write("hello, server".getBytes());
//4. 关闭流对象和socket, 必须关闭
outputStream.close();
socket.close();
System.out.println("客户端退出.....");
}
}


[TCP Socket 编程原理详解 ](https://www.cnblogs.com/FengZeng666/p/15610953.html#:~:text=Socket (套接字) 是网络编程的一种接口,它是一种特殊的 I%2FO。 Socket可以理解为TCP%2FIP网络的API,它定义了许多函数或例程,程序员可以用它们来开发TCP%2FIP网络上的应用程序。 电脑上运行的应用程序通常通过”套接字”向网络发出请求或者应答网络请求。,在 TCP%2FIP 协议中,”IP地址+TCP或UDP端口号”可以唯一标识网络通讯中的一个进程。 可以简单地认为 :”IP地址+端口号”就称为socket。 在TCP协议中,建立连接的两个进程各自有一个socket来标识,这两个 socket组成的socket对就唯一标识一个连接。)

应用案例2

  • 编写一个服务端,和一个客户端
  • 服务端在9999端口监听
  • 客户端连接到服务端,发送“hello,server”,并接收服务器回发的hello,client.再退出
  • 服务端接收到客户端发送的信息,输出,并发送”hello,client”,再退出

注意:在客户端回复服务端发送的消息,因为不确定对方什么时候是将上一次发送或接收的信息处理完成,在回复或再次重发时,需要设置一个结束标记,证明上一条讯息已成功读取或发送完成,才能进行下一次的讯息发送

1
2
socket.shutdownOutput();

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.hspedu.socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author 韩顺平
* @version 1.0
* 服务端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的9999端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接..");
//2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回Socket对象,程序继续

Socket socket = serverSocket.accept();

System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根据读取到的实际长度,显示内容.
}
//5. 获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, client".getBytes());
// 设置结束标记
socket.shutdownOutput();

//6.关闭流和socket
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();//关闭

}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.hspedu.socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
* @author 韩顺平
* @version 1.0
* 客户端,发送 "hello, server" 给服务端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回=" + socket.getClass());
//2. 连接上后,生成Socket, 通过socket.getOutputStream()
// 得到 和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道
outputStream.write("hello, server".getBytes());
// 设置结束标记
socket.shutdownOutput();

//4. 获取和socket关联的输入流. 读取数据(字节),并显示
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}

//5. 关闭流对象和socket, 必须关闭
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客户端退出.....");
}
}

TCP网络编程

应用案例3

  • 编写一个服务端,和一个客户端
  • 服务端在9999端口监听
  • 客户端连接到服务端,发送“hello,server”,并接收服务器回发的“hello,client”,在退出
  • 服务端接收到客户端发送的信息输出,并发送“hello,client”再退出

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.hspedu.socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author 韩顺平
* @version 1.0
* 服务端, 使用字符流方式读写
*/
@SuppressWarnings({"all"})
public class SocketTCP03Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本机 的9999端口监听, 等待连接
// 细节: 要求在本机没有其它服务在监听9999
// 细节:这个 ServerSocket 可以通过 accept() 返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端,在9999端口监听,等待连接..");
//2. 当没有客户端连接9999端口时,程序会 阻塞, 等待连接
// 如果有客户端连接,则会返回Socket对象,程序继续

Socket socket = serverSocket.accept();

System.out.println("服务端 socket =" + socket.getClass());
//
//3. 通过socket.getInputStream() 读取客户端写入到数据通道的数据, 显示
InputStream inputStream = socket.getInputStream();
//4. IO读取, 使用字符流, 老师使用 InputStreamReader 将 inputStream 转成字符流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);//输出

//5. 获取socket相关联的输出流
OutputStream outputStream = socket.getOutputStream();
// 使用字符输出流的方式回复信息
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello client 字符流");
bufferedWriter.newLine();// 插入一个换行符,表示回复内容的结束
bufferedWriter.flush();//注意需要手动的flush


//6.关闭流和socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.close();//关闭

}
}


客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.hspedu.socket;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
* @author 韩顺平
* @version 1.0
* 客户端,发送 "hello, server" 给服务端, 使用字符流
*/
@SuppressWarnings({"all"})
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
//思路
//1. 连接服务端 (ip , 端口)
//解读: 连接本机的 9999端口, 如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回=" + socket.getClass());
//2. 连接上后,生成Socket, 通过socket.getOutputStream()
// 得到 和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3. 通过输出流,写入数据到 数据通道, 使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello, server 字符流");
bufferedWriter.newLine();//插入一个换行符,表示写入的内容结束, 注意,要求对方使用readLine()!!!!
bufferedWriter.flush();// 如果使用的字符流,需要手动刷新,否则数据不会写入数据通道

//4. 获取和socket关联的输入流. 读取数据(字符),并显示
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);

//5. 关闭流对象和socket, 必须关闭
bufferedReader.close();//关闭外层流
bufferedWriter.close();
socket.close();
System.out.println("客户端退出.....");
}
}


应用案例4

  • 编写一个服务端,和一个客户端
  • 服务端在9999端口监听
  • 客户端连接到服务端,发送一张图片
  • 服务端接收到客户端发送的图片,保存到src下,发送收到图片再退出
  • 客户端接收到服务端发送的收到图片,再退出
  • 该程序要求使用StreamUtils.java

提示:再字符流刷新输入socket中的数据,使用flush(),再字节流中设置结束标记socket.shutdownOutput()即可

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.hspedu.upload;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author 韩顺平
* @version 1.0
* 文件上传的服务端
*/
public class TCPFileUploadServer {
public static void main(String[] args) throws Exception {

//1. 服务端在本机监听8888端口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在8888端口监听....");
//2. 等待连接
Socket socket = serverSocket.accept();


//3. 读取客户端发送的数据
// 通过Socket得到输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//4. 将得到 bytes 数组,写入到指定的路径,就得到一个文件了
String destFilePath = "src\\abc.mp4";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);
bos.close();

// 向客户端回复 "收到图片"
// 通过socket 获取到输出流(字符)
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("收到图片");
writer.flush();//把内容刷新到数据通道
socket.shutdownOutput();//设置写入结束标记

//关闭其他资源
writer.close();
bis.close();
socket.close();
serverSocket.close();
}
}


客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.hspedu.upload;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;


/**
* @author 韩顺平
* @version 1.0
* 文件上传的客户端
*/
public class TCPFileUploadClient {
public static void main(String[] args) throws Exception {

//客户端连接服务端 8888,得到Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
//创建读取磁盘文件的输入流
//String filePath = "e:\\qie.png";
String filePath = "e:\\abc.mp4";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));

//bytes 就是filePath对应的字节数组
byte[] bytes = StreamUtils.streamToByteArray(bis);

//通过socket获取到输出流, 将bytes数据发送给服务端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes);//将文件对应的字节数组的内容,写入到数据通道
bis.close();
socket.shutdownOutput();//设置写入数据的结束标记,设置的是StreamUtils.streamToString()方法中的结束标记

//=====接收从服务端回复的消息=====

InputStream inputStream = socket.getInputStream();
//使用StreamUtils 的方法,直接将 inputStream 读取到的内容 转成字符串
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);


//关闭相关的流
inputStream.close();
bos.close();
socket.close();

}


}


StreamUtils类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
* 此类用于演示关于流的读写方法
*
*/
public class StreamUtils {
/**
* 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
* @param is
* @return
* @throws Exception
*/
public static byte[] streamToByteArray(InputStream is) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
byte[] b = new byte[1024];//字节数组
int len;
while((len=is.read(b))!=-1){//循环读取
bos.write(b, 0, len);//把读取到的数据,写入bos
}
byte[] array = bos.toByteArray();//然后将bos 转成字节数组
bos.close();
return array;
}
/**
* 功能:将InputStream转换成String
* @param is
* @return
* @throws Exception
*/

public static String streamToString(InputStream is) throws Exception{
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder= new StringBuilder();
String line;
while((line=reader.readLine())!=null){
builder.append(line+"\r\n");
}
return builder.toString();
}
}


netstat指令

  • netstat -an 可以查看当前主机的网络情况,包括端口监听情况和网络连接情况

  • netstat -an | more 可以分页显示

  • 要求再dos控制台下执行

  • Listening表示某个端口在监听

  • 如果有一个外部程序连接到该端口,就会显示一条连接信息

  • ctrl+c退出指令

  • 想要查看是哪个程序在占用某个端口,需要使用管理员命令提示符,输入 netstat -anb

1668646899637

1668647674883

TCP网络通讯的秘密

当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP来分配的

1668649031491

1
2
3
TCP 192.168.12.1:8888	192.168.12.1:60285	ESTABLISHED //这个60285就是TCP/IP分配给客户端的端口
TCP 102.168.12.1:60285 192.168.12.1:8888 ESTABLISHED //由于是一台主机,所以两个端口都可见

UDP网络编程

  • 类DatagramSocket和DatagramPacket [数据包,数据报] 实现了基于UDP协议网络程序
  • UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不确定什么时候可以抵达
  • DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
  • UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接

发送接收的流程图

1668650354847

  • UDP说明:
  • 接收数据和发送数据是通过DatagramSocket对象来完成的
  • 将数据封装到DatagramPacket对象装包,发送数据
  • 当接收到DatagramPacket对象,需要进行拆包,取出数据
  • DatagramSocket可以指定在哪个端口接收数据

发送基本流程

  • 核心的两个类/对象 DatagramSocket和DatagramPacket
  • 建立发送端,接收端(没有服务端和客户端概念)
  • 发送数据前,建立数据包/报 DatagramPacket对象
  • 调用DatagramSocket的发送,接收方法
  • 关闭DatagramSocket
1
2
3
4
5
6
7
8
9
datagramPacket的构造器

DatagramPacket(byte[],int,int)//字节数组,开始长度,结束长度
DatagramPacket(byte[],int)
DatagramPacket(byte[],int,int,InetAddress,int) //字节数组,开始长度,结束长度,网络地址,端口
DatagramPacket(byte[],int,int,SocketAddress)
DatagramPacket(byte[],int,InetAddress,int)
DatagramPacket(byte[],int,SocketAddress)

应用案例

  • 编写一个接收端A(Receiver),和一个发送端(Sender)
  • 接收端A在9999端口等待接收数据(receive)
  • 发送端A向接收端B发送数据“hello,明天吃火锅”
  • 接收端B接收到发送端A发送的数据,回复“好的,明天见”,再退出
  • 发送端接收回复的数据,就退出

接收端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.hspedu.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
* @author 韩顺平
* @version 1.0
* UDP接收端
*/
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 创建一个 DatagramSocket 对象,准备在9999接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet对象
//老师提示: 当有数据包发送到 本机的9999端口时,就会接收到数据
// 如果没有数据包发送到 本机的9999端口, 就会阻塞等待.
System.out.println("接收端A 等待接收数据..");
socket.receive(packet);

//4. 可以把packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
byte[] data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);


//===回复信息给B端
//将需要发送的数据,封装到 DatagramPacket对象
data = "好的, 明天见".getBytes();
//说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
packet = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);
socket.send(packet);//发送

//5. 关闭资源
socket.close();
System.out.println("A端退出...");
}
}

发送端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.hspedu.udp;

import java.io.IOException;
import java.net.*;

/**
* @author 韩顺平
* @version 1.0
* 发送端B ====> 也可以接收数据
*/
@SuppressWarnings({"all"})
public class UDPSenderB {
public static void main(String[] args) throws IOException {

//1.创建 DatagramSocket 对象,准备在9998端口 接收数据
DatagramSocket socket = new DatagramSocket(9998);

//2. 将需要发送的数据,封装到 DatagramPacket对象
byte[] data = "hello 明天吃火锅~".getBytes(); //

//说明: 封装的 DatagramPacket对象 data 内容字节数组 , data.length , 主机(IP) , 端口
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9999);

socket.send(packet);

//3.=== 接收从A端回复的信息
//(1) 构建一个 DatagramPacket 对象,准备接收数据
// 在前面讲解UDP 协议时,老师说过一个数据包最大 64k
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
//(2) 调用 接收方法, 将通过网络传输的 DatagramPacket 对象
// 填充到 packet对象
//老师提示: 当有数据包发送到 本机的9998端口时,就会接收到数据
// 如果没有数据包发送到 本机的9998端口, 就会阻塞等待.
socket.receive(packet);

//(3) 可以把packet 进行拆包,取出数据,并显示.
int length = packet.getLength();//实际接收到的数据字节长度
data = packet.getData();//接收到数据
String s = new String(data, 0, length);
System.out.println(s);

//关闭资源
socket.close();
System.out.println("B端退出");
}
}

反射

反射机制

一个需求引出反射

根据配置文件re.properties指定信息,创建对象并调用方法

classfullpath = com.hspedu.Cat

method=hi

创建Cat对象并调用hi方法?

这样的需求再学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的ocp原则(开闭原则),指的是在不修改源码的情况下,来扩容扩展功能

设计模式六大原则之–开闭原则OCP

re.properties

1
2
3
classfullpath=com.hspedu.Cat
method=hi

Cat类

1
2
3
4
5
6
7
8
9
10
11
12
public class Cat{
private String name = "招财猫";
public int age = 10;
public Cat(){}
public Cat(String name){
this.name = name;
}
public void hi(){
Syste.out.println("hi," + name);
}
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void main(String[] args) throws throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//传统方式 new对象 -> 调用方法
Cat cat = new Cat();
cat.hi();
//尝试通过文件流读取 -> 做完之后明白反射
//使用Properties类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classFullPath = properties.get("classfullpath").toString();//com.hspedu.cat
String method = properties.get("method").toString();//hi
System.out.println("classfullPath=" + classFullPath);
System.out.println("method=" + method);
//创建对象,传统的方法无法实现 -> 反射机制
new classFullPath()//无法创建,因为是字符串引用,但是要创建一个cat类
//使用反射
//加载类,返回一个Class类型的对象
//这是一个类对象,他的类型是Class
Class aclass = Class.forName(classFullPath);
//通过aclass得到加载的类创建对象实例
Object o = aclass.newInstance();
System.out.println(o.getClass());
//通过aclass来获取你加载类的方法对象,即在反射机制中,可以把方法当作视为对象,万物皆对象
Method method = aclass.getMethod(method);
//通过method调用方法:即通过方法对象来实现调用方法
System.out.println("=========================");
method.invoke(o);//传统方法 对象.方法() ,反射机制: 方法.invoke(对象)
}

反射机制解释

  • 反射机制允许程序在执行十七借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和底层框架都会用到。
  • 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整构造信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射

java反射机制原理的图

1668661392947

java反射机制可以完成

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时得到任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的成员变量和方法
  5. 生成动态代理

反射的主要类

java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象

java.lang.reflect.Method :代表类的方法

java.lang.reflect.Field:代表类的成员变量

java.lang.reflect.Constructor:代表类的构造方法,Constructor表示构造器对象

这些类在java.lang.reflect

1668661758007

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.hspedu.reflection;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

/**
* @author 韩顺平
* @version 1.0
*/
public class Reflection01 {

public static void main(String[] args) throws Exception {
//1. 使用Properties 类, 可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();//"com.hspedu.Cat"
String methodName = properties.get("method").toString();//"hi"
//2. 使用反射机制解决
//(1) 加载类, 返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2) 通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o的运行类型=" + o.getClass()); //运行类型
//(3) 通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4) 通过method1 调用方法: 即通过方法对象来实现调用方法
System.out.println("=============================");
method1.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
//java.lang.reflect.Field: 代表类的成员变量, Field对象表示某个类的成员变量
//得到name字段
//getField不能得到私有的属性
Field nameField = cls.getField("age"); //
System.out.println(nameField.get(o)); // 传统写法 对象.成员变量 , 反射 : 成员变量对象.get(对象)
//java.lang.reflect.Constructor: 代表类的构造方法, Constructor对象表示构造器
Constructor constructor = cls.getConstructor(); //()中可以指定构造器参数类型, 返回无参构造器
System.out.println(constructor);//Cat()
Constructor constructor2 = cls.getConstructor(String.class); //这里老师传入的 String.class 就是String类的Class对象
System.out.println(constructor2);//Cat(String name)
}
}


反射优点和缺点

优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑

缺点:使用反射基本是解释执行,对执行速度有影响

缺点可以用反射调用优化-关闭访问检查来弥补

Method和Field,Constructor对象都有setAccessible()方法

setAccessible作用是启动和禁用访问开关检查的开关

参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.hspedu.reflection;

import com.hspedu.Cat;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @author 韩顺平
* @version 1.0
* 测试反射调用的性能,和优化方案
*/
public class Reflection02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

//Field
//Method
//Constructor
m1();
m2();
m3();
}

//传统方法来调用hi
public static void m1() {

Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 90; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("m1() 耗时=" + (end - start));
}

//反射机制调用方法hi
public static void m2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

Class cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//反射调用方法
}
long end = System.currentTimeMillis();
System.out.println("m2() 耗时=" + (end - start));
}

//反射调用优化 + 关闭访问检查
public static void m3() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

Class cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//反射调用方法
}
long end = System.currentTimeMillis();
System.out.println("m3() 耗时=" + (end - start));
}
}

Class类

  1. Class类也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class实例所生成
  5. 通过Class可以完整的得到一个类的完整结构,通过一系列API
  6. Class类对象是存放在堆中的

对class类和方法的演示类图

1668680989613

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.hspedu.reflection.class_;

import com.hspedu.Cat;

import java.util.ArrayList;

/**
* @author 韩顺平
* @version 1.0
* 对Class类特点的梳理
*/
public class Class01 {
public static void main(String[] args) throws ClassNotFoundException {
//看看Class类图
//1. Class也是类,因此也继承Object类
//Class
//2. Class类对象不是new出来的,而是系统创建的
//(1) 传统new对象
/* ClassLoader类
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//Cat cat = new Cat();
//(2) 反射方式, 刚才老师没有debug到 ClassLoader类的 loadClass, 原因是,我没有注销Cat cat = new Cat();
/*
ClassLoader类, 仍然是通过 ClassLoader类加载Cat类的 Class对象
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
Class cls1 = Class.forName("com.hspedu.Cat");

//3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
Class cls2 = Class.forName("com.hspedu.Cat");
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
Class cls3 = Class.forName("com.hspedu.Dog");
System.out.println(cls3.hashCode());
}
}


1668681726278

Car类

1
2
3
4
5
6
public class Car{
private String brand = "宝马";
public int piece = 500000;
public String color = "白色";
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.hspedu.reflection.class_;

import com.hspedu.Car;

import java.lang.reflect.Field;

/**
* @author 韩顺平
* @version 1.0
* 演示Class类的常用方法
*/
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
String classAllPath = "com.hspedu.Car";
//1 . 获取到Car类 对应的 Class对象
//<?> 表示不确定的Java类型
Class<?> cls = Class.forName(classAllPath);
//2. 输出cls
System.out.println(cls); //显示cls对象, 是哪个类的Class对象 com.hspedu.Car
System.out.println(cls.getClass());//输出cls运行类型 java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName());//包名
//4. 得到全类名
System.out.println(cls.getName());
//5. 通过cls创建对象实例
Car car = (Car) cls.newInstance();
System.out.println(car);//car.toString()
//6. 通过反射获取属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));//奔驰
//8 我希望大家可以得到所有的属性(字段)
System.out.println("=======所有的字段属性====");
Field[] fields = cls.getFields();
for (Field f : fields) {
System.out.println(f.getName());//名称
}
}
}

获取Class对象的方式

前提:已知一个类的全类名,且在该类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,实例Class aclass = Class.forName(“java.lang.Cat”);

应用场景:多用于配置文件,读取类全路径,加载类

前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高实例:Class aclass = Cat.class;

应用场景:多用于参数传递,比如通过反射到对应的构造器对象

前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象,实例:Class aclass = 对象.getClass();

应用场景:通过创建好的对象,获取Class对象

其他方式

ClassLoader cl = 对象.getClass().getClassLoader();

Class aclass = cl.loadClass(“类的全类名”);

演示获取类的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.hspedu.reflection.class_;

import com.hspedu.Car;

/**
* @author 韩顺平
* @version 1.0
* 演示得到Class对象的各种方式(6)
*/
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.forName
String classAllPath = "com.hspedu.Car"; //通过读取配置文件获取
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);

//2. 类名.class , 应用场景: 用于参数传递
Class cls2 = Car.class;
System.out.println(cls2);

//3. 对象.getClass(), 应用场景,有对象实例
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);

//4. 通过类加载器【4种】来获取到类的Class对象
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);

//cls1 , cls2 , cls3 , cls4 其实是同一个对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());

//5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class类对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);//int

//6. 基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE; //其它包装类BOOLEAN, DOUBLE, LONG,BYTE等待
System.out.println(type1);

System.out.println(integerClass.hashCode());//?
System.out.println(type1.hashCode());//?
}
}

哪些类型有Class对象

  1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.hspedu.reflection.class_;

import java.io.Serializable;

/**
* @author 韩顺平
* @version 1.0
* 演示哪些类型有Class对象
*/
public class AllTypeClass {
public static void main(String[] args) {
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
//枚举
Class<Thread.State> cls6 = Thread.State.class;
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void数据类型
Class<Class> cls9 = Class.class;//Class
System.out.println(cls1);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);
System.out.println(cls5);
System.out.println(cls6);
System.out.println(cls7);
System.out.println(cls8);
System.out.println(cls9);
}
}

类加载机制

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

静态加载:编译时加载相关的类,如果没有则报错,依赖性太强

动态加载:运行该时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性

类加载时机

  • 当创建对象时new
  • 当子类被加载时
  • 调用类中的静态成员时
  • 通过反射Class.forName(“com.test.Cat”);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.*;
import java.lang.reflect.*;

public class ClasLoad_{
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请输入key:");
String key = sc.next();
switch(key){
case "1":
Dog dog = new Dog();//静态加载,依赖性强,如果这个分支不触发也必须加载该类
dog.cry();
break;
case "2":
Class aclass = Class.forName("Person");//动态加载,依赖性不强,这个分支不触发不会加载Person类
Object 0 = aclass.newInstance();
Method m = aclsss.getMethod("hi");
m.invoke(o);
System.out.println("ok");
break;
default:
System.out.println("do nothing...");
}
}
}
//因为new Dog()是静态加载,因此必须编写Dog
//Person类是动态加载,所以,没有编写Person类也不会报错,只有当动态加载该类时,才会报错
class Dog{
public void cry(){
System.out.println("小狗汪汪叫");
}
}

1668685820871

加载阶段

JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

连接阶段-验证

目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

包括:文件格式验证(是否以魔数开头:oxcafebabe),元数据验证,字节码验证和符号引用验证

可以考虑使用 -Xverify:none 参数来关闭大部分的类验证,缩短虚拟机类加载的时间

1668688186427

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hspedu.reflection.classload_;

/**
* @author 韩顺平
* @version 1.0
* 我们说明一个类加载的链接阶段-准备
*/
public class ClassLoad02 {
public static void main(String[] args) {

}
}
class A {
//属性-成员变量-字段
//老韩分析类加载的链接阶段-准备 属性是如何处理
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是20
//3. n3 是static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}


连接阶段-解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

JVM 链接阶段之 Resolution——解析

Initialization初始化

到初始化阶段,才真正开始执行类中定义的java程序代码,此阶段是执行()方法的过程

()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中所有的静态变量的赋值动作和静态代码块中的语句,并进行合并。

虚拟机会保证一个类的()方法在多线程环境中被正确的加锁,同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的() 方法,其他线程都需要阻塞等待,直到活动线程执行() 方法完毕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.hspedu.reflection.classload_;

/**
* @author 韩顺平
* @version 1.0
* 演示类加载-初始化阶段
*/
public class ClassLoad03 {
public static void main(String[] args) throws ClassNotFoundException {
//老韩分析
//1. 加载B类,并生成 B的class对象
//2. 链接 num = 0
//3. 初始化阶段
// 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
/*
clinit() {
System.out.println("B 静态代码块被执行");
//num = 300;
num = 100;
}
合并: num = 100

*/

//new B();//类加载
//System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载

//看看加载类的时候,是有同步机制控制
/*
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象
synchronized (getClassLoadingLock(name)) {
//....
}
}
*/
B b = new B();
}
}

class B {
static {
System.out.println("B 静态代码块被执行");
num = 300;
}

static int num = 100;

public B() {//构造器
System.out.println("B() 构造器被执行");
}
}

总结:[Java类加载机制 ](https://www.cnblogs.com/dmzna/p/12916335.html#:~:text=2.什么是类加载机制 概念:Java中的类加载机制指虚拟机把描述类的数据从 Class,文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。)

获取类结构信息

  • getName()获取全类名
  • getSimpleName()获取简单类名
  • getFields()获取所有public修饰的属性,包含本类以及父类的
  • getDeclaredFields()获取本类中所有属性
  • getMethods()获取所有public修饰的方法,包含本类以及父类的
  • getDeclaredMethods()获取本类中所有方法
  • getConstructors()获取所有public修饰的构造器,包含本类的
  • getDeclaredConstructors()获取本类中所有的构造器
  • getpackage()以Package形式返回包信息
  • getSuperClass以Class形式返回父类信息
  • getInterfaces()以Class形式返回接口信息
  • getAnnotations()以Annotation[]形式返回注解信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package com.hspedu.reflection;

import org.junit.jupiter.api.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* @author 韩顺平
* @version 1.0
* 演示如何通过反射获取类的结构信息
*/
public class ReflectionUtils {
public static void main(String[] args) {

}
@Test
public void api_02() throws ClassNotFoundException, NoSuchMethodException {
//得到Class对象
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//getDeclaredFields:获取本类中所有属性
//规定 说明: 默认修饰符 是0 , public 是1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName()
+ " 该属性的修饰符值=" + declaredField.getModifiers()
+ " 该属性的类型=" + declaredField.getType());
}

//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName()
+ " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
+ " 该方法返回类型" + declaredMethod.getReturnType());

//输出当前这个方法的形参数组情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型=" + parameterType);
}
}

//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("====================");
System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名

Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参类型=" + parameterType);
}
}

}
//第一组方法API
@Test
public void api_01() throws ClassNotFoundException, NoSuchMethodException {

//得到Class对象
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//getName:获取全类名
System.out.println(personCls.getName());//com.hspedu.reflection.Person
//getSimpleName:获取简单类名
System.out.println(personCls.getSimpleName());//Person
//getFields:获取所有public修饰的属性,包含本类以及父类的
Field[] fields = personCls.getFields();
for (Field field : fields) {//增强for
System.out.println("本类以及父类的属性=" + field.getName());
}
//getDeclaredFields:获取本类中所有属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName());
}
//getMethods:获取所有public修饰的方法,包含本类以及父类的
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println("本类以及父类的方法=" + method.getName());
}
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName());
}
//getConstructors: 获取所有public修饰的构造器,包含本类
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类的构造器=" + constructor.getName());
}
//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
}
//getPackage:以Package形式返回 包信息
System.out.println(personCls.getPackage());//com.hspedu.reflection
//getSuperClass:以Class形式返回父类信息
Class<?> superclass = personCls.getSuperclass();
System.out.println("父类的class对象=" + superclass);//
//getInterfaces:以Class[]形式返回接口信息
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息=" + anInterface);
}
//getAnnotations:以Annotation[] 形式返回注解信息
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息=" + annotation);//注解
}
}
}

class A {
public String hobby;

public void hi() {

}

public A() {
}

public A(String name) {
}
}

interface IA {
}

interface IB {

}

@Deprecated
class Person extends A implements IA, IB {
//属性
public String name;
protected static int age; // 4 + 8 = 12
String job;
private double sal;

//构造器
public Person() {
}

public Person(String name) {
}

//私有的
private Person(String name, int age) {

}

//方法
public void m1(String name, int age, double sal) {

}

protected String m2() {
return null;
}

void m3() {

}

private void m4() {

}
}

通过反射获取对象

方式一:调用类中的public修饰的无参构造器

方式二:调用类中的指定构造器

Class相关的方法:

newInstance:调用类中的午餐构造器,获取对应类的都西昂

getConstructor(Class…clazz)根据参数列表,获取对应的public构造器对象

getDecalaredConstructor(Class…clazz)根据参数列表,获取对应的所有构造器对象

Constructor类相关方法

setAccessible:爆破

newInstance(Object..obj)调用构造器

演示反射获取私有构造器并赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.hspedu.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
* @author 韩顺平
* @version 1.0
* 演示通过反射机制创建实例
*/
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

//1. 先获取到User类的Class对象
Class<?> userClass = Class.forName("com.hspedu.reflection.User");
//2. 通过public的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//3. 通过public的有参构造器创建实例
/*
constructor 对象就是
public User(String name) {//public的有参构造器
this.name = name;
}
*/
//3.1 先得到对应构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
//3.2 创建实例,并传入实参
Object hsp = constructor.newInstance("hsp");
System.out.println("hsp=" + hsp);
//4. 通过非public的有参构造器创建实例
//4.1 得到private的构造器对象
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例
//暴破【暴力破解】 , 使用反射可以访问private构造器/方法/属性, 反射面前,都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(100, "张三丰");
System.out.println("user2=" + user2);
}
}

class User { //User类
private int age = 10;
private String name = "韩顺平教育";

public User() {//无参 public
}

public User(String name) {//public的有参构造器
this.name = name;
}

private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}

public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}

演示反射获取私有属性并赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.hspedu.reflection;

import java.lang.reflect.Field;

/**
* @author 韩顺平
* @version 1.0
* 演示反射操作属性
*/
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {

//1. 得到Student类对应的 Class对象
Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
//2. 创建对象
Object o = stuClass.newInstance();//o 的运行类型就是Student
System.out.println(o.getClass());//Student
//3. 使用反射得到age 属性对象
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来操作属性
System.out.println(o);//
System.out.println(age.get(o));//返回age属性的值

//4. 使用反射操作name 属性
Field name = stuClass.getDeclaredField("name");
//对name 进行暴破, 可以操作private 属性
name.setAccessible(true);
//name.set(o, "老韩");
name.set(null, "老韩~");//因为name是static属性,因此 o 也可以写出null
System.out.println(o);
System.out.println(name.get(o)); //获取属性值
System.out.println(name.get(null));//获取属性值, 要求name是static

}
}

class Student {//类
public int age;
private static String name;

public Student() {//构造器
}

public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}

通过反射获取私有方法并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.hspedu.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @author 韩顺平
* @version 1.0
* 演示通过反射调用方法
*/
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {

//1. 得到Boss类对应的Class对象
Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");
//2. 创建对象
Object o = bossCls.newInstance();
//3. 调用public的hi方法
//Method hi = bossCls.getMethod("hi", String.class);//OK
//3.1 得到hi方法对象
Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
//3.2 调用
hi.invoke(o, "韩顺平教育~");

//4. 调用private static 方法
//4.1 得到 say 方法对象
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//4.2 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
say.setAccessible(true);
System.out.println(say.invoke(o, 100, "张三", '男'));
//4.3 因为say方法是static的,还可以这样调用 ,可以传入null
System.out.println(say.invoke(null, 200, "李四", '女'));

//5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, "王五", '男');
System.out.println("reVal 的运行类型=" + reVal.getClass());//String


//在演示一个返回的案例
Method m1 = bossCls.getDeclaredMethod("m1");
Object reVal2 = m1.invoke(o);
System.out.println("reVal2的运行类型=" + reVal2.getClass());//Monster


}
}

class Monster {}
class Boss {//类
public int age;
private static String name;

public Boss() {//构造器
}

public Monster m1() {
return new Monster();
}

private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}

public void hi(String s) {//普通public方法
System.out.println("hi " + s);
}
}