【Java基础四】

文章目录


九.File、IO流

File

  • File是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹).
    在这里插入图片描述

IO流

  • 用于读写数据的(可以读写文件,或网络中的数据…)
    在这里插入图片描述

1.File

1.1创建对象

创建File类的对象
在这里插入图片描述
注意

  • File对象既可以代表文件、也可以代表文件夹.
  • File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的.

绝对路径、相对路径

  • 绝对路径:从盘符开始
    在这里插入图片描述
  • 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件.
    在这里插入图片描述
    示例:
    在这里插入图片描述

Test1

package com.itheima.d1_file;

import java.io.File;

//目标:掌握File创建对象,代表具体文件的方案.
public class Test1 {
    public static void main(String[] args) {
        //1、创建一个FiLe对象,指代某个具体的文件,三种写法
        //File f1=new File("D:/迅雷下载/abc.docx");
        //File f1=new File("D:\\迅雷下载\\abc.docx");
        File f1=new File("D:"+File.separator+"迅雷下载"+File.separator+"abc.docx");
        System.out.println(f1.length());//文件大小(单位字节)

        //文件夹本身的大小
        File f2=new File("D:/迅雷下载");
        System.out.println(f2.length());

        //注意:File对象可以指代一个不存在的文件路径
        File f3 =new File("D:/resource/aaaa.txt");
        System.out.println(f3.length());//0
        System.out.println(f3.exists());//false

        //我现在要定位的文件是在模块中,应该怎么定位呢?
        //绝对路径:带盘符的
        File f4=new File("D:\\Code\\javasepro\\file-io-app\\src\\HH.txt");

        //相对路径(重点):不带盘符,默认是直接去工程下寻找文件的.
        File f5=new File("src/HH.txt");
        System.out.println(f5.length());

    }

}

1.2常用方法1:判断文件类型、获取文件信息

File提供的判断文件类型、获取文件信息功能
在这里插入图片描述
示例:
在这里插入图片描述
Test2

package com.itheima.d1_file;

import org.w3c.dom.ls.LSOutput;

import java.io.File;
import java.text.SimpleDateFormat;

//目标:掌握File提供的判断文件类型、获取文件信息功能
public class Test2 {
    public static void main(String[] args) {
        //1,创建文件对象,指代某个文件
        File f1 = new File("c");

        //2、public boolean exists():判断当前文件对象,对应的文件路径是否存在,存在返回true
        System.out.println(f1.exists());

        //3、public boolean isFile():判断当前文件对象指代的是否是文件,是文件返回true,反之.
        System.out.println(f1.isFile());

        //4、public boolean isDirectory():判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之.
        System.out.println(f1.isDirectory());

        //5.public String getName():获取文件的名称(包含后缀)
        System.out.println(f1.getName());

        //6.public long length():获取文件的大小,返回字节个数
        System.out.println(f1.length());

        //7.pubL1 c long lastModified():获取文件的最后修改时间.
        long time =f1.lastModified();
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        System.out.println(sdf.format(time));

        //8.public String getPath():获取创建文件对象时,使用的路径
        File f2=new File("D:/迅雷下载/abc.docx");
        File f3=new File("src/HH.txt");
        System.out.println(f2.getPath());//D:\迅雷下载\abc.docx
        System.out.println(f3.getPath());//src\HH.txt

        //9.public String getAbsolutePath():获取绝对路径
        System.out.println(f2.getAbsolutePath());//D:\迅雷下载\abc.docx
        System.out.println(f3.getAbsolutePath());//D:\Code\javasepro\file-io-app\src\HH.txt
    }

}

1.3常用方法2:创建文件、删除文件

File类创建文件的功能
在这里插入图片描述

File类删除文件的功能
在这里插入图片描述
注意:delete方法默认只能删除**文件和空文件夹,**删除后的文件不会进入回收站.
示例:
在这里插入图片描述
Test3

package com.itheima.d1_file;

import java.io.File;
import java.io.IOException;

//目标:掌握File创建和删除文件相关的方法
public class Test3 {
    public static void main(String[] args) throws IOException {
        //1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之,
        File f1 =new File("D:/迅雷下载/itheima.txt");
        System.out.println(f1.createNewFile());

        //2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹
        File f2=new File("D:/迅雷下载/abv");
        System.out.println(f2.mkdir());

        //3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹
        File f3=new File("D:/迅雷下载/dsasd/af/ads/ds");
        System.out.println(f3.mkdirs());

        //4、public boolean delete():删除文件,或者空文件夹,注意:不能删除非空文件夹.
        System.out.println(f1.delete());
        System.out.println(f2.delete());
        //非空文件夹
        File f4=new File("D:/迅雷下载/dsasd");
        System.out.println(f4.delete());
    }

}

1.4常用方法3:遍历文件夹

File类提供的遍历文件夹的功能
在这里插入图片描述

使用listFiles方法时的注意事项:

  • 当主调是文件,或者路径不存在时,返回null
  • 当主调是空文件夹时,返回一个长度为0的数组
  • 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
  • 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
  • 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null

示例
在这里插入图片描述
Test4

package com.itheima.d1_file;

import java.io.File;

//目标:掌握FiLe提供的遍历文件夹的方法,
public class Test4 {
    public static void main(String[] args) {
        //1、public String[] List():获取当前目录下所有的一级文件名称"到一个字符申数组中去返回.
        File f1=new File("D:\\迅雷下载");
        String[]names= f1.list();
        for (String name : names) {
            System.out.println(name);
        }
        //2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
        File[] files=f1.listFiles();
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }

        //主调是文件
        File f=new File("D:\\迅雷下载\\abc.docx");
        File[] files1=f.listFiles();
        System.out.println(files1);//null
    }
}

2.前置知识:方法递归

2.1认识递归的形式

什么是方法递归?

  • 递归是一中算法,在程序设计语言中广泛应用.
  • 从形式上说:方法调用自身的形式称为方法递归(recursion).
    递归的形式
  • 直接递归:方法自己调用自己.
  • 间接递归:方法调用其他方法,其他方法又回调方法自己.

使用方法递归时需要注意的问题:
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误.

示例:
在这里插入图片描述
Test1

package com.itheima.d2_recursion;

//目标:认识一下递归的形式.

public class Test1{
    public static void main(String[]args){
        test1();
    }
    //直接方法递归
    public static void test1() {
        System.out.println("----test1---");
        test1();//直接方法递归
    }
         //间接方法递归
        public static void test2(){
            System.out.println("---test2---");
            test3();
        }
        public static void test3(){
            test2();//间接递归
        }
}

2.2应用、执行流程、算法思想

案例
在这里插入图片描述
在这里插入图片描述
终止后逐层返回
在这里插入图片描述
递归结束
在这里插入图片描述
递归算法三要素:


示例:
在这里插入图片描述
Test2

package com.itheima.d2_recursion;

//目标:掌握通归的应用,执行流程和算法思想

public class Test2 {
    public static void main(String[] args) {
        System.out.println("5的阶乘是:" + f(5));
    }

    public static int f(int n) {
        //终结点
        if (n == 1) {
            return 1;
        } else{
            return f(n - 1) * n;
        }

    }
}

2.3其他应用:文件搜索

案例:文件搜索
**需求:**从D:盘中,搜索"Apifox.exe"这个文件,找到后直接输出其位置.
分析:
1.先找出D:盘下的所有一级文件对象
2. 遍历全部一级文件对象,判断是否是文件
3. 如果是文件,判断是否是自己想要的
4. 如果是文件夹,需要继续进入到该文件夹,重复上述过程

拓展:删除文件夹
**需求:**删除非空文件夹
分析:

  • File默认不可以删除非空文件夹
  • 我们需要遍历文件夹,先删除里面的内容,再删除自己.

示例:
在这里插入图片描述
Test3

package com.itheima.d2_recursion;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

//目标:掌握文件搜索的实现
public class Test3 {
    public static void main(String[] args) throws IOException {
        searchFile(new File("D:/"),"Apifox.exe");
        //找到了:D:\download\Apifox\Apifox.exe
    }

    /**
     * 去目录下搜索某个文件
     * @param dir 目录
     * @param fileName 要搜索的文件名称
     */
    public static void searchFile(File dir,String fileName) throws IOException {
        //1.把非法的情况都拦截住
        if(dir==null||!dir.exists()||dir.isFile()){
            return;//代表无法搜索
        }
        //2.dir不是null,存在,一定是目录对象
        //获取当前目录下的全部一级文件对象
        File[] files=dir.listFiles();

        //3.判断当前目录下是否存在一级文件对象,以及是否可以拿到文件对象
        if(files!=null&&files.length>=0){
            //4.遍历全部一级文件对象
            for (File f : files) {
                //5、判断文件是否是文件,还是文件夹
                if(f.isFile()){
                    //是文件,判断这个文件名是否是我们所要找的
                    if(f.getName().equals(fileName)){
                        System.out.println("找到了:"+f.getAbsolutePath());
                        //启动
                        Runtime runtime=Runtime.getRuntime();
                        runtime.exec(f.getAbsolutePath());
                    }
                }else {
                    //是文件夹,继续重夏这个过程(递归),深度优先
                    searchFile(f,fileName);
                }

            }
        }
    }
}

Test4

package com.itheima.d2_recursion;

import java.io.File;
import java.nio.file.Files;
 //目标:删除非空文件夹.独立功能独立成方法.
public class Test4 {
    public static void main(String[] args) {
        File dir = new File("D:\\迅雷下载\\dsasd");
        delectDir(dir);
    }

    public static void delectDir(File dir){
        if(dir==null||!dir.exists()){
            return;
        }
        if(dir.isFile()){
            dir.delete();
            return;
        }
        //1.dir存在且是文件夹,拿里面的一级文件对象
        File[] files =dir.listFiles();
        if(files==null){
            return;//没权限
        }

        //2、这是一个有内容的文件夹,干掉里面的内容,再干掉自己.
        for (File file : files) {
            if(file.isFile()){
                file.delete();
            }else {
                delectDir(file);
            }
        }
        dir.delete();

    }
}

3.前置知识:字符集

3.1常见字符集介绍

标准ASCII字符集

  • ASCII(American Standard Code for Information Interchange):美国信息交换标准代码,包括了英文、符号等.
  • 标准ASCI使用1个字节存储一个字符,首位是0,总共可表示128个字符,对美国佬来说完全够用.

GBK(汉字内码扩展规范,国标)

  • 汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储.
  • 注意:GBK兼容了ASCII字符集.
  • GBK规定:汉字的第一个字节的第一位须是1

Unicode字符集(统一码,也叫万国码)

  • Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集.

UTF-8

  • 是Uicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节
  • 英文字符、数字等只占1个字节(兼容标准ASC川编码),汉字字符占用3个字节.
    在这里插入图片描述
    注意:技术人员在开发时都应该使用UTF-8编码!

本节要点

  • ASCI字符集:只有英文、数字、符号等,占1个字节.
  • GBK字符集:汉字占2个字节,英文、数字占1个字节.
  • UTF-8字符集:汉字占3个字节,英文、数字占1个字节.
  • 注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
  • 注意2:英文,数字一般不会乱码,因为很多字符集都兼容了ASCII编码.

3.2字符集的编码、解码操作

Java代码完成对字符的编码
在这里插入图片描述

Java代码完成对字符的解码
在这里插入图片描述
示例:
在这里插入图片描述
Test

package com.itheima.d3_charset;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

//目标:掌握如何使用Java代码完成对字符的编码和解码
public class Test {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //1.编码
        String data="a我b";
        byte[] bytes=data.getBytes();//平台默认,此处为UTF-8
        System.out.println(Arrays.toString(bytes));

        //按照指定字符集进行编码
        byte[] bytes1=data.getBytes("GBK");
        System.out.println(Arrays.toString(bytes1));

        //2.解码
        String s1=new String(bytes);
        System.out.println(s1);//a我b

        String s2=new String(bytes1);
        System.out.println(s2);//a��b

        String s3=new String(bytes1,"GBK");
        System.out.println(s3);//a我b
    }


}


4.IO流

4.1认识IO流

IO流概述
在这里插入图片描述
IO流应用场景
在这里插入图片描述
怎么学IO流
1、先搞清楚IO流的分类、体系.
2、再挨个学习每个IO流的作用、用法.

IO流分类
在这里插入图片描述
总结流的四大类:

  • 字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流
  • 字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流.
  • 字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流.
  • 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流

IO流的体系
在这里插入图片描述
直接继承的才有箭头

4.2 IO流-字节流

4.2.1文件字节输入流:每次读取一个字节

FilelnputStream(文件字节输入流)

  • 作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去.
    在这里插入图片描述
    注意事项
    -使用FilelnputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码.

示例:
在这里插入图片描述
FileInputStreamTest1

package com.itheima.d4_byte_stream;

import java.io.*;

//目标:掌握文件字节输入流,每次读取一个字节.
public class FileInputStreamTest1 {
    public static void main(String[] args) throws IOException {
        //1.创建文件字节输入流管道,与源文件接通.
        //InputStream is = new FileInputStream(new File("src/01.txt"));
        //简化写法:推荐使用
        InputStream is = new FileInputStream("src/01.txt");

        //2、开始读取文件的字节数据.
        //public int read():每次读取1个字节返回,如果没有数据了,返回-1.
     /*   int b1 =is.read();
        System.out.println((char)b1);//a

        int b2=is.read();
        System.out.println((char)b2);//b

        int b3=is.read();
        System.out.println(b3);//-1
        */

        //3、使用循环改造上述代码
        int b;//用于记住读取的字节.
        while((b=is.read())!=-1){
            System.out.print((char)b);//ab
        }
        //读取数据的性能很差!访问硬盘次数太多
        //读取汉字输出会乱码!!无法避免的!!
        //流使用后必须关闭
        is.close();
    }

}

4.2.2文件字节输入流:每次读取多个字节

注意事项

  • 使用FilelnputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码.

示例:
在这里插入图片描述
FileInputStreamTest2

package com.itheima.d4_byte_stream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

//目标:掌握使用FileInputStream每次读取多个字节.
public class FileInputStreamTest2 {
    public static void main(String[] args) throws IOException {
        //1、创建一个字节输入流对象代表字节输入流管道与源文件接通.
        InputStream is =new FileInputStream("src/02.txt");

        //2、开始读取文件中的字节数据:每次读取多个字节
        //public int read(byte b[])throws IOException
        //每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1

/*        byte[]buffer =new byte[3];
        int len =is.read(buffer);
        String rs=new String(buffer);
        System.out.println(rs);//abc
        System.out.println("当次读取的字节数量:"+len);//当次读取的字节数量:3



        //buffer [66c],第二次读不满3个,c留在里面

  *//*      int len2 =is.read(buffer);
        String rs2 =new String(buffer);
        System.out.println(rs2);//66c
        System.out.println("当次读取的字节数量:"+len2);//当次读取的字节数量:2
  *//*
        int len2 =is.read(buffer);
        //注意:读取多少,例出多少.
        String rs2 =new String(buffer,0,len2);
        System.out.println(rs2);//66
        System.out.println("当次读取的字节数量:"+len2);//当次读取的字节数量:2

        int len3 = is.read(buffer);
        System.out.println(len3);//-1

        */

        //3.使用循环改造
        byte[]buffer= new byte[3];
        int len;//记住每次读取了多少个字节.
        while ((len =is.read(buffer))!=-1) {
            //(注意:读取多少,例出多少.
            String rs = new String(buffer, 0, len);
            System.out.println(rs);
        }
        //性能得到明显提升
        //读取汉字还是会出现乱码
        is.close();//关闭流
    }
}

4.2.3文件字节输入流:一次读取完全部字节
  • 方式一:自己定义一个字节数组与被读取的文件大小一样大,然后使用该字节数组,一次读完文件的全部字节.
    在这里插入图片描述
  • 方式二:Java官方为InputStream提供了如下方法,可以直接把文件的全部字节读取到一个字节数组中返回.
    在这里插入图片描述
    示例:
    在这里插入图片描述

FileInputStreamTest3

package com.itheima.d4_byte_stream;

import java.io.*;

//目标:使用文件字节输入流一次读取完文件的全部字节.
public class FileInputStreamTest3 {
    public static void main(String[] args) throws IOException {
        //1、一次性读取完文件的全部字节到一个字节数组中去.
        //创建一个字节输入流管道与源文件接通
        InputStream is =new FileInputStream("src/03.txt");
/*
        //2、准备一个字节数组,大小与文件的大小正好一样大,
        File f = new File("src/03.txt");
        long size = f.length();
        byte[]buffer = new byte[(int)size];//byte最大int,再大装不下了
        int len =is.read(buffer);
        System.out.println(new String(buffer));

        System.out.println(size);
        System.out.println(len);

 */
        byte[]buffer =is.readAllBytes();
        System.out.println(new String(buffer));

    }
}

4.2.4文件字节输出流:写字节出去

FileOutputStream(文件字节输出流)

  • 作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去.
    在这里插入图片描述
    示例:
    在这里插入图片描述

FileOutputStreamTest4

package com.itheima.d4_byte_stream;

import java.io.FileOutputStream;
import java.io.OutputStream;

//目标:掌握文件字节输出流FileOutputstream的使用.
public class FileOutputStreamTest4 {
    public static void main(String[] args) throws Exception {
        //1、创建一个字节输出流管道与目标文件接通,没文件则创建文件
        //OutputStream os =new FileOutputStream("src/04out.txt");//覆盖
        OutputStream os =new FileOutputStream("src/04out.txt",true);//追加

        //2、开始写字节数据出去了
        os.write(97);//97就是一个字节,代表a
        os.write('b');//'b'也是一个字节
        //os.write('磊');//[ooo]默认只能写出去一个字节
        byte[] bytes = "我爱你中国abc".getBytes();
        os.write(bytes);

        os.write(bytes,0,15);
        //换行符
        os.write("\r\n".getBytes());
        os.close();//关闭流
    }
}

4.2.5案例:文件复制

文件复制
在这里插入图片描述
字节流非常适合故一t切文件的复制操作

  • 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!

示例:
在这里插入图片描述
CopyTest5

package com.itheima.d4_byte_stream;

import java.io.*;

//目标:使用字节流完成对文件的夏制操作.
public class CopyTest5 {
    public static void main(String[] args) throws Exception {
        //需求:复制照片.
        //1、创建一个字节输入流管道与源文件接通
        InputStream is = new FileInputStream("D:\\360downloads\\test5.png");
        //2、创建一个字节输出流管道与目标文件接通.
        OutputStream os = new FileOutputStream("D:\\迅雷下载\\test5.png");
        //3、创建一个字节数组,负责转移字节数据.
        byte[] buffer = new byte[1024];//1KB.
        //4、从字节输入流中读取字节数据,写出去到字节输出流中.读多少写出去多少.
        int len;//记住每次读取了多少个字节.
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }os.close();
        is.close();
        System.out.println("复制完成!!");
    }
}

4.3 释放资源的方式

try-catch-finally
在这里插入图片描述

  • finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止.
  • 作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法).

在这里插入图片描述

JDK7开始提供了更简单的资源释放方案:try-with-resource
在这里插入图片描述
在这里插入图片描述
MyConnection

package com.itheima.d5_resource;

public class MyConnection implements AutoCloseable{

    @Override
    public void close() throws Exception {
        System.out.println("释放了与某个硬件资源的链接");
    }
}

Test1

package com.itheima.d5_resource;
//目标:认识try-catch-finally
public class Test1 {
    public static void main(String[] args) {
        try {
            System.out.println(10 / 0);//异常也得执行finally
            //return;return也得在finally之后执行
            //唯一例外:System.exit(0)虚拟机挂了,finally也没用
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("===finally执行了一次===");
        }
        System.out.println(chu(10,2));//111
    }

    public static int chu(int a, int b) {
        try {
            return a / b;
        } catch (Exception e) {
            e.printStackTrace();
            return -1;//代表的是出现异常
        } finally {
            //千万不要在Finally中返回数据!
            return 111;
        }
    }
}

Test2

package com.itheima.d5_resource;

import java.io.*;

//目标:掌握释放资源的方式.
public class Test2 {
    public static void main(String[] args) {
        InputStream is=null;
        OutputStream os=null;
        try {
            //1.创建一个字节输入流管道与源文件接通
            is = new FileInputStream("src\\03.txt");
            //2、创建一个字节输出流管道与目标文件接通.
             os = new FileOutputStream("src\\03copy.txt");

            //3、创建一个字节数组,负责转移字节数据.
            byte[] buffer = new byte[1024];//1KB.
            // 4、从字节输入流中读取字节数据,写出去到字节输出流中.读多少写出去多少.
            int len;//记住每次读取了多少个字节.
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成!!");
        }catch (IOException e) {
            e.printStackTrace();
        }finally{
            //释放资源的操作
            try {
                if(os!=null)os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(os!=null)is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
            }
}

Test3

package com.itheima.d5_resource;

import java.io.*;
//try-with-resource
public class Test3 {
    public static void main(String[] args) {

        try(
                //1.创建一个字节输入流管道与源文件接通
                InputStream is = new FileInputStream("src\\03.txt");
                //2、创建一个字节输出流管道与目标文件接通.
                OutputStream os = new FileOutputStream("src\\03copy.txt");
                //注意:这里只能放置资源对象.(流对象)
                //什么是资源呢?资源都是会实现AutoCloseable接口,资源都会有一个close方法,并且资源放到这里后
                //用完之后,会被自动调用其close方法完成资源的释放操作
                MyConnection conn=new MyConnection();
        ) {


            //3、创建一个字节数组,负责转移字节数据.
            byte[] buffer = new byte[1024];//1KB.
            // 4、从字节输入流中读取字节数据,写出去到字节输出流中.读多少写出去多少.
            int len;//记住每次读取了多少个字节.
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成!!");
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.4IO流-字符流

4.4.1文件字符输入流-读字符数据进来

FileReader(文件字符输入流)

  • 作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去.
    在这里插入图片描述
    示例:
    在这里插入图片描述
    FileReaderTest1
package com.itheima.d1_char_stream;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;

//目标:学握文件字符输入流每次读取一个字符.
public class FileReaderTest1 {
    public static void main(String[] args) {
        //一次读一个字符
        try(
                //1、创建一个文件字符输入流管道与源文件接通
                Reader fr =new FileReader("src\\01.txt");

                ) {
            //2.读取文本文件内容
            int c;//记住每次读取的字符编号
            while((c=fr.read())!=-1){
                System.out.print((char) c);
            }
        } catch (Exception e) {
           e.printStackTrace();
        }

        System.out.println("---------------------");

        //一次读多个字符
        try(

                Reader fr =new FileReader("src\\01.txt");

        ) {
            char[] buffer =new char[3];
            int len;
            while((len=fr.read(buffer))!=-1){
                System.out.print(new String(buffer, 0, len));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}


4.4.2文件字符输出流-写字符数据出去

FileWriter(文件字符输出流)

  • 作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去.
    在这里插入图片描述
    字符输出流使用时的注意事项
  • 字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效
    在这里插入图片描述
    字节流、字符流的使用场景小结:
  • 字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出.
  • 字符流适合做文本文件的操作(读,写).

示例:
在这里插入图片描述
FileReaderTest2

package com.itheima.d1_char_stream;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

//目标:掌握文件字符输出流:写字符数据出去
public class FileWriterTest2 {
    public static void main(String[] args) {
        //0、创建一个文件字符输出流管道与目标文件接通.
        try (
                Writer fw =new FileWriter("src/02out.txt",true);//追加

                ){
            //1、public void write(intc):写一个字符出去
            fw.write('a');
            fw.write(97);
            fw.write('我');//写一个字符出去
            fw.write("\r\n");

            //2、public void write(String c)写一个字符串出去
            fw.write("我爱你中国abc");
            fw.write("\r\n");

            //3、public void write(String c,int pos,int len):写字符串的一部分出去
            fw.write("我爱你中国abc",0,5);
            fw.write("\r\n");

            //4、public void write(char[]buffer):写一个字符数组出去
            char[]buffer={'黑','马','a','b','c'};
            fw.write(buffer);
            fw.write("\r\n");

            //5、public void write(char[]buffer,int pos,int len):写字符数组的一部分出去
            fw.write(buffer,1,2);//马a
            fw.write("\r\n");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

FileReaderTest3

package com.itheima.d1_char_stream;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

//目标:搞清楚字符输出流使用时的注意事项.
public class FileWriterTest3 {
    public static void main(String[] args) throws Exception {
        Writer fw =new FileWriter("src/03out.txt");

        //写字符数据出去
        fw.write('a');
        fw.write('b');
        fw.write('c');
        fw.write('d');
        fw.write("\r\n");
        fw.write("我爱你中国");
        fw.write("\r\n");
        fw.write("我爱你中国");
        //文件被创建,但是空,因为只写到缓冲区,缓冲区装满会自动刷新

     /*   fw.flush();//刷新流
        fw.write("张三");
        fw.flush();
        */

        fw.close();//关闭流,关闭流包含刷新操作!

    }
}

4.5 IO流-缓冲流

在这里插入图片描述

4.5.1字节缓冲流

字节缓冲流的作用

  • 提高字节流读写数据的性能
  • 原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池.
    在这里插入图片描述

在这里插入图片描述
示例
在这里插入图片描述
BufferedInputStreamTest1

package com.itheima.d2_buffered_stream;

import java.io.*;

//目标:掌握字节缓冲流的作用.
public class BufferedInputStreamTest1 {
    public static void main(String[] args) {
        try (
                InputStream is = new FileInputStream("src/01.txt");
                //1、定义1个字节缓冲输入流包装原始的字节输入流
                InputStream bis = new BufferedInputStream(is,8192*2);//可自定义缓冲池大小
                OutputStream os = new FileOutputStream("src/01_bak.txt");
                //2、定义1个字节缓冲输出流包装原始的字节输出流
                OutputStream bos = new BufferedOutputStream(os);
        ) {
            byte[] buffer =new byte[1024];
            int len;
            while ((len =bis.read(buffer))!=-1){
                bos.write(buffer,0, len);
            }
            System.out.println("复制完成!!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.5.2字符缓冲流

BufferedReader(字符缓冲输入流)

  • 作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能.
    在这里插入图片描述
    在这里插入图片描述
    BufferedWriter(字符缓冲输出流)
  • 作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能.
    在这里插入图片描述
    在这里插入图片描述
    示例
    在这里插入图片描述
    BufferedReaderTest2
package com.itheima.d2_buffered_stream;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;

//目标:掌握字符缓冲输入流的用法.
public class BufferedReaderTest2 {
    public static void main(String[] args) {
        try (
                Reader fr = new FileReader("src\\04.txt");
                //创建一个字符缓冲输入流包装原始的子符输入流
                BufferedReader br = new BufferedReader(fr);
        ) {
          /*  char[] buffer =new char[3];
            int len;
            while ((len = br.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, len));

            }
            */

            String line;//记住每次读取的一行数据
            while ((line=br.readLine())!=null){
                System.out.println(line);
            }
        }catch(Exception e){
                e.printStackTrace();
            }

        }
    }





BufferedWriterTest3

package com.itheima.d2_buffered_stream;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
//目标:掌握字符缓冲输出流的用法.
public class BufferedWriterTest3 {
    public static void main(String[] args) {
        try (
                Writer fw =new FileWriter("src/05out.txt",true);//追加
                //创建一个字符缓冲输出流管道包装原始的字符输出流
                BufferedWriter bw =new BufferedWriter(fw);
        ){
            bw.write('a');
            bw.write(97);
            bw.write('磊');
            bw.newLine();
            bw.write("我爱你中国abc");
            bw.newLine();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

4.5.3案例

在这里插入图片描述
示例
在这里插入图片描述
Demo5

package com.itheima.d2_buffered_stream;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Demo5 {
    public static void main(String[] args) {
        try (
                2、创建缓冲字符输入流管道与源文件接通
                BufferedReader br =new BufferedReader(new FileReader("src/csb.txt"));
                BufferedWriter bw =new BufferedWriter(new FileWriter("src/newcsb.txt"));

                ){
            //目标:恢复出师表的顺序到新文件中.
            //1、定义一个ArrayList集合存储每段内容
            List<String> data =new ArrayList<>();

            //3、按照行读取每段,存入到集合中去
            String line;
            while ((line =br.readLine())!=null) {
                data.add(line);
            }

            //4、L1st集合中的每段文章进行排序.
            Collections.sort(data);
            System.out.println(data);

            //5、遍历list集合的每段内容,依次写出去到新文件中.
            for (String ln :data){
                bw.write(ln);
                bw.newLine();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.5.3原始流、缓冲流的性能分析[重点]

在这里插入图片描述
示例
在这里插入图片描述
TimeTest5

package com.itheima.d2_buffered_stream;

import java.io.*;

public class TimeTest5 {
        //复制的视频路径
        private static final String SRC_FILE="D:\\迅雷下载\\abc.docx";
        //复制到娜个目的地
        private static final String DEST_FILE ="D:\\迅雷下载\\";
        public static void main(String[] args) {
            copy01();//低级字节流一个一个字节复制耗时:13s
            copy02();//低级字节流使用字节数组复制耗时:0.016s
            copy03();//缓冲流一个一个字节复制耗时:0.029s
            copy04();//缓冲流使用字节数组复制耗时:0.004s


        }

    private static void copy01() {
        long startTime = System.currentTimeMillis();
        try (
                InputStream is = new FileInputStream(SRC_FILE);
                OutputStream os = new FileOutputStream(DEST_FILE + "1.avi");
        ) {
            int b;
            while ((b = is.read()) != -1) {
                os.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("低级字节流一个一个字节复制耗时:" + (endTime - startTime) / 1000 + "s");
    }

    private static void copy02() {
        long startTime = System.currentTimeMillis();
        try (
                InputStream is = new FileInputStream(SRC_FILE);
                OutputStream os = new FileOutputStream(DEST_FILE + "2.avi");
        ) {
            byte[] buffer = new byte[1024];//1KB
            int len;
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("低级字节流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
    }


    private static void copy03() {
        long startTime = System.currentTimeMillis();
        try (
                InputStream is = new FileInputStream(SRC_FILE);
                BufferedInputStream bis=new BufferedInputStream(is);
                OutputStream os = new FileOutputStream(DEST_FILE + "3.avi");
                BufferedOutputStream bos=new BufferedOutputStream(os);
                ) {

            int b;
            while ((b= bis.read()) != -1) {
                bos.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("缓冲流一个一个字节复制耗时:" + (endTime - startTime) / 1000.0 + "s");
    }

    private static void copy04() {
        Long startTime = System.currentTimeMillis();
        try ( InputStream is = new FileInputStream(SRC_FILE);
              BufferedInputStream bis=new BufferedInputStream(is);
              OutputStream os = new FileOutputStream(DEST_FILE + "4.avi");
              BufferedOutputStream bos=new BufferedOutputStream(os);
        ) {
            byte[] buffer = new byte[1024];//1KB
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("缓冲流使用字节数组复制耗时:" + (endTime - startTime) / 1000.0 + "s");
    }
}


4.6 IO流-转换流

4.6.1引出问题:不同编码读取时会乱码

不同编码读取出现乱码的问题

  • 如果代码编码和被读取的文本文件的编码是一致的,使用字符流读取文本文件时不会出现乱码!
  • 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!
4.6.2字符输入转换流

在这里插入图片描述
InputStreamReader(字符输入转换流)

  • 解决不同编码时,字符流读取文本内容乱码的问题.
  • 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了.
    在这里插入图片描述
    示例
    在这里插入图片描述
    Test1
package com.itheima.d3_transform_stream;

import java.io.*;

//目标:掌握字符输入转换流的作用
public class Test1 {
    public static void main(String[] args) {

        try ( //1.得到文件的原始字节流(GBK)
              InputStream is=new FileInputStream("src/06.txt");
              //2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
              Reader isr = new InputStreamReader(is,"GBK");
              //3、把字符输入流包装成缓冲字符输入流
              BufferedReader br =new BufferedReader(isr);
                ){
            String line;
            while ((line=br.readLine())!=null){
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.6.3字符输出转换流

OutputStreamWriter字符输出转换流

  • 作用:可以控制写出去的字符使用什么字符集编码.
  • 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了.

在这里插入图片描述
示例
在这里插入图片描述
Test2

package com.itheima.d3_transform_stream;

import java.io.*;

//目标:掌握字符输出转换流的使用.
public class Test2{
    public static void main(String[]args){
        //指定写出去的字符编码.
    try(
            //1、创建一个文件字节输出流
            OutputStream os =new FileOutputStream("src/07out.txt");
            //2、把原始的字节输出流,按照指定的字符集编码转换成字符输出转换流.
            Writer osw =new OutputStreamWriter(os,"GBK");
            //3、把字符输出流包装成缓冲字符输出流
                BufferedWriter bw =new BufferedWriter(osw);
) {
        bw.write("我是中国人abc");
        bw.write("我爱你中国123");
    }catch (Exception e){
            e.printStackTrace();
        }
    }
}

4.7 IO流-打印流

在这里插入图片描述
PrintStream/PrintWriter(打印流)

  • 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去.

PrintStream提供的打印数据的方案
在这里插入图片描述
在这里插入图片描述
PrintWriter提供的打印数据的方案
在这里插入图片描述
PrintStream和PrintWriter的区别

  • 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
  • PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法.
  • PrintWriter继承自字符输出流Writer,因此支持写字符数据出去.

打印流的一种应用:输出语句的重定向
在这里插入图片描述

  • 可以把输出语句的打印位置改到某个文件中去.
    在这里插入图片描述
    示例:
    在这里插入图片描述
    Test1
package com.itheima.d4_printstream;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.nio.charset.Charset;

//目标:掌握打印流:PrintStream的用法,
public class Test1 {
    public static void main(String[] args) {
        try (
                //1、创建一个打印流管道
                PrintStream ps =
                new PrintStream("src/08.txt", Charset.forName("UTF-8"));

                ){
            ps.println(97);
            ps.println('a');
            ps.println("我爱你中国abc");
            ps.println(true);
            ps.println(99.5);

            ps.write(97);//'a'  写数据和打印数据的区别
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Test2

package com.itheima.d4_printstream;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;

//目标:掌握打印流:PrintWrite的用法,
public class Test2 {
    public static void main(String[] args) {
        try (
                //1、创建一个打印流管道,
                PrintWriter ps =
                        new PrintWriter("src/09.txt", Charset.forName("UTF-8"));
                //不能直接追加,得换成低级流
                //PrintWriter ps = new PrintWriter(new FileOutputStream("src/09.txt", true));

        ){
            ps.println(97);
            ps.println('a');
            ps.println("我爱你中国abc");
            ps.println(true);
            ps.println(99.5);

            ps.write(97);//'a'  PrintWrite和PrintStream写上面有区别,其他方面几乎没区别

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Test3

package com.itheima.d4_printstream;

import java.io.FileNotFoundException;
import java.io.PrintStream;

//目标:了解下输出语句的重定向.
public class Test3 {
    public static void main(String[] args) {
        System.out.println("老攘伏枥");
        System.out.println("志在千里");
        try(PrintStream ps= new PrintStream("src/09.txt");) {
            //把系统默认的打印流对象改成自己设置的打印流

            System.setOut(ps);
            System.out.println("烈士暮年");
            System.out.println("壮心不已");

        } catch (Exception e) {
           e.printStackTrace();
        }

    }
}

4.8IO流-数据流

在这里插入图片描述

DataOutputStream(数据输出流)

  • 允许把数据和其类型一并写出去.
    在这里插入图片描述
    DatalnputStream(数据输入流)
  • 用于读取数据输出流写出去的数据.
    在这里插入图片描述
    示例
    在这里插入图片描述
    Test1
package com.itheima.d5_data_stream;

import java.io.DataOutputStream;
import java.io.FileOutputStream;

//目标:数据输出流.
public class Test1 {
    public static void main(String[] args) {
        try (
//1、创建一个数据输出流包装低级的字节输出流
                DataOutputStream dos =
                        new DataOutputStream(new FileOutputStream("src/10out.txt"));
        ) {
            dos.writeInt(97);
            dos.writeDouble(99.5);
            dos.writeBoolean(true);
            dos.writeUTF ( "黑马程序员666!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Test2

package com.itheima.d5_data_stream;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

//目标:使用数据输入流读取特定类型的数据.
public class Test2 {
    public static void main(String[] args) {
        try (    DataInputStream dis=
                         new DataInputStream(new FileInputStream("src/10out.txt"));
        ){
            int i=dis.readInt();
            System.out.println(i);
            double d =dis.readDouble();
            System.out.println(d);
            boolean b =dis.readBoolean();
            System.out.println(b);
            String rs =dis.readUTF();
            System.out.println(rs);
        } catch (Exception e) {
                e.printStackTrace();
        }
    }
}

4.9IO流-序列化流

在这里插入图片描述
ObjectOutputStream(对象字节输出流)

  • 可以把Java对象进行序列化:把Java对象存入到文件中去.
    在这里插入图片描述
    注意:对象如果要参与序列化,必须实现序列化接口(java.io.Serializable)

ObjectInputStream(对象字节输入流)

  • 可以把]ava对象进行反序列化:把存储在文件中的]ava对象读入到内存中来.

在这里插入图片描述
如果要一次序列化多个对象,怎么办?

  • 用一个ArrayList:集合存储多个对象,然后直接对集合进行序列化即可
    注意:ArrayList集合已经实现了序列化接口!

示例
在这里插入图片描述
Test1

package com.itheima.d6_object_stream;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

//目标:掌握对象字节输出流的使用:序列化对象.
public class Test1 {
    public static void main(String[] args) {

        try (
                //2、创建一个对象字节输出流包装原始的字节输出流.
                ObjectOutputStream oos =
                        new ObjectOutputStream(new FileOutputStream("src/11out.txt"));

        ){
            //1.创建一个Java对象
            User  u=new User("admin","张三",32,"123abc");
            //3、序列化对象到文件中去
            oos.writeObject(u);
            System.out.println("序列化对象成功!!");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}


Test2

package com.itheima.d6_object_stream;

import java.io.*;

//目标:掌握对象字节输入流的使用:反序列化对象.
public class Test2 {
    public static void main(String[] args) {

        try (
                //2、创建一个对象字节输入流管道,包装低级的字节输入流与源文件接通
              ObjectInputStream ois =
                      new ObjectInputStream(new FileInputStream("src/11out.txt"));

        ){
            //1.创建一个Java对象
            User  u=(User)ois.readObject();
            System.out.println(u);
            //User{loginName='admin', userName='张三', age=32, passWord='123abc'}
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}


User

package com.itheima.d6_object_stream;

import java.io.Serializable;

//注意:对象如果需要序列化,必须实现序列化接口,
public class User implements Serializable {
    private String loginName;
    private String userName;
    private  int age;
    //transient这个成员变量将不参与序列化
    private transient String passWord;

    public User() {
    }

    public User(String loginName, String userName, int age, String passWord) {
        this.loginName = loginName;
        this.userName = userName;
        this.age = age;
        this.passWord = passWord;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    @Override
    public String toString() {
        return "User{" +
                "loginName='" + loginName + '\'' +
                ", userName='" + userName + '\'' +
                ", age=" + age +
                ", passWord='" + passWord + '\'' +
                '}';
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
}

4.10补充知识:IO框架

什么是框架

  • 解决某类问题,编写的一套类、接口等,可以理解成一个半成品,大多框架都是第三方研发的.
  • 好处:在框架的基础上开发,可以得到优秀的软件架构,并能提高开发效率
  • 框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去.

什么是IO框架

  • 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等.

Commons-io

  • Commons-io是apache开源基金组织提供的一组有关IO操作的小框架,目的是提高IO流的开发效率.

在这里插入图片描述
下载框架
在这里插入图片描述
点击下载
在这里插入图片描述
解压后选择所要的jar包
在这里插入图片描述
在这里插入图片描述

复制到新建文件夹lib中
在这里插入图片描述
右键lib添加为库
在这里插入图片描述
在这里插入图片描述

示例
在这里插入图片描述

Test

package com.itheima.d7_commons_io;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

//目标:使用CommonsIO框架进行IO相关的操作
public class Test {
    public static void main(String[] args) throws Exception {
        FileUtils.copyFile(new File("src\\01.txt"),new File("src\\a.txt"));
        FileUtils.copyDirectory(new File("D:\\迅雷下载\\G-menu"),new File("D:\\迅雷下载\\Ggg"));
        FileUtils.deleteDirectory(new File("D:\\迅雷下载\\Ggg"));

       /* Java提供的原生的一行代码搞定很多事
        Files.copy(Path.of("src\\01.txt"),Path.of("src\\a.txt"));
        System.out.println(Files.readString(Path.of("src\\01.txt")));
        */
    }
}

十.特殊文件与日志技术

特殊文件
在这里插入图片描述
日志技术

  • 把程序运行的信息,记录到文件中,方便程序员定位Bug、并了解程序的执行情况等

1.特殊文件:Properties,属性文件

1.1Properties文件的特点与作用

特点

  • 都只能是键值对
  • 键不能重复
  • 文件后缀一般是.properties结尾的

作用

  • 存储密码等信息

1.2读取属性文件

Properties

  • 是一个Map集合(键值对集合),但是我们一般不会当集合使用.
  • 核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容.
    在这里插入图片描述
    使用Properties读取属性文件里的键值对数据
    在这里插入图片描述
    示例:
    在这里插入图片描述
    Test1
package com.itheima.d1_properties;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Properties;
import java.util.Set;

//目标:掌握使用Properties类读取属性文件中的键值对信息,
public class Test1 {
    public static void main(String[] args) throws Exception {
        //1、创建一个Properties的对象出来(键值对集合,空容器)
        Properties properties =new Properties();
        System.out.println(properties);

        //2、开始加载属性文件中的键值对数据到properties对象中
        properties.load(new FileReader("src\\users.properties"));
        System.out.println(properties);

        //3、根据键取值
        System.out.println(properties.getProperty("赵敏"));
        System.out.println(properties.getProperty("张无忌"));

        //4、遍历全部的健和值.
        Set<String> keys= properties.stringPropertyNames();
        for (String key: keys){
            String value =properties.getProperty(key);
            System.out.println(key +"---->"+value);
        }

        properties.forEach((k,v)->{
            System.out.println(k+"---->"+v);
        });
    }
}

在这里插入图片描述

1.3写出到属性文件

使用Properties:把键值对数据写出到属性文件里去
在这里插入图片描述
示例:
在这里插入图片描述
Test2

package com.itheima.d1_properties;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

//目标:掌握把键值对数据存入到属性文件中去f
public class Test2 {
    public static void main(String[] args) throws Exception {
        //1、创建Properties对象出来,先用它存储一些键值对数据
        Properties properties =new Properties();
        properties.setProperty("张三","12345");
        properties.setProperty("李四","abcd");
        properties.setProperty("王五","123abc");

        //2、把properties对象中的键值对数据存入到属性文件中去
        //管道作为参数,会自动关
        properties.store(new FileWriter("src\\users2.properties")
        ,"i saved many users");
    }
}

在这里插入图片描述

Test3

package com.itheima.d1_properties;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Properties;

public class Test3 {
    public static void main(String[] args) throws Exception {
        //目标:读取属性文件,判断是否存在李方,存在年龄改成18
        //1、加载属性文件的键值对到程序中来.
        Properties properties = new Properties();//{}

        //2、开始加载
        properties.load(new FileReader("src\\users.txt"));

        //3、判断是否包含李方这个键
        if (properties.containsKey("李方")) {
            properties.setProperty("李方", "18");
        }
        //4、把porperties对象的键值对数据从新写出去到属性文件中去."
        properties.store(new FileWriter("src\\users.txt"),
                "success");
    }
}

在这里插入图片描述

2. 特殊文件:XML文件

2.1概述

XML(全称EXtensible Markup Language,可扩展标记语言)

  • 本质是一种数据的格式,可以用来存储复杂的数据结构,和数据关系.

XML的特点

  • XML中的"<标签名>"称为一个标签或一个元素,一般是成对出现的.
  • XML中的标签名可以自己定义(可扩展),但必须要正确的嵌套.
  • XML中只能有一个根标签.
  • XML中的标签可以有属性.
  • 如果一个文件中放置的是XML格式的数据,这个文件就是XML文件,后缀一般要写成.xml

XML的创建

  • 就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml

IDEA创建XML文件
在这里插入图片描述
XML的语法规则

  • XML文件的后缀名为:xml,文档声明必须是第一行
    在这里插入图片描述
  • XML中可以定义注释信息:<! --注释内容 -->
  • XML中书写"<“、”&"等,可能会出现冲突,导致报错,此时可以用如下特殊字符替代.
    在这里插入图片描述
  • XML中可以写一个叫CDATA的数据区:<![CDATA[...内容...]]>,里面的内容可以随便写.

XML的作用和应用场景

  • 本质是一种数据格式,可以存储复杂的数据结构,和数据关系.
  • 应用场景:经常用来做为系统的配置文件;或者作为一种特殊的数据结构,在网络中进行传输.
    在这里插入图片描述

2.2读取XML文件中的数据

解析XML文件

  • 使用程序读取XML文件中的数据
    在这里插入图片描述
  • 注意:程序员并不需要自己写原始的IO流代码来解析XML,难度较大!也相当繁琐!
    其实,有很多开源的,好用的,解析XML的框架,最知名的是:dom4j(第三方研发的
    在这里插入图片描述
    DOM4J解析XML文件的思想:文档对象模型
    在这里插入图片描述
    Dom4j解析XML-得到Document对象
  • SAXReader:Dom4j提供的解析器,可以认为是代表整个Dom4j框架
    在这里插入图片描述
  • Document
    在这里插入图片描述
    Element提供的方法
    在这里插入图片描述
    示例
    在这里插入图片描述
    在这里插入图片描述

book.xml

<?xml version="1.0"encoding="UTF-8"?>
<book>
<name>从入门到跑路</name>
<author>dlei</author>r
<price>999.99</price>
</book>

helloworld.xml

<?xml version="1.0" encoding="utf-8" ?>
<!--此为注释:以上抬头声明必须放在第一行,必须有-->
<!-- 根标签只能有一个-->
<users>
    <user id="1">
        <name>张无忌</name>
        <sex></sex>
        <地址>光明顶</地址>
        <password>minmin</password>
        <data>3&lt; 2&amp;&amp;5>4 </data>
        <data1>
            <![CDATA[
        3<2&&5>4
        ]]>
        </data1>
    </user>
    <people>很多人</people>
    <user id="2">
        <name>敏敏</name>
        <sex></sex>
        <地址>光明顶</地址>
        <password>wuji</password>
    </user>
</users>

Dom4JTest1

package com.itheima.d2_xml;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

//目标:学握使用Dom4j框架解析XML文件.
public class Dom4JTest1 {
    public static void main(String[] args) throws Exception {
        //1、创建一个Dom4J框架提供的解析器对象
        SAXReader saxReader= new SAXReader();
        //2.使用saxReader对象把需要解析的XML读成一个Document对象
        Document document=saxReader.read("src\\helloworld.xml");

        //3、从文档对象中解析XML文件的全部数据了
        Element root =document.getRootElement();
        System.out.println(root.getName());

        //4、获取根元素下的全部一级子元素.
        // List<Element> elements = root.elements();
        List<Element> elements = root.elements("user");
        for (Element element : elements) {
            System.out.println(element.getName());
        }

        //5、获取当前元素下的某个子元素.
        Element people = root.element("people");
        System.out.println(people.getText());

        //如果下面有很多子元泰UseP,默认获取第一个.
        Element user = root.element("user");
        System.out.println(user.elementText("name"));

        //6、获取元素的属性信息呢?
        System.out.println(user.attributeValue("id"));
        Attribute id=user.attribute("id");
        System.out.println(id.getName());
        System.out.println(id.getValue());

        List<Attribute> attributes=user.attributes();
        for (Attribute attribute : attributes) {
            System.out.println(attribute.getName()+"="+attribute.getValue());
        }

        //7、如何获取全部的文本内容:获取当前元素下的子元素文本值
        System.out.println(user.elementText("name"));
        System.out.println(user.elementText("地址"));
        System.out.println(user.elementText("password"));

        Element data = user.element("data");
        System.out.println(data.getText());
        System.out.println(data.getTextTrim());//去除前后空格
    }
}

Dom4JTest2

package com.itheima.d2_xml;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/*
目标:如何使用程序把数据写出到XML文件中去.
<?xml version="1.0"encoding="UTF-8"?>
<booK>
<name>从入门到跑路</name>
<author>dlei</author>
<price>999.9</price>
</book>

 */
public class Dom4JTest2 {
    public static void main(String[] args)  {
        //1、使用一个StringBuilder对象来拼接XML格式的数据.
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\"encoding=\"UTF-8\"?>\r\n");
        sb.append("<book>\r\n");
        sb.append("\t<name>").append("从入门到跑路").append("</name>\r\n");
        sb.append("\t<author>").append("dlei").append("</author>r\n");
        sb.append("\t<price>").append(999.99).append("</price>\r\n");
        sb.append("</book>");

        try(
                BufferedWriter bw =new BufferedWriter(new FileWriter("src/book.xml"))

                ) {
            bw.write(sb.toString());
        } catch (Exception e) {
           e.printStackTrace();
        }
    }
}

2.3补充知识:约束XML文件的编写[了解]

在这里插入图片描述
什么是约束XML文件的书写?

  • 就是限制XML文件只能按照某种格式进行书写.

约束文档

  • 专门用来限制xl书写格式的文档,比如:限制标签、属性应该怎么写.

约束文档的分类

  • DTD文档
  • Schema文档
    在这里插入图片描述

3.日志技术

3.1概述

在这里插入图片描述
在这里插入图片描述
日志技术

  • 可以将系统执行的信息,方便的记录到指定的位置(控制台、文件中、数据库中).
  • 可以随时以开关的形式控制日志的启停,无需侵入到源代码中去进行修改.

3.2日志技术的体系、Logback日志框架的概述

日志技术的体系结构
在这里插入图片描述

  • 日志框架:牛人或者第三方公司已经做好的实现代码,后来者直接可以拿去使用.
  • 日志接口:设计日志框架的一套标准,日志框架需要实现这些接口.
  • 注意1:因为对Commons Logging接口不满意,有人就搞了SLF4;因为对Log4j的性能不满意,有人就搞了Logback…
  • 注意2:Logback是基于slf4j的日志规范实现的框架.

Logback日志框架官方网站

Logback日志框架有以下几个模块:
在这里插入图片描述
想使用Logback日志框架,至少需要在项目中整合如下三个模块:

  • slf4j-api:日志接口
  • logback-core
  • logback-classic

3.3Logback快速入门

在这里插入图片描述
核心配置文件logback.xml

  • 对Logback日志框架进行控制的.

日志的输出位置、输出格式的设置

  • 通常可以设置2个输出日志的位置:一个是控制台、一个是系统文件中
    在这里插入图片描述
    开启日志(ALL),取消日志(OFF)
    在这里插入图片描述

3.4Logback设置日志级别

什么是日志级别

  • 日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下**(优先级依次升高)**:
    在这里插入图片描述
    为什么要学习日志级别
    在这里插入图片描述

  • 只有日志的级别是大于或等于核心配置文件配置的日志级别,才会被记录,否则不记录.

示例
在这里插入图片描述
logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target>
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>D:/迅雷下载/data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>D:/迅雷下载/log-%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--
    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR    |    ALL 和 OFF
   , 默认debug
    <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
    -->
    <root level="info">
        <!-- 注意:如果这里不配置关联打印位置,该位置将不会记录日志-->
        <appender-ref ref = "CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>
</configuration>

LogBackTest

package com.itheima.d3_log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


//目标:掌握LogBack日志框架的使用,
public class LogBackTest {
        //创建一个Logger日志对象
        public static  final Logger LOGGER=  LoggerFactory.getLogger("LogBackTest");
        public static void main(String[] args) {


                try {
                    LOGGER.info("chu法方法开始执行~~~");
                    chu(10, 0);
                    LOGGER.info("chu法方法执行成功~~~");
                } catch (Exception e) {

                    LOGGER.error("chu法方法执行失败了,出现了bug~~~");
                }

        }

    public static void chu(int a, int b) {
    LOGGER.debug(("参数a")+a);
    LOGGER.debug(("参数b")+b);
        int c = a / b;
     LOGGER.info("结果是"+c);
    }
}

十一.多线程

1.概述

什么是线程?

  • 线程(Thread)是一个程序内部的一条执行流程.
    在这里插入图片描述
  • 程序中如果只有一条执行流程,那这个程序就是单线程的程序.

多线程是什么?

  • 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行).

如何在程序中创建出多条线程?

  • Java是通过java.lang.Thread类的对象来代表线程的.

1.多线程的创建

1.1方式一:继承Thread类

多线程的创建方式一:继承Thread类

  1. 定义一个子类MyThread继承线程类java.lang.Thread,重写run(O方法
  2. 创建MyThread类的对象
  3. 调用线程对象的start()方法启动线程(启动后还是执行run方法的

方式一优缺点:

  • 优点:编码简单
  • 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展.

多线程的注意事项

  1. 启动线程必须是调用start方法,不是调用run方法.
  2. 不要把主线程任务放在启动子线程之前.

示例
在这里插入图片描述
MyThread

package com.itheima.d1_create_thread;
//1、让子类继承Thread线程类
public class MyThread extends Thread{
    //2.必须重写Thread类的run方法
    @Override
    public void run() {
        //描述线程的执行任务
        for (int i = 0; i <5 ; i++) {
            System.out.println("子线程MyThread线程输出"+i);
        }
    }
}

ThreadTest1

package com.itheima.d1_create_thread;
//目标:掌握线程的创建方式一:继承Thread类
public class ThreadTest1 {
    //main方法是由一条默认的主线程负责执行.
    public static void main(String[] args) {
        //3、创建MyThread.线程类的对象代表一个线程
        Thread t =new MyThread();
        //4、启动线程(自动执行run方法的)
        t.start();//main线程t线程

        for (int i = 0; i <5 ; i++) {
            System.out.println("主线程输出"+i);
        }
    }
}

1.2方式二:实现Runnable接口

多线程的创建方式二:实现Runnable接口

  1. 定义一个线程任务类MyRunnable:实现Runnable接口,重写run()方法
  2. 创建MyRunnable任务对象
  3. 把MyRunnable任务对象交给Thread处理.
    在这里插入图片描述
  4. 调用线程对象的start(0方法启动线程

方式二的优缺点

  • 优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强.
  • 缺点:需要多一个Runnable对象.

线程创建方式二的匿名内部类写法

  1. 可以创建Runnable的匿名内部类对象.
  2. 再交给Thread线程对象.
  3. 再调用线程对象的start()启动线程.

示例
在这里插入图片描述
MyRunnable

package com.itheima.d1_create_thread;
//1、定义一个任务类,实现Runnable接口
public class MyRunnable implements Runnable {
    //2.重写runnable的run方法

    @Override
    public void run() {
        //线程要执行的任务,
        for (int i = 1;i <5;i++){
            System.out.println("子线程输出===》"+i);
        }
    }
}

ThreadTest2

package com.itheima.d1_create_thread;
//目标:掌握多线程的创建方式二:实现Runnable接口,
public class ThreadTest2 {
    public static void main(String[] args) {
        //3、创建任务对象.
        Runnable target = new MyRunnable();
        //4、把任务对象交给一个线程对象处理.
        //public Thread(Runnable target)
        new Thread(target).start();
        for(int i=1;i<=5;i++)
        {
            System.out.println("主线程main输出===》" + i);
        }
    }

}

ThreadTest2_2

package com.itheima.d1_create_thread;
//目标:掌握多线程创建方式二的匿名内部类写法.
public class ThreadTest2_2 {
    public static void main(String[] args) {
        //1、直接创建Runnable接口的匿名内部类形式(任务对象)
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    System.out.println("子线程1输出" + i);
                }


            }
        };
        new Thread(target).start();

        //简化形式1
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    System.out.println("子线程2输出" + i);
                }
            }
        }).start();

        //简化形式2
        new Thread(()->{
            for (int i = 1; i < 5; i++) {
                System.out.println("子线程3输出" + i);
            }
        }).start();

        for (int i = 1; i < 5; i++) {
            System.out.println("主线输出" + i);
        }
    }
}

1.3方式三:实现Callable接口

前两种线程创建方式都存在的一个问题
-假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果.

怎么解决这个问题?

  • JDK5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式).
  • 这种方式最大的优点:可以返回线程执行完毕后的结果.

多线程的第三种创建方式:利用Callable接▣、FutureTask类来实现.

  1. 创建任务对象
    • 定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据.
    • 把Callable类型的对象封装成FutureTask(线程任务对象).
  2. 把线程任务对象交给Thread对象.
  3. 调用Thread对象的start方法启动线程.
  4. 线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果.

FutureTask的API
在这里插入图片描述
线程创建方式三的优缺点

  • 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果.
  • 缺点:编码复杂一点.
    示例
    在这里插入图片描述
    MyCallable
package com.itheima.d1_create_thread;

import java.util.concurrent.Callable;

//1、让这个类实现Callable接口
public class MyCallable implements Callable<String> {
    private int n;

    public int getN() {
        return n;
    }

    public MyCallable(int n) {
        this.n = n;
    }

    public void setN(int n) {
        this.n = n;
    }
    //2.重写call方法
    @Override
    public String call() throws Exception {
        //描述线程的任务,返回线程执行返回后的结果
        //需求:求1-n的和返回,
        int sum=  0;
        for (int i=1;i<=n;i++) {
            sum += i;

        }
         return "线程求出了1-"+n+"的和是"+sum;
    }
}

ThreadTest3

package com.itheima.d1_create_thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//目标:掌握线程的创建方式三:实现Callable接口.
public class ThreadTest3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.创建一个Callable的对象
        Callable<String> call = new MyCallable(100);
        //4、把Callable的对象时装成个FutureTask对象(任务对象
        //未来任务对象的作用?
        //(1)是一个任务对象,实现了Runnable
        //(2)可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果.
        FutureTask<String> f1 =new FutureTask<>(call);

        //5、把任务对象交给一个Thread对象
        new Thread(f1).start();

        //6、获取线程执行完毕后返回的结果
        //注意:如果执行到这儿,假如上面的线程还没有执行完华
        //这里的代码会暂停,等待上面线程执行完毕后才会获取结果.
        String rs= f1.get();
        System.out.println(rs);

    }
}

2.Thread的常用方法

Thread提供了很多与线程操作相关的方法
在这里插入图片描述
在这里插入图片描述
示例
在这里插入图片描述
MyThread

package com.itheima.d2_thread_api;

public class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }

    public MyThread() {
    }

    @Override
    public void run(){
        //哪个线程执行它,它就会得到哪个线程对象.
        Thread t = Thread.currentThread();
        for (int i= 1;i<=3;i++){
            System.out.println(t.getName()+"输出:"+i);
        }
    }
}

ThreadTest1

package com.itheima.d2_thread_api;
//目标:掌握Thread的常用方法.
public class ThreadTest1 {
    public static void main(String[] args) {
        Thread t1 = new MyThread("1号线程");
        //启动之前取名字
       // t1.setName("1号线程");
        t1.start();
        System.out.println(t1.getName());

        Thread t2 = new MyThread("2号线程");
      //  t1.setName("2号线程");
        t2.start();
        System.out.println(t2.getName());
        //主线程对象的名字
        //哪个线程执行它,它就会得到哪个线程对象.
        Thread m = Thread.currentThread();
        m.setName("最牛的线程");
        System.out.println(m.getName());
        for (int i = 1; i <= 5; i++) {
            System.out.println("main线程输出:" + i);
        }
    }
}

ThreadTest2

package com.itheima.d2_thread_api;
//目标:掌握sleep方法,join方法的作用.
public class ThreadTest2 {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i <= 5; i++) {
            System.out.println(i);
            //休眠5s
            if(i==3){
                //会让当前执行的线程暂停5秒,再继续执行
                Thread.sleep(5000);
            }
        }
        Thread t1 = new MyThread("1号线程");
        t1.start();
        t1.join();
        Thread t2 = new MyThread("2号线程");
        t2.start();
        t2.join();
        Thread t3 = new MyThread("3号线程");
        t3.start();
        t3.join();

    }
}

3.线程安全

3.1什么是线程安全问题

什么是线程安全问题?

  • 多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题.

取钱的线程安全问题
-场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?

在这里插入图片描述
线程安全问题出现的原因?

  • 存在多个线程在同时执行
  • 同时访问一个共享资源
  • 存在修改该共享资源

3.2用程序模拟线程安全问题

在这里插入图片描述
示例
在这里插入图片描述
Account

package com.itheima.d3_thread_safe;

public class Account {
    private String cardId;//卡号
    private double money;//余额.

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    //小明小红同时来
    public void drawMoney(double money) {
        //先搞清楚是谁来取钱?
        String name =Thread.currentThread().getName();
        //1、判断余颜是否足够
        if(this.money >=money) {
            System.out.println(name+"来取钱"+money+"成功");
            this.money-=money;
            System.out.println(name+"来取钱后,余额剩余:"+this.money);
        }
        else
            System.out.println(name+"来取钱:余额不足~");
        }

    }


DrawThread

package com.itheima.d3_thread_safe;

public class DrawThread extends Thread{
    private Account acc;
    @Override
    public void run() {
        //取钱
        acc.drawMoney(100000);
    }

    public DrawThread(Account acc,String name) {
        super(name);
        this.acc=acc;
    }
}

ThreadTest

package com.itheima.d3_thread_safe;
//目标:模拟线程安全问题.
public class ThreadTest {
    public static void main(String[] args) {
        //1、创建1个账户对象,代表两个人的共享账户.
        Account acc =new Account("ICBC-110",100000);
        //2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
        new DrawThread(acc,"小明").start();//小明
        new DrawThread(acc,"小红").start();//小红

    }
}

4.线程同步

4.1认识线程同步

线程同步

  • 解决线程安全问题的方案.

线程同步的思想

  • 让多个线程实现先后依次访问共享资源,这样就解决了安全问题.

线程同步的常见方案

  • **加锁:**每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来.

4.2方式一:同步代码块

同步代码块

  • **作用:**把访问共享资源的核心代码给上锁,以此保证线程安全.
    在这里插入图片描述
  • **原理:**每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行.

同步锁的注意事项

  • 对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug.

锁对象随便选择一个唯一的对象好不好呢?

  • 不好,会影响其他无关线程的执行.

锁对象的使用规范

  • 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象.
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象.

示例
在这里插入图片描述

Account

package com.itheima.d4_synchronized_code;

public class Account {
    private String cardId;//卡号
    private double money;//余额.

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }


    public static void test(){
        //静态方法用类名.class作为锁
        synchronized (Account.class){
        }
    }

    //小明小红同时来
    public void drawMoney(double money) {
        //先搞清楚是谁来取钱?
        String name =Thread.currentThread().getName();
        //1、判断余颜是否足够
        //"黑马"这个字符串在计算机中只有一份,所以对于小红小明来说是同一个对象
      //  synchronized ("黑马") {
        //this正好代表共享资源!
      synchronized ("this") {
            if(this.money >=money) {
                System.out.println(name+"来取钱"+money+"成功");
                this.money-=money;
                System.out.println(name+"来取钱后,余额剩余:"+this.money);
            }
            else{
                System.out.println(name+"来取钱:余额不足~");
            }
        }

    }

    }


DrawThread

package com.itheima.d4_synchronized_code;

public class DrawThread extends Thread{
    private Account acc;
    @Override
    public void run() {
        //取钱
        acc.drawMoney(100000);
    }

    public DrawThread(Account acc, String name) {
        super(name);
        this.acc=acc;
    }
}

ThreadTest

package com.itheima.d4_synchronized_code;

//目标:模拟线程安全问题.
public class ThreadTest {
    public static void main(String[] args) {
        //1、创建1个账户对象,代表两个人的共享账户.
        Account acc =new Account("ICBC-110",100000);
        //2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
        new DrawThread(acc,"小明").start();//小明
        new DrawThread(acc,"小红").start();//小红

    }
}

4.3方式二:同步方法

同步方法

  • 作用:把访问共享资源的核心方法给上锁,以此保证线程安全.
    在这里插入图片描述
  • 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行.

同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码.
  • 如果方法是实例方法:同步方法默认用this作为的锁对象.
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象.

是同步代码块好还是同步方法好一点?

  • 范围上:同步代码块锁的范围更小,同步方法锁的范围更大.
  • 可读性:同步方法更好.
    示例
    在这里插入图片描述

Account

package com.itheima.d5_synchronized_method;

public class Account {
    private String cardId;//卡号
    private double money;//余额.

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    //小明小红同时来
    //同步方法,隐含锁
    public synchronized void drawMoney(double money) {
        //先搞清楚是谁来取钱?
        String name =Thread.currentThread().getName();
        //1、判断余颜是否足够
        if(this.money >=money) {
            System.out.println(name+"来取钱"+money+"成功");
            this.money-=money;
            System.out.println(name+"来取钱后,余额剩余:"+this.money);
        }
        else
            System.out.println(name+"来取钱:余额不足~");
        }

    }


DrawThread

package com.itheima.d5_synchronized_method;

public class DrawThread extends Thread{
    private Account acc;
    @Override
    public void run() {
        //取钱
        acc.drawMoney(100000);
    }

    public DrawThread(Account acc, String name) {
        super(name);
        this.acc=acc;
    }
}

ThreadTest

package com.itheima.d5_synchronized_method;

//目标:模拟线程安全问题.
public class ThreadTest {
    public static void main(String[] args) {
        //1、创建1个账户对象,代表两个人的共享账户.
        Account acc =new Account("ICBC-110",100000);
        //2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
        new DrawThread(acc,"小明").start();//小明
        new DrawThread(acc,"小红").start();//小红

    }
}

4.4方式三:Lock锁

Lock锁

  • Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大.
  • Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象.
    在这里插入图片描述
    Lock的常用方法
    在这里插入图片描述
    示例
    在这里插入图片描述

Account

package com.itheima.d6_synchronized_lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Account {
    private String cardId;//卡号
    private double money;//余额.
    //创建了一个锁对象(每个实例都有各自的锁)
    private final Lock lk = new ReentrantLock();

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    //小明小红同时来
    public void drawMoney(double money) {
        //先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        lk.lock();

        try {
            //1、判断余颜是否足够
            if (this.money >= money) {
                System.out.println(name + "来取钱" + money + "成功");
                this.money -= money;
                System.out.println(name + "来取钱后,余额剩余:" + this.money);
            } else {
                System.out.println(name + "来取钱:余额不足~");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lk.unlock();
        }


    }
}


DrawThread

package com.itheima.d6_synchronized_lock;

public class DrawThread extends Thread{
    private Account acc;
    @Override
    public void run() {
        //取钱
        acc.drawMoney(100000);
    }

    public DrawThread(Account acc, String name) {
        super(name);
        this.acc=acc;
    }
}

ThreadTest

package com.itheima.d6_synchronized_lock;

//目标:模拟线程安全问题.
public class ThreadTest {
    public static void main(String[] args) {
        //1、创建1个账户对象,代表两个人的共享账户.
        Account acc =new Account("ICBC-110",100000);
        //2、创建两个线程,分别代表小明小红,再去同一个账户对象中取钱10万
        new DrawThread(acc,"小明").start();//小明
        new DrawThread(acc,"小红").start();//小红

    }
}

5.线程通信

什么是线程通信?
-当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺.

线程通信的常见模型(生产者与消费者模型)

  • 生产者线程负责生产数据
  • 消费者线程负责消费生产者生产的数据.
  • 注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产
    在这里插入图片描述
    Object类的等待和唤醒方法:
    在这里插入图片描述

注意

  • 上述方法应该使用当前同步锁对象进行调用.

示例
在这里插入图片描述
Desk

package com.itheima.d7_thread_communication;

import java.util.ArrayList;
import java.util.List;

public class Desk {
    private List<String> list =new ArrayList<>();
    //放1个包子的方法
    //厨师1 厨师2 厨师3
    public synchronized void put() {

        try {
            String name = Thread.currentThread().getName();
            //判断是否有包子,
            if(list.size()==0){
                list.add(name+"做的肉包子");
                System.out.println(name+"做了一个肉包子~~");
                Thread.sleep(2000);

                //等待自己,唤醒别人
                //先唤醒,后等待
                this.notifyAll();
                this.wait();

            }else {
                //有包子了,不做了
                //唤醒别人,等待自己
                this.notifyAll();
                this.wait();
            }

        } catch (Exception e) {
           e.printStackTrace();
        }
    }


    //吃货1 吃货2
    public synchronized void get() {

            try {
                String name = Thread.currentThread().getName();
                if(list.size()==1) {
                    //有包子,吃了
                    System.out.println(name + "吃了:" + list.get(0));
                    list.clear();
                    Thread.sleep(1000);
                    this.notifyAll();
                    this.wait();
                }else {
                //没有包子
                    this.notifyAll();
                    this.wait();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

ThreadTest

package com.itheima.d7_thread_communication;
//目标:了解一下线程通信.
public class ThreadTest {
    public static void main(String[] args) {
        //需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子
        //2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃.
                Desk desk = new Desk();
        //创建3个生产者线程(3个时师)
        new Thread(()->{
            while (true) {
                desk.put();
            }
        },"厨师1").start();

        new Thread(()->{
            while (true) {
                desk.put();
            }
        },"厨师2").start();

        new Thread(()->{
            while (true) {
                desk.put();
            }
        },"厨师3").start();

        //创建2个消费者线程(2个吃货)
        new Thread(()->{
            while (true) {
                desk.get();
            }
        },"吃货1").start();


        new Thread(()->{
            while (true) {
                desk.get();
            }
        },"吃货2").start();
    }
}

6.线程池

6.1认识线程池

什么是线程池?

  • 线程池就是一个可以复用线程的技术.

不使用线程池的问题

  • 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能.

线程池的工作原理

  • 有任务区和线程区,既可以控制线程数量,又可以控制任务数量
    在这里插入图片描述

6.2如何创建线程池?

谁代表线程池?

  • JDK5.0起提供了代表线程池的接口:ExecutorService.

如何得到线程池对象?

  • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象.
    在这里插入图片描述
  • 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象.

ThreadPoolExecutor:构造器
在这里插入图片描述

  • 参数一:corePoolSize:指定线程池的核心线程的数量.
  • 参数二:maximumPoolSize:指定线程池的最大线程数量.心
  • 参数三:keepAliveTime:指定临时线程的存活时间.
  • 参数四:uit:指定临时线程存活的时间单位(秒、分、时、天)
  • 参数五:workQueue:指定线程池的任务队列.
  • 数六:threadFactory:指定线程池的线程工厂.
  • 参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

线程池的注意事项

  1. 临时线程什么时候创建?
    • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程.
  2. 什么时候会开始拒绝新任务?
    • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务.

6.3线程池处理Runnable任务

ExecutorService的常用方法
在这里插入图片描述
新任务拒绝策略
在这里插入图片描述

示例
在这里插入图片描述
ThreadTest1

package com.itheima.d8_thread_pool;

import java.util.concurrent.*;

//目标:掌握线程池的创建.
public class ThreadTest1 {
    public static void main(String[] args) {
        /*
         public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

         */
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,
                TimeUnit.SECONDS,new LinkedBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());

       Runnable target =new MyRunnable();
       pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
       pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
       pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!pool.execute(target);
       pool.execute(target);
       pool.execute(target);
       pool.execute(target);
       pool.execute(target);
        //到了临时线程的创建时机了
       pool.execute(target);
       pool.execute(target);
        //到了新任务的拒绝时机了!
        pool.execute(target);


      //  pool.shutdown();//等线程池的任务全部执行完毕后,再关闭线程池
      // pool.shutdownNow();//立即关闭线程池!不管任务是否执行完毕!

    }
}

MyRunnable

package com.itheima.d8_thread_pool;

public class MyRunnable implements Runnable {
    @Override

    public void run() {
        //任务是干什么的
        System.out.println(Thread.currentThread().getName() + "==>666~~");
        try {
            Thread.sleep(50000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6.4线程池处理Callable任务

ExecutorService的常用方法
在这里插入图片描述
示例
在这里插入图片描述

ThreadTest2

package com.itheima.d8_thread_pool;

import java.util.concurrent.*;
//目标:掌握线程池的创建.
public class ThreadTest2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,
                TimeUnit.SECONDS,new LinkedBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        //2、使用线程处理Callable任务,
       Future<String> f1= pool.submit(new MyCallable(100));
       Future<String> f2= pool.submit(new MyCallable(200));
       Future<String> f3= pool.submit(new MyCallable(300));
       Future<String> f4= pool.submit(new MyCallable(400));
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

MyCallable

package com.itheima.d8_thread_pool;

import java.util.concurrent.Callable;
import java.util.concurrent.ThreadFactory;

//1、让这个类实现Callable接口
public class MyCallable implements Callable<String> {
    private int n;

    public int getN() {
        return n;
    }

    public MyCallable(int n) {
        this.n = n;
    }

    public void setN(int n) {
        this.n = n;
    }
    //2.重写call方法
    @Override
    public String call() throws Exception {
        //描述线程的任务,返回线程执行返回后的结果
        //需求:求1-n的和返回,
        int sum=  0;
        for (int i=1;i<=n;i++) {
            sum += i;

        }
         return Thread.currentThread().getName()+ "线程求出了1-"+n+"的和是"+sum;
    }
}

6.5Executors工具类实现线程池

Executors

  • 是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象.
    在这里插入图片描述
  • 注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象.

Executors使用可能存在的陷阱

  • 大型并发系统环境中使用Executors如果不注意可能会出现系统风险.
    在这里插入图片描述

示例
在这里插入图片描述
ThreadPoolTest3

package com.itheima.d8_thread_pool;

import java.util.concurrent.*;

//目标:掌握线程池的创建.
public class ThreadTest1 {
    public static void main(String[] args) {
        /*
         public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

         */
        ExecutorService pool = new ThreadPoolExecutor(3,5,8,
                TimeUnit.SECONDS,new LinkedBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());

       Runnable target =new MyRunnable();
       pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
       pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
       pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!pool.execute(target);
       pool.execute(target);
       pool.execute(target);
       pool.execute(target);
       pool.execute(target);
        //到了临时线程的创建时机了
       pool.execute(target);
       pool.execute(target);
        //到了新任务的拒绝时机了!
        pool.execute(target);


      //  pool.shutdown();//等线程池的任务全部执行完毕后,再关闭线程池
      // pool.shutdownNow();//立即关闭线程池!不管任务是否执行完毕!

    }
}

6.6其它细节知识:并发、并行

进程

  • 正在运行的程序(软件)就是一个独立的进程.
  • 线程是属于进程的,一个进程中可以同时运行很多个线程.
  • 进程中的多个线程其实是并发和并行同时执行的.

并发的含义

  • 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发.

并行的理解

  • 在同一个时刻上,同时有多个线程在被CPU调度执行.

6.7其它细节知识:线程的生命周期

线程的生命周期

  • 也就是线程从生到死的过程中,经历的各种状态及状态转换
  • 理解线程这些状态有利于提升并发编程的理解能力.

Java线程的状态

  • Java总共定义了6种状态
  • 6种状态都定义在Thread类的内部枚举类中.
    在这里插入图片描述
    线程的6种状态互相转换
    在这里插入图片描述

在这里插入图片描述
拓展
在这里插入图片描述
MyRunnable1

package com.itheima.d9_tz;

public class MyRunnable1 implements Runnable {
    private int count;//记录浏览人次

    @Override
    public void run() {
        //100次
        for (int i = 0; i < 100; i++) {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName()+"count =======>" + (++count));
            }
        }
    }
}

MyRunnable2

package com.itheima.d9_tz;

import java.util.concurrent.atomic.AtomicInteger;

public class MyRunnable2 implements Runnable{
    //整数修改的乐观锁:原子类实现的.
    private AtomicInteger count =new AtomicInteger();


    @Override
    public void run() {
        //100次
        for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+"count =======>" + count.incrementAndGet());
        }
    }
}

Test1

package com.itheima.d9_tz;

public class Test1 {
    public static void main(String[] args) {
        //目标:拓展悲观锁,乐观锁原理.
        //悲观锁:一上来就加锁,没有安全感.每次只能一个线程进入访问完毕后,再解锁.线程安全,性能较差!
        //乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制.线程安全,性能较好.

        //需求:1变量,100个线程,每个线程对其加100次.
        Runnable target =new MyRunnable1();
        for(int i=1;i<=100;i++){
            new Thread(target).start();
        }

    }
}

Test2

package com.itheima.d9_tz;

public class Test2 {
    public static void main(String[] args) {
        //目标:拓展悲观锁,乐观锁原理.
        //悲观锁:一上来就加锁,没有安全感.每次只能一个线程进入访问完毕后,再解锁.线程安全,性能较差!
        //乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制.线程安全,性能较好.

        //需求:1变量,100个线程,每个线程对其加100次.
        Runnable target =new MyRunnable1();
        for(int i=1;i<=100;i++){
            new Thread(target).start();
        }
    }
}

6.8练习题

在这里插入图片描述
示例
在这里插入图片描述
Demo1

package com.itheima.d10_test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Demo1 {
    public static void main(String[] args) {
        //目标:有100份礼物,小红,小明两人同时发送,当剩下的礼品小于10份的时候则不再送出,
        //利用多线程模拟该过程并将线程的名称打印出来,并最后在控制台分别打印小红,小明各白送出多少份礼物,

        //1.拿100份礼品到程序中来
        List<String> gift=new ArrayList<>();
        String[] names={"口红","包包","鲜花","剃须刀","皮带","手表"};
        Random r=new Random();
        for (int i=0;i <100;i++){
            gift.add(names[r.nextInt(names.length)]+(i+1));
        }
        System.out.println(gift);
        //2、定义线程类,创建线程对象,去集合中拿礼物给别人
        SendThread xm=new SendThread(gift,"小明");
        xm.start();
        SendThread xh=new SendThread(gift,"小红");
        xh.start();
        System.out.println("小明一共发了"+xm.getCount()+"件礼物");
        System.out.println("小红一共发了"+xh.getCount()+"件礼物");

    }
}

SendThread

package com.itheima.d10_test;

import java.util.List;
import java.util.Random;

public class SendThread extends  Thread{
    private List<String> gift;
    private int count;
    public SendThread(List<String>gift,String name) {
        super(name);
        this.gift = gift;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    @Override
    public void run(){
        String name=Thread.currentThread().getName();
        //小明,小红发礼物出去
        // 实现线程安全问题
                //注意1:锁必须唯一
        Random r=new Random();
        while (true) {
            synchronized (gift) {
                if(gift.size()<10){
                    System.out.println(name+"一共发了"+this.getCount()+"件礼物");
                    break;
                }
               String rs = gift.remove(r.nextInt(gift.size()));
                System.out.println(name+"发出了"+rs);
                count++;


            }
        }
    }
}

十二.网络编程

1.网络编程概述

什么是网络编程?

  • 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的).

基本的通信架构

  • 基本的通信架构有2种形式:CS架构(Client:客户端/Server服务端)、BS架构(Browser浏览器/Server服务端).
    在这里插入图片描述
    在这里插入图片描述
    无论是CS架构,还是BS架构的软件都必须依赖网络编程!

2.网络通信三要素

dddddd
在这里插入图片描述

2.1IP地址

IP地址

  • IP(Internet Protocol):全称"“互联网协议地址”,是分配给上网设备的唯一标志.
  • IP地址有两种形式:IPV4、IPV6
    在这里插入图片描述
    IPV6地址
  • IPV6:共128位,号称可以为地球每一粒沙子编号.
  • IPV6分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号(:)分开.
    在这里插入图片描述
    IP域名
    在这里插入图片描述
    公网IP,内网IP
  • 公网IP:是可以连接互联网的IP地址;内网IP:也叫局域网IP,只能组织机构内部使用.
  • 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0-192.168.255.255,专门为组织机构内部使用.

特殊IP地址:

  • 127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机.

IP常用命令:

  • ipconfig:查看本机lP地址.
  • ping IP地址:检查网络是否连通.

InetAddress

  • 代表IP地址.

InetAddress的常用方法如下
在这里插入图片描述
示例
在这里插入图片描述
InetAddressTest

package com.itheima.d1_ip;

import java.net.InetAddress;
import java.net.UnknownHostException;

//目标:掌握InetAddress类的使用.
public class InetAddressTest {
    public static void main(String[] args) throws Exception {
        //1、获取本机IP地址对象的
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());
        System.out.println();

        //2、获取指定IP或者域名的IP地址对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());

        System.out.println(ip2.isReachable(6000));
    }

}

2.2端口号

端口

  • 标记正在计算机设备上运行的应用程序的,被规定为一个16位的二进制,范围是0~65535.

分类

  • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
  • 注册端口:1024~49151,分配给用户进程或某些应用程序.
  • 动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配.
    注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错.

2.3协议

通信协议

  • 网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议.

开放式网络互联标准:OS引网络参考模型

  • OSI网络参考模型:全球网络互联标准.
  • TCP/IP网络模型:事实上的国际标准.
    在这里插入图片描述
    传输层的2个通信协议
  • UDP(User Datagram Protocol):用户数据报协议;TCP(Transmission Control Protocol):传输控制协议.

UDP协议

  • 特点:无连接、不可靠通信.
  • 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(限制在64KB内)等.
  • 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的.

TCP协议

  • 特点:面向连接、可靠通信.
  • TCP的最终目的:要保证在不可靠的信道上实现可靠的传输.
  • TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接.

TCP协议:三次握手建立可靠连接

  • 可靠连接:确定通信双方,收发消息都是正常无问题的!(全双工)
    在这里插入图片描述
    TCP协议:四次握手断开连接
    在这里插入图片描述

3.UDP通信

3.1快速入门

UDP通信

  • 特点:无连接、不可靠通信.
  • 不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP等信息封装成一个数据包,发出去就不管了.
  • Java提供了一个java.net.DatagramSocket类来实现UDP通信.

DatagramSocket:用于创建客户端、服务端
在这里插入图片描述

DatagramPacket:创建数据包
在这里插入图片描述
案例
在这里插入图片描述
在这里插入图片描述
示例
在这里插入图片描述
Client

package com.itheima.d2_udp1;

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

//目标:完成UDP通信快速入门:实现1发1收.
public class Client {
    public static void main(String[] args) throws Exception {
        //1、创建客户端对象(发非菜出去的人)
        DatagramSocket socket = new DatagramSocket(8888);
        //2、创建数据包对象封装要发出去的数据
        /* public DatagramPacket(byte buf[], int offset, int length,
                 InetAddress address, int port)
                 参数一:封装要发出去的数据,
                 参数二:发送出去的数据大小(字节个数)
                 参数三:服务瑞的IP地址(找到服务端主机)
                 参数四:服务瑞程序的端口
        */
        byte[] bytes="我是快乐的客户端,我爱你abc".getBytes();
        DatagramPacket packet= new DatagramPacket(bytes,bytes.length
                        , InetAddress.getLocalHost(),7777);

        //3、开始正式发送这个数据包的数据出去了
        socket.send(packet);
        System.out.println("客户端数据发送完毕");
        socket.close();//释放资源
    }
}

Server

package com.itheima.d2_udp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

//目标:完成UDP通信快速入门-服务端开发
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动------");
        //1、创建一个服务端对象
        DatagramSocket socket = new DatagramSocket(7777);

        //2.创建一个数据包对象,用于接受数据的
        byte[] buffer=new byte[1024*64];//64KB
        DatagramPacket packet=new DatagramPacket(buffer, buffer.length);


            //3.开始正式使用数据包来接收客户端发来的数据
            socket.receive(packet);

            //4、从字节数组中,把接收到的数据直接打印出来
            //接收多少就例出多少
            //获取本次数据包接收了多少数据
            int len =packet.getLength();
            String rs = new String(buffer,0,len);
            System.out.println(rs);

            System.out.println(packet.getAddress().getHostAddress());
            System.out.println(packet.getPort());



    }
}

3.2多发多收

在这里插入图片描述
在这里插入图片描述
示例
在这里插入图片描述

Client

package com.itheima.d3_udp2;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

//目标:完成UDP多发多收
public class Client {
    public static void main(String[] args) throws Exception {
        //1、创建客户端对象(发非菜出去的人)
        DatagramSocket socket = new DatagramSocket();
        //2、创建数据包对象封装要发出去的数据
        /* public DatagramPacket(byte buf[], int offset, int length,
                 InetAddress address, int port)
                 参数一:封装要发出去的数据,
                 参数二:发送出去的数据大小(字节个数)
                 参数三:服务瑞的IP地址(找到服务端主机)
                 参数四:服务瑞程序的端口
        */
        Scanner sc=new Scanner(System.in);
        while (true) {
            System.out.println("请说");
            String msg=sc.nextLine();
            //一旦发现用户输入的exit命令,就退出客户端
            if("exit".equals(msg)){
                System.out.println("欢迎下次光临!退出成功!");
                socket.close();//释放资
                break;//跳出死循环
            }

            byte[] bytes=msg.getBytes();
            DatagramPacket packet= new DatagramPacket(bytes,bytes.length
                            , InetAddress.getLocalHost(),7777);

            //3、开始正式发送这个数据包的数据出去了
            socket.send(packet);
        }
        System.out.println("----------------------");
        System.out.println("客户端数据发送完毕");

    }
}

Server

package com.itheima.d3_udp2;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

//目标:完成UDP通信快速入门-服务端开发
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动------");
        //1、创建一个服务端对象
        DatagramSocket socket = new DatagramSocket(7777);

        //2.创建一个数据包对象,用于接受数据的
        byte[] buffer=new byte[1024*64];//64KB
        DatagramPacket packet=new DatagramPacket(buffer, buffer.length);

        //3.开始正式使用数据包来接收客户端发来的数据
        socket.receive(packet);

        while (true) {
            //3.开始正式使用数据包来接收客户端发来的数据
            socket.receive(packet);

            //4、从字节数组中,把接收到的数据直接打印出来
            //接收多少就例出多少
            //获取本次数据包接收了多少数据
            int len =packet.getLength();
            String rs = new String(buffer,0,len);
            System.out.println(rs);

            System.out.println(packet.getAddress().getHostAddress());
            System.out.println(packet.getPort());
            System.out.println("-----------------------");
        }

    }
}

4.TCP通信

4.1快速入门

TCP通信

  • 特点:面向连接、可靠通信.
  • 通信双方事先会采用"三次握手"方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端.
  • Java提供了一个java.net.Socket类来实现TCP通信.

在这里插入图片描述
TCP通信之-客户端开发

  • 客户端程序就是通过java.net包下的Socket类来实现的.
    在这里插入图片描述

在这里插入图片描述

TCP通信-服务端程序的开发

  • 服务端是通过iava.net包下的ServerSocket类来实现的

ServerSocket
在这里插入图片描述
在这里插入图片描述
示例
在这里插入图片描述
Client

package com.itheima.d4_tcp1;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

//目标:完成TCP通信快速入门-客户端开发:实现1发1收,
public class Client {
    public static void main(String[] args) throws Exception {
        //1、创建Socket对象,并同时请求与服务端程序的连接,
        Socket socket =new Socket("127.0.0.1",
        8888);

        //2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
        OutputStream os =socket.getOutputStream();

        //3、把低级的字节输出流包装成数据输出流
        DataOutputStream dos =new DataOutputStream(os);
        //4、开始写数据出去了

        dos.writeUTF("在一起,好吗?");
        dos.close();
        socket.close();//释放连接资

    }
}

Server

package com.itheima.d4_tcp1;

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

//目标:完成TCP通信快速入门-服务端开发:实现1发1收.
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功------");
        //1、创建ServerSocket的对象,同时为服务端注册端口,
        ServerSocket serverSocket =new ServerSocket(8888);
        //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
        Socket socket =serverSocket.accept();

        //3、从socket通信管道中得到一个字节输入流.
        InputStream is =socket.getInputStream();

        //4、把原始的字节输入流包技成数据输入流
        DataInputStream dis = new DataInputStream(is);

        //5、使用数据输入流读取客户端发送过来的消息
        String rs = dis.readUTF();
        System.out.println(rs);

        //其实我们也可以获取客户端的IP地址
        System.out.println(socket.getRemoteSocketAddress());
        dis.close();
        socket.close();
    }
}

4.2多发多收

在这里插入图片描述
在这里插入图片描述

Client

package com.itheima.d5_tcp2;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

//目标:完成TCP通信多发多收
public class Client {
    public static void main(String[] args) throws Exception {
        //1、创建Socket对象,并同时请求与服务端程序的连接,
        Socket socket =new Socket("127.0.0.1",
        8888);

        //2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
        OutputStream os =socket.getOutputStream();

        //3、把低级的字节输出流包装成数据输出流
        DataOutputStream dos =new DataOutputStream(os);

        Scanner sc=new Scanner(System.in);
        while(true){
            System.out.println("请说");
            String msg=sc.nextLine();
            if(msg.equals("exit")){
                System.out.println("欢迎您下次光临,退出成功");
                dos.close();
                socket.close();
                break;
            }
            //4、开始写数据出去了
            dos.writeUTF(msg);
            dos.flush();
        }


    }
}

Server

package com.itheima.d5_tcp2;

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

//目标:完成TCP通信快速入门-服务端开发:实现1发1收.
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功------");
        //1、创建ServerSocket的对象,同时为服务端注册端口,
        ServerSocket serverSocket =new ServerSocket(8888);
        //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
        Socket socket =serverSocket.accept();

        //3、从socket通信管道中得到一个字节输入流.
        InputStream is =socket.getInputStream();

        //4、把原始的字节输入流包技成数据输入流
        DataInputStream dis = new DataInputStream(is);

        while (true) {
            try {
                //5、使用数据输入流读取客户端发送过来的消息
                String rs = dis.readUTF();
                System.out.println(rs);
            } catch (Exception e) {
                System.out.println(socket.getRemoteSocketAddress()+"离线了");
                socket.close();
                dis.close();
                System.out.println("服务端关闭");
                break;
            }

            //其实我们也可以获取客户端的IP地址
            //  System.out.println(socket.getRemoteSocketAddress());

        }
    }
}

4.3TCP通信-支持与多个客户端同时通信

目前我们开发的服务端程序,是否可以支持与多个客户端同时通信?

  • 不可以的.
  • 因为服务端现在只有一个主线程,只能处理一个客户端的消息.

在这里插入图片描述
示例
Client

package com.itheima.d6_tcp3;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;


public class Client {
    public static void main(String[] args) throws Exception {
        //1、创建Socket对象,并同时请求与服务端程序的连接,
        Socket socket =new Socket("127.0.0.1",
        8888);

        //2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
        OutputStream os =socket.getOutputStream();

        //3、把低级的字节输出流包装成数据输出流
        DataOutputStream dos =new DataOutputStream(os);

        Scanner sc=new Scanner(System.in);
        while(true){
            System.out.println("请说");
            String msg=sc.nextLine();
            if(msg.equals("exit")){
                System.out.println("欢迎您下次光临,退出成功");
                dos.close();
                socket.close();
                break;
            }
            //4、开始写数据出去了
            dos.writeUTF(msg);
            dos.flush();
        }


    }
}

Server

package com.itheima.d6_tcp3;

import java.net.ServerSocket;
import java.net.Socket;


public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功------");
        //1、创建ServerSocket的对象,同时为服务端注册端口,
        ServerSocket serverSocket =new ServerSocket(8888);

        while (true){
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket =serverSocket.accept();
            System.out.println(socket.getRemoteSocketAddress()+"上线了");
            //3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }
    }
}

ServerReaderThread

package com.itheima.d6_tcp3;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ServerReaderThread extends Thread{
    private Socket socket;
   public ServerReaderThread(Socket socket){
       this.socket=socket;
   }
    @Override
    public void run(){
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true){
                try {
                    String msg =dis.readUTF();
                    System.out.println(msg);
                } catch (Exception e) {
                    System.out.println(socket.getRemoteSocketAddress()+"下线了");
                    dis.close();
                    socket.close();
                   return;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

4.4TCP通信-综合案例

4.4.1即时通信-群聊

在这里插入图片描述
示例
在这里插入图片描述
Client

package com.itheima.d7_tcp4;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

//目标:完成TCP通信快速入门-客户端开发:实现1发1收,
public class Client {
    public static void main(String[] args) throws Exception {
        //1、创建Socket对象,并同时请求与服务端程序的连接,
        Socket socket =new Socket("127.0.0.1",
        8888);

        //创建一个独立的线程,负责随机从s0ckt中接收服务端发送过来的消息.
           new ClientReaderThread(socket).start();


        //2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序.
        OutputStream os =socket.getOutputStream();

        //3、把低级的字节输出流包装成数据输出流
        DataOutputStream dos =new DataOutputStream(os);

        Scanner sc=new Scanner(System.in);
        System.out.println("请说");
        while(true){

            String msg=sc.nextLine();
            if(msg.equals("exit")){
                System.out.println("欢迎您下次光临,退出成功");
                dos.close();
                socket.close();
                break;
            }
            //4、开始写数据出去了
            dos.writeUTF(msg);
            dos.flush();
        }


    }
}

ClientReaderThread

package com.itheima.d7_tcp4;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ClientReaderThread extends Thread {
    private Socket socket;
    public ClientReaderThread(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run() {
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true){
                try {
                    String msg =dis.readUTF();
                    System.out.println(msg);
                    //把这个消息分发给全部客户端进行接收.

                } catch (Exception e) {
                    System.out.println("自己下线了"+socket.getRemoteSocketAddress());
                    dis.close();
                    socket.close();
                    return;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Server

package com.itheima.d7_tcp4;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

//
public class Server {
    public static List<Socket> onlineSockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功------");
        //1、创建ServerSocket的对象,同时为服务端注册端口,
        ServerSocket serverSocket =new ServerSocket(8888);

        while (true){
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket =serverSocket.accept();
            onlineSockets.add(socket);
            System.out.println(socket.getRemoteSocketAddress()+"上线了");
            //3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }
    }
}

ServerReaderThread

package com.itheima.d7_tcp4;

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

public class ServerReaderThread extends Thread{
    private Socket socket;
   public ServerReaderThread(Socket socket){
       this.socket=socket;
   }
    @Override
    public void run(){
        try {
            InputStream is=socket.getInputStream();
            DataInputStream dis=new DataInputStream(is);
            while (true){
                try {
                    String msg =dis.readUTF();
                    System.out.println(msg);
                    //把这个消息分发给全部客户端进行接收.
                    onsendMsgtoAll(msg);
                } catch (Exception e) {
                    System.out.println(socket.getRemoteSocketAddress()+"下线了");
                    dis.close();
                    socket.close();
                    Server.onlineSockets.remove(socket);
                   return;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void onsendMsgtoAll(String msg) throws Exception {
       //发送给全部在线的socket管道接收.
        for (Socket onlineSocket : Server.onlineSockets) {
            OutputStream os=onlineSocket.getOutputStream();
            DataOutputStream dos=new DataOutputStream(os);
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

4.4.2实现一个简易版的BS架构

任务

  • 要求从浏览器中访问服务器,并立即让服务器响应一个很简单的网页给浏览器展示,网页内容就是"黑马程序员666"

BS架构的基本原理
在这里插入图片描述

  • HTTP协议规定:响应给浏览器的数据格式必须满足如下格式
    在这里插入图片描述
    示例
    在这里插入图片描述

Server

package com.itheima.d8_tcp5;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功------");
        //1、创建ServerSocket的对象,同时为服务端注册端口,
        ServerSocket serverSocket =new ServerSocket(8888);

        while (true){
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket =serverSocket.accept();

            System.out.println(socket.getRemoteSocketAddress()+"上线了");
            //3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }
    }
}

ServerReaderThread

package com.itheima.d8_tcp5;

import com.itheima.d7_tcp4.Server;

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

public class ServerReaderThread extends Thread{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        //立即响应一个网页内容:"黑马程序员"给浏览器展示.
        try {
            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();//必须换行
            ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员666<div>");
            ps.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

在这里插入图片描述
使用线程池进行优化
在这里插入图片描述
示例
在这里插入图片描述
Server

package com.itheima.d9_tcp6;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功------");
        //1、创建ServerSocket的对象,同时为服务端注册端口,
        ServerSocket serverSocket =new ServerSocket(9999);

        //创建出一个线程池,负责处理通信管道的任务.
       ThreadPoolExecutor pool= new ThreadPoolExecutor(8*2,8*2,0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        while (true){
            //2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket =serverSocket.accept();

            System.out.println(socket.getRemoteSocketAddress()+"上线了");
            //3.把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
           pool.execute(new ServerReaderRunnable(socket));
        }
    }
}

ServerReaderRunnable

package com.itheima.d9_tcp6;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

//Thread本身就是任务类
public class ServerReaderRunnable extends Thread{
    private Socket socket;
    public ServerReaderRunnable(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
        //立即响应一个网页内容:"黑马程序员"给浏览器展示.
        try {
            OutputStream os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            ps.println();//必须换行
            ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员666<div>");
            ps.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

十三.Java高级

1.单元测试

1.1概述、Junit框架快速入门

单元测试

  • 就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试.

咱们之前是如何进行单元测试的?有啥问题?

  • 只能在main方法编写测试代码,去调用其他方法进行测试.
  • 无法实现自动化测试,一个方法测试失败,可能影响其他方法的测试.
  • 无法得到测试的报告,需要程序员自己去观察测试是否成功.

Junit单元测试框架

  • 可以用来对方法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Juit框架,比如IDEA)

优点

  • 可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立.
    -不需要程序员去分析测试的结果,会自动生成测试报告出来.
    在这里插入图片描述
    在这里插入图片描述

1.2Junit框架的常见注解

Junit单元测试框架的常用注解( Junit4.xxxx版本)
在这里插入图片描述

  • 在测试方法执行前执行的方法,常用于:初始化资源.
  • 在测试方法执行完后再执行的方法,常用于:释放资源.

Junit单元测试框架的常用注解(Junit5.Xxx版本)
在这里插入图片描述
示例
在这里插入图片描述
StringUtil

package com.itheima.d1_junit;
//字符串工具类
public class StringUtil {

    public static void printNumber(String name) {
        if(name==null){
            System.out.println("null");
            return;
        }
        System.out.println("名字长度是:" + name.length());
    }

    /**
     *求字符串的最大索引
     */
        public static int getMaxIndex(String data){
            if (data == null) {
                return -1;
            }
                return data.length()-1;
            }
}

StringUtilTest

package com.itheima.d1_junit;

import org.junit.*;

//测试类
public class StringUtilTest {
    @Before
    public void test1(){
        System.out.println("--->test1 Before执行了");
    }

    @After
    public void test2(){
        System.out.println("--->test2 After执行了");
    }

    @BeforeClass
    public static void test3(){
        System.out.println("--->test3 BeforeClass执行了");
    }

    @AfterClass
    public static void test4(){
        System.out.println("--->test4 AfterClass执行了");
    }

    @Test//测试方法
    public void testPrintNumber(){
        StringUtil.printNumber("admin");
        StringUtil.printNumber(null);
    }
    @Test //测试方法
    public void testGetMaxIndex(){
        int index1 = StringUtil.getMaxIndex(null);
        System.out.println(index1);

        int index2 = StringUtil.getMaxIndex("admin");
        System.out.println(index2);

        Assert.assertEquals("方法内部有bug!",4,index2);

    }


}

2.反射

2.1认识反射、获取类

反射(Reflection)

  • 反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等).
    在这里插入图片描述
    反射学什么?
  • 学习获取类的信息、操作它们
    1. 反射第一步:加载类,获取类的字节码:Class对象
    2. 获取类的构造器:Constructorj对象
    3. 获取类的成员变量:Field对象
    4. 获取类的成员方法:Method对象
  • 全部认识完后,再看反射的应用场景

在这里插入图片描述
获取Class对象的三种方式

  • Class c1=类名.class
  • 调用Class提供方法:public static Class forName(String package);
  • Objecti提供的方法:public Class getClass();Class c3=对象.getClass(0;

示例
在这里插入图片描述
Student

package com.itheima.d2_reflect;

public class Student {
    private String name;
    private int age;
    private char sex;
    private double height;

    public Student() {
    }

    public Student(String name, int age, char sex, double height, String hobby) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.height = height;
        this.hobby = hobby;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public String getHobby() {
        return hobby;
    }

    public void setHobby(String hobby) {
        this.hobby = hobby;
    }

    private String hobby;
}

Test1Class

package com.itheima.d2_reflect;
//目标:获取Class对象
public class Test1Class {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1= Student.class;
        System.out.println(c1.getName());//全类名 com.itheima.d2_reflect.Student
        System.out.println(c1.getSimpleName());//Student

        Class c2=Class.forName("com.itheima.d2_reflect.Student");
        //指向同一个对象
        System.out.println(c1==c2);//true

        Student s=new Student();
        Class c3=s.getClass();
        System.out.println(c3==c2);
    }
}

2.2获取类的构造器

获取类的构造器、并对其进行操作

  • Class提供了从类中获取构造器的方法.
    在这里插入图片描述
    获取类构造器的作用:依然是初始化对象返回
    在这里插入图片描述
    示例
    在这里插入图片描述
    Cat
package com.itheima.d2_reflect;

public class Cat {
    public static int a;
    public static final String COUNTRY="中国";
    public String name;
    private int age;

    public Cat() {
        System.out.println("无参数构造器执行了");
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    private void run(){
        System.out.println("w跑的贼快w~");
    }
    public void eat(){
        System.out.println("爱吃猫粮~");
    }
    private String eat(String name){
        return"猫最爱吃:"+name;
    }
    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Test2Constructor

package com.itheima.d2_reflect;

import org.junit.Test;

import java.lang.reflect.Constructor;

//目标:掌握获取类的构造器,并对其进行操作.
public class Test2Constructor {
    @Test
    public void testGetConstructors() {
        //1、反第一步:必须先得到这个类的Class对象
        Class c = Cat.class;
        //2.获取类的全部构造器,getConstructors只能获取public修饰的构造器
       // Constructor[] constructors = c.getConstructors();

        //获取全部构造器
        Constructor[] constructors1=c.getDeclaredConstructors();

        //3、遍历数组中的每个构造器对象
        for (Constructor constructor : constructors1) {
            System.out.println(constructor.getName() + "--->"
                    + constructor.getParameterCount());
        }

    }
    @Test
    public void testGetConstructor()throws Exception{
    //1、反射第一步:必须先得到这个类的Class对象
    Class c =Cat.class;
    //2、获取某个构造器:无参数构造器(public)
    //Constructor constructor =c.getConstructor();

        Constructor constructor =c.getDeclaredConstructor();

        System.out.println(constructor.getName() + "--->"
                + constructor.getParameterCount());

        constructor.setAccessible(true);//禁止检查访问权限
        Cat cat=(Cat)constructor.newInstance();
        System.out.println(cat);

        //3.获取有参数构造器
        Constructor constructor1=
                c.getDeclaredConstructor(String.class,int.class);

        System.out.println(constructor.getName() + "--->"
                + constructor1.getParameterCount());
        Cat cat1=(Cat) constructor1.newInstance("叮当猫",3);
        System.out.println(cat1);
    }
}

2.3获取类的成员变量

获取类的成员变量
在这里插入图片描述
获取到成员变量的作用:依然是赋值、取值
在这里插入图片描述

示例
在这里插入图片描述
Test3Field

package com.itheima.d2_reflect;

import org.junit.Test;

import java.lang.reflect.Field;

//目标:掌握获取类的成员变量,并对其进行操作.
public class Test3Field {
    @Test
    public  void testGetFields() throws Exception {
        //1、反射第一步:必须是先得到类的C1ass对象
        Class c = Cat.class;
        //2、获取类的全部成员变量
        Field[] fields=c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getName()+"--->"+field.getType());
        }
        //4.定位某个成员变量
        Field fName=c.getDeclaredField("name");
        System.out.println(fName.getName()+"--->"+fName.getType());
        Field fAge =c.getDeclaredField("age");
        System.out.println(fAge.getName()+"--->"+fAge.getType());

        //赋值
        Cat cat=new Cat();
        fName.setAccessible(true);
        fName.set(cat,"咖啡猫");
        System.out.println(cat);

        //取值
        String name=(String) fName.get(cat);
        System.out.println(name);
    }
}

2.4获取类的成员方法

获取类的成员方法

  • Class提供了从类中获取成员方法的API
    在这里插入图片描述
    成员方法的作用:依然是执行
    在这里插入图片描述
    示例
    在这里插入图片描述
    Test4Method
package com.itheima.d2_reflect;

import org.junit.Test;

import java.lang.reflect.Method;

//目标:掌握获取类的成员方法,并对其进行操作.
public class Test4Method {
    @Test
    public void testGetMethods() throws Exception {
       // 1、反射第一步:先得Class对象,
                Class c = Cat.class;
        //2、获取类的全部成员方法,
        Method[] methods =c.getDeclaredMethods();
        //3、遍历这个数组中的每个方法对象
        for (Method method :methods) {
            System.out.println(method.getName() + "--->"
                    + method.getParameterCount() + "--->"
                    + method.getReturnType());
        }
        //4、获取某个方法对象
        Method run=c.getDeclaredMethod("run");//拿run方法,无参数的
        System.out.println(run.getName()+"--->"
                +run.getParameterCount()+"--->"
                +run.getReturnType());

        Method eat =c.getDeclaredMethod("eat",String.class);
        System.out.println(eat.getName()+"--->"
                +eat.getParameterCount()+"---->"
                +eat.getReturnType());

        Cat cat =  new Cat();
        run.setAccessible(true);
        Object rs=run.invoke(cat);//调用无参数的rUn方法,用cat对象触发调
        System.out.println(rs);//null ,因为没有返回对象

        eat.setAccessible(true);
        String rs2=(String) eat.invoke(cat, "鱼儿");
        System.out.println(rs2);
    }
   }

2.5作用、应用场景

反射的作用?

  • 基本作用:可以得到一个类的全部成分然后操作.
    在这里插入图片描述
  • 可以破坏封装性.
  • 最重要的用途是:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能.

案例

在这里插入图片描述
在这里插入图片描述
示例
在这里插入图片描述
ObjectFrame

package com.itheima.d2_reflect;

import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;

public class ObjectFrame {
    //目标:保存任意对象的字段和其数据到文件中去
    public static void saveObject(Object obj) throws Exception {
        PrintStream ps=new PrintStream(new FileOutputStream("src\\data.txt",true));
        //0bj是任意对象,到底有多少个字段要保存.
        Class c = obj.getClass();
        String cName = c.getSimpleName();
        ps.println("---------------"+cName+"--------------");
        //2、从这个类中提取它的全部成员变量
        Field[] fields =c.getDeclaredFields();
        //3.遍历每个成员变量
        for (Field field : fields) {
            //4、拿到成员变量的名字
            String name =field.getName();
            //5、拿到这个成员变量在对象中的数据,
            field.setAccessible(true);
            String value =field.get(obj)+"";
            ps.println(name+"="+value);
        }
    }
}

Student

package com.itheima.d2_reflect;

public class Student {
    private String name;
    private int age;
    private char sex;
    private double height;

    public Student() {
    }

    public Student(String name, int age, char sex, double height, String hobby) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.height = height;
        this.hobby = hobby;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public String getHobby() {
        return hobby;
    }

    public void setHobby(String hobby) {
        this.hobby = hobby;
    }

    private String hobby;
}

Teacher

package com.itheima.d2_reflect;

public class Teacher {
    private  String name;
    private double salary;

    public Teacher(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public Teacher() {
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

Test5Frame

package com.itheima.d2_reflect;

import org.junit.Test;

//目标:使用反射技术:设计一个保存对象的简易版框架.
public class Test5Frame {
    @Test
    public void save()throws Exception{
        Student s1=new Student("黑马吴彦祖",45,'男',185.3,"蓝球,冰球");
        Teacher t1=new Teacher("播妞",999.9);
        //需求:把任意对象的字段名和其对应的值等信息,保存到文件中去.
        ObjectFrame.saveObject(s1);

        ObjectFrame.saveObject(t1);
}
}

3.注解

3.1概述、自定义注解

注解(Annotation)

  • 就是ava代码里的特殊标记,比如:@Override、@Test等,作用是:让其他程序根据注解信息来决定怎么执行该程序.
  • 注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处

自定义注解

  • 就是自己定义注解
    在这里插入图片描述
    特殊属性名:value
  • 如果注解中只有一个value属性,使用注解时,value名称可以不写!

注解的原理

  • 注解本质是一个接口,Java中所有注解都是继承了Annotation接口的.
  • @注解(…):其实就是一个实现类对象,实现了该注解以及Annotation:接口.
    在这里插入图片描述
    示例
    在这里插入图片描述
    AnnotationTest1
package com.itheima.d3_annotation;

@MyTest1(aaa="牛魔王",ccc={"HTML","Java"})
@MyTest2("孙悟空")//省略属性名称
public class AnnotationTest1 {
    @MyTest1(aaa="铁扇公主",bbb = false,ccc={"Python","前端"})
    public void  test1(){

    }
}

MyTest1

package com.itheima.d3_annotation;

public @interface MyTest1 {
    String aaa();
    boolean bbb()default true;
    String[]ccc();
}

MyTest2

package com.itheima.d3_annotation;

public @interface MyTest2 {
    String value();//只有value或者
    int age() default 23;//其他属性有默认值
}

3.2元注解

元注解

  • 指的是:修饰注解的注解.
    在这里插入图片描述
    在这里插入图片描述
    示例
    在这里插入图片描述
    AnnotationTest2
package com.itheima.d3_annotation;
//目标:认识元注解,搞清楚元注解的作用.
@Mytest3
public class AnnotationTest2 {
   // @Mytest3  规定不能使用成员变量上
    private String name;

    @Mytest3
    public void test(){
    }

}

Mytest3

package com.itheima.d3_annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})//当前被修饰的注解只能用在类上.
@Retention(RetentionPolicy.RUNTIME)
public @interface Mytest3 {
}

3.3注解的解析

什么是注解的解析?

  • 就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来.
    如何解析注解?
  • 指导思想:要解析谁上面的注解,就应该先拿到谁.
  • 比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解.
  • 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解.
  • Class、Method、Field,Constructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力.


在这里插入图片描述
示例

在这里插入图片描述
Demo

package com.itheima.d3_annotation;
@MyTest4(value="蜘蛛精",aaa=99.5,bbb={"至尊宝","黑马"})

public class Demo {
    @MyTest4(value="孙悟空",aaa=199.9,bbb={"紫霞","牛夫人"})
    public void test1(){
    }
}

AnnotationTest3

package com.itheima.d3_annotation;

import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

//目标:掌握注解的解析.
public class AnnotationTest3 {
    @Test
    public void parseClass() {
        //1.先得到Class对象
        Class c = Demo.class;

        //2、解析类上的注解
        //判断类上是否包含了某个注解
        if (c.isAnnotationPresent(MyTest4.class)) {
            MyTest4 myTest4 = (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(Arrays.toString(myTest4.bbb()));
        }

    }
    @Test
    public void parseMethod() throws Exception {
        //1.先得到Class对象
        Class c= Demo.class;
        Method m=c.getDeclaredMethod("test1");
        //2、解析方法上的注解

        //判断方法上是否包含了某个注解
        if(c.isAnnotationPresent(MyTest4.class)){
            MyTest4 myTest4=(MyTest4) m.getDeclaredAnnotation(MyTest4.class);
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(Arrays.toString(myTest4.bbb()));
        }
    }
}

MyTest4

package com.itheima.d3_annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
    String value();
    double aaa()default 100;
    String[]bbb();
}

3.4应用场景

在这里插入图片描述
示例
在这里插入图片描述
MyTest

package com.itheima.d3_annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)

public @interface MyTest {
}

AnnotationTest4

package com.itheima.d3_annotation;

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

//目标:模拟Junit框架的设计.
public class AnnotationTest4 {
    //@MyTest
    public void test1() {
        System.out.println("===test1====");
    }

    @MyTest
    public void test2() {
        System.out.println("===test2====");
    }

   //ddddddddddddddddddd @MyTest
    public void test3() {
        System.out.println("===test3====");
    }

    @MyTest
    public void test4() {
        System.out.println("===test4====");
    }
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        AnnotationTest4 a =new AnnotationTest4();
        //启动程序
        //1.得到Class对象
        Class c= AnnotationTest4.class;

        //2.提取这个类中的全部成员方法
        Method[] methods=c.getDeclaredMethods();
        //3.遍历所有方法,有MyTest注解就触发执行
        for (Method method:methods){
            if(method.isAnnotationPresent(MyTest.class)){
                //说明当前方法上是存在@MyTest,触发当前方法执行.
                method.invoke(a);
            }
        }
    }
}

4.动态代理

4.1程序为什么需要代理?代理长什么样?

程序为什么需要代理?代理长什么样?

  • 对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责.
  • 对象有什么方法想被代理,代理就一定要有对应的方法

如何为ava对象创建一个代理对象?

  • java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
    在这里插入图片描述
    示例
    在这里插入图片描述
    BigStar
package com.itheima.d4_proxy;

public class BigStar implements Star {
    private String name;

    public BigStar(String name) {
        this.name = name;
    }

    public String sing(String name){
        System.out.println(this.name+"正在唱:"+name);
        return"谢谢!谢谢!";
    }

    public void dance(){
        System.out.println(this.name+"正在优美的跳舞~~");
    }
}

ProxyUtil

package com.itheima.d4_proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyUtil {

    public  static Star createProxy(BigStar bigStar){
       /* newProxyInstance(ClassLoader loader,
                Class<?>[] interfaces,
                InvocationHandler h)
                参数1:用于指定一个类加载器
                参数2:指定生成的代理长什么样子,也就是有哪些方法
                参数3:用来指定生成的代理对象要干什么事情
                */
        Star starProxy =(Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{Star.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //代理对象要做的事情,会在这里写代码
                        if(method.getName().equals("sing")){
                            System.out.println("准备话筒,收钱20万");
                            //return method.invoke(bigStar,args);
                        }else if(method.getName().equals("dance")){
                            System.out.println("准备场地,收钱1000万");
                           // return method.invoke(bigStar,args);
                        }
                            return method.invoke(bigStar,args);


                    }
                });
        return starProxy;
    }
}

Star

package com.itheima.d4_proxy;

public interface Star {
    String sing(String name);
    void  dance();
}

Test

package com.itheima.d4_proxy;

public class Test {
    public static void main(String[] args) {
        BigStar s=new BigStar("杨超越");
        Star starProxy =ProxyUtil.createProxy(s);

        String rs=starProxy.sing("好日子");
        System.out.println(rs);

        starProxy.dance();
    }
}

4.2解决实际问题、掌握使用代理的好处

在这里插入图片描述
示例
在这里插入图片描述
ProxyUtil

package com.itheima.d5_proxy2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyUtil {
    public static UserService createProxy(UserService userService){
        UserService userServiceProxy =(UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{UserService.class},new InvocationHandler(){
                    @Override
                    public Object invoke(Object proxy, Method method, Object[]args)throws Throwable {

                        if (method.getName().equals("Login") || method.getName().equals("deleteUsers") ||
                                method.getName().equals("selectUsers")) {
                            long startTime = System.currentTimeMillis();

                            Object rs = method.invoke(userService, args);

                            long endTime = System.currentTimeMillis();
                            System.out.println(method.getName() + "方法执行耗时:" + (endTime - startTime) / 1000.0 + "s");
                            return rs;
                        } else {
                            Object rs = method.invoke(userService, args);
                            return rs;
                        }
                    }
                });
        return userServiceProxy;
        }
    }

Test

package com.itheima.d5_proxy2;

import java.util.Arrays;

//目标:使用动态代理解决实际问题,并掌挥使用代理的好处.
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建用户业务对象.
        UserService userService = ProxyUtil.createProxy(new UserServiceImpl());

        //2、调用用户业务的功能.
        userService.login("admin","123456");
        System.out.println("---------------------------------");
        userService.deleteUsers();
        System.out.println("---------------------------------");
        String[] names =userService.selectUsers();
        System.out.println("查询到的用户是:"+ Arrays.toString(names));
        System.out.println("----------------------------------");
    }
}

UserService

package com.itheima.d5_proxy2;

//用户业务接口

public interface UserService{
    //登录功能
    void login(String loginName,String passWord)throws Exception;
    //删除用户
    void deleteUsers()throws Exception;
    //查询用户,返回数组的形式,
    String[]selectUsers()throws Exception;
}

UserServiceImpl

package com.itheima.d5_proxy2;

//用户业务实观类(面向接口编程)
public class UserServiceImpl implements UserService {
    @Override
    public void login(String loginName, String passWord) throws Exception {

        if ("admin".equals(loginName) && "123456".equals(passWord)) {
            System.out.println("您登录成功,欢迎光临本系统~");
        } else {
            System.out.println("您登录失败,用户名或密码错误~");
        }
        Thread.sleep(1000);

    }

    @Override
    public void deleteUsers() throws Exception {
        System.out.println("成功删除了1万个用户~");
        Thread.sleep(1500);

    }

    @Override
    public String[] selectUsers() throws Exception {

        System.out.println("查询出了3个用户");
        String[] names = {"张全蛋", "李二狗", "牛爱花"};
        Thread.sleep(500);
        return names;

    }
}

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值