天天看点

Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

Java三大版本

  • JavaSE 标准版 (桌面程序,控制台开发… )
  • JavaME 嵌入式开发 (手机,小家电… )
  • JavaEE E企业级开发 (web端,服务器开发… )

JDK , JRE , JVM

  • JDK: java Development Kit (Java 开发者工具 , JDK 中包含 JRE)
  • JRE: java Runtime Environment (Java运行环境)
  • JVM: java Virtual Machine (Java虚拟机)

HelloWorld详解

  1. 随便新建一个文件夹,存放代码
  2. 新建一个java文件
    • 文件后缀名为 .java
    • HelloWorld.java
    • [注意点] 系统可能没有显示文件后缀名,我们需要手动打开
  3. 编写代码
    public class HelloWorld{
    	public static void main(String[] args){
    		System.out.print("Hello,World!");
    	}
    }
               
  4. 编译 javac java文件,会生成一个class文件
  5. 运行class 文件, java class文件 (运行class文件不用加class后缀)
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

Java八大基础数据类型

数据类型
  • 强类型语言
    • 要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用
  • Java的数据类型分为两大类
    • 基本类型 (primitive type)
    • 引用类型 (reference type)
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO
  1. 整型(byte、short、int、long)

    虽然byte、short、int、long 数据类型都是表示整数的,但是它们的取值范围可不一样。

    byte 的取值范围:-128~127(-2的7次方到2的7次方-1)

    short 的取值范围:-32768~32767(-2的15次方到2的15次方-1)

    int 的取值范围:-2147483648~2147483647(-2的31次方到2的31次方-1)

    long 的取值范围:-9223372036854774808~9223372036854774807(-2的63次方到2的63次方-1)

    由上可以看出 byte、short 的取值范围比较小,而long的取值范围时最大的,所以占用的空间也是最多的。int 取值范围基本上可以满足我们的日常计算需求了,所以 int 也是我们使用的最多的一个整型类型。

  2. 浮点型(float、double)

    float 和 double 都是表示浮点型的数据类型,它们之间的区别在于精确度的不同。

    float(单精度浮点型)取值范围:3.402823e+38~1.401298e-45(e+38 表示乘以10的38次方,而e-45 表示乘以10的负45次方)

    double(双精度浮点型)取值范围:1.797693e+308~4.9000000e-324(同上)

    double 类型比float 类型存储范围更大,精度更高。

    通常的浮点型数据在不声明的情况下都是double型的,如果要表示一个数据时float 型的,可以在数据后面加上 “F” 。

    浮点型的数据是不能完全精确的,有时候在计算时可能出现小数点最后几位出现浮动,这时正常的。

  3. 字符型(char)

    char 有以下的初始化方式:

    char ch = ‘a’; // 可以是汉字,因为是Unicode编码

    char ch = 1010; // 可以是十进制数、八进制数、十六进制数等等。

    char ch = ‘\0’; // 可以用字符编码来初始化,如:’\0’ 表示结束符,它的ascll码是0,这句话的意思和 ch = 0 是一个意思。

    Java是用unicode 来表示字符,“中” 这个中文字符的unicode 就是两个字节。

    String.getBytes(encoding) 方法获取的是指定编码的byte数组表示。

    通常gbk / gb2312 是两个字节,utf-8 是3个字节。

    如果不指定encoding 则获取系统默认encoding 。

  4. boolean 没有什么好说的,它的取值就两个:true 、false 。

类型转换

  • 由于Java是强类型语言,所以要进行有些运算的时候,需要用到类型转换

    低 -------------------------------------------------------> 高

    byte,short,char --> int --> long --> float --> double

  • 运算中,不同类型的数据先转换为同一类型,然后进行运算
  • 强制类型转换
  • 自动类型转换
package com.byxx.yunan;

import java.util.HashMap;
import java.util.concurrent.*;

/**
 * @Author yww
 * @CreateTime 2020-10-25
 */
public class Test {
    public static void main(String[] args){

        int i = 128;
        byte b = (byte) i; //强制转换内存溢出, Byte最大值为127 ,最小值为-128

        //输出值 为 -128
        System.out.println(b);

        //自动转换
        double d = i;
        System.out.println(d);

        /*
          注意点:
            1. 不能对布尔值进行转换
            2. 不能把对象类型转换为不相干的类型
            3. 在把高容量转换到低容量时,需要强制转换,反正,自动转换
            4. 转换的时候可能存在内存溢出,或者精度问题!
         */

        System.out.println((int)23.7);//23
        System.out.println((int)-45.89f);//-45
    }
}

           
注意 操作比较大的数的时候,内存溢出问题
package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test2 {
    public static void main(String[] args) {
        //操作比较大的数的时候,注意溢出问题
        //JDK7新特性,数字直接可以用下划线分割
        int money = 10_0000_0000;
        int years = 20;
        int total = money * years;//-1474836480 , 内存溢出

        long total2 = money * years;//-1474836480 , 先是两个int类型运算 得出结果还是int类型 (结果已经出问题了,再转long也是不行)

        long total3 = money * ((long)years);//20000000000 , 先把一个数转为long
        
        System.out.println(total3);
    }
}
           

变量 ,常量 , 作用域

  • 默认初始化值:
    • 数字: 0 0.0
    • char: \u0000
    • boolean: false
    • 引用:null
package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test3 {

    //静态常量
    static final String str = "1";

    //类变量,静态变量
    static String msg = "123";

    //实例变量: 从属于对象,如果不自行初始化值, 则拿 类型的默认值
    //int 0,以此类推,除了基本类型,其余的默认都是 null ,布尔值为false
    String name;
    int age;

    public static void main(String[] args) {
        //局部变量
        int i = 1;

        //new 对象
        Test3 test = new Test3();
        System.out.println(test.name+"\n"+test.age);
    }
}
           

运算符

  • 算术运算符:+,-,*,/,%,++,–
  • 赋值运算符:=
  • 关系运算符:>,<,>=,<=,==,!=,instanceof
  • 逻辑运算符:&&,||,!
  • 位运算符:&,|,^,~,>>,>>>
  • 条件运算符:?,:
  • 扩展赋值运算符:+=,-=,*=,/=

算术运算符

package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test4 {
    public static void main(String[] args) {
        //++  --   自增,自减    一元运算符
        int a = 3;

        int b = a++; //先给b赋值,再自增

        int c = ++a; //先自增,再赋值

        System.out.println(a);// 5
        System.out.println(b);// 3
        System.out.println(c);// 5

        //幂运算  2^3  2*2*2 = 8
        double pow = Math.pow(2,3);//java中的幂运算
        System.out.println(pow);
    }
}
           

逻辑运算符

package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test5 {
    public static void main(String[] args) {
        //& 与(and) , | 或(or) , ! 非(取反)
        boolean a = true;
        boolean b = false;

        System.out.println(a&&b);//false    逻辑与运算,两个变量都为true,结果才为true
        System.out.println(a||b);//true     逻辑或运算,两个变量有一个为true,结果才为true
        System.out.println(!(a&&b));//true  如果是真,则为假  如果是假,则为真

        //短路运算
        System.out.println(b&&a);//第一位数为false则不执行后面操作,输出结果为false

        //验证: (短路运算)
        int i = 5;
        // i>6 = false  &&  i++>7
        System.out.println((i>6) && (i++)>7);
        //根据 算术运算符定律  i++ 如果执行了,则i=6
        System.out.println(i);//输出结果为5,验证了短路运算,前者为false,后者不执行
    }
}
           

位运算符

package com.byxx.yunan.test;

import javax.sound.midi.Soundbank;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test6 {
    public static void main(String[] args) {
        /*
            A = 0011 1100
            B = 0000 1101
            -----------------
            A&B = 0000 1100     位运算(与运算),同级位,两个值相等则为相等值,否则为0
            A|B = 0011 1101     位运算(或运算),同级位,
            A^B = 0011 0001     位运算(异或运算),同级位,两个值相等则为0,不相等则为1
             ~B = 1111 0010     (取反运算),0为1,1为0
         */

        //左移运算 (第一个数值 乘 2的第二个数值次方)
        System.out.println(3*2);
        System.out.println(3<<1);//3 * 2的一次方(3乘2的一次方)   3 * 2 = 6
        System.out.println(3<<2);//3 * 2的二次方(3乘2的二次方)   3 * (2*2) = 12
        System.out.println(3<<3);//3 * 2的三次方(3乘2的三次方)   3 * (2*2*2) = 24

        //右移运算 (第一个数值 除 2的第二个数值次方)
        System.out.println("-------------------------");
        System.out.println(4>>1);//4 / 2的一次方(4除2的一次方)   4 / 2 = 2
        System.out.println(4>>2);//4 / 2的二次方(4除2的二次方)   4 / (2*2) = 1
        System.out.println(4>>3);//4 / 2的三次方(4除3的三次方)   4 / (2*2*2) = -4

    }
}

           

条件运算符 (三元运算符)

package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test8 {
    public static void main(String[] args) {

        int age = 22;
        //普通 三元运算符
        String type1 = age>18 ? "成年" : "未成年"; //输出结果为 成年

        //嵌套 三元运算符
        String type2 = age>30 ? "成年" : (age==22) ? "我22岁啦" : "未成年";//输出结果为 我22岁啦
    }
}
           

扩展赋值运算符

package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test7 {
    public static void main(String[] args) {
        int a = 10;
        int b = 2;

        a+=b;//a = a + b;
        a-=b;//a = a - b;
        a*=b;//a = a * b;
        a/=b;//a = a / b;

        //字符串连接符  + , String
        System.out.println(""+a+b);//字符串在前,则输出字符串连接 结果为 102,要想结果正确加括号即可  ""+(a+b)

        System.out.println(a+b+"");//从左开始运算,a+b= 12 加字符串 = 12
    }
}
           

JavaDoc生成文档

  • cmd指令 javadoc -encoding UTF-8 -charset UTF-8 Test9.java
    需要文档注释
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO
  • 生成JavaAPI文档

Switch 多选择结构

  • switch case 语句判断一个变量与系列值中某个值是否相等,每个值称为一个分支
  • switch 语句中的变量类型可以是:
    • byte, short, int 或者char
    • 从Java SE7 开始 switch支持字符串String 类型了
    • 同时 case 标签必须为字符串常量或字面量
package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test9 {

    public static void main(String[] args) {
        byte a = 0;
        short b = 1;
        int c = 2;
        char d = 'A';
        String str = "高达";
        //JDK 7 ,表达式结果可以是字符串!
        //字符的本质还是数字

        switch (str){
            case "张三":
                System.out.println("张三");
                break;//没有写break会出现switch穿透
            case "李四":
                System.out.println("李四");
                break;
            case "高达":
                System.out.println("这里是高达!");
                break;
            default:
                System.out.println("what are you? 弄啥嘞?");
        }
    }
}

           

验证字符本质还是数字 (查看编译后源码)

Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO
编译后class文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.byxx.yunan.test;

public class Test9 {
    public Test9() {
    }

    public static void main(String[] args) {
        byte a = false;
        short b = true;
        int c = true;
        char d = true;
        String str = "高达";
        byte var7 = -1;
        switch(str.hashCode()) {
        case 774889:
            if (str.equals("张三")) {
                var7 = 0;
            }
            break;
        case 842061:
            if (str.equals("李四")) {
                var7 = 1;
            }
            break;
        case 1265638:
            if (str.equals("高达")) {
                var7 = 2;
            }
        }

        switch(var7) {
        case 0:
            System.out.println("张三");
            break;
        case 1:
            System.out.println("李四");
            break;
        case 2:
            System.out.println("这里是高达!");
            break;
        default:
            System.out.println("what are you? 弄啥嘞?");
        }

    }
}

           

循环结构

  • while 循环
  • do … while 循环
  • for 循环
  • 增强 for 循环
    • while 循环
      • 只要布尔表达式为true,循环就会一直执行下去,false则退出
      int a = 0;
      while (a<10){
          a++;
          System.out.println(a);
      }
                 
    • do … while 循环
      • do … while 和 while 循环相似,不同的是,do … while 循环至少会执行一次。
      • while 和 do … while 的区别:
        • while 先判断,后执行。do … while 先执行,后判断!
        • do … while 总是保证循环体至少会被执行一次!这是他们的主要差别。
      int a = 0;
      do{
          a++;
          System.out.println(a);
      }while(a<10);
                 
    • for 循环
      • for 循环执行的次数是在执行前就确定的。语法格式如下:
        for(初始化; 布尔表达式; 更新){
        	//代码语句
        }    
                   
      • for 循环 的 死循环写法
        for(;;){
            
        }
                   
      • 打印99乘法表
        package com.byxx.yunan.test;
        
        /**
         * @Author yww
         * @CreateTime 2021-02-03
         */
        public class Test10 {
        
            public static void main(String[] args) {
                /*
                    打印99乘法表
                    1*1=1
                    2*1=2	2*2=4
                    3*1=3	3*2=6	3*3=9
                    4*1=4	4*2=8	4*3=12	4*4=16
                    5*1=5	5*2=10	5*3=15	5*4=20	5*5=25
                    6*1=6	6*2=12	6*3=18	6*4=24	6*5=30	6*6=36
                    7*1=7	7*2=14	7*3=21	7*4=28	7*5=35	7*6=42	7*7=49
                    8*1=8	8*2=16	8*3=24	8*4=32	8*5=40	8*6=48	8*7=56	8*8=64
                    9*1=9	9*2=18	9*3=27	9*4=36	9*5=45	9*6=54	9*7=63	9*8=72	9*9=81
        
                    步骤解析:
                        1. 我们先打印第一列
                        2. 去掉重复项 b <= a
                        3. 调整样式
                 */
                for(int a=1;a<=9;a++){
                    for(int b=1;b<=a;b++){
                        System.out.print(a+"*"+b+"="+(a*b)+"\t");
                    }
                    System.out.println();
                }
        
            }
        }
                   
    • 增强 for 循环
      • 主要用于遍历数组和集合的增强型 for 循环
      int[] numbers = {10,20,30,40,50};
      
      for(int x: numbers){
          System.out.println(x);
      }
                 

Break ,Continue

  • break 在任何循环语句的主体部分, 均可用break控制循环的流程。break用于强制退出循环,不执行循环中剩余的语句。(break语句也可以在switch中使用)
  • continue 语句用在循环语句体中,用于终止某次循环过程,即跳出循环体中尚未执行的语句,接着进行下一次是否执行循环的判定。

方法重载

  • 方法名必须相同
  • 参数列表必须不同
  • 返回值一致或者相同 (返回值不能决定方法是不是重载)
//重载
public String appendMsg(String str1,int a){
    return str1+a;
}

//重载
public Integer appendMsg(int a,int b){
    return a+b;
}

//原型
public String appendMsg(String str1,String str2){
    return str1+str2;
}
           

可变参数

  • 在方法声明中,在指定参数类型后加一个省略号(…)
  • 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-03
 */
public class Test12 {
    public static void main(String[] args) {
        Test12 t1 = new Test12();
        t1.getName("1","2","3");//123
        t1.getName("1","4");//14
    }

    //可变参数
    public String getName(String... names){
        StringBuffer sb = new StringBuffer();
        for(String str : names){
            sb.append(str);
        }
        return sb.toString();
    }
}
           

Math 数学工具类

  • 平常基本方法:
    • Math.abs(); 获取绝对值
    • Math.ceil(); 向上取整
    • Math.floor(); 向下取整
    • Math.round(); 四舍五入
package com.byxx.yunan.test.arrays;

/**
 *
 * @Author yww
 * @CreateTime 2021-02-07
 */
public class Test7 {
    public static void main(String[] args) {
        /*
           Math 数学工具类, 平常基本方法使用
            public static double abs(double num):获取绝对值。有多种重载
            public static double ceil(double num):向上取整
            public static double floor(double num):向下取整
            public static long round(double num):四舍五入
         */

        //获取绝对值
        System.out.println(Math.abs(3.14));//3.14
        System.out.println(Math.abs(0));//0
        System.out.println(Math.abs(-3.14));//3.14

        //向上取整
        System.out.println(Math.ceil(3.9));//4.0
        System.out.println(Math.ceil(3.0));//3.0
        System.out.println(Math.ceil(3.1));//4.0

        //向下取整
        System.out.println(Math.floor(3.9));//3.0
        System.out.println(Math.floor(3.1));//3.0
        System.out.println(Math.floor(3.0));//3.0

        //四舍五入
        System.out.println(Math.round(3.5));//4
        System.out.println(Math.round(3.4));//3
        System.out.println(Math.round(3.6));//4

    }
}
           

Random 随机数工具类

package com.byxx.yunan.test.arrays;

import java.util.Random;

/**
 * @Author yww
 * @CreateTime 2021-02-07
 */
public class Test8 {
    public static void main(String[] args) {
        //创建随机数
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            int num = random.nextInt(5);// 范围是 0-5, 实际取值范围是 0-4
//            System.out.println(num);
        }

        //需求: 随机数实际取值 从1开始

        for (int i = 0; i < 100; i++) {
            int num = random.nextInt(9)+1;// 范围是 (0-9)+1, 原范围是0-8, +1后实际取值范围是 1-9
            System.out.println(num);
        }

    }
}
           

数组

数组详解

  • 必须声明数组变量,才能在程序中使用数组,使用new操作符来创建数组
    int[] arrays1 = new int[3];//首选定义方式
    int arrays3[] = new int[3];//效果相同,但不是首选
    int[] arrays2 = {1,2,3};
               
  • 数组的元素是通过索引访问的,数组索引从0开始
  • 获取数组长度:
    arrays.length
               
  • 数组的四个基本特点
    • 其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
    • 其元素必须是相同类型,不允许出现混合类型。
    • 数组中的元素可以是任何数据类型,包括基本类型和引用类型。
    • 数组变量属于引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。
获取数组中的最大值
package com.byxx.yunan.test;

/**
 * @Author yww
 * @CreateTime 2021-02-04
 */
public class Test {

    public static void main(String[] args) {
        //查询数组中最大值
        int[] arrays = {1,5,2,8,3};

        int max = 0;
        for(int a: arrays){
            if(a>max){
                max = a;
            }
        }
        System.out.println(max);//8
    }
}
           
反转数组
  1. 第一种方法
package com.byxx.yunan.test.arrays;

import javax.sound.midi.Soundbank;
import java.util.Arrays;

/**
 * @Author yww
 * @CreateTime 2021-02-04
 */
public class Test1 {
    public static void main(String[] args) {
        //反转数组
        int[] arrays = {1,2,3,4,5};

        int[] newArrays = new int[arrays.length];

        int initLength = arrays.length-1;

        //第一种方法
        int index = 0;
        for(int a=initLength; a>=0; a--){
            newArrays[index] = arrays[a];
            index++;
        }
        System.out.println(Arrays.toString(newArrays));
        
    }
}
           
  1. 第二种方法
package com.byxx.yunan.test.arrays;

import javax.sound.midi.Soundbank;
import java.util.Arrays;

/**
 * @Author yww
 * @CreateTime 2021-02-04
 */
public class Test1 {
    public static void main(String[] args) {
        //反转数组
        int[] arrays = {1,2,3,4,5};

        int[] newArrays = new int[arrays.length];

        int initLength = arrays.length-1;

        //第二种方法
        for(int a = 0,b = initLength; a <=initLength; a++,b--){
            newArrays[a] = arrays[b];
        }
        System.out.println(Arrays.toString(newArrays));

    }
}

           

多维数组

  • 多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组。
  • 二维数组
    package com.byxx.yunan.test.arrays;
    
    /**
     * @Author yww
     * @CreateTime 2021-02-04
     */
    public class Test2 {
        public static void main(String[] args) {
            //创建二维数组
            //int[][] a = new int[2][5];
    
            int[][] a = {{1,2},{3,4},{5,6}};
    
            for(int i=0;i<a.length;i++){
                for (int j=0;j<a[i].length;j++){
                    System.out.print(a[i][j]+"\t");
                }
                System.out.println();
            }
            /*
            输出结果:
                1	2
                3	4
                5	6
             */
        }
    }
               
  • 二维数组结构解析
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

Arrays 工具类

  • sort 用法 (升序,降序)
    Integer[] arrays = {5,2,4,3,8};
    String[] strs = {"aaa","bbb","ccC","eee","ddd"};  //字母也可以,默认按26个英文字母升序排列
    //升序 排序
    Arrays.sort(arrays);
    System.out.println(Arrays.toString(arrays));//[2, 3, 4, 5, 8]
    
    //降序排序
    Arrays.sort(arrays,Collections.reverseOrder());
    System.out.println(Arrays.toString(arrays));//[8, 5, 4, 3, 2]
               
  • 冒泡排序
    package com.byxx.yunan.test.arrays;
    
    import java.util.Arrays;
    
    /**
     * @Author yww
     * @CreateTime 2021-02-04
     */
    public class Test4 {
    
        public static void main(String[] args) {
            /*
             冒泡排序
                1.比较数组中,两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置
                2.每一次比较,都会产生一个最大,或者最小的数字
                3.下一轮则可以少一次排序
                4.一次循环,直到结束
             */
    
            int[] arrays = {1,3,2,1,7,4,8};
            //临时变量
            int temp = 0;
            boolean flag = false;//是否交换的标志
    
            for (int i = 0; i < arrays.length-1; i++) {
                // 每次遍历标志位都要先置为false,才能判断后面的元素是否发生了交换
                flag = false;
                for (int j = 0; j < arrays.length-1-i; j++) {
                    if(arrays[j+1] > arrays[j]){ // > 大于为 降序  , < 小于 为升序
                        temp = arrays[j];
                        arrays[j] = arrays[j+1];
                        arrays[j+1] = temp;
                        flag = true;
                    }
                }
                // 判断标志位是否为false,如果为false,说明后面的元素已经有序,就直接return
                if(!flag){
                    break;
                }
            }
    
            System.out.println(Arrays.toString(arrays));//[8, 7, 4, 3, 2, 1, 1]
    
        }
    }
               

Object ,Objects

  • Object.equals() Object为null,会报空指针异常
  • Objects.equals(a,b) a为null,不会出现空指针异常
    package com.byxx.yunan.test.oop;
    
    import java.util.Objects;
    
    /**
     * @Author yww
     * @CreateTime 2021-02-07
     */
    public class Test1 {
        public static void main(String[] args) {
    
            String str = null;
            //普通equals() 不允许第一个值null,否则报错
            boolean flagA = str.equals("123");
            System.out.println(flagA);//报错,空指针异常
    
            //Objects.equals(a,b); 不会报错, 允许第一个值为null,防止空指针
            boolean flagB = Objects.equals(str, "123");
            System.out.println(flagB);
    
        }
    }
               

日期

Date

  • Date date = new Date(); 无参构造,获取当前系统日期时间
  • Date date = new Date(long date); 有参构造,传递毫秒数,把毫秒转换为Date日期
  • date.getTime(); 把日期转换为毫秒(相当于System.currentTimeMillis()) ,

    ​ 返回 1970年 1月 1日 00:00:00 GMT 以来此表示 Date 对象表示的毫秒数

package com.byxx.yunan.test.oop;

import com.sun.org.apache.bcel.internal.generic.NEW;

import java.util.Date;

/**
 * @Author yww
 * @CreateTime 2021-02-07
 */
public class Test2 {
    public static void main(String[] args) {
        demo1();
        demo2();
        demo3();
    }


    /*
        Date 的无参构造方法
            new Date(); 获取当前系统日期时间
     */
    private static void demo1(){
        Date date = new Date();
        System.out.println(date);//Sun Feb 07 15:30:24 CST 2021
    }

    /*
        Date 的有参构造方法
            new Date(long date); 传递毫秒值,把毫秒转换为Date日期
    */
    private static void demo2(){
        Date date = new Date(0L);//初始日期时间
        System.out.println(date);//Thu Jan 01 08:00:00 CST 1970
    }

    /*
        Date 类的成员方法
            long getTime() 把日期转换为毫秒(相当于System.currentTimeMillis())
                返回 1970年 1月 1日 00:00:00 GMT 以来此表示 Date 对象表示的毫秒数
     */
    private static void demo3(){
        Date date = new Date();
        long time = date.getTime();
        System.out.println(time);
    }

}
           

SimpleDateFormat

  1. SimpleDateFormat 常用方法
    • format(); 将日期转为字符串格式
    • parse(); 将字符串转为日期格式
    package com.byxx.yunan.test.oop;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * java.text.SimpleDateFormat:是日期/时间格式化子类的抽象类
     * 作用:
     *      格式化(日期--转--文本) (文本--转--日期)
     * 成员方法:
     *      String format(Date date) 按照指定的模式,把Date日期,格式化为符合模式的字符串
     *      Date parse(String source) 把符合模式的字符串,解析为Date日期
     * @Author yww
     * @CreateTime 2021-02-08
     */
    public class Test3 {
        public static void main(String[] args) throws ParseException {
    
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm");
    
            //日期转字符串   format()
            Date date = new Date();
            String timeStr = sdf.format(date);
            System.out.println(timeStr);//2021-02-08 09:55:06
    
            //字符串转Date  parse()
            String nowDate = "2021-02-08 09:28:06";
            Date timeDate = sdf.parse(nowDate);
            System.out.println(timeDate);//Mon Feb 08 09:06:28 CST 2021
    
        }
    }
               

Calendar 日历类

  • Calendar类的成员方法
    • public int get (int field); 返回该日历字段的值
    • public void set (int field,int value); 将给定的日历字段设置为定值
    • public abstract void add (int field,int amount); 根据日历的规则,为给定的日历字段添加或减去指定的时间量
    • public Date getTime (); 返回一个表示此Calendar的值 (从历元到现在的毫秒偏移量) 的Date对象
  • 成员方法的参数
    • int field; 日历类的字段,可以使用Calendar类的静态成员变量获取
    • public static final int YEAR = 1; 年
    • public static final int MONTH = 2; 月 西方的0-11,东方的1-12,所以结果+1
    • public static final int DATE = 5; 月中的某一天
    • public static final int DAY_OF_MONTH = 5;月中的某一天
    • public static final int HOUR = 10; 时
    • public static final int MINUTE = 12; 分
    • public static final int SECOND = 13; 秒
package com.byxx.yunan.test.oop;

import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.util.Calendar;
import java.util.Date;

/**
 * Calendar类的成员方法:
 *      public int get(int field); 返回给日历字段的值。
 *      public void set(int field,int value); 将给定的日历字段设置为定值
 *      public abstract void add(int field,int amount); 根据日历的规则,为给定的日历字段添加或减去指定的时间量
 *      public Date getTime(); 返回一个表示此Calendar的值 (从历元到现在的毫秒偏移量)的Date对象
 *
 * 成员方法的参数:
 *      int field:日历类的字段,可以使用Calendar类的静态成员变量获取
 *      public static final int YEAR = 1; 年
 *      public static final int MONTH = 2; 月 //西方的0-11, 东方的1-12, 所以结果+1
 *      public static final int DATE = 5; 月中的某一天
 *      public static final int DAY_OF_MONTH = 5; 月中的某一天
 *      public static final int HOUR = 10; 时
 *      public static final int MINUTE = 12; 分
 *      public static final int SECOND = 13; 秒
 */
public class Test4 {
    public static void main(String[] args) {
        //日历类
        Calendar c = Calendar.getInstance();

        //没有打印内存地址,代表重写了toString()方法
        //java.util.GregorianCalendar[time=1612747829597,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2021,MONTH=1,WEEK_OF_YEAR=7,WEEK_OF_MONTH=2,DAY_OF_MONTH=8,DAY_OF_YEAR=39,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=9,HOUR_OF_DAY=9,MINUTE=30,SECOND=29,MILLISECOND=597,ZONE_OFFSET=28800000,DST_OFFSET=0]
        System.out.println(c);

        int year = c.get(Calendar.YEAR);
        int month = c.get(Calendar.MONTH)+1;//西方的0-11, 东方的1-12, 所以结果+1
        int date = c.get(Calendar.DATE);
        System.out.println(year+"年 "+month+"月 "+date+"号");

        //给指定日历字段赋值  calendar.get(int field);
        c.set(Calendar.YEAR,2088);
        System.out.println(c.get(Calendar.YEAR));// 输出: 2088

        //根据日历的规则,为给定的日历字段添加或减去指定的时间量  calendar.add(int field);
        c.add(Calendar.YEAR,-2);//减两年
        System.out.println(c.get(Calendar.YEAR));// 输出:在2088年上减两年   2086

        c.add(Calendar.MONTH,3);//加三月
        System.out.println(c.get(Calendar.MONTH)+1);// 输出:5

    }
}
           

面向对象

面向对象特征

  • 面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据
  • 抽象
  • 三大特征:
    • 封装
      • 提高了程序的安全性,保护数据
      • 隐藏了代码的实现细节
      • 统一接口
      • 系统可维护性增加
    • 继承
      • extends 的意思是 “扩展” 。子类是父类的扩展
      • Java中的 类 只有单继承,没有多继承!,接口可以多继承。(一个儿子只有一个亲爸爸(单继承))
      • 类被 final 修饰后不能被继承,因为 final 不可更改,子类无法重写父类方法
    • 多态
      • 父类的引用指向子类 (父 new 子)
      • 多态存在的条件:
        • 有继承关系
        • 子类重写父类方法
        • 父类引用指向子类对象
        package com.byxx.yunan.test.arrays;
        
        /**
         * @Author yww
         * @CreateTime 2021-02-04
         */
        public class Test6 {
            public static void main(String[] args) {
        
                //Student 能调用自身和父类的方法
                Student student = new Student();
        
                //Person 只能调用自身的方法, 不可调用子类的方法
                Person person = new Student();
        
                //验证父 new 子,类全部都隐式继承了Object
                Object object = new Student();
            }
        }
        
        //学生  继承 人
        class Student extends Person{
        
        }
                   
值传递,引用传递
package com.byxx.yunan.test.arrays;

/**
* @Author yww
* @CreateTime 2021-02-04
*/
public class Test5 {
  public static void main(String[] args) {

      String name = "李四";

      change(name);//值传递,原值不变
      System.out.println(name);//输出结果为:李四

      Person person = new Person();
      person.name = "李四";

      changePerson(person);
      System.out.println(person.name);//输出结果为:王五

  }
  //值传递
  public static void change(String name){
      name = "张三";
  }

  //引用传递: 对象,本质还是值传递
  public static void changePerson(Person person){
      //person是一个对象, 指向的 ---> Person person = new Person(); 这是一个具体的对象,可以改变属性!
      person.name = "王五";
  }

}

//person类
class Person{
  String name;
}
           
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

super,this

super注意点:

​ 1. super 调用父类的构造方法,必须在构造方法的第一个

​ 2. super 必须只能出现在子类的方法或者构造方法中

​ 3. super 和 this 不能同时调用构造方法

VS this:

  1. 代表的对象不同:

    ​ this:本身调用者这个对象

    ​ super:代表父类对象的应用

    1. 前提:

    ​ this:没有继承也可以使用

    ​ super:只能在继承条件中才可以使用构造方法

    1. 构造方法:

    ​ this() ; 本身的构造

    ​ super() ; 父类的构造

重写

重写:需要有继承关系,子类重写父类的方法!
  1. 方法名必须相同
  2. 参数列表必须相同
  3. 修饰符:范围可以扩大但不能缩小 public > protected > default > private
  4. 抛出的异常:范围,可以被缩小,但不能扩大 ClassNotFoundException --> Exception (大)
重写:子类的方法和父类必须一致:方法体不同!

为什么需要重写 ?

​ 父类的功能,子类不一定需要,或者不一定满足!

static 关键字详解

package com.byxx.yunan.test.arrays;

/**
 * @Author yww
 * @CreateTime 2021-02-04
 */
public class User {

    //2
    {
        System.out.println("匿名代码块");
    }

    //1  只执行一次
    static {
        System.out.println("静态代码块");
    }

    //3
    public User(){
        System.out.println("构造方法");
    }

    public static void main(String[] args) {
        /*
            创建 User 对象时, 先执行  静态代码块, 再执行 匿名代码块, 最后执行 构造方法
         */
        User user = new User();

        /*
            第二次创建 User 对象,先执行 匿名代码块, 再执行 构造方法,
                注意: 第二次创建 该对象 不再 执行静态代码块, 因为 static 修饰 只执行一次
         */
        User user2 = new User();
    }
}
           

抽象

  • 抽象类不能实例化(new),只能靠子类去实现它
  • 抽象类中可以写普通的方法
  • 抽象方法必须在抽象类中
  • 抽象方法存在构造器

1. 抽象类存在的意义?

​ 更利于代码的维护性和重用,提高开发效率。

2. 验证理论

package com.byxx.yunan.test.oop;

//abstract 抽象类: 类 extends 单继承~  (接口可以多继承)
public abstract class Action {

 //abstract, 抽象方法, 只有方法名字, 没有方法的实现!
 public abstract void sayHi();

 //普通方法
 public String getName(){
     return "张三";
 }

 //1. 抽象类不能实例化(new), 只能靠子类去实现它
 //2. 抽象类中可以写普通的方法
 //3. 抽象方法必须在抽象类中
 //4. 抽象方法存在构造器
 /*
    抽象类存在的意义?
       更利于代码维护和重用,提高开发效率
  */
}

//抽象类的所有方法, 继承了它的子类, 都必须要实现它的抽象方法, 除非子类也是抽象
class ChildActionB extends Action{

 @Override
 public void sayHi() {

 }
}

//子类抽象, 不需要重写 父抽象类 的方法
abstract class ChildActionC extends Action{

}
           

接口

  • interface 关键字定义接口,接口中可以多继承
  • 接口中定义变量 实际为 常量 默认加上 public static final
  • 接口中的所有定义其实都是抽象的 默认加上 public abstract
  • 实现接口必须实现接口中的所有方法,因为接口中的方法 都是抽象的
  • 接口不能被实例化,因为接口中没有构造方法
  • implement 可以实现多个接口
package com.byxx.yunan.test.oop;

//interface  关键字定义接口, 接口中可以多继承
public interface UserService {

    //接口中定义变量 实际为常量  默认加上 public static final
    int AGE = 99;


    //接口中的所有定义其实都是抽象的  默认加上 public abstract
    public abstract String getName();

    //等价 public abstract Integer getAge();
    public Integer getAge();

    //等价 public abstract void sayHello();
    void sayHello();

}
           

N种内部类

  • 成员内部类
    import javax.sound.midi.Soundbank;
    
    /**
     * 外部类
     * @Author yww
     * @CreateTime 2021-02-05
     */
    public class Outer {
    
        //成员内部类
        class Inner{
            public void say(){
                System.out.println("这是成员内部类的方法");
            }
        }
    }
               
  • 静态内部类
    import javax.sound.midi.Soundbank;
    
    /**
     * 外部类
     * @Author yww
     * @CreateTime 2021-02-05
     */
    public class Outer {
    
        //静态内部类
        static class Inner2{
            public void say(){
                System.out.println("这是静态内部类的方法");
            }
        }
    }
               
  • 局部内部类
    import javax.sound.midi.Soundbank;
    
    /**
     * 外部类
     * @Author yww
     * @CreateTime 2021-02-05
     */
    public class Outer {
    
        //局部内部类
        class Inner{
            public void say(){
                System.out.println("这是局部内部类的方法");
            }
        }
    }
               
  • 匿名内部类
    package com.byxx.yunan.test.oop;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    /**
     * 外部类
     * @Author yww
     * @CreateTime 2021-02-05
     */
    public class Outer {
        public static void main(String[] args) {
    
            //匿名内部类
            new PersonService(){
                @Override
                public void hello() {
    
                }
            };
            
        }
    
    }
    
    interface PersonService{
        void hello();
    }
    
               

异常

Error 和 Exception

  • Error
    • Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
  • Exception
    • Exception异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

Error 和 Exception 的区别 :

​ Error 通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机 (JVM) 一般会选择终止线程;

​ Exception 通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

捕获和抛出异常

  • 异常处理机制
    • 抛出异常
    • 捕获异常
  • 异常处理五个关键字
    • try (监控区域)
    • catch (想要捕获的异常类型!) 捕获异常
    • finally (一般用于关闭资源)
    • throw (主动抛出异常,一般在 方法中 使用)
    • throws (主要抛出异常,一般在 方法上 使用)
    package com.byxx.yunan.test.exception;
    
    /**
     * @Author yww
     * @CreateTime 2021-02-05
     */
    public class Test1 {
        public static void main(String[] args) {
    
            int a = 1;
            int b = 0;
    
            //假设要捕获多个异常:从小到大
    
            try{//监控区域
    
                new Test1().test(1,0);
    
            }catch (Exception e){ //catch(想要捕获的异常类型!) 捕获异常
                System.out.println("Exception");
            }catch (Throwable t){
                System.out.println("Throwable");
            }finally {
                System.out.println("finally");
            }
            //finally 可以不要, 假设IO,资源操作时,则需要finally 用于关闭资源
        }
    
        //假设这方法中,处理不了这个异常。 方法上 抛出异常 ,使用 throws
        public void test(int a,int b) throws ArithmeticException{
    
            if(b == 0){//throw
                throw new ArithmeticException();//主动抛出的异常,一般在 方法中 使用
            }
    
        }
    }
               

自定义异常

自定义异常
package com.byxx.yunan.test.exception;

/**
 * 自定义的异常类
 * @Author yww
 * @CreateTime 2021-02-05
 */
public class MyException extends Exception{

    //传递数字 > 10
    private int detail;

    public MyException(int a){
        this.detail = a;
    }

    //toString: 异常的打印信息
    @Override
    public String toString() {
        return "MyException{ detail=" + detail +"} ";
    }
}
           
自定义异常使用
package com.byxx.yunan.test.exception;

/**
 * @Author yww
 * @CreateTime 2021-02-05
 */
public class ExceptionMain {
    public static void main(String[] args) throws MyException {
        //调用getNumber方法
        new ExceptionMain().getNumber(11);
    }

    public int getNumber(int num) throws MyException {
        if(num>10){//传递值 > 10 抛出自定义异常
            throw new MyException(num);
        }
        return num;
    }
}
           

System类

  • public static long currentTimeMillis(); 返回以毫秒为单位的当前时间
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 将数组中指定的数据拷贝到另一个数组中
package com.byxx.yunan.test.string;

import java.util.Arrays;

/**
 * @Author yww
 * @CreateTime 2021-02-10
 */
public class Test1 {
    public static void main(String[] args) {

        /*
           System.currentTimeMillis(); 计算循环1000次后所需要的执行时间 (毫秒为单位)
         */
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
        }
        long endTime = System.currentTimeMillis();

        System.out.println("程序共用时: "+(endTime - startTime)+"毫秒");

        /*
           System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
           将数组中指定的数据拷贝到另一个数组中
           参数:
              src - 源数组
              srcPos - 源数组中的起始位置
              dest - 目标数组
              destPos - 目标数据中的起始位置
              length - 要复制的数组元素的数量
         */
        int[] arrays1 = {1,2,3,4,5,6};
        int[] arrays2 = {7,8,9,1,2,3};

        System.arraycopy(arrays1,0,arrays2,0,3);

        System.out.println(Arrays.toString(arrays1));//输出: [1,2,3,4,5,6]

        System.out.println(Arrays.toString(arrays2));//输出: [1,2,3,1,2,3]

    }
}
           

StringBuilder

String 类

字符串是常量:它们的值在创建之后不能更改

字符串的底层是一个被final修饰的数组,不能改变,是一个常量

private final byte[] value;

由于String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象。列如:

package com.byxx.yunan.test.string;

/**
 * @Author yww
 * @CreateTime 2021-02-10
 */
public class Test2 {
    public static void main(String[] args) {
        String str = "a"+"b"+"c";
        // "a", "b", "c"  3个字符串
        // "a" + "b"  "ab" 一个字符串
        // "ab" + "c" 一个字符串
    }
}
           

StringBuilder类

字符串缓冲区,可以提高字符串的操作效率 (看成一个长度可以变化的字符串) 底层也是一个数组,但没有被final修饰,可以改变长度。

byte[] value = new byte[16];

“a” + “b” + “c” = “abc”;

StringBuilder 在内存中始终是一个数组,占用空间少,效率高,如果超出了StringBuilder的容量,会自动的扩容。

Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO
package com.byxx.yunan.test.string;

/**
 * @Author yww
 * @CreateTime 2021-02-10
 */
public class Test3 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        StringBuilder sb2 = sb.append("a");
        System.out.println(sb == sb2);// == 比较的内存地址(两个对象是同一个对象)  所以 sb == sb2 输出结果为true

        //StringBuilder 链式写法
        sb.append(1).append("2").append('3').append(4.0);
        System.out.println(sb); // a1234.0

        //reverse(); 反转
        sb.reverse();
        System.out.println(sb);// 0.4321a
    }
}
           

循环

for 循环

//for i
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
           

增强 for 循环

//增强for
for (String s : list) {
    System.out.println(s);
}
           

Iterator 迭代器

  • boolean hasNext(); 如果仍有元素可以迭代,则返回true,判断集合中还有没有下一个元素,有就返回true,没有就返回false
  • E next(); 返回迭代的下一个元素
迭代器的使用步骤
  1. 使用集合的方法iterator() 获取迭代器的实现类对象,使用Iterator接口接收(多态)
  2. 使用Iterator接口中的方法hasNext判断还有没有下一个元素
  3. 使用Iterator接口中的方法next取出集合中的下一个元素
package com.byxx.yunan.test.string;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * @Author yww
 * @CreateTime 2021-02-12
 */
public class Test4 {
    public static void main(String[] args) {
        Collection<String> lists = new ArrayList<>();
        lists.add("张三");
        lists.add("李四");
        lists.add("王五");
        lists.add("刘六");
        lists.add("陈七");

        //使用iterator迭代器 迭代
        Iterator<String> iterator = lists.iterator();
        //hasNext()判断还有没有下一个元素
        while(iterator.hasNext()){
            //iterator.next() 取出集合中的下一个元素
            System.out.println(iterator.next());
        }

    }
}
           

集合

Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

Collection顶级集合接口

  • boolean add (E e); 向集合中添加元素
  • boolean remove (E e); 删除集合中的某个元素
  • void clear (); 清空集合中所有的元素
  • boolean contains (E e); 判断集合中是否包含某个元素
  • boolean isEmpty (); 判断集合是否为空
  • int size (); 获取集合的长度
  • Object[] toArray (); 将集合转成一个数组

List

  • List接口特点:
    1. 有序的集合,存储元素和取出元素的顺序是一致的(存储123,取出123)
    2. 有索引,包含了一些带索引的方法
    3. 允许存储重复的元素
  • List接口中带索引的方法(特有)
    • public void add(int index,E element); 将指定的元素,添加到该集合的指定位置上
    • public E get(int index); 返回集合中指定位置的元素
    • public E remove(int index); 移出列表中指定位置的元素,返回的是被移出的元素
    • public E set(int index,E element); 用指定元素替换集合中指定位置的元素,返回值返回更新前的元素
package com.byxx.yunan.test.arrays;

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

/**
 * List接口特点:
 *   1. 有序的集合,存储元素和取出元素的顺序是一致的(存储123,取出123)
 *   2. 有索引,包含了一些带索引的方法
 *   3. 允许存储重复的元素
 * List接口中带索引的方法(特有)
 *   - public void add(int index,E element); 将指定的元素,添加到该集合的指定位置上
 *   - public E get(int index); 返回集合中指定位置的元素
 *   - public E remove(int index); 移出列表中指定位置的元素,返回的是被移出的元素
 *   - public E set(int index,E element); 用指定元素替换集合中指定位置的元素,返回值返回更新前的元素
 */
public class Test9 {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        //使用add方法往集合中添加元素
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("a");
        //打印list,没有输出内存地址,代表了内部重写了toString()方法
        System.out.println(list);//[a,b,c,d,a]

        //public void add(int index,E element); 将指定的元素,添加到该集合的指定位置上
        list.add(2,"DDDDD");
        System.out.println(list);//[a, b, DDDDD, c, d, a]

        //public E remove(int index); 移出列表中指定位置的元素,返回的是被移出的元素
        String removeStr = list.remove(2);
        System.out.println(removeStr);//DDDDD

        //public E set(int index,E element); 用指定元素替换集合中指定位置的元素,返回值返回更新前的元素
        list.set(0,"A");
        System.out.println(list);//[A,b,c,d,a]

        //public E get(int index); 返回集合中指定位置的元素

        //for i
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        //增强for
        for (String s : list) {
            System.out.println(s);
        }

        //iterator迭代器
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String next = iterator.next();
            System.out.println(next);
        }

    }
}

           

ArrayList

ArrayList集合数据存储的结构是数组结构,元素增删慢,查找快,线程不安全,由于日常开发中使用最多的功能为查询数据,遍历数据,所以ArrayList是最常用的集合。

LinkedList

LinkedList集合数据存储的结构是双向链表,元素增删快,查找快,线程不安全。

package com.byxx.yunan.test.arrays;

import java.util.LinkedList;

/**
 * java.util.LinkedList集合 implements List接口
 * LinkedList集合的特点:
 *      1.底层是一个链表结构:查询慢,增删快
 *      2.里面包含了大量操作首尾元素的方法
 *      注意:使用LinkedList集合特有的方法,不能使用多态
 *      
 *      - public void addFirst(E e); 将指定元素插入此列表的开头
 *      - public void addLast(E e); 将指定元素添加到此列表的结尾
 *      - public void push(E e); 将元素推入此列表所表示的堆栈 push()方法等同于addFirst()方法
 *
 *      - public E getFirst(); 返回此列表的第一个元素
 *      - public E getLast(); 返回此列表的最后一个元素
 *
 *      - public E removeFirst(); 移出并返回此列表的第一个元素
 *      - public E removeLast(); 移出并返回此列表的最后一个元素
 *      - public E pop(); 从此列表所表示的堆栈处弹出一个元素 pop()方法等同于removeFirst()方法
 *
 *      - public boolean isEmpty(); 如果列表不包含元素,则返回true
 */
public class Test10 {
    public static void main(String[] args) {
        LinkedList<String> linked = new LinkedList<>();
        linked.add("aaa");
        linked.add("bbb");
        /*
            - public void addFirst(E e); 将指定元素插入此列表的开头
            - public void addLast(E e); 将指定元素添加到此列表的结尾
            - public void push(E e); 将元素推入此列表所表示的堆栈 push()方法等同于addFirst()方法
         */
        linked.addFirst("www");
        linked.addLast("com");
        linked.push("YYYYYY");

        System.out.println(linked);//[YYYYYY, www, aaa, bbb, com]

        /*
            - public E getFirst(); 返回此列表的第一个元素
            - public E getLast(); 返回此列表的最后一个元素

         */
        String first = linked.getFirst();
        String last = linked.getLast();

        System.out.println("第一个元素:"+first+" 最后一个元素:"+last);
        System.out.println(linked);

        /*
            - public E removeFirst(); 移出并返回此列表的第一个元素
            - public E removeLast(); 移出并返回此列表的最后一个元素
            - public E pop(); 从此列表所表示的堆栈处弹出一个元素 等同于removeFirst()方法
         */
        String pop = linked.pop();

        linked.clear();//清空集合中的元素 在获取集合中的元素会抛出NoSuchElementException


    }
}
           

Set

Set接口和List接口一样,同样继承Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。

Set集合中有多个子类,比如:HashSet,TreeSet这两个集合。

Set集合取出元素的方式可以采用:迭代器,增强for

HashSet

HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。HashSet底层实现其实是一个HashMap支持。

HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode和equals方法。

package com.byxx.yunan.test.arrays;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * Set接口 extends Collection接口
 * Set接口的特点:
 *      1.不允许存储重复的元素
 *      2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
 * HashSet特点:
 *      1.不允许存储重复的元素
 *      2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
 *      3.是一个无序的集合,存储元素和取出元素的顺序有可能不一致
 *      4.底层是一个哈希表结构(查询速度非常的快)
 */
public class Test11 {
    public static void main(String[] args) {
        Set<Integer> hashSet = new HashSet<>();

        hashSet.add(1);
        hashSet.add(3);
        hashSet.add(2);
        hashSet.add(1);

        //迭代器 循环
        Iterator<Integer> iterator = hashSet.iterator();
        while(iterator.hasNext()){
            Integer next = iterator.next();
            System.out.println(next);//输出123
        }

        //增强for 循环
        for (Integer iter : hashSet) {
            System.out.println(iter);//输出123
        }
    }
}
           

哈希值

哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址,是一个逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址)。

package com.byxx.yunan.test.collection;

import lombok.ToString;

/**
 * 哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址,是一个逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址)
 * 在Object类由一个方法,可以获取对象的哈希值
 * int hashCode(); 返回该对象的哈希码值
 * hashCode 源码:
 *      public native int hashCode():
 *      native:代表该方法调用的是本地操作系统的方法
 */
public class Test1 {
    public static void main(String[] args) {
        //User类继承了Object类,所以可以使用Object类的hashCode方法
        User u1 = new User();
        int code1 = u1.hashCode();
        System.out.println(code1);//521645586

        User u2 = new User();
        int code2 = u2.hashCode();
        System.out.println(code2);//1296064247
        u2.toString();

        /*
            toString()方法源码:
                return getClass().getName() + "@" + Integer.toHexString(hashCode());
         */
        System.out.println(u1);//[email protected]
        System.out.println(u2);//[email protected]
        System.out.println(u1 == u2);//false

    }
}
           

HashSet集合存储数据的结构 (哈希表)

在jdk1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一个hash值的链表都存储在一个链表里。但是当一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而在jdk1.8中,哈希表存储采用数组+链表+红黑树实现,数组初始长度为16,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找实际。

Set集合存储元素不重复的原理

Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

HashSet存储自定义类型元素

  • 存储元素(String,Integer,…Person),必须重写hashCode和equals方法
package com.byxx.yunan.test.collection;

import java.util.HashSet;
import java.util.Objects;

/**
 * HashSet存储自定义类型元素
 *
 * set集合保存元素唯一
 *  存储的元素(String,Integer,...Person),必须重写hashCode方法和equals方法
 *
 * 要求:
 *   同名同年龄的人,视为同一个人,只能存储一次
 */
public class Test3 {
    public static void main(String[] args) {
        Person p1 = new Person("张三",18);
        Person p2 = new Person("张三", 18);
        Person P3 = new Person("张三", 19);

        HashSet<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);
        set.add(P3);
        //存储类型没有重写hashCode和equals方法时
        System.out.println(p1==p2);//对比内存地址: false
        System.out.println(p1.equals(p2));//对比值: false
        System.out.println(set);//[Person{name='张三', age=18}, Person{name='张三', age=19}, Person{name='张三', age=18}]

        //对象重写hashCode和equals方法后(完成需求,同名同年龄人,只能存储一次)
        System.out.println(p1==p2);//对比内存地址: false
        System.out.println(p1.equals(p2));//对比值: true
        System.out.println(set);//[Person{name='张三', age=18}, Person{name='张三', age=19}]
    }

}

//person类
class Person{
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(age, person.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

LinkedHashSet

  • LinkedHashSet集合 extends HashSet集合
  • 底层是一个哈希表 + 链表 (记录元素的存储顺序),保证元素有序
package com.byxx.yunan.test.collection;

import java.util.HashSet;
import java.util.LinkedHashSet;

/**
 * @Author yww
 * @CreateTime 2021-02-21
 */
public class Test4 {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("www");
        hashSet.add("ccc");
        hashSet.add("bbb");
        System.out.println(hashSet);//[ccc, bbb, www]

        /*
            LinkedHashSet集合 extend HashSet集合
            特点:
                底层是一个哈希表 + 双向链表 (多了一条链表是为了 记录元素的存储顺序),保证存储元素有序
         */
        LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add("www");
        linkedHashSet.add("ccc");
        linkedHashSet.add("bbb");
        System.out.println(linkedHashSet);//[www, ccc, bbb]
    }
}
           

Collections工具类

  • Collections 是集合工具类,用来对集合进行操作,部分方法如下:
  • public static boolean addAll(Collection c,T… elements); 往集合中添加一些元素
  • public static void shuffle(List<?> list); 打乱集合顺序
  • public static void sort(List list); 将集合中元素按照默认规则排序
  • public static void sort(List list,Comparator<? super T> ); 将集合中元素按照指定规则排序,被排序的集合里存储的元素,重写Comparator接口中的方法compare定义排序的规则
package com.byxx.yunan.test.collection;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * @Author yww
 * @CreateTime 2021-02-22
 */
public class Test5 {
    public static void main(String[] args) {
        /*
          Collections工具类:
            public static <T> boolean addAll(Collection<T> c,T... elements); 往集合中添加一些元素
         */
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"1","2","3","4","5");
        System.out.println(list);//[1, 2, 3, 4, 5]

        //public static void shuffle(List<?> list);打乱集合顺序,每次顺序都不一致
        Collections.shuffle(list);
        System.out.println(list);//[2, 3, 4, 5, 1]

        //public static <T> void sort(List<T> list); 将集合中元素按照默认规则排序
        Collections.sort(list);
        System.out.println(list);//[1, 2, 3, 4, 5]

        /*
          按年龄大小排序
          sort(List<T> list)使用前提
          被排序的集合里存储的元素,必须实现Comparable,重写接口中的compareTo定义排序的规则
         */
        ArrayList<Role> roleList = new ArrayList<>();
        roleList.add(new Role("张三",17));
        roleList.add(new Role("李四",19));
        roleList.add(new Role("王五",20));

        /*
           public static <T> void sort(List<T> list,Comparator<? super T> );
           将集合中元素按照指定规则排序,被排序的集合里存储的元素,重写Comparator接口中的方法compare定义排序的规则 	
         */
        //升序 o1.getAge().compareTo(o2.getAge())
        Collections.sort(roleList, new Comparator<Role>() {
            @Override
            public int compare(Role o1, Role o2) {
                return o1.getAge().compareTo(o2.getAge());
            }
        });
        System.out.println(roleList);//[Role{name='张三', age=17}, Role{name='李四', age=19}, Role{name='王五', age=20}]

        //降序 o2.getAge().compareTo(o1.getAge())
        Collections.sort(roleList, new Comparator<Role>() {
            @Override
            public int compare(Role o1, Role o2) {
                return o2.getAge().compareTo(o1.getAge());
            }
        });
        System.out.println(roleList);//[Role{name='王五', age=20}, Role{name='李四', age=19}, Role{name='张三', age=17}]
    }
}

//角色类
class Role{
    private String name;
    private Integer age;
    
    public Role() {
    }
    
	public Role(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Role{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

Map

  • Map集合的特点:
    1. Map集合是一个双列集合,一个元素包含两个值 (一个key,一个value)
    2. Map集合中的元素,key和value的数据类型可以相同,也可以不同
    3. Map集合中的元素,key是不允许重复的,value是可以重复的 (如果出现key重复,后put重复的value值会替换之前put的value)
    4. Map集合中的元素,key和value是一 一对应的
  • Map接口常用方法
    1. public V put(K key,V value); 把指定的键与指定的值添加到Map集合中
    2. public V remove(Object key); 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值
    3. public V get(Object key); 根据指定的键,在Map集合中获取对应的值
    4. boolean containsKey(Object key); 判断集合中是否包含指定的键
    5. public Set keySet(); 获取Map集合中所有的键,存储到Set集合中
    6. public Set<Map.Entry<K,V>> entrySet(); 获取到Map集合中所有的键值对对象的集合(Set集合)
package com.byxx.yunan.test.collection;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @Author yww
 * @CreateTime 2021-02-24
 */
public class Test6 {
    public static void main(String[] args) {
        HashMap<String, Object> map = new HashMap<>();
        //public V put(K key,V value) 添加元素到集合
        map.put("name", "张三");
        map.put("sex", "男");
        map.put("age", 20);
        System.out.println(map);//{sex=男, name=张三, age=20}

        //public V remove(Object key) 删除集合中指定的key
        map.remove("age");
        System.out.println(map);//{sex=男, name=张三}

        //public V get(Object key) 根据指定的值,获取对应的值
        Object name = map.get("name");
        System.out.println(name);//张三

        //public Set<K> keySet() 获取Map集合中所有的键,存储到Set集合中
        Set<String> keySet = map.keySet();
        //for增强
        for (String key : keySet) {
            Object value = map.get(key);
            System.out.print(value);//男 张三
        }
        //迭代器
        Iterator<String> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            Object value = map.get(key);
            System.out.print(value);//男 张三
        }

        //public Set<Map.Entry<K,V>> entrySet(); 获取map集合中的 键值对 对象
        Set<Map.Entry<String, Object>> entrySet = map.entrySet();
        //for增强
        for (Map.Entry<String, Object> entry : entrySet) {
            String key = entry.getKey();
            Object value = entry.getValue();
            /*
                key:sex  value:男
                key:name  value:张三
             */
            System.out.println("key:" + key + "  value:" + value);
        }

        //迭代器
        Iterator<Map.Entry<String, Object>> entryIterator = map.entrySet().iterator();
        while (entryIterator.hasNext()) {
            Map.Entry<String, Object> entryMap = entryIterator.next();
            String key = entryMap.getKey();
            Object value = entryMap.getValue();
            /*
                key:sex  value:男
                key:name  value:张三
             */
            System.out.println("key:" + key + "  value:" + value);
        }

    }
}
           

HashMap

  • HashMap集合 implements Map接口
  • HashMap集合特点:
    1. HashMap集合底层是哈希表,查询效率快

      ​ JDK1.8之前:数组 + 链表

      ​ JDK1.8之后:数组 + 链表 + 红黑树

    2. HashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致
  • HashMap存储自定义类型键值
    1. Map集合保证key是唯一的
    2. 作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一
package com.byxx.yunan.test.collection;

import com.sun.org.apache.bcel.internal.generic.NEW;
import org.aspectj.weaver.ast.Or;

import java.util.HashMap;
import java.util.Objects;

/**
 * HashMap集合 implements Map接口
 */
public class Test7 {
    public static void main(String[] args) {
        /*
          hashMap存储自定义类型
            Map集合保证key是唯一的:
                作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一
         */
        HashMap<Organ, Object> hashMap = new HashMap<>();
        //Organ对象没有重写hashCode和equals时,同值对象作为key是不会去重的
        hashMap.put(new Organ("张三","19"),20);
        hashMap.put(new Organ("李四","20"),31);
        hashMap.put(new Organ("张三","19"),23);
        System.out.println(hashMap);//{Organ{name='张三', age='19'}=20, Organ{name='张三', age='19'}=23, Organ{name='李四', age='20'}=31}

        //Organ对象重写hashCode和equals后,同值对象作为key会去重
        HashMap<Organ, Object> organMap = new HashMap<>();
        organMap.put(new Organ("张三","19"),20);
        organMap.put(new Organ("李四","20"),31);
        organMap.put(new Organ("张三","19"),23);
        System.out.println(organMap);//{Organ{name='李四', age='20'}=31, Organ{name='张三', age='19'}=23}
    }

}

//Organ类
class Organ{
    private String name;
    private String age;

    public Organ() {
    }

    public Organ(String name, String age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Organ organ = (Organ) o;
        return Objects.equals(name, organ.name) &&
                Objects.equals(age, organ.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Organ{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

LinkedHashMap

  • LinkedHashMap集合 extends HashMap集合
  • LikedHashMap集合的特点:
    1. LinkedHashMap集合底层是哈希表 + 链表 (保证迭代的顺序)
    2. LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的
package com.byxx.yunan.test.collection;

import java.util.HashMap;
import java.util.LinkedHashMap;

/**
 * LinkedHashMap<K,V> extends HashMap<K,V>
 * 底层结构:
 *      哈希表 + 链表 (记录元素的顺序)
 */
public class Test8 {
    public static void main(String[] args) {
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("a","D");
        hashMap.put("c","C");
        hashMap.put("b","B");
        hashMap.put("a","A");
        System.out.println(hashMap);// key不允许重写,无序 {a=A, b=B, c=C}

        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("a","D");
        linkedHashMap.put("c","C");
        linkedHashMap.put("b","B");
        linkedHashMap.put("a","A");
        System.out.println(linkedHashMap);// key不允许重写,有序 {a=A, c=C, b=B}
    }
}
           

TreeMap

  • TreeMap<K,V> extends AbstractMap<K,V>
  • TreeMap特点:
    1. key唯一,按照自然顺序排序
package com.byxx.yunan.test.collection;

import org.apache.commons.collections4.map.LinkedMap;

import java.util.*;

/**
 * @Author yww
 * @CreateTime 2021-02-24
 */
public class Test9 {
    public static void main(String[] args) {
        TreeMap<Object, Object> treeMap1 = new TreeMap<>();
        //TreeMap按照自然顺序迭代
        treeMap1.put(1,"aaa");
        treeMap1.put(3,"ccc");
        treeMap1.put(2,"bbb");
        System.out.println(treeMap1);//{1=aaa, 2=bbb, 3=ccc}

        TreeMap<String, Object> treeMap2 = new TreeMap<>();
        treeMap2.put("a","AAA");
        treeMap2.put("c","CCC");
        treeMap2.put("b","BBB");
        System.out.println(treeMap2);//{a=AAA, b=BBB, c=CCC}

    }
}
           

HashTable

  • HashTable<K,V> 集合 implements Map<K,V> 集合
  • HashTable 与 HashMap区别
    1. HashTable 底层是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢

      HashMap 底层是一个哈希表,是一个线程不安全的集合,是多线程的集合,速度快

    2. HashTable 不能存储 null 键 null 值

      HashMap(包括其他集合) 可以存储 null 键 null 值

    3. HashTable 和 vector 集合一样,在jdk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了
    package com.byxx.yunan.test.collection;
    
    import java.util.HashMap;
    import java.util.Hashtable;
    
    /**
     * @Author yww
     * @CreateTime 2021-02-24
     */
    public class Test10 {
        public static void main(String[] args) {
            /*
               HashT ,able<K,V>集合 implements Map<K,V>集合
    
               HashTable: 底层是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢
               HashMap: 底层是一个哈希表,是一个线程不安全的集合,是多线程的集合,速度快
    
               HashTable: 不能存储null键null值
               HashMap(包过其他集合): 可以存储null键null值
    
               HashTable和vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了
             */
            HashMap<String, Object> hashMap = new HashMap<>();
            hashMap.put(null,"A");
            hashMap.put("B",null);
            hashMap.put(null,null);
            System.out.println(hashMap);//{null=null, B=null}
    
            Hashtable<String, Object> hashTable = new Hashtable<>();
            hashTable.put(null,"A");//报异常: NullPointerException
            hashTable.put("B",null);//报异常: NullPointerException
            hashTable.put(null,null);//报异常: NullPointerException
    
        }
    }
               

数据结构

常见的数据结构

数据存储的常用结构有:栈,队列,数组,链表,红黑树。

  • 栈 :stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加,查找,删除等操作。

简单的来说:采用该结构的集合,对元素的存取有如下的特点

  • 先进后出(即,存进去的元素,要在它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
  • 栈的入口,出口都是栈的顶端位置。
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

队列

  • 队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 先进先出(即,存进去的元素,要在它前面的元素依次取出来后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

数组

  • 数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间放元素,就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 查找元素快:通过索引,可以快速访问指定位置的元素。
  • 增删元素慢
    • 指定索引位置增加元素,需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

链表

  • 链表:linked list,由一系列节点node(链表中每一个元素称为节点)组成,节点可以在运行时动态生成。每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域,链表结构有单向链表与双向链表
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

File

概述

File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建,查找和删除等操作。

File类中静态成员变量

  • static String pathSeparator 与系统有关的路径分隔符 //windows: 分号; linux: 冒号:
  • static char pathSeparatorChar 与系统有关的路径分隔符
  • static String separator 与系统有关的默认名称分割符 //windows: 反斜杆\ linux: 正斜杆/
  • static String separatorChar 与系统有关的默认名称分隔符
package com.byxx.yunan.test.io;

import java.io.File;

/**
 * @Author yww
 * @CreateTime 2021-02-24
 */
public class Test1 {
    public static void main(String[] args) {
        /*
            路径分隔符pathSeparator 与 pathSeparatorChar 一致,内部就是将Char变成了字符串
            public static final String pathSeparator = "" + pathSeparatorChar;
         */
        String pathSeparator = File.pathSeparator;
        System.out.println(pathSeparator);//windows: 分号;  linux:冒号:

        /*
           文件名称分隔符separator 与 separatorChar 一致,内部就是将Char变成了字符串
           public static final String separator = "" + separatorChar;
         */
        String separator = File.separator;
        System.out.println(separator);//windows: 反斜杆\  linux: 正斜杆/
    }
}
           

File类构造方法

  • File (String pathname) 通过将给定路径名称字符串转换为抽象路径名来创建一个新 File 实例
  • File (String parent,String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例
  • File (File parent,String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例
package com.byxx.yunan.test.io;

import java.io.File;

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test2 {
    public static void main(String[] args) {
        /*
           File(String pathname)
           通过将给定路径名称字符串转换为抽象路径名来创建一个新 File 实例
         */
        File file1 = new File("D://a.txt");
        System.out.println(file1);//D:\a.txt

        /*
           File(String parent,String child)
           根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例
         */
        File file2 = new File("D://","a.txt");
        System.out.println(file2);//D:\a.txt

        /*
           File(File parent,String child)
           根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例
         */
        File parentFile = new File("E://");
        File file3 = new File(parentFile,"a.txt");
        System.out.println(file3);//E:\a.txt

    }
}
           

File类常用方法

获取功能的方法

  • public String getAbsolutePath() 返回此File的绝对路径名 字符串
  • public String getPath() 将此File转换为路径名 字符串
  • public String getName() 返回由此File表示的文件或目录的名称
  • public long length() 返回由此File表示的文件的长度
package com.byxx.yunan.test.io;

import java.io.File;

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test3 {
    public static void main(String[] args) {
        getAbsolutePath();
        getPath();
        getName();
        length();
    }

    /**
     * public String getAbsolutePath()  返回此File的绝对路径名 字符串
     * 获取的构造方法中的路径无论路径是绝对的还是相对的,getAbsolutePath()方法返回的都是绝对路径
     */
    public static void getAbsolutePath(){
        File file1 = new File("D:\\log\\a.txt");
        String absolutePath1 = file1.getAbsolutePath();
        System.out.println(absolutePath1);// D:\log\a.txt

        //如果直接填写文件,则按当前项目路径作为参照物 获取绝对路径
        File file2 = new File("c.txt");
        String absolutePath2 = file2.getAbsolutePath();
        System.out.println(absolutePath2);// F:\WorkSpaces\IdeaWork\yunan\c.txt
    }


    /**
     * public String getPath() 将此File转换为路径名 字符串
     * 获取的构造方法中传递的路径
     */
    public static void getPath(){
        File file1 = new File("D:\\log\\a.txt");
        String path1 = file1.getPath();
        System.out.println(path1);// D:\log\a.txt

        File file2 = new File("a.txt");
        String path2 = file2.getPath();
        System.out.println(path2);// a.txt
    }

    /**
     * public String getName() 获取File结尾的文件名称
     */
    public static void getName(){
        File file1 = new File("D:\\log");
        String name1 = file1.getName();
        System.out.println(name1);//log

        File file2 = new File("a.txt");
        String name2 = file2.getName();
        System.out.println(name2);//a.txt
    }

    /**
     * public long length() 返回由此File表示的文件的长度
     * 获取的是构造方法指定文件的大小,以字节为单位
     * 注意:
     *    文件夹或者 给File构造方法中的路径不存在,length 返回0
     */
    public static void length(){
        File file1 = new File("D:\\log\\a.txt");
        long length = file1.length();
        System.out.println(length);//858
    }

}
           

判断功能的方法

  • public boolean exists() 判断文件是否存在,存在返回 true, 不存在返回 false
  • public boolean isDirectory() 判断是否为目录(文件夹),目录(文件夹) 返回 true, 其他返回 false
  • public boolean isFile() 判断是否为文件,是文件 返回 true,非文件 返回 false
package com.byxx.yunan.test.io;

import java.io.File;

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test4 {
    public static void main(String[] args) {
        exists();
        isDirectory();
        isFile();
    }

    /**
     * public boolean exists() 判断文件是否存在
     * 存在返回 true, 不存在返回 false
     */
    public static void exists(){
        File file1 = new File("D:\\log\\a.txt");
        boolean exists1 = file1.exists();
        System.out.println(exists1);//true

        File file2 = new File("D:\\log");
        boolean exists2 = file2.exists();
        System.out.println(exists2);//true
    }

    /**
     * public boolean isDirectory() 判断是否为目录(文件夹)
     * 目录(文件夹) 返回 true, 其他返回 false
     */
    public static void isDirectory(){
        File file1 = new File("D:\\log\\a.txt");
        boolean directory1 = file1.isDirectory();
        System.out.println(directory1);//false

        File file2 = new File("D:\\log");
        boolean directory2 = file2.isDirectory();
        System.out.println(directory2);//true
    }

    /**
     * public boolean isFile() 判断是否为文件
     * 是文件 返回 true,非文件 返回 false
     */
    public static void isFile(){
        File file1 = new File("D:\\log\\a.txt");
        boolean directory1 = file1.isFile();
        System.out.println(directory1);//true

        File file2 = new File("D:\\log");
        boolean directory2 = file2.isFile();
        System.out.println(directory2);//false
    }

}
           

创建删除功能的方法

  • public boolean createNewFile() 创建一个空的新文件,文件不存在,创建文件成功返回 true,文件存在返回 false
  • public boolean delete() 删除文件(文件,文件夹都可以),删除成功 返回 true,删除失败 返回 false
  • public boolean mkdir() 创建单层文件夹,不可创建多层,创建成功返回 true,文件已存在,创建失败返回 false
  • public boolean mkdirs() 创建文件夹,可以创建单层,也可以创建多层文件夹,创建成功返回 true,文件已存在,创建失败返回 false
package com.byxx.yunan.test.io;

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

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test5 {
    public static void main(String[] args) throws IOException {
        createNewFile();
        delete();
        mkdir();
        mkdirs();
    }

    /**
     * public boolean createNewFile() 创建一个空的新文件
     * 文件不存在,创建文件成功返回 true
     * 文件存在返回 false
     * @throws IOException
     */
    public static void createNewFile() throws IOException {
        File file1 = new File("D:\\log\\a.txt");//本地有此文件
        boolean newFile1 = file1.createNewFile();
        System.out.println(newFile1);//false

        File file2 = new File("D:\\log\\ccc.txt");//本地无此文件
        boolean newFile2 = file2.createNewFile();
        System.out.println(newFile2);//true
    }

    /**
     * public boolean delete() 删除文件(文件,文件夹都可以)
     * 删除成功 返回 true
     * 删除失败 返回 false
     */
    public static void delete(){
        File file1 = new File("D:\\log\\ccc.txt");//本地存在
        boolean delete1 = file1.delete();
        System.out.println(delete1);//true

        File file2 = new File("D:\\log\\ccc.txt");//delete()删除后,不存在,返回false
        boolean delete2 = file1.delete();
        System.out.println(delete2);//false

        File file3 = new File("D:\\aaaaa");//文件夹
        boolean delete3 = file3.delete();
        System.out.println(delete3);//true
    }

    /**
     * public boolean mkdir() 创建单层文件夹,不可创建多层
     * 创建成功返回 true
     * 文件夹存在,创建失败返回 false
     */
    public static void mkdir(){
        File file1 = new File("D:\\log\\第一个文件夹");//创建单层
        boolean mkdir1 = file1.mkdir();
        System.out.println(mkdir1);//true

        File file2 = new File("D:\\log\\第一个文件夹");//创建单层
        boolean mkdir2 = file2.mkdir();
        System.out.println(mkdir2);//false

        File file3 = new File("D:\\log\\第一层文件夹\\第二层文件夹");//创建多层
        boolean mkdir3 = file3.mkdir();
        System.out.println(mkdir3);//false

    }

    /**
     * public boolean mkdirs() 创建文件夹,可以创建单层,也可以创建多层文件夹
     * 创建成功返回 true
     * 文件夹存在,创建失败返回 false
     */
    public static void mkdirs(){
        File file1 = new File("D:\\log\\第一层文件夹");//单层文件夹
        boolean mkdirs1 = file1.mkdirs();
        System.out.println(mkdirs1);//true

        File file2 = new File("D:\\log\\第一次文件夹\\第二层文件夹\\第三层文件夹\\第四层文件夹");//多层文件夹
        boolean mkdirs2 = file2.mkdirs();
        System.out.println(mkdirs2);//true
    }

}
           

目录的遍历

  • public String[] list() 返回一个String 数组,获取 File 目录下所有文件及文件夹(不包括子子文件或文件夹),获取为文件或文件夹名称
  • public File[] listFiles() 返回一个File数组,获取 File 目录下所有文件及文件夹(不包括子子文件或文件夹),获取为文件对象,可以获取更多信息
package com.byxx.yunan.test.io;

import java.io.File;

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test6 {
    public static void main(String[] args) {
        list();
        listFiles();
    }

    /**
     * public String[] list() 获取目录下所有文件及文件夹(不包括子子文件或文件夹)
     * 获取为文件或文件夹名称
     */
    public static void list(){
        File file = new File("D:\\这是一个目录文件夹");
        String[] list = file.list();
        for (String name : list) {
            /*
              输出结果:
                  子文件1.txt
                  子文件夹1
                  子文件夹2
             */
            System.out.println(name);
        }
    }

    /**
     * public File[] listFiles() 获取目录下所有文件及文件夹(不包括子子文件或文件夹)
     * 获取为文件对象,可以获取更多信息
     */
    public static void listFiles(){
        File file = new File("D:\\这是一个目录文件夹");
        File[] files = file.listFiles();
        for (File str : files) {
            /*
              输出结果:
                  D:\这是一个目录文件夹\子文件1.txt
                  D:\这是一个目录文件夹\子文件夹1
                  D:\这是一个目录文件夹\子文件夹2
             */
            System.out.println(str);
        }
    }
}
           

递归

实例:

package com.byxx.yunan.test.io;

import java.io.File;

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test7 {
    public static void main(String[] args) {
        File file = new File("D:\\这是一个目录文件夹");
        getAllFile(file);
        /*
           输出结果:
                D:\这是一个目录文件夹\子文件1.txt
                D:\这是一个目录文件夹\子文件夹1\子子文件夹1-1\子子子文件1-1-1.txt
                D:\这是一个目录文件夹\子文件夹1\子子文件夹1-1\子子子文件夹1-1-1
                D:\这是一个目录文件夹\子文件夹1\子子文件夹1-1
                D:\这是一个目录文件夹\子文件夹1
                D:\这是一个目录文件夹\子文件夹2\子子类文件夹2-1
                D:\这是一个目录文件夹\子文件夹2
         */
    }

    /**
     * 获取目录下所有文件,递归调用
     * @param file
     */
    public static void getAllFile(File file){
        File[] files = file.listFiles();
        for (File fileStr : files) {
            if(fileStr.isDirectory()){//如果是文件夹,就递归调用自身
                getAllFile(fileStr);
            }
            System.out.println(fileStr);
        }
    }
}
           

IO

字节流

字节输出流 OutputStream

OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写道目的地,它定义了字节输出流的基本共性功能方法

  • public void close() 关闭此输出流并释放与此流相关联的任何系统资源
  • public void flush() 刷新此输出流并强制任何缓冲的输出字节被写出
  • public void write(byte[] b) 将b.length字节从指定的字节数组写入此输出流
  • public void write(byte[] b, int off, int len) 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
  • public abstract void write(int b) 将指定的字节输出流

FileOutputStream类

  • FileOutputStream extends OutputStream
  • FileOutputStream 文件字节输出流 (把内存中的数据写入到硬盘的文件中)
  • 构造方法
    1. FileOutputStream(String name) 创建一个向具有指定名称的文件中写入数据的输出文件流
    2. FileOutputStream(String name, boolean append) 创建一个向具有指定 name 的文件中写入数据的文件输出流,是否开启数据写入追加
    3. FileOutputStream(File file) 创建一个向指定File对象表示的文件中写入数据的文件输出流
    4. FileOutputStream(File file, boolean append) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流,是否开启数据写入追加

    参数:写入数据的目的

    ​ String name :目的地是一个文件的路径

    ​ File file :目的地是一个文件

    构造方法的作用:

    1. 创建一个FileOutputStream对象
    2. 会根据构造方法中传递的文件 / 文件路径,创建一个空的文件
    3. 会把FileOutputStream对象指向创建好的路径
package com.byxx.yunan.test.io;

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

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test8 {
    public static void main(String[] args) throws IOException {
        /*
           FileOutputStream(String name)
               String name: 构造方法中传递写入数据的目的地
         */
        //1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
        FileOutputStream fos1 = new FileOutputStream("D:\\log\\c.txt");
        //2.调用FileOutputStream对象中的方法write,把数据写入到文件中
        fos1.write("你好哇,HelloWorld".getBytes());
        //3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
        fos1.close();


        /*
           FileOutputStream(String name, boolean append)
               String name: 构造方法中传递写入数据的目的地
               boolean append: 追加写入开关
                       true: 创建对象不会覆盖原文件,继续在文件的末尾追加写入数据
                       false: 创建一个新文件,覆盖原文件
         */
        FileOutputStream fos2 = new FileOutputStream("D:\\log\\d.txt",true);//数据写入目的地,开启末尾追加写入数据
        for (int i = 0; i < 5; i++) {
            String str = "序号"+(i+1);
            fos2.write(str.getBytes());//字符转字节数组写入文件中
            fos2.write("\r\n".getBytes());//  \r\n 换行写入数据
        }
        fos2.close();

        /*
           d.txt中写入结果为:
                序号1
                序号2
                序号3
                序号4
                序号5
         */

    }
}
           

字节输入流 InputStream

InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中,它定义了字节输入流的基本共性功能方法

  • public void close() 关闭此输入流并释放与此流相关的任何系统资源
  • public abstract int read() 从输入流读取数据的下一个字节
  • public int read(byte[] b) 从输入流中读取一些字节数,并将他们存储到字节数组 b 中

FileInputStream类

  • FileInputStream extends InputStream
  • FileInputStream 文件字节输出流 (从文件中读取字节)
  • 构造方法
    1. FileInputStream(File file) 通过打开与实际文件的连接创建一个FileInputStream,该文件由文件系统中的File对象 file命名
    2. FileInputStream(String name) 通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名 name 命名
      package com.byxx.yunan.test.io;
      
      import com.sun.corba.se.spi.presentation.rmi.IDLNameTranslator;
      
      import javax.print.DocFlavor;
      import java.io.FileInputStream;
      import java.io.IOException;
      
      /**
       * FileInputStream extends InputStream
       * FileInputStream 文件字节输入流
       * 作用: 把硬盘文件中的数据,读取到内存中使用
       *
       * 构造方法:
       *      FileInputStream(String name)
       *      FileInputStream(File file)
       *      参数:读取文件的数据源
       *          String name: 文件的路径
       *          File file: 文件
       *      构造方法的作用:
       *          1.会创建一个FileInputStream对象
       *          2.会把FileInputStream对象指定构造方法中要读取的文件
       *
       * 读取数据的原理(硬盘-->内存)
       *      java程序 --> JVM --> OS操作系统 --> OS读取数据的方法 --> 读取文件
       *
       * 字节输入流的使用步骤(重点):
       *      1.创建FileInputStream对象,构造方法中绑定要读取的数据源
       *      2.使用FileInputStream对象中的方法read,读取文件
       *      3.释放资源
       */
      public class Test {
          public static void main(String[] args) throws IOException {
              FileInputStream fis = new FileInputStream("D:\\log\\d.txt");
              /*
                public abstract int read()
                读取文件中的一个字节并返回,读取到文件的末尾返回-1
                布尔表达式(len = fis.read()!=-1
                      1.fis.read() 读取一个字节
                      2.len = fis.read() 把读取到的字节赋值给变量len
                      3.(len = fis.read())!=-1 判断变量len是否不等于-1
              */
              int len = 0;
              while((len = fis.read())!=-1){
                  System.out.print((char)len);//abc
              }
              fis.close();//关闭流,释放资源
      
              //============================read(byte[] byte)=================================
              /*
                public int read(byte b[]) 从输出流中读取一些字节数,并将它们存储到字节数组 b 中
                使用byte[]数组作为缓冲区读取
               */
              FileInputStream fis2 = new FileInputStream("D:\\log\\d.txt");
              byte[] bytes = new byte[1024];
              int len2 = 0;
              while((len2 = fis2.read(bytes))!=-1){
                  //因为byte[]初始化长度为1024,所以会输出很多多余空格
                  System.out.println(new String(bytes));//abcdefg     ....
                  /*
                     public String(byte bytes[], int offset, int length)
                     将字节数组转String,从哪开始到哪结束
                   */
                  System.out.println(new String(bytes,0,len2));//不会输出多余空格 abcdefg
              }
              //关闭资源
              fis2.close();
      
          }
      }
                 

复制文件案例

package com.byxx.yunan.test.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @Author yww
 * @CreateTime 2021-02-25
 */
public class Test9 {
    public static void main(String[] args) throws IOException {
        /*
           实现文件复制需求:
              1.使用输入流FileInputStream读取原文件,转成字节数组
              2.使用输出流FileOutputStream读取获取到的字节数组
              3.使用输出流FileOutputStream将数据写入磁盘
         */
        File file = new File("D:\\log\\abc.jpg");
        if(file.exists()){//存在
            //输入流,读取文件
            FileInputStream fis = new FileInputStream(file.getAbsolutePath());

            //输出流,写入文件
            FileOutputStream fos = new FileOutputStream("F:\\abc.jpg");
            //1.第一种方式,单字节读取
            int len = 0;
            while((len = fis.read())!=-1){
                fos.write(len);//将字节输入流读取出来的数据写入输出流指定的文件中
            }

            //2.第二种方式,使用byte[]数组作为缓冲区读取 (效率高)
            byte[] bytes = new byte[1024];
            int len2 = 0;
            while ((len2 = fis.read(bytes))!=-1){
                //public void write(byte b[], int off, int len) offset 开始索引  count 转换的个数
                fos.write(bytes,0,len2);
            }

            fis.close();//关闭输入流
            fos.close();//关闭输出流

        }

    }
}
           

字符流

当使用字节流读取文本文件时,可能会有一个小问题,就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储,所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件

字符输入流 Reader

Reader 抽象类是用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法

  • public void close() 关闭此流并释放与此流相关联的任何系统资源
  • public int read() 从输入流读取一个字符
  • public int read(char[] cbuf) 从输入流中读取一些字符,并将它们存储到字符数组cbuf中

FileReader类

  • FileReader extends InputStreamReader , InputStreamReader extends Reader
  • FileReader 文件字符输入流 (把硬盘文件中的数据以字符的方式读取到内存中)
  • 构造方法
    1. FileReader(String fileName) 创建一个新的FileReader,给定要读取的文件的名称
    2. FileReader(File file) 创建一个新的FileReader,给定File读取
package com.byxx.yunan.test.io;


import java.io.FileReader;
import java.io.IOException;

/**
 * @Author yww
 * @CreateTime 2021-02-26
 */
public class Test10 {
    public static void main(String[] args) throws IOException {
        //字符输入流,将磁盘中的数据读取到内存中
        FileReader fr = new FileReader("D:\\log\\d.txt");

        /*
           第一种读取方式
              public int read()
         */
        int len1 = 0;
        while((len1 = fr.read())!=-1){
            System.out.print((char)len1);
        }

        /*
           第二种读取方式,效率高
              public int read(char cbuf[])
         */
        char[] cbuf = new char[1024];
        int len2 = 0;
        while((len2 = fr.read(cbuf))!=-1){
            //public String(char value[], int offset, int count) offset 开始索引  count 转换的个数
            System.out.print(new String(cbuf,0,len2));
        }

        //关闭资源
        fr.close();
    }
}
           

字符输出流 Writer

Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法

  • void write(int c) 写入单个字符
  • void write(char[] cbuf) 写入字符数组
  • abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
  • void write(String str) 写入字符串
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
  • void flush() 刷新该流的缓冲
  • void close() 关闭此流,但要先刷新它。

FileWriter

  • FileWriter extends OutputStreamWriter , OutputStreamWriter extends Writer
  • FileWriter 文件字符输出流 (将内存中的数据写入磁盘中)
  • 构造方法
    1. FileWriter(File file) 给一个File对象构造一个FileWriter对象
    2. FileWriter(File file, boolean append) 给一个File对象构造一个FileWriter对象
    3. FileWriter(String fileName) 构造一个给定文件名的FileWriter对象
    4. FileWriter(String fileName, boolean append) 构造一个FileWriter对象,给出一个带有布尔值的文件名,表示释放附加写入的数据
package com.byxx.yunan.test.io;

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

/**
 * @Author yww
 * @CreateTime 2021-02-26
 */
public class Test11 {
    public static void main(String[] args) throws IOException {
        /*
          字符输出流
          public FileWriter(String fileName, boolean append)
             String fileName  文件目的地
             boolean append  是否追加写入数据
         */
        FileWriter fw = new FileWriter("D:\\log\\e.txt",true);
        /*
          write()将数据填充到内存缓冲区
         */
        //void write(char[] cbuf) 写入字符数组
        char[] cs = {'A','B','C','D','E'};
        fw.write(cs);//ABCDE

        //void write(char[] cbuf, int off, int len) 写入字符数组的一部分,off数组的开始索引,len写的个数
        fw.write(cs,2,3);//CDE

        fw.write("\r\n");//换行

        //void write(String str) 写入字符串
        String str = "showme我的time ";
        fw.write(str);//showme我的time
        
        //void write(String str, int off, int len) 写入字符串的一部分,off数组的开始索引,len写的个数
        fw.write(str,8,4);//time


        //刷新缓冲区数据写入到文件中
        fw.flush();
        //关闭资源
        fw.close();
    }
}
           

flush()方法和close()方法的区别

  • flush() 刷新缓冲区,流对象可以继续使用
  • close() 先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

Properties类

Properties 继承于 HashTable,来表示一个持久的属性集,它使用键值结构存储数据,每个键及其对应值都是一个字符串,该类也被许多java类使用,比如获取系统属性时,getProperties() 方法就是返回一个Properties对象。

  • 构造方法
    • public Properties() 创建一个空的属性列表
  • 基本的存储方法
    • public Object setProperty(String key, String value) 保存一对属性
    • public String getProperty(String key) 使用此属性列表中指定的键搜索属性值
    • public Set stringPropertyNames() 获取所有key存入set集合
  • 流相关操作的方法
    • public void store(Write write, String comments) 将Properties集合中的数据以字符串输出流的方式写入到文件中
    • public synchronized void load(Reader reader) 将文件中的数据以字符串输入流的方法读取到集合中
    package com.byxx.yunan.test.io;
    
    import javafx.scene.chart.ValueAxis;
    
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.Properties;
    import java.util.Set;
    
    /**
     * Properties集合 extends HashTable集合
     *      HashTable特性:
     *          不允许空key,不允许空value
     *      Properties特性:
     *          1.key和value都是字符串
     *          2.不允许空key,不允许空value
     */
    public class Test12 {
        public static void main(String[] args) {
            store();
            load();
        }
    
        /**
         * 使用Properties类中的store(Write write,String comments)方法
         * 将集合中的数据存储到文件中
         */
        public static void store(){
            Properties pro = new Properties();
            pro.setProperty("张三","18岁");
            pro.setProperty("李四","19岁");
            pro.setProperty("王五","20岁");
    
            FileWriter fileWriter = null;
            try {
                //字符串输出流
                fileWriter = new FileWriter("D:\\log\\AAA.txt");
    
                /*
                   properties的store(Write write,String comments)
                   把集合中的数据,持久化写入硬盘中存储
                 */
                pro.store(fileWriter,"wo shi zhu shi");//Write:字符串输出流 comments:注释
    
    
    
            }catch (IOException e){
                e.printStackTrace();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try{
                    fileWriter.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 使用Properties类中的load(Reader reader)方法
         * 将文件中的数据以key,value的形式读取到集合中
         * 特点:
         *     使用#注释的不会读取到集合中
         *     使用 =  或者 空格 作为key,value标识
         * 示例:
         *     王五=20岁 或者 王五 20岁
         */
        public static void load(){
            Properties pro = new Properties();
            FileReader fileReader = null;
            try{
    
                /*
                   properties的load(Reader reader)方法
                   把硬盘中保存的文件(键值对),读到集合中使用
                 */
                fileReader = new FileReader("D:\\log\\AAA.txt");
                pro.load(fileReader);
    
                //获取properties集合中的数据
                Set<String> keys = pro.stringPropertyNames();
                for (String key : keys) {
                    String value = pro.getProperty(key);
                    System.out.println("key:"+key+"  value:"+value);
                }
    
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    fileReader.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    
    }
               
    • store方法将集合中的数据写入文件中效果示例:
Java基础总结Java三大版本JDK , JRE , JVMHelloWorld详解Java八大基础数据类型类型转换变量 ,常量 , 作用域运算符JavaDoc生成文档Switch 多选择结构循环结构Break ,Continue方法重载可变参数Math 数学工具类Random 随机数工具类数组Object ,Objects日期面向对象static 关键字详解抽象接口N种内部类异常System类StringBuilder循环集合数据结构File递归IO

缓冲流

缓冲流,也叫高校流,是对4个基本的FIleXxx流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流:BufferedInputStream,BufferedOutputStream
  • 字符缓冲流:BufferedReader,BufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率

字节缓冲流

  • 构造方法
    1. public BufferedInputStream(InputStream in) 创建一个新的缓冲字节输入流
    2. public BufferedOutputStream(OutputStream out) 创建一个新的缓冲字节输出流
package com.byxx.yunan.test.io;

import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.Buffer;

/**
 * @Author yww
 * @CreateTime 2021-02-28
 */
public class Test13 {
    public static void main(String[] args) throws IOException {
        bufferedInputStream();
        bufferedOutputStream();
    }


    /**
     * 字节缓冲流输入流BufferedInputStream(InputStream in)
     * @throws IOException
     */
    public static void bufferedInputStream() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\log\\a.txt"));
        //第一种方式,效率低,依次读取文件数据
        /*long startTime1 = System.currentTimeMillis();
        int len1 = 0;
        while((len1 = bis.read())!=-1){
            System.out.println(len1);
        }
        long endTime1 = System.currentTimeMillis();
        System.out.print(endTime1-startTime1+"毫秒");*/

        System.out.println("==========================缓冲=============================");
        //第二种方式,效率高,按数组大小一次性读取文件数据,做缓冲
        long startTime2 = System.currentTimeMillis();
        byte[] bytes = new byte[1024];
        int len2 = 0;
        while((len2 = bis.read(bytes))!=-1){
            System.out.print(new String(bytes,0,len2));
        }
        long endTime2 = System.currentTimeMillis();
        System.out.print(endTime2-startTime2+"毫秒");
        bis.close();
    }

    /**
     * 缓冲字节输出流BufferedOutputStream(OutputStream out)
     * @throws IOException
     */
    public static void bufferedOutputStream() throws IOException{
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\log\\ABC.txt"));
        bos.write("这是缓冲字节输出流".getBytes());
        bos.close();
    }

}
           

字符缓冲流

  • 构造方法
    1. public BufferedReader(Reader in) 创建一个新的缓冲字符输入流
    2. public BufferedWriter(Writer out) 创建一个新的缓冲字符输出流
package com.byxx.yunan.test.io;

import java.io.*;

/**
 * @Author yww
 * @CreateTime 2021-03-02
 */
public class Test14 {
    public static void main(String[] args) throws IOException {
        BufferedReader();
        BufferedWriter();
    }

    /**
     * 缓冲字符输入流:
     * BufferedReader extends Reader
     *
     * 构造方法:
     *      BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流
     *      BufferedReader(Reader in, int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流
     *
     * 特有方法:
     *      readLine() 读取一个文本行,如果已到达流末尾,则返回null
     *
     * @throws IOException
     */
    public static void BufferedReader() throws IOException {
        //1.创建一个缓冲字符输入流
        BufferedReader br = new BufferedReader(new FileReader("D:\\log\\d.txt"));
        //2.读取数据
        //第一种方式,单个字符读取,效率低
        /*int len1 = 0;
        while((len1 = br.read())!=-1){
            System.out.print((char)len1);
        }*/

        //第二种方式,创建缓冲数组,效率高
        /*char[] chars = new char[1024];
        int len2 = 0;
        while((len2 = br.read(chars))!=-1){
            System.out.println(new String(chars,0,len2));
        }*/

        //第三种方式,BufferedReader特有方法:readLine(),读取到结尾为null
        String len3;
        while((len3 = br.readLine())!=null){
            System.out.println(len3);
        }
        //3.关闭资源
        br.close();
    }

    /**
     * 缓冲字符输出流:
     * BufferedWriter extends Writer
     *
     * 构造方法:
     *      BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
     *      BufferedWriter(Writer out,int sz) 创建一个使用给定大小输出缓冲区的缓冲字符输出流
     *
     *  特有方法:
     *      newLine() 换行符
     * @throws IOException
     */
    public static void BufferedWriter() throws IOException {
        //1.创建缓冲字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\log\\f.txt"));
        //2.写入数据
        for (int i = 0; i < 10; i++) {
            bw.write("我是"+i);
            bw.newLine();//换行符
        }
        //3.将缓冲区的数据刷新到文件中
        bw.flush();
        //4.关闭资源
        bw.close();
    }
}
           

对文本内容进行排序

package com.byxx.yunan.test.io;

import com.sun.org.apache.bcel.internal.generic.NEW;

import java.io.*;
import java.util.Set;
import java.util.TreeMap;

/**
 * @Author yww
 * @CreateTime 2021-03-02
 */
public class Test15 {
    public static void main(String[] args) throws IOException {

        /**
         * 需求:
         * 将文件改为正常序号填充到文件中
         * 原文件
         * 2.我是第二行
         * 4.我是第四行
         * 1.我是第一行
         * 3.我是第三行
         */
        //创建缓冲输入流,读取数据
        BufferedReader br = new BufferedReader(new FileReader("D:\\log\\f.txt"));

        TreeMap<String, String> treeMap = new TreeMap<>();
        String len;
        while((len = br.readLine())!=null){
            String number = len.substring(0, 2);//序号
            String content = len.substring(2, len.length());
            System.out.println(number+"------"+content);
            treeMap.put(number,content);
        }
        System.out.println(treeMap);
        br.close();

        /**
         * 调整后文件
         * 1.我是第一行
         * 2.我是第二行
         * 3.我是第三行
         * 4.我是第四行
         */
        //创建缓冲输出流,写入数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\log\\g.txt"));
        Set<String> keySet = treeMap.keySet();
        for (String key : keySet) {
            String value = treeMap.get(key);
            bw.write(key+value);
            bw.newLine();
        }
        bw.close();
    }
}
           

转换流

输出转换流OutputStreamWriter

  • OutputStreamWriter extends Writer
  • OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。
  • 继承自父类的共性成员方法
    1. void write(int c) 写入单个字符
    2. void write(char[] cbuf) 写入字符数组
    3. abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数
    4. void write(String str) 写入字符串
    5. void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
    6. void flush() 刷新该流的缓冲
    7. void close() 关闭此流
  • 构造方法
    • OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter
    • OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter
  • 参数
    • OutputStream out : 字节输出流,可以用来写转换之后的字节到文件中
    • String charsetName : 指定的编码表名称,不区分大小写,可以是utf-8/gbk…编码,不指定默认使用UTF-8
package com.byxx.yunan.test.io;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

/**
 * @Author yww
 * @CreateTime 2021-03-03
 */
public class Test16 {
    public static void main(String[] args) throws IOException{
        OutputStreamWrite();
    }

    /**
     * 转换输出流
     * OutputStreamWrite extends Write
     * OutputStreamWrite 可以指定输出编码集
     *
     * 构造方法:
     *      OutputStreamWrite(OutputStream out) 创建使用默认字符编码的OutputStreamWrite
     *      OutputStreamWrite(OutputStream out,String charsetName) 创建使用指定字符集OutputStreamWrite
     *      参数:
     *          OutputStream out: 字节输出流,可以用来写转换之后的字节到文件中
     *          String charsetName: 指定的编码表名称,不区分大小写,可以使用utf-8,gbk...编码,不指定默认使用UTF-8
     */
    public static void OutputStreamWrite() throws IOException {
        /**
         * 不指定,默认使用UTF-8
         */
        long startTime1 = System.currentTimeMillis();
        //1.创建转换流对象
        OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("D:\\log\\yyyy.txt"));
        //2.写入数据
        for (int i = 0; i < 100000; i++) {
            osw1.write("你好,转换流OutputStreamWriter"+i);
        }
        //3.将缓冲数据刷新到文件中
        osw1.flush();
        //4.关闭资源
        osw1.close();
        long endTime1 = System.currentTimeMillis();
        System.out.println("使用缓冲字节输出流作为构造,运行 "+(endTime1-startTime1)+" 毫秒");

        /**
         * 指定编码集
         */
        long startTime2 = System.currentTimeMillis();
        //1.创建转换流对象,这次指定编码集
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:\\log\\aaaa.txt"),"utf-8");
        //2,写入数据
        for (int i = 0; i < 100000; i++) {
            osw2.write("你好,转换流OutputStreamWriter"+i);
        }
        //3.将缓冲区数据刷新到文件中
        osw2.flush();
        //4.关闭资源
        osw2.close();
        long endTime2 = System.currentTimeMillis();
        System.out.println("使用字节输出流作为构造,运行 "+(endTime2-startTime2)+" 毫秒");
    }
}
           

输入转换流InputStreamReader

  • InputStreamReader extends Reader
  • InputStreamReader 是字符流通向字符流的桥梁,它使用指定的 charset 读取字节并将其解码为字符。
  • 继承自父类的共性成员方法
    1. int read() 读取单个字符并返回
    2. int read(char[] cbuf) 一次读取多个字符,将字符读入数组
    3. void close() 关闭该流并释放与之关联的所有资源
  • 构造方法
    • InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader
    • InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader
  • 参数
    • InputStream in : 字节输入流,用来读取文件中保存的字节
    • String charsetName : 指定的编码表名称,不区分大小写,可以是utf-8/gbk…编码,不指定默认使用UTF-8
  • 注意事项
    • 构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码
package com.byxx.yunan.test.io;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @Author yww
 * @CreateTime 2021-03-03
 */
public class Test17 {
    public static void main(String[] args) throws IOException{
        InputStreamReader();
    }

    /**
     * InputStreamReader extends Reader
     * InputStreamReader 是字节流通向字符流的桥梁,它使用指定的 charset 读取字节并将其解码为字符。
     *
     * 构造方法:
     *      InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader
     *      InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader
     *      参数:
     *          InputStream in: 字节输入流,用来读取文件中保存的字节
     *          String charsetName: 指定的编码表名称,不区分大小写,可以是utf-8/gbk...编码,不指定默认使用UTF-8
     * 注意事项:
     *      构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码
     *
     * @throws IOException
     */
    public static void InputStreamReader() throws IOException {
        //不指定默认使用UTF-8
        //1.创建输入转换流
        InputStreamReader isr1 = new InputStreamReader(new FileInputStream("D:\\log\\aaaa.txt"));
        //2.读取数据
        int len1 = 0;
        while((len1 = isr1.read())!=-1){
            System.out.print((char)len1);
        }
        //3.关闭资源
        isr1.close();


        //指定字符编码集,UTF-8
        //1.创建输入转换流
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:\\log\\aaaa.txt"),"utf-8");
        //2.读取数据
        int len2 = 0;
        while((len2 = isr2.read())!=-1){
            System.out.print((char)len2);
        }
        //3.关闭资源
        isr2.close();
    }
}
           

序列化流

ObjectOutputStream类

ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

  • 构造方法
    1. public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。

ObjectInputStream类

ObjectInputStream 类,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

  • 构造方法
    1. public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream
package com.byxx.yunan.test.io;

import java.io.*;

/**
 * @Author yww
 * @CreateTime 2021-03-04
 */
public class Test18 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /**
         * 序列化输出流
         * 将对象写入文件中
         * ObjectOutputStream extends OutputStream
         */
        //1.创建ObjectOutputStream
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\log\\tttt.txt"));
        //2.将对象写入到文件中 (如果对象没有序列化,则会报java.io.NotSerializableException异常)
        oos.writeObject(new Survey("测试问卷",18));
        //3.释放资源
        oos.close();

        /**
         * 序列化输入流
         * 将序列化输出流写入文件的数据读取出来
         * ObjectInputStream extends InputStream
         */
        //1.创建ObjectInputStream
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\log\\tttt.txt"));
        //2.读取文件中数据
        Object o = ois.readObject();//读取一个对象
        Survey survey = (Survey) o;
        System.out.println(survey.getName());
        //3.关闭资源
        ois.close();

    }
}

//问卷类
class Survey implements Serializable {

    private String name;
    private Integer age;

    public Survey() {
    }

    public Survey(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
           
  • transient 瞬态关键字
    //使用transient关键字修饰不会被序列号
    private transient Integer ages;
               

打印流

PrintStream类

  • 构造方法
    1. public PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。
package com.byxx.yunan.test.io;

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

/**
 * 打印流
 * java.io.PrintStream extends OutputStream
 *      PrintStream 为其他输出流添加了功能,使它们能够方便的打印各种数据值表示形式
 * PrintStream特点:
 *      1.只负责数据的输出,不负责数据的读取
 *      2.与其他输出流不同,PrintStream永远不会抛出IOException
 *      3.有特有的方法,print,println
 *          void print(任意类型的值)
 *          void println(任意类型的值并换行)
 * 构造方法:
 *      PrintStream(File file) 输出的目的地是一个文件
 *      PrintStream(OutputStream out) 输出的目的地是一个字节输出流
 *      PrintStream(String fileName) 输出目的地是一个文件路径
 * 注意:
 *      如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
 *      如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
 */
public class Test19 {
    public static void main(String[] args) throws FileNotFoundException {
        //1.创建打印流
        PrintStream ps = new PrintStream("D:\\log\\BBBB.txt");
        //2.使用父类的write()方法写入数据,那么查看数据的时候会查询编码表 97->a
        ps.write(97);//打印a
        //3.使用自身print/println方法,写的数据原样输出 97->97
        ps.println(97);//打印97
        ps.println("abc");//打印abc
        ps.println("这是打印流");//打印 打印流
        //4.关闭资源
        ps.close();
        /*
           打印结果:
                a97
                abc
                这是打印流
         */
    }
}