基础语法

注释

对代码的解释和说明,其目的是让人们能够更加轻松地了解代码

  • 单行注释(// 注释内容):只能注释一行内容,用在注释信息内容少的地方

  • 多行注释(/* 注释内容 */):能注释很多行的内容。为了可读性比较好,一般首行和尾行不写注释信息(这样也比较美观好看)

  • 文档注释(/** 注释内容 */):一般用在类、方法和变量上面,用来描述其作用

数据类型

由于Java是强类型语言,即对每种数据都有其特定的数据类型,并对此分配了不同的内存空间

基本数据类型 占用内存大小 最小值 最大值
boolean 8 bits
char 16 bits \u0000(即为0) \uffff(即为65535)
byte 8 bits -128 127
short 16 bits -32768 32767
int 32 bits -231 +231-1
long 64 bits -263 +263-1
float 32 bits 1.4E-45 3.4028235E38
double 64 bits 4.9E-324 1.7976931348623157E308
  1. 这里的 E-45 表示的是 10的-45次方。float和double类型 都符合IEEE 754标准的浮点数。
  2. long类型的变量定义时,为了防止整数过大,一般需要在变量值后加L。float类型的变量定义时,为了防止类型不兼容,一般需要在变量值后加F

基本数据类型之间的转换

同种数据类型之间的转换比较的是内存空间大小

public class Test{
    public static void main(String args[]){
        // 整数的默认数据类型为 int
        int x = 1000;

        // 变量y的空间比变量x的空间大,所以发生自动类型转换
        long y = x; // 1000

        // 变量z的空间比变量x的空间小,所以发生强制类型转换
        byte z = (byte)x; // -24(数据溢出)
    }
}

整型和浮点型之间的转换比较的是精确程度

public class Test{
    public static void main(String args[]){
        // 小数的默认数据类型是 double
        double x = 5.0;

        // 变量x的精确程度比变量y的精确程度高,所以发生强制类型转换
        int y = (int)x;

        // 变量y的精确程度比变量z的精确程度低,所以发生自动类型转换
        int z = y;
    }
}

字符型有对应的Unicode编码,能转换为int

public class Test{
    public static void main(String args[]){
        // 常用字符对应的整数:字符 'a' -> 97,'A' -> 65,'0' -> 48
        int x = 97;

        // 整型转换为字符型,进行强制类型转换
        char y = (char)x;

        // 字符型转换为整型,发生自动类型转换
        int z = y;
    }
}

布尔型与其他基本数据类型之间不能发生转换,因此不存在 1 可以认为是 true 的说法

标识符

Java中标识符的命名规则

  • 包含数字、_(下划线)、字母和$(美元符号)。不能以数字开头,且由于$在特殊场合会使用,所以一般不会用来声明一个标识符

  • 不能以Java的关键字和保留字命名

  • 类名和接口名采用大驼峰式命名法(每个单词的首字母大写)、方法名和变量名采用小驼峰式命名法(第一个单词的首字母小写,之后单词的首字母大写)、常量名全部大写、包名全部小写

  • 标识符最好见名知意

关键字对编译器有特殊的意义,它们用来表示一种数据类型,或者表示程序的结构。保留字则是当前尚未使用,但可能未来会使用的关键字

常量和变量

常量,在程序运行过程中,栈内存中的变量内容不能被再次修改。通常使用final关键字修饰,一般用来表示某个固定的值和含义

变量,在程序运行过程中,栈内存中的变量内容能被再次修改。只能保存一个内容

public class Test{
    public static void main(String args[]){
        /*
        * 变量的声明(int x)和初始化(x = 5)
        */
        int x = 5;

        // 常量的标识符通常采用大写来表示
        final int X = 5;
    }
}

运算符

运算符用于执行程序代码运算,会针对两个或两个以上的操作数来进行运算。

算术运算符

一般包括 +(加)、-(减)、*(乘)、/(除)、%(取模)这几种。

public class Test{
	public static void main(String[] args){
		// 定义两个整数
		int a = 10;
		int b = 4;
		
		// 进行算术运算,并输出
		System.out.println(a + b);
		System.out.println(a - b);
		System.out.println(a * b);
		// 由于是两个整数的相除。在Java中,会将小数点给截断,即最终得到的结果为 2 ,而不是 2.5
		System.out.println(a / b);
		System.out.println(a % b);

		// 使用运算符 + ,如果遇到字符串,则会自动转换为字符串,即String类型
		System.out.println(a + "字符串");
	}
}

赋值运算符

=在Java中表示的是赋值的意思,即int a = 10; 解释为将字面值10赋值给int类型的变量a

public class Test{
	public static void main(String[] args){
		// 在Java中,只有在 -128 ~ 127 的整数之间都可以用byte类型接收
		byte x = 1;
		
		x += 2; //底层会进行强制类型转换,将2转换为byte类型
		System.out.println("x:" + x);
		
		//赋值操作符右边的 x+2 会自动进行类型提升,提升为int类型
		int y = x + 2;
		System.out.println("y:" + y);
	}
}

自增运算符和自减运算符

一般来说,很少会去关注是在前置还是后置,最终目的都是让该变量自增1或自减1。

public class Test{
	public static void main(String[] args){
            // 一元运算符除了可以获取变量的正负值,同时还可以将较小的数据类型转换为int型
            int x = +1;

            // 后置++,先赋值后运算
            x = x++; // 2

            // 前置++,先运算后赋值
            x = ++x; // 3
	}
}

关系运算符

一般包括 <(小于)、>(大于)、<=(小于或等于)、>=(大于或等于)、==(等于)、!=(不等于),其中 ==(等于)和 !=(不等于)适用所有的数据类型。

一般来说,对象之间适用 ==(等于)和!=(不等于)时,比较的是引用。但也有个别情况:

// Integer 存在一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以在该范围内的Integer对象比较与基本类型int相同
public class Equivalence {
    public static void main(String[] args) {
        Integer n1 = 47;
        Integer n2 = 47;
        Integer n3 = new Integer(47);
        System.out.println(n1 == n2);
        System.out.println(n1 != n2);
        System.out.println(n1 != n3); //true
    }
}

一般来说,对象之间的比较使用的是equals()方法,此种方法默认比较两个对象的引用。其实可以通过重写Object类中的该方法自定义比较方式

逻辑运算符

一般包括 &&(短路与)、||(短路或)、!(非)

public class Test{
	public static void main(String[] args){
		int i = 10;
		int j = 20;
		
		//短路与:遇到false,后续不执行
		boolean x = (i++ > 100) && (j++ > 100);
		System.out.println("i:" + i); //11
		System.out.println("j:" + j); //20
		
		
		//短路或:遇到true,后续不执行
		boolean y = (i++ > 100) || (j++ > 100);
		System.out.println("i:" + i); //12
		System.out.println("j:" + j); //21
	}
}

三元运算符

表达格式:布尔表达式 ? 值1 : 值2,当表达式计算为 true,则返回结果 值1 ;如果表达式的计算为 false,则返回结果 值2。

互换两个变量的值

public class Test{
	public static void main(String[] args){
            // 第一种方式:采用中间变量,会在栈内存中需要新建一个空间。此种方式最常用
            int a = 1,b = 2;
            int c = a;
            a = b;
            b = c;
	}
}
public class Test{
	public static void main(String[] args){
            // 第二种方式:进行加法运算,可能出现 越界(超出该数据类型的取值范围)
            int a = 1,b = 2;
            a = a + b;
            b = a - b;
            a = a - b;
	}
}
public class Test{
	public static void main(String[] args){
            // 第三种方式:利用位运算符的按位异或(^) 
            int a = 1,b = 2;
            a = a ^ b;
            b = a ^ b;
            a = a ^ b;
	}
}

流程控制语句

顺序结构

该结构时Java中最常见的一种结构,代码按照上下文从上到下依次执行

分支结构

  1. if语句
// 用户输入月份,控制台输出对应的季节
package moe;

import java.util.Scanner;

public class Session {
    public static void main(String[] args) {
        getSession();
    }

    public static void getSession() {
        //用户输入
        Scanner input = new Scanner(System.in);
        //保存用户输入的数字
        int i = input.nextInt();
        //根据用户输入的数字进行判断
        if (i < 1 || i > 12) {
            System.out.println(i);
            System.out.println("数据有误,请重新输入");
        // 通过else if进行多重判断
        } else if (i >= 3 && i <= 5) {
            System.out.println("春天");
        } else if (i >= 6 && i <= 8) {
            System.out.println("夏天");
        } else if (i >= 9 && i <= 11) {
            System.out.println("秋天");
        // 当前面的判断都为false时,执行最终判断结果
        } else {
            System.out.println("冬天");
        }
    }
}

  1. switch语句
// 多分支switch语句执行效率高,但只能做相等的判断
package moe;

import java.util.Scanner;

public class StudentScore {
    public static void main(String[] args) {
        getScoreLevel();
    }

    public static void getScoreLevel() {
        //输入成绩
        Scanner input = new Scanner(System.in);
        //保存成绩
        int i = input.nextInt();
        //判断成绩
        if (!(i < 0 || i > 100)) {
            switch (i/10){
                // i/10 等于 6 或 7 或 8 时执行代码
                case 6:
                case 7:
                case 8:
                    System.out.println("及格");
                    break;
                case 9:
                    System.out.println("优秀");
                    break;
                case 10:
                    System.out.println("太强了!");
                    break;
                // 上述判断都是false,则执行最终判断代码
                default:
                    System.out.println("不及格");
            }
        }
    }

}

循环结构

// 求2-100的素数(除1外,所有不被自身以外的数整除的数)
package moe;

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

public class PrimeNumber {
    public static void main(String[] args) {
        getPrimaryNumber(100);
    }

    public static void getPrimaryNumber(int num) {
        List<Integer> list = new ArrayList<Integer>();
        //标识
        boolean flag = true; //表示是素数
        for (int i = 2; i <= num; i++) {
            for (int j = 2; j < i; j++) {
                if (i % j == 0) {
                    flag = false; //表明不是素数
                    break;
                }
            }
            //保存素数
            if (flag) {
                list.add(i);
            } else {
                flag = true; //重置
            }
        }
        System.out.println(list);
    }
}

关键字 break 能跳出当前循环语句,而关键字 continue 则能借结束本次循环,开始下一次循环。

while语句 和 do-while语句 也是循环语句,只是两者用的比较少

数组

相同类型的元素(element)的集合所组成的数据结构,在堆内存中分配一块连续的地址空间来存储。利用索引(index)可以获得数组对应位置的元素

初始化

  1. 动态初始化:只指定数组长度,元素值由系统生成。格式:数据类型[] 变量名 = new 数据类型[数组长度]

  2. 静态初始化:只指定元素值,数组长度由系统生成。格式:数据类型[] 变量名 = new 数据类型[]{多个数据}。 简写:数据类型[] 变量名 = {多个数据}

数组的元素、类的属性,系统都会提供一个默认值:整型默认值为0,浮点型默认值为0.0,字符型默认值为0的Unicode编码,布尔型默认值为false,引用数据类型的默认值为null

常见小问题

  1. 数组越界异常(ArrayIndexOutOfBoundsException):访问数组了中不存在的索引对应的元素

  2. 空指针异常(NullPointException):引用没有指向一个对象

面向对象编程思想

"纯粹"的面向对象程序设计方法

  1. 万物皆对象。可以认为你说知道的任何实体都可以作为对象来使用。

  2. 程序是一组对象,通过消息传递来告知彼此该做什么。对象可以执行一些操作,这些操作由方法来定义

  3. 每个对象都有自己的存储空间,可容纳其他对象。每个对象之间可以建立组合或聚合的关系

  4. 每个对象都有一种类型。一个类可以创建多个对象实例

  5. 同一类所有对象都能接收相同的消息。类定义了属性和方法,这些都可以被该类的对象实例调用

  1. 面向对象编程(Object-Oriented Programming OOP)是一种编程思维方式和编码架构
  2. 面向对象的程序设计着重点在问题身上,通过问题中的元素来表示对象,从而解决问题。因此在阅读代码时,其实就是在阅读问题的解决方案

三大特性

面向对象编程有三大特性:封装、多态、继承。

封装

封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问。

通过如下四个权限修饰符定义外界对类内部的成员访问权限:

  1. public(公开) 表示任何人都可以访问和使用

  2. private(私有) 除了类本身和类内部的方法,外界无法直接访问

  3. protected(受保护) 表示同一个包的和存在继承关系的两个类可以访问外,其余都不能访问到

  4. 默认权限修饰符 上述三个不使用,则默认会使用它。表示只能访问同一个包下的

多态

多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。多态允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

// 父类:Animal、子类:Dog
public class Test{
    public static void main(String[] args){
        /*
        * new Dog():表示在堆内存中创建一个对象,并分配地址空间
        * Animal animal = new Dog():表示将该对象的引用赋值给animal变量
        */
        Animal animal = new Dog(); // 向上转型,将子类对象赋值给父类引用

        //向下转型:父类引用转化为子类
        Dog dog = (Dog) a;
    }
}

一般用于不能通过new创建对象的抽象类或接口中。其中需要注意的是:此时父类在进行属性调用时,会执行了静态绑定,即调用了自已的属性,而进行方法调用时,则执行了动态绑定,即只能调用new出来的那个对象的方法,本身(即父类)的方法是调用不了的

继承

继承可以使得子类具有父类的属性和方法,且可以进行在此基础上进行扩展。

继承的子类可以重写父类的方法,但是重写父类的方法有一些需要注意的:

  1. 子类重写的方法权限修饰符等级(public < protected < 包访问权限 < private)小于父类

  2. 子类不能重写父类中特征修饰符为final或static的方法

  3. 父类方法抛出编译时的异常,子类抛出异常的个数和类型要少于等于父类

super表示当前调用属性或方法的对象的父类。通常必须放在子类构造方法的第一行。

类是一个模板,它描述一类对象的行为和状态。

普通类

一个普通类拥有以下成员:属性、方法、程序块。

//包:用来管理java文件,通常写在第一行
package moe;

// 声明一个类,一般以public修饰
public class Person {
    private String userName; //属性

    /*
    * 构造方法:
    *       在使用 new关键字时会调用
    * 注意:系统默认会创建没有参数的构造方法(没有构造方法时)
    */
    public Person(String userName){
        //this关键字指调用该方法的对象,其中this可以作用在类的所有成员上
        this.userName = userName;
    }

    /*
    * 方法的组成:
    *       权限修饰符 [特征修饰符] 返回值 方法名(参数列表){ 方法体 }
    */
    public void callUser(){
        System.out.println(userName);
    }

    /*
    * 重载:
    *     方法的参数顺序 或 参数类型 或 参数个数 不同
    * 注意点:方法名要相同,权限修饰符、返回值这些可以不同
    */
    public void callUser(int number){
        System.out.println("这是" + number);
    }

    //程序块:只有方法体的方法
    {
        System.out.println("类加载时优先执行");
    }
}

抽象类

抽象类是在普通类的基础上添加了抽象方法的一个类。

package moe;

//一个抽象类,需要特征修饰符abstract修饰
public abstract class AbstractPerson {

    public int x = 5;

    /*
    * 抽象方法是没有方法体的,需要子类去实现
    * 注意:抽象类不一定必须有抽象方法,而有抽象方法的类一定是抽象类
    */
    public abstract void callPerson();

    public void test(){
        System.out.println("父类");
    }

}
package moe;

//extends关键字:用来表示两个类的继承关系
public class Boy extends AbstractPerson{

    public int x = 6;

    @Override //注解,用来标识该方法被重写
    public void callPerson() {
        System.out.println("实现该方法");
    }

    public void test(){
        System.out.println("子类");
    }

    //主方法,程序的入口
    public static void main(String[] args) {
        //需要通过多态的方式创建抽象类的对象
        AbstractPerson person = new Boy();
        //调用属性,使用静态绑定,调用的是引用的属性
        System.out.println(person.x); //5
        //调用方法,使用动态绑定,调用的是对象的方法
        person.test(); //子类
    }
}

接口

定义特定的请求,用子类来实现的一个抽象类型。

package moe;

/*
* 通过关键字interface来定义一个接口
* 注意:接口不是类,但它与类很相似
*/
public interface PersonInterface {
    /*
    * 接口的属性默认是以 public final static 来修饰的
    * 因此在接口定义属性时可以省略,即 int X = 5;即可
    */
    public final static int X = 5;

    /*
    * 接口的方法默认是以 public abstract 来修饰的
    * 因此在接口定义方法时可以省略,即 void callHello();
    */
    public abstract void callHello();
}
package moe;

//关键字implements表示实现关系
public class IPerson implements PersonInterface{

    @Override
    public void callHello() {
        System.out.println("子类");
    }

    public static void main(String[] args) {
        //接口与抽象类类似,需要通过多态的方式创建对象
        PersonInterface person = new IPerson();
    }
}

内部类

在一个类中定义另一个类,这另一个类被称为内部类。其中内部类既可作为类的内部成员,也可以存在方法内部(这种内部类被称为局部内部类)

public class Outer{

    private int x = 10;

    public class Inner{
        //内部类方法
        public void call(){
            //内部类可以直接调用外部类成员
            System.out.println(x);
        }
    }

    //外部类方法
    public void callInner(){
        Inner inner = new Inner();
        //外部类想使用内部类的方法,需创建内部类对象
        inner.call();
    }
}
public class Test{
    public static void main(String[] args){
        //外部获取内部类对象
        Outer.Inner inner = new Outer().new Inner();
        inner.call();

        // 匿名内部类
        new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("线程已启动");
           }
       }).start();
    }
}

包装类

秉承 “万物皆对象” 这一想法,将基本数据类型包装为对象,这些对象对应的类为包装类

基本数据类型 int short byte long float double char boolean
包装类 Integer Short Byte Long Float Double Character Boolean

自动装箱和自动拆箱

  1. 自动装箱:Integer number = 10;,将 基本数据类型 自动转换为 包装类

  2. 自动拆箱:int value = new Integer(10);,将 包装类 自动转换为 基本数据类型

字符串

String类

创建

public class StringTest{
    public static void main(String args[]){
        //字面值方式:String类在加载前会在字符串常量池中创建一个空间保存对象
        String str1 = "abc";
        String str2 = "abc";

        //使用构造方法方式:这种方式每次new时会在堆内存中创建一个空间来保存对象,其中参数可以有 字节数组、字符数组、字符串
        String str3 = new String("abc");
        String str4 = new String("abc");
        System.out.println(str1 == str2);
        System.out.println(str1 == str3);
        System.out.println(str1 == str4);
        }
    }
}

常用方法
{% note primary %}
根据索引获取

public class Test{
    public static void main(String[] args){
        String str = "abc";
        // 根据索引获取char值
        char x = str.charAt(0); 

        //根据索引获取char值对应的Unicode编码
        int y = str.codePointAt(0); 
    }
}

字符串拼接

public class Test{
    public static void main(String[] args){
        String str = "a";
        //当前时间的毫秒值
        long oldTime = System.currentTimeMillis();
        //通过循环测试 "+" 和 concat()方法的效率
        for (int i = 0; i < 200000000; i++) {    
            // 1. str += "a"; 会在堆内存中创建新的对象 
            str.concat("a"); //只是在当前字符串的基础商拼接,不会创建新对象
        }
        //循环后时间的毫秒值
        long newTime = System.currentTimeMillis();

        System.out.println(newTime-oldTime);
    }
}

经测试,concat()方法的效率高,但 只适用于字符串之间的拼接

长度和包含

public class Test{
    public static void main(String[] args){
        String str = "abc";
        //获得字符串的长度
        int length = str.length(); 

        //判断给定字符串是否在字符串内
        boolean ab = str.contains("ab"); 
    }
}

开头和结尾的字符串判断

public class Test{
    public static void main(String[] args){
        String str = "abc";
        //判断字符串是否以给定参数的字符串为开头
        boolean a = str.startsWith("a"); 

    //判断字符串是否以给定参数的字符串为结尾
        boolean c = str.endsWith("cx"); 
    }
}

字符数组和字节数组的转化

public class Test{
    public static void main(String[] args){
        String str = "abc";
        //将字符串转化为字节数组。中文字符串转化不建议
        byte[] bytes = str.getBytes();

        //将字符串转化为字符数组
        char[] chars = str.toCharArray(); 
    }
}

寻找指定字符串的位置

public class Test{
    public static void main(String[] args){
        String str = "abc";
        //两个方法存在重载,主要将第一个参数改为int型,表示通过数字对应的Unicode编码中的字符查询
        //从起始位置(索引为0)寻找,找到字符串中第一次出现给定参数的索引位置(索引值)
        int index = str.indexOf("b"); 

        //给定起始位置的索引(2),找到字符串中第一次出现给定参数的索引位置(索引值)
        int index1 = str.indexOf("b",2); 
        System.out.println(index1); //返回-1表示找不到

        //下面方法分别与indexOf()方法和其重载方法类似,不同在于该方法是从结尾位置(索引为字符串.length()-1)寻找
        int index2 = str.lastIndexOf("b");

        int index3 = str.lastIndexOf("b",3);
    }
}

判断字符串是否为空字符串

public class Test{
    public static void main(String[] args){
        String str = "abc";
        //判断字符串是否为空字符串
        boolean x = str.isEmpty();
    }
}

字符串替换

public class Test{
    public static void main(String[] args){
        String str = "abc";
        //常使用参数为字符串的重载方法

        //将字符串中的给定字符串替换成需要替换的字符串
        str = str.replace("a","b"); //替换所有给定的字符串

        str = str.replaceAll("b","a"); //替换所有的给定字符串

        str = str.replaceFirst("a","c"); //只替换第一个出现的给定字符串
    }
}

分割字符串

public class Test{
    public static void main(String[] args){
        String string = "a-b-c";
        //split(String regex[,int limit]),按照给定的表达式(regex)拆分字符串,返回一个字符串数组。limit表示拆分的个数
        String[] value = string.split("-",2);
    }
}

截取字符串

public class Test{
    public static void main(String[] args){
        //substring(int beginIndex[,int endIndex]),按照给定的开始位置截取字符串到结尾位置,返回一个字符串
        String string = "abcdefg";
        String substring = string.substring(2,5); //截取的索引位置 -> [2,5)
    }
}

字符串大小写转换

public class Test{
    public static void main(String[] args){
        String string = "abcdefg";
        //将字符串全部转换为大写
        string = string.toUpperCase();

        //将字符串全部转换为小写
        string = string.toLowerCase();
    }
}

去除前后空格

public class Test{
    public static void main(String[] args){
        String string = "  abcdefg ";
        //将字符串前后空格给去掉
        string = string.trim();
    }
}

转换为字符串

public class Test{
    public static void main(String[] args){
        int x = 5;
        String value = String.valueOf(x);
    }
}

StringBuilder类

String类和StringBuilder类的互换

public class Test{
    public static void main(String[] args){
        String str = "abc";
        //转化为StringBuilder类
        StringBuilder sbr = new StringBuilder(str); 

        //转换为String类
        String str1 = sbr.toString();
    }
}

StringBuilder类底层数组默认长度为16

常用方法
{% note primary %}
字符串拼接

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");

        //拼接字符串,不会产生新对象。效率高
        sbr.append("a"); //abcdefga
    }
}

删除字符串

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //delete(int start,int end)方法可以指定位置,将部分字符串删除
        sbr.delete(0,3);//[0,3)

        //deleteCharAt(int index)方法,删除字符串对应索引位置的字符
        sbr.deleteCharAt(0);
    }
}

插入和反转字符串

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //insert(int offset,value[任意数据类型])方法可以在字符串的任意位置添加数据
        sbr.insert(3,"cb");

        //reverse()可以将字符串反转
        sbr.reverse();
    }
}

字符串截取

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");

        //substring(int start[,int end])方法返回一个String类型的值,需要一个返回值接收
        String value = sbr.substring(0,3);
    }
}

寻找字符串

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //indexOf(String str[,int fromindex])和lastIndexOf(String str[,int fromindex]方法与String类相同
        int index = sbr.indexOf("a");
    }
}

替换字符串

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //replace(int start,int end,String str),将部分字符串替换为给定字符串
        sbr.replace(1,4,"亚丝娜"); //[1,4)
    }
}

{% endnote %}

不常用方法
{% note primary %}
修改指定位置的字符

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //setCharAt(int index,char ch)方法,修改指定位置的字符
        sbr.setCharAt(3,'h');
    }
}

确认底层数组有效长度

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //capacity()确定底层数组的有效长度
        int capacity = sbr.capacity(); //7+16
    }
}

字符串长度,忽略底层

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //length()方法确定当前字符串有效长度
        int length = sbr.length();
    }
}

设置底层数组有效个数

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //setLength()方法设置当前字符串有效个数
        sbr.setLength(20);
    }
}

去除底层数组无用的空间

public class Test{
    public static void main(String[] args){
        StringBuilder sbr = new StringBuilder("abcdefg");
        //根据length去掉底层数组无用的空间
        sbr.trimToSize();
    }
}

{% endnote %}

StringBuilder类和StringBuffer类不同的地方

  1. StringBuffer类在jdk1.0出现,是 线程同步安全性比较高,执行效率比较慢
  2. StringBuilder类在jdk1.5出现,是 线程非同步安全性比较低,执行效率比较快

集合

变量只能存储一个内容,数组只能存储固定长度的内容。而集合可以存储不定长度的内容。

Collection集合

一个接口,其常用的实现接口有List、Set,所存储的数据一组value值对象

List接口

常用实现类有ArrayList(适合遍历)LinkedList(适合插入和删除),为 有序可重复

有序指的是存进去的元素的顺序,取出来时也是相同的顺序

ArrayList类

创建

public class Test{
    public static void main(String[] args){
        //底层数组长度默认为10
        ArrayList<String> al = new ArrayList<String>();

        //指定底层数组的长度12
        ArrayList<String> al1 = new ArrayList<String>(12);
    }
}

{% note primary %}
泛型

  1. 在定义时用 <类>来表示,在使用时可以 指定所使用的引用数据类型

  2. 可以用在类、接口、方法中,都是在不清楚使用时需要什么数据类型而使用的

  3. 一种高级形式:<? extend E>(表示使用时可以是E或者是继承E的子类) 和 <? super E>(表示使用时可以是E或者是E继承的父类)

常用方法
{% note primary %}
增加元素

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        //add(E element),在末尾添加元素
        al.add(1);
        al.add(2);
        al.add(3);

        //add(int index,E element),将索引为2的元素移后一位,并添加元素"jkl"
        al.add(2,"jkl");
    }
}

删除元素

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(1);
        al.add(2);
        al.add(3);

        //remove(int index)删除指定位置的元素
        al.remove(0);
    }
}

修改元素

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(1);
        al.add(2);
        al.add(3);

        //set(int index,Object o),修改指定位置的元素
        al.set(2,5);
    }
}

查元素

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(1);
        al.add(2);
        al.add(3);

        //get(int index),获取指定位置元素
        int x = al.get(1);
    }
}

清空集合中的所有元素

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(1);
        al.add(2);
        al.add(3);

        //clear(),清空集合中的所有元素
        al.clear();
    }
}

确认集合

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(1);
        al.add(2);
        al.add(3);

        //contains(E e),确定参数是否在集合中
        boolean x = al.contains("10");

        //isEmpty(),确认当前集合中是否没有元素
        boolean x = al.isEmpty();

        //ensureCapacity(int minCapacity),确认指定的最小容量是否大于当前数组容量,如果是则进行扩容,即扩容为10
        al.ensureCapacity(10);
    }
}

并集、差集和交集

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(1);
        al.add(2);
        al.add(3);
        al.add(4);
        al.add(5);
        ArrayList<Integer> al2 = new ArrayList<Integer>();
        al2.add(6);
        al2.add(7);

        //addAll(Collect<? extends E> c),将集合的全部添加到另一个集合中(并集),返回值为boolean
        al.addAll(al2);

        //addAll(int index,Collect<? extends E> c),将集合的全部添加到另一个集合的某个位置中,返回值为boolean
        al.addAll(1,al2);

        //removeAll(Collection<?> c),差集,删除al集合中存在al2集合的元素,返回值为boolean
        al.removeAll(al2);

        //retainAll(Collection<?> c),交集,获得al集合和al2集合中相同的元素,返回值为boolean
        al.retainAll(al2);
    }
}

元素个数

public class Test{
    public static void main(String[] args){
        ArrayList<String> al = new ArrayList<String>();
        //由于size()方法动态发生改变,所以下面的方式不能删除所有元素
        al.add("abc");
        al.add("def");
        al.add("ghi");

        /*
        for (int i = 0; i < al.size(); i++) {
            //剩下def没有删除
            al.remove(i); 
        }
        */

        //通过这种方式可以将元素全部删除
        int size = al.size();
        for (int i = 0; i < size; i++) {
            al.remove(0);
        }
    }
}

其余

public class Test{
    public static void main(String[] args){
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(1);
        al.add(2);
        al.add(3);
        al.add(4);
        al.add(5);
        
        //indexOf(Object o),lastIndexOf(Object o)参数在集合中出现的第一次时的索引
        int index = al.indexOf(2);

        //subList(int fromIndex,int toIndex),获取al集合中[0,2)索引范围的元素,返回值为以一个List集合
        List<Integer> list = al.subList(0,2);

        //toArray(T[] a),Object - toArray()的重载,给定一个空数组,将集合中的元素转移到这个空数组中
        Integer[] i = new Integer[al.size()];
        al.toArray(i);

        //trimToSize(),根据当前size个数删除底层数组多余的位置
        al.trimToSize();
    }
}

{% endnote %}

封装ArrayBox,类似ArrayList类底层

/**
 * 数组的长度一旦确定,就不能改变,这对于添加元素,删除元素等操作会很不友好
 * 设计一个类,这个类能存储用户需要添加的元素,还能删除元素、查看元素和查看元素个数
 */
public class ArrayBox<E> {

    private Object[] elementData;
    private int size = 0;

    //初始化数组长度
    public ArrayBox(){
        elementData = new Object[10];
    }
    public ArrayBox(int length){
        elementData = new Object[length];
    }

    //判断当前数组长度
    private void ensureLength(int number){
        //如果当前数组长度小
        if(number - elementData.length > 0){
            //扩充当前数组
            this.grow(number);
        }
    }

    //扩充当前数组
    private void grow(int number){
        //获取当前数组的长度
        int oldLength = elementData.length;
        //适当增加数组长度
        int newLength = oldLength + (oldLength >> 1);
        //如果依然不行,则沿用number作为新数组长度
        if(number - newLength > 0){
            //将number作为新数组长度
            newLength = number;
        }
        //将原有数组中的元素添加到新数组中
        elementData = this.copy(elementData,newLength);
    }

    //转移数组的元素
    private Object[] copy(Object[] oldArray, int number){
        //创建一个新数组
        Object[] newArray = new Object[number];
        //将原来数组中的元素添加到新数组中
        for(int i = 0; i < oldArray.length; i++){
            newArray[i] = oldArray[i];
        }
        return newArray;
    }

    //判断index是否在数组范围内
    private void rangeCheck(int index){
        if(index < 0 || index >= size){
            //如果index不在数组范围内,则抛出异常
            throw new BoxIndexOutIfBoundsException("当前数组有效元素长度:" + (size-1));
        }
    }
    //添加元素
    public void add(E element){
        //判断当前数组长度
        this.ensureLength(size+1);
        //将元素添加到新数组中
        elementData[size++] = element;
    }
    //获取元素
    public E get(int index){
        //判断index是否在数组索引范围内
        this.rangeCheck(index);
        //如果index在范围内
        return (E) elementData[index];
    }
    //删除元素
    public E remove(int index){
        //判断index是否在数组范围内
        this.rangeCheck(index);
        //如果在数组范围内
        //保存要删掉的数组元素
        E oldValue = (E) elementData[index];
        for(int i = index; i <= size-1; i++){
            elementData[i] = elementData[i+1];
        }
        elementData[--size] = 0;
        return oldValue;
    }
    //获取数组有效元素个数
    public int size(){
        return this.size;
    }
}
/*异常,当index不在数组范围时抛出*/
public class BoxIndexOutIfBoundsException extends RuntimeException {
    public BoxIndexOutIfBoundsException(){}
    public BoxIndexOutIfBoundsException(String msg){
        super(msg);
    }
}

LinkedList类

方法与ArrayList类基本相同

封装LinkedBox类,与LinkedList底层类似

package box;
/*保存数据的节点*/
public class Node {
    //上一个节点
    private Node prev;
    //数据
    private int data;
    //下一个节点
    private Node next;

    public Node(Node prev, int data, Node next) {
        this.prev = prev;
        this.data = data;
        this.next = next;
    }

    public Node getPrev() {
        return prev;
    }

    public void setPrev(Node prev) {
        this.prev = prev;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}
package box;
/*box索引越界异常*/
public class BoxIndexOutOfBoundsException extends RuntimeException{
    public BoxIndexOutOfBoundsException(){}
    public BoxIndexOutOfBoundsException(String msg){
        super(msg);
    }
}
package box;

/*一个长度不固定的类数组,可以插入、删除和遍历元素,由于插入、删除没有用到了循环,所以执行效率会比较快,但是遍历效率不好*/
public class LinkedBox{

    //首节点
    private Node firstNode;
    //尾节点
    private Node lastNode;
    //有效元素个数
    private int size;

    //添加元素
    private void addElement(int element){
        //获取尾节点
        Node last = lastNode;
        //添加元素到节点中
        Node newNode = new Node(last,element,null); //Node newNode = new Node(null,element,null);
        //将新节点作为尾节点
        lastNode = newNode; //*
        //检查当前节点
        if(last == null){
            //如果尾节点为null,说明没有节点,将新节点作为首节点
            firstNode = newNode; // Node newNode = new Node(null,element,null) == firstNode
        }else{
            //如果尾节点不为null,说明有节点,将新节点作为尾节点的下一个节点
            last.setNext(newNode);
        }
    }
    //判断index是否在数组索引范围内
    private void check(int index){
        if(index < 0 || index >= size){
            throw new BoxIndexOutOfBoundsException("index:" + index + ",size:" + size);
        }
    }
    //获取index对应的节点
    private Node getNodeByIndex(int index){
        //声明目标节点
        Node targetNode;
        //判断索引大概在节点的那个部分
        if(index < (size >> 1)){
            //如果在前半部分
            targetNode = firstNode;
            for (int i = 0; i < index; i++) {
                targetNode = targetNode.getNext();
            }
        }else{
            //否则在后半部分
            targetNode = lastNode;
            for (int i = size-1; i > index; i--) {
                targetNode = targetNode.getPrev();
            }
        }
        return targetNode;
    }
    //根据目标节点删除元素
    private int removeByIndex(Node targetNode){
        //获取节点中的元素
        int oldValue = targetNode.getData();
        //获取目标节点的前一个和后一个节点
        Node prev = targetNode.getPrev();
        Node next = targetNode.getNext();
        //判断前一个节点是否为空
        if(prev == null){
            firstNode = next;
        }else{
            prev.setNext(next);
            targetNode = null;
        }
        //判断后一个节点是否为空
        if(next == null){
            lastNode = prev;
        }else{
            next.setPrev(prev);
        }
        //有效元素个数减一
        size--;
        return oldValue;
    }


    //添加元素
    @Override
    public boolean add(int element) {
        //添加元素
        this.addElement(element);
        //有效元素个数加一
        size++;
        //添加元素成功
        return true;
    }

    //获取元素
    @Override
    public int get(int index) {
        //判断index是否在数组索引范围内
        this.check(index);
        //获取index对应的节点
        Node targetNode = this.getNodeByIndex(index);
        //返回当前节点的数据
        return targetNode.getData();
    }

    //删除元素
    @Override
    public int remove(int index) {
        //检查index是否在数组范围内
        this.check(index);
        //获取要删除元素的节点
        Node targetNode = this.getNodeByIndex(index);
        //删除元素
        int oldValue = this.removeByIndex(targetNode);
        return oldValue;
    }

    //获取有效元素个数
    @Override
    public int size() {
        return size;
    }
}

特征修饰符

  1. 关键字final 修饰的 普通方法、类、成员变量 是不允许改变的,故重写、继承、修改变量值这些操作是不行的

  2. 关键字static 修饰的普通方法、程序块、成员变量 会在项目运行时加载到静态存储空间中,且只会有一份

静态的普通方法和程序块只能访问静态成员

Set接口

常用实现类有HashSet,为 无序不可重复

HashSet

public class Test {
    public static void main(String[] args){
        //HashSet类创建通常使用无参数的构造方法,当然也有将Collection接口实现的子类转化为HashSet
        HashSet<String> hashSet = new HashSet<String>();

        //由于HashSet是无序的,所有没有set()方法,并且不能通过索引添加、删除、获取元素等
        hashSet.add("a");
        hashSet.remove("a");

        //HashSet类一般使用迭代器获取元素,且我们使用的增强for循环(java1.5出现)底层其实就是实现了迭代器
        Iterator<String> iterable = hashSet.iterator();
        //Iterator接口常使用两个方法寻找元素和获取元素
        while(iterable.hasNext()){
            String value = iterable.next();
            System.out.println(value);
        }
    }
}

{% note info %}

  1. HashSet类底层是HashMap,数据结构是散列表(或 临接链表:数组+链表

  2. HashSet的底层使用hash算法进行排序,故我们存进去时的元素顺序与取出来时的元素顺序不一致,所以无序

public class Test {
    public static void main(String[] args){
        HashSet<String> hashSetString = new HashSet<String>();

        //5个重复字符串的String对象
        hashSetString.add(new String("亚丝娜"));
        hashSetString.add(new String("亚丝娜"));
        hashSetString.add(new String("亚丝娜"));
        hashSetString.add(new String("亚丝娜"));
        hashSetString.add(new String("亚丝娜"));

        //可以看出,只有一个String对象存储进集合中了
        System.out.println(hashSetString.size()); //1
    }
}

HashSet的常用方法与 ArrayList 类似

通过重写equals()和hashCode()方法,使HashSet判定对象重复

package collection.set;

import java.util.Objects;

public class Person {
    private String name;

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

    public Person() {
    }

    //通过重写equals()和hashCode()方法,可以判断当两个对象name属性一样时,视为同一个对象
    public boolean equals(Object o) {
        //比较地址,如果地址相同,返回true
        if (this == o) {
            return true;
        }
        if(o instanceof Person){
            //将Object类型的转化为Person类型
            Person anotherPerson = (Person)o;
            //比较name属性,如果相同,返回true
            if(anotherPerson.name.equals(this.name)){
                return true;
            }
        }
        //如果o不是Person类或Person的子类,则返回false
        return false;
    }

    public int hashCode() {
        return this.name.hashCode();
    }

    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(this.name);
        return stringBuilder.toString();
    }
}
public class Test {
    public static void main(String[] args){
        HashSet<Person> hashSetPerson = new HashSet<Person>();
        hashSetPerson.add(new Person("亚丝娜"));
        hashSetPerson.add(new Person("亚丝娜"));
        hashSetPerson.add(new Person("亚丝娜"));
        hashSetPerson.add(new Person("亚丝娜"));
        hashSetPerson.add(new Person("亚丝娜"));

        //可以看出,5个Person对象都被存储进集合了
        System.out.println(hashSetPerson.size()); //5

        //当Person类重写了equals()和hashCode()方法后
        System.out.println(hashSetPerson.size()); //1

        //通过重写toString()方法可以按照自已定义的方式输出对象
        System.out.println(hashSetPerson);
    }
}

通过重写compareTo()方法,使TreeSet判定对象重复

package collection.set;

import java.util.Objects;

public class Person implements Comparable<Person>{
    private String name;

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

    public Person() {
    }

    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(this.name);
        return stringBuilder.toString();
    }

    public int compareTo(Person person) {
        //如果compareTo()比较的结果为0,则说明相同
        return this.name.compareTo(person.name);
    }
}
public class Test {
    public static void main(String[] args){
        TreeSet<Person> treeSetPerson = new TreeSet<Person>();
        treeSetPerson.add(new Person("亚丝娜"));
        treeSetPerson.add(new Person("亚丝娜"));
        treeSetPerson.add(new Person("亚丝娜"));
        treeSetPerson.add(new Person("亚丝娜"));
        treeSetPerson.add(new Person("亚丝娜"));

        //可以看出,5个Person对象都被存储进集合了
        System.out.println(hashSetPerson.size()); //5
        
        //当Person类重写了compareTo()方法后
        System.out.println(treeSetPerson.size()); //1
        
        //通过重写toString()方法可以按照自已定义的方式输出对象
        System.out.println(treeSetPerson);
    }
}

TreeSet 是 Set接口 的一个实现类

Map集合

键值对形式存储,其中 键(key)是无序无重复 的,值(value)是无序可重复的

常见的实现类有HashMap、TreeMap、Properties这三个类,其中Properties类主要用来读取文件中的信息

HashMap

常用方法
{% note primary %}
增删改查

public class Test{
    public static void main(String[] args){
        //通常使用无参数的构造方法创建HashMap对象,默认长度为16,当存储元素个数在75%左右时,以0.75倍进行扩容
        HashMap<String,String> hashMap = new HashMap<String,String>();

        //put(K k,V v)方法将一对映射存储进集合中,返回一个value值
        hashMap.put("1","a");
        hashMap.put("2","b");
        hashMap.put("3","c");
        hashMap.put("1","d");
        hashMap.put("4","b");
        //可以看出:key相同,则后出现的覆盖前面的;key不相同,但value值可以相同
        System.out.println(hashMap); //{1=d, 2=b, 3=c, 4=b}

        //remove(K k)方法通过key找到value值,并将这对映射删除,返回一个value值
        hashMap.remove("3");
        System.out.println(hashMap); //{1=d, 2=b, 4=b},如果参数为集合中不存在的key值,不进行删除

        //replace(K k,V v)方法通过key值找到value值,并将value值修改,返回一个value值
        hashMap.replace("1","a");
        System.out.println(hashMap); //{1=a, 2=b, 4=b},如果如果参数为集合中不存在的key值,不进行修改

        //get(K k)方法通过key寻找到value值,并返回该value值
        String value = hashMap.get("2");
        System.out.println(value); //"b",如果参数为集合中不存在的key值,返回null
    }
}

遍历

public class Test throws ParseException{
    public static void main(String[] args){
        HashMap<String,String> hashMap = new HashMap<String,String>();
        hashMap.put("1","a");
        hashMap.put("2","b");
        hashMap.put("3","c");
        hashMap.put("1","d");
        hashMap.put("4","b");

        //1.ketSet()方法能获取所有的key,返回值为一个Set集合
        Set<String> keys = hashMap.keySet();

        //2.通过迭代器遍历keys
        Iterator<String> iterator = keys.iterator();
        while(iterator.hasNext()){
            String key = iterator.next();
            //获取每个key对应的value值
            String value = hashMap.get(key);
            System.out.println(key + "--" + value);
        }
    }
}

剩余方法

public class Test throws ParseException{
    public static void main(String[] args){
        HashMap<String,String> hashMap = new HashMap<String,String>();
        hashMap.put("1","a");
        hashMap.put("2","b");
        hashMap.put("3","c");
        hashMap.put("1","d");
        hashMap.put("4","b");

        //clear()方法可以将一个集合清空
        hashMap.clear();
        System.out.println(hashMap); //{}

        //putAll(Map map)方法可以将Map集合下的子类添加到HashMap中
        HashMap<String,String> hashMap1 = new HashMap<String,String>();
        hashMap1.put("1","a");
        hashMap1.put("2","b");
        hashMap1.put("3","c");
        hashMap1.put("4","d");
        hashMap.putAll(hashMap1);
        System.out.println(hashMap); //{1=a, 2=b, 3=c, 4=d},如果另一个集合中存在相同的key,会覆盖集合中的key对应的value值

        //putIfAbsent(K k,V v)方法如果集合中有key,则不进行覆盖;如果集中没有key,则添加
        hashMap.putIfAbsent("1","e");
        hashMap.putIfAbsent("5","e");
        System.out.println(hashMap); //{1=a, 2=b, 3=c, 4=d, 5=e}

        //getOrDefault(K k,V defaultValue),如果在集合中找到key,则返回key对应的value值,否则返回默认值
        System.out.println(hashMap.getOrDefault("1","f")); //"a"
        System.out.println(hashMap.getOrDefault("6","f")); //"f"

        //isEmpty()方法判断当前集合是否为空
        boolean x = hashMap.isEmpty();
        System.out.println(x); //false

        //size()方法返回当前集合的有效个数
        int size = hashMap.size();
        System.out.println(size); //5

        //containsKey(K k)方法寻找集合中是否存在key
        boolean y = hashMap.containsKey("1"); 
        System.out.println(y); //true

        //containsValue(V v)方法寻找集合中是否存在value
        boolean z = hashMap.containsValue("a");//true,即使有多个相同的value值,也返回true
        System.out.println(z);
    }
}

{% endnote %}

HashMap底层

public class Test throws ParseException{
    public static void main(String[] args){
        HashMap<String,String> hashMap = new HashMap<String,String>();
        hashMap.put("1","a");
        hashMap.put("2","b");
        hashMap.put("3","c");
        hashMap.put("1","d");
        hashMap.put("4","b");

        //HashMap底层是一个散列表
        /**
        * 不同对象的hashCode码可能会出现相同的
        * 不同的hashCode码一定表示不同的对象
        * 散列表表现为数组+链表,其中数组存储不同的hashCode码,当出现相同的hashCode码时,在当前数组的这个位置上连接一个节点
        */
        //底层中的节点名为Entry
        //entrySet()方法获得Set集合,里面存储一组Entry对象,包含key--value
        Set<Map.Entry<String, String>> entries = hashMap.entrySet();

        //通过迭代器遍历
        Iterator<Map.Entry<String, String>> iterator = entries.iterator();

        while(iterator.hasNext()){
            //获得每一个Entry对象
            Map.Entry<String, String> next = iterator.next();
            String key = next.getKey();//获取节点中的key
            String value = next.getValue(); //获得节点中的value
            System.out.println(key + "--" + value);
        }
    }
}

集合的使用
{% note info %}
存储一组元素:如果长度固定,使用数组,反之使用集合

使用集合时

  1. 有序时,使用List集合,其中ArrayList适合遍历,LinkedList适合插入和删除

  2. 无重复时,使用Set集合,其中HashSet顺序不清楚,但性能高(底层是数组),TreeSet能通过Unicode编码自动排序

  3. 需要使用键值对时,使用Map集合,其中HashMap顺序不清楚,但性能高,TreeMap能通过Unicode编码自动排序

TreeMap底层是一个 红黑二叉树(jdk1.8),key 根据Comparable接口中的CompareTo()方法定义进行 自然排序;同时常用方法与 HsahMap类似

集合练习

学生登录考试机进行考试,老师进行批卷

package exam;

/**
 * 考试题目
 */
public class Question {
    private String title; //考试题目
    private String answer; //考试答案

    //初始化考试题目
    public Question(){}
    public Question(String title,String answer){
        this.title = title;
        this.answer = answer;
    }

    //获取考试题目和考试答案
    public String getTitle(){
        return this.title;
    }
    public String getAnswer(){
        return this.answer;
    }

    //重写equals和hashCode
    public boolean equals(Object obj){
        if(this == obj){
            return false;
        }
        if(obj instanceof Question){
            Question anotherQuestion = (Question)obj;
            if(this.title.equals(anotherQuestion.title)){
                return true;
            }
        }
        return false;
    }
    public int hashCode(){
        return this.title.hashCode();
    }
}
package exam;

import java.util.*;

public class ExamMachine {

    //学生信息
    private HashMap<String,String> userBox = new HashMap<String, String>();
    {
        userBox.put("亚丝娜","123");
        userBox.put("桐人","456");
        userBox.put("诗乃","789");
    }

    /**
     * 考试题目不能重复,所以使用Set集合
     */
    private HashSet<Question> examQuestion = new HashSet<Question>();

    //添加考试题目
    {
        examQuestion.add(new Question("以下哪一个是Java的基本数据类型?\n\tA.int\n\tB.Integer\n\tC.String\n\tD.Object","A"));
        examQuestion.add(new Question("以下哪一个不是Java的权限修饰符?\n\tA.public\n\tB.protected\n\tC.abstract\n\tD.private","C"));
        examQuestion.add(new Question("以下哪一个不是Java中特征修饰符?\n\tA.synchronized\n\tB.transient\n\tC.violent\n\tD.class","D"));
        examQuestion.add(new Question("以下哪一个不是Java的工具类?\n\tA.Math\n\tB.Collection\n\tC.Person\n\tD.Object","C"));
        examQuestion.add(new Question("以下哪一个不是Java的引用数据类型?\n\tA.Integer\n\tB.Character\n\tC.String\n\tD.boolean","D"));
        examQuestion.add(new Question("以下哪一个是Java的又一个里程牌?\n\tA.java4.0发布\n\tB.java5.0发布\n\tC.java6.0发布\n\tD.java7.0发布","B"));
        examQuestion.add(new Question("以下哪一个人被认为是Java之父?\n\tA.詹姆斯·高斯林\n\tB.亚丝娜\n\tC.郑中拓\n\tD.姬成","A"));
        examQuestion.add(new Question("以下哪一个不是是Java的框架?\n\tA.Spring\n\tB.Mybatis\n\tC.String MVC\n\tD.Bootstrap","D"));
        examQuestion.add(new Question("以下哪一个是Java最开始的名字?\n\tA.Java\n\tB.Oak\n\tC.JavaScript\n\tD.LiveScript","B"));
        examQuestion.add(new Question("以下哪一个是Java的最初的公司?\n\tA.Sun\n\tB.Oracle\n\tC.Google\n\tD.IBM","A"));
    }
    //随机生成考试题目
    public ArrayList<Question> prepareExam(){
        System.out.println("生成试卷中,等耐心等待...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //随机生成的考试题目
        HashSet<Question> prepare = new HashSet<Question>();
        //为了简单获取题目
        ArrayList<Question> pre = new ArrayList<Question>(examQuestion);
        while(prepare.size() !=5) {
            //随机数,产生考试题目
            int index = new Random().nextInt(examQuestion.size());
            prepare.add(pre.get(index));
        }
        return new ArrayList<Question>(prepare);
    }
    //登录操作
    public String login(String name,String password){
        String realPassword = this.userBox.get(name);
        if(realPassword != null && realPassword.equals(password)){
            return "登录成功";
        }
        return "用户名或密码错误";
    }
}
package exam;

import java.util.ArrayList;
import java.util.Scanner;

public class Student {
    private String name; //学生名字
    private String password; //学生密码
    //初始化
    public Student(){

    }
    public Student(String name,String password){
        this.name = name;
        this.password = password;
    }

    //获取学生名字和密码
    public String getName(){
        return this.name;
    }
    public String getPassword(){
        return this.password;
    }

    //学生考试
    public String[] exam(ArrayList<Question> examQuestion){
        //存储学生答案
        String answers[] = new String[examQuestion.size()];
        Scanner input = new Scanner(System.in);
        for (int i = 0; i < examQuestion.size(); i++) {
            System.out.println((i+1)+"."+examQuestion.get(i).getTitle());
            System.out.println("请输入正确选项:");
            answers[i] = input.nextLine();
        }
        return answers;
    }
}
package exam;

import java.util.ArrayList;

public class Teacher {

    public int checkExam(ArrayList<Question> questions,String[] answers){
        System.out.println("批卷中,请耐心等待...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int score = 0;
        for (int i = 0; i < questions.size(); i++) {
            Question question = questions.get(i);
            if(question.getAnswer().equalsIgnoreCase(answers[i])){
                score += (100/questions.size());
            }
        }
        return score;
    }
}

API

(Application Programming Interface),一个别人编写好的类,方便开发者能更好的进行程序开发

Math

该类无需导包,且不可被其他类继承。内部构造方法私有化,方法、属性都是静态的(通过类名直接访问)

常用方法
{% note primary %}
绝对值

public class Test{
    public static void main(String[] args){
        //该方法有int、long、float、double类型的参数,并返回对应参数类型的值
        System.out.println(Math.abs(-5));
    }
}

取整

public class Test{
    public static void main(String[] args){
        //参数为double或float类型,返回值为double        
        System.out.println(Math.ceil(1.4)); //向上取整

        System.out.println(Math.floor(1.4)); //向下取整

        System.out.println(Math.rint(1.4)); //临近的整数,如果左右边距离一样,返回偶数  1.4离1较近,故返回1

        //如果参数是float型,则返回int型的值;如果参数是double型,返回long型的值
        System.out.println(Math.round(1.5)); //四舍五入
    }
}

最值

public class Test{
    public static void main(String[] args){
        //两者参数为int、long、double、float型,返回值为对应类型
        System.out.println(Math.max(5,6)); //求最大值

        System.out.println(Math.min(5,6)); //求最小值
    }
}

平方根 AND 次方根

public class Test{
    public static void main(String[] args){
        //两者的参数为double型。返回值也为double型
        System.out.println(Math.sqrt(1)); //求一个数的平方根

        System.out.println(Math.pow(1,2)); //求1的2次方
    }
}

随机数

public class Test{
    public static void main(String[] args){
        //该方法存在精度丢失问题
        System.out.println(Math.random()); //获得[0,1)之间的随机double型数
    }
}

{% endnote %}

System

该类无需导包,且不可被其他类继承。内部构造方法私有化,方法、属性都是静态的(通过类名直接访问)

常用方法
{% note primary %}
退出Java虚拟机

public class Test{
    public static void main(String[] args){
        //参数为0,表示正常退出;参数为非0,表示异常退出
        System.exit(0);
    }
}

时间戳

public class Test{
    public static void main(String[] args){
        //获取当前系统时间的时间戳
        System.currentTimeMillis();
    }
}

{% endnote %}

Object

所有类间接或直接的继承该类

需要注意的方法
{% note primary %}
toString()

public class Object{
    //将任意数据类型以 指定格式 转换为 字符串
    public String toString() {
        return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
    }
}

hashCode()

public class Object{
    //通过调用本地c语言或其他语言的方式,获取对象的hashcode编码值
    @HotSpotIntrinsicCandidate
    public native int hashCode();
    }
}

equals()

public class Object{
    //比较两个对象的地址值
    public boolean equals(Object obj) {
        return this == obj;
    }
}

getClass()

public class Object{
    //获取对象的类映射
    @HotSpotIntrinsicCandidate
    public final native Class<?> getClass();
}

线程操作

public class Object{
    //线程进入挂起等待状态
    public final void wait() throws InterruptedException {
        this.wait(0L);
    }

    public final native void wait(long var1) throws InterruptedException;

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0L) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        } else if (nanos >= 0 && nanos <= 999999) {
            if (nanos > 0) {
                ++timeoutMillis;
            }

            this.wait(timeoutMillis);
        } else {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    }

    //线程唤醒
    @HotSpotIntrinsicCandidate
    public final native void notify();

    //唤醒所有线程
    @HotSpotIntrinsicCandidate
    public final native void notifyAll();
}

finalize()

public class Object{
    //GC(垃圾回收器)回收对象时默认调用
    protected void finalize() throws Throwable {
    }
}

clone()

public class Object{
    //克隆对象
    @HotSpotIntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;
}

{% endnote %}

Date

public class Test{
    public static void main(String[] args){
        //在java.util包下的Date对象,通常通过无参构造方法和带long类型的参数的构造方法创建对象
        Date date = new Date();

        //该对象返回一个当前系统时间的对象,使用格林威治时间格式
        System.out.println(date); // Tue Jun 30 18:01:56 CST 2020 --> 星期 月份 天数 时间 CST 年份
    }
}

常用方法
{% note primary %}
比较时间前后

public class Test{
    public static void main(String[] args){
        Date date = new Date();

        //参数为Date对象,比较调用者的时间是否在参数的时间之前,返回一个boolean值
        boolean x =date.before(new Date(1573716940846L)); 
        
        //参数为Date对象,比较调用者的时间是否在参数的时间之后,返回一个boolean值
        boolean y = date.after(new Date(1573716940846L)); 

        //如果调用者在参数后面,返回1;如果调用者在参数前面,返回-1;如果调用者和参数相等,返回0
        int z = date.compareTo(new Date(1573716940846L));
    }
}

设置和获取时间

public class Test{
    public static void main(String[] args){
      Date date = new Date();

      //设置一个时间
      date.setTime(1573716940846L); 

     //获取一个时间,返回long类型的毫秒值
     long newTime = date.getTime();
    }
}

{% endnote %}

SimpleDateFormat

将 Date类返回的格林威治时间 转换为我们熟悉的格式

SimpleDateFormat类

常用方法
{% note primary %}
将Date类型转换为String类型

public class Test{
    public static void main(String[] args){
        Date date = new Date();

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //指定格式("yyyy-MM-dd HH:mm:ss"):2020-06-30 18:18:50,默认格式:2020/6/30 下午6:19
        String format = dateFormat.format(date);
    }
}

将String类型转换为Date类型

public class Test throws ParseException{
    public static void main(String[] args){
        String format = "2020-06-30 18:18:50";

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //SimpleDateFormat dateFormat = new SimpleDateFormat(); 以默认格式创建对象,将String类型转换为Date类型时出现异常 ParseException

        Date parse = dateFormat.parse(format);
    }
}

如果 指定的格式 与 字符串对应的格式 不同,则将出现异常:java.text.ParseException: Unparseable date: "2020-06-30 18:18:50"

Calendar类

public class Test{
    public static void main(String[] args){
        //Calendar类是用来完善Date类的。在java.util包中,由于构造方法都以protected修饰,所以一般通过方法获取calendar对象
        Calendar calendar = Calendar.getInstance();

        //java.util.GregorianCalendar(底层子类)[name=value,name=value,...],里面主要包含calendar的时间信息
        System.out.println(calendar);

        //通过calendar可以操作时间,其中month以0开始
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH)+1;
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        System.out.println(year + "年" + month + "月" + day + "日");

        //calendar对象与Date对象有相同的方法,其中有getTime()返回值不同,calendar返回一个date对象
        Date newDate = calendar.getTime();
        System.out.println(newDate); //返回一个格林威治时间格式

        //获取当前时间与计算机元年(1970-1-1 00:00:00)之间的毫秒值
        long time1 = calendar.getTimeInMillis(); 
        System.out.println(time1);
    }
}

TimeZone类

public class Test{
    public static void main(String[] args){
        Calendar calendar = Calendar.getInstance();

        //获取TimeZone对象有两种方式:1.TimeZone timeZone = TimeZone.getDefault();    2.TimeZone timeZone = calendar.getTimeZone();
        TimeZone timeZone = calendar.getTimeZone();

        //与calendar对象一样,重写了toString方法,返回sun.util.calendar.ZoneInfo(底层子类)[name=value,name=value,...]
        System.out.println(timeZone);

        //在TimeZone类中有两个常用的方法
        System.out.println(timeZone.getID()); //获取当前时区的号码:Asia/Shanghai

        System.out.println(timeZone.getDisplayName()); //获取当前时区的描述:中国标准时间
    }
}

异常

程序在编译或运行时出现的不正常现象

除最终继承类Object类,所有的异常和错误都直接或间接的继承该类

异常继承图

Throwable类 分为 Error类和Exception类,Exception类 又分为 RuntimeException(运行时异常)和非运行时异常 这两种

{% note primary %}
出现异常

package moe.exception;

public class Test {
    public static void main(String[] args) {
        int[] array = {1,2,3};

        System.out.println(array[3]);
    }
}

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at moe.exception.Test.main(Test.java:7)

可通过try-catch-finally来捕获异常

package moe.exception;

public class Test {
    public static void main(String[] args) {
        int[] array = {1,2,3};

        try {
            System.out.println(array[3]);
        }catch (ArrayIndexOutOfBoundsException e){
            //控制台输出异常、异常原因、异常位置
            e.printStackTrace();
            //控制台输出异常原因
            System.out.println(e.getMessage());
        }finally {
            System.out.println("结束");
        }
    }
}

// 其中 finally 可省略,该关键字表示无论是否发生异常,都会执行里面的内容

也可通过关键字throws抛出异常

package moe.exception;

//由该类处理ArrayIndexOutOfBoundsException异常
public class Test throws ArrayIndexOutOfBoundsException{
    public static void main(String[] args) {
        int[] array = {1,2,3};

        System.out.println(array[3]);
    }
}

{% endnote %}

自定义异常

package moe.exception;

public class NumberException extends RuntimeException {
    public NumberException() {}

    public NumberException(String message) {
        super(message);
    }
}
package moe.exception;

public class Test {
    public static void main(String[] args){
        int x = 5;
        if(x < 0){
            throw new NumberException("该数字小于0");
        }else{
            System.out.println("该数字大于0");
        }
    }
}

IO流

数据传输 的总称,即数据在设备之间的传输称为流。流的本质是数据传输

IO:Input & Output,数据的输入和输出

File类

文件和目录路径名抽象表示

File类

{% note primaey %}

  1. 文件和目录是可以通过File封装称对象的

  2. 对于File而言,其封装的并不是一个真正存在的文件,仅仅 是一个路径名 而已。它可以是存在的,也可以是不存在的,将来是要通过具体的操作把这个路径的内容转换为具体存在的

常用方法
{% note primary %}
一般方法

public class Test{
    public static void main(Strig[] args){
        //通过带String类型的参数创建对象,并且参数不区分大小写(硬盘一样)
        File file = new File("E:\\test\\Test.txt");

        //canRead()方法判断该文件能否读取
        System.out.println(file.canRead());

        //canWrite()方法判断该文价是否可写
        System.out.println(file.canWrite());

        //isHidden()方法判断该文件是否在隐藏
        System.out.println(file.isHidden());

        //isFile()方法判断是否为文件
        System.out.println(file.isFile());

        //isDirectory()方法判断是否为文件夹
        System.out.println(file.isDirectory());

        //length()方法获取文件的字节,返回一个long类型
        System.out.println(file.length());

        //lastModified()方法获取最后修改文件的时间,返回long类型
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(file.lastModified()));
    }
}

剩余方法

public class Test{
    public static void main(String[] args){
        File file = new File("E:\\test\\Test.txt");

        //getName()方法获取当前文件名
        String name = file.getName();
        System.out.println(name); //Test.txt

        //getAbsoluteFile()方法获取文件的绝对路径
        File absoluteFile = file.getAbsoluteFile();
        System.out.println(absoluteFile); //E:\test\Test.txt

        //createNewFile()方法可以在没有该文件的情况下创建一个新文件,并返回boolean值
        try {
            //如果没有为文件添加一个后缀名,则创建一个没有后缀名的文件
            //如果文件存在,返回false,反之返回true,并创建一个文件
            System.out.println(file.createNewFile()); 
        } catch (IOException e) {
            e.printStackTrace();
        }

        //mkdir()方法可以创建一个文件夹(目录)
        System.out.println(file.mkdir());

        //mkdirs()方法可以创建多个文件夹
        System.out.println(file.mkdirs());

        //getParent()方法可以获得当前file的父亲file名字
        String parent = file.getParent();
        System.out.println(parent); //"E:\\test"

        //getParentFile()方法获取当前file的父亲file对象
        //逐级遍历父亲文件,最终停在当前盘符下
        File parentFile = file.getParentFile();
        while(parentFile != null){
            System.out.println(parentFile);
            parentFile = parentFile.getParentFile();
        }

        //list()获取当前文件夹的文件或目录,返回值为一个字符串数组
        //listFile()获取当前文件夹的文件或目录,返回值为一个文件数组
        File[] files = file.listFiles();
        //如果返回null,说明没有文件;如果不返回null,而数组的长度为0,说明为空文件夹
        System.out.println(files);

        //delete()方法可以删除文件或文件夹,但是但文件夹存在内容时,不能删除
        file.delete();

        //exist()方法可以判断文件是否存在
        file.exist();
    }
}

{% endnote %}

IO文件夹遍历和删除(递归)

package file;

public class TestMethod {
    public void testOne(){
        this.testTwo();
        System.out.println("testOne执行");
    }
    public void testTwo(){
        this.testThree();
        System.out.println("testTwo执行");
    }
    public void testThree(){
        System.out.println("testThree执行");
    }

    public static void main(String[] args) {
        TestMethod tm = new TestMethod();
        tm.testOne();
    }
}

/**
如果将方法都修改为testOne(),可以得出定义:递归就是调用自身方法,并且存在一个出口
*/
public class TestMethod {
    public void testOne(){ //第一个testOne方法
        this.testOne(); //调用第二个testOne方法
        System.out.println("testOne执行");
    }
    public void testOne(){
        this.testOne();  //调用第三个testOne方法
        System.out.println("testTwo执行");
    }
    public void testOne(){ //执行
        System.out.println("testThree执行");
    }

    public static void main(String[] args) {
        TestMethod tm = new TestMethod();
        tm.testOne();
    }
}

递归初解答

package file;

public class TestMethod {
    //设计一个方法,通过循环建立楼层
    public void bulidTowerFor(int floor){
        //循环从头开始,从1到floor为止
        for(int i = 0; i <= floor; i++){
            System.out.println("当前楼层盖到" + i + "层");
        }
    }

    //假设floor为3
    public void bulidTower(int floor){
        //先从第floor层开始,但需要第floor-1层先执行
        //可以看出,每层执行的方法都类似,区别在与第一层不需要等待,就可以直接执行
        //故当floor不为1时,调用自身==执行其他楼层方法
        if(floor != 1){
            this.bulidTower(floor-1);
        }
        System.out.println("当前楼层盖到" + floor + "层");
    }
    
//    public void bulidTower(int floor){
//        //先从第floor-1层开始,但需要第floor-2层先执行
//        System.out.println("当前楼层盖到" + floor-1 + "层");
//    }
//    public void bulidTower(int floor){
//        //直接执行
//        System.out.println("当前楼层盖到" + floor + "层");
//    }

    public static void main(String[] args) {
        TestMethod tm = new TestMethod();
        tm.bulidTower(3);
    }
}

循环和递归

{% note info %}

  1. 在使用递归时,先以楼层为例,考虑每个楼层以什么为例才执行,并知道能直接执行的楼层

  2. 循环和递归的区别:循环每次只在栈内存中创建一个空间执行,但是递归会在栈内存中创建多个空间执行,所以递归性能慢

通过递归的方式遍历或删除文件夹

package file;

import java.io.File;

public class TestFiles {
    //设计一个方法,遍历文件夹
    public void showFile(File file){
        //如果文件夹中存在另一个有内容的文件夹,则需要等待里面的文件或文件夹执行完才能执行
        //files==null-->没有文件夹 files!=null-->有文件夹 files.length=0-->空文件夹
        File[] files = file.listFiles(); //获取子文件夹

        //如果不是文件或空文件,说明还存在空文件夹,则需要等待里面的文件夹或文件执行完才能执行
        if(files!= null && files.length!=0){
            for(File f: files){
                this.showFile(f);
            }
        }
        //当遍历到的是文件或空文件夹时
        System.out.println(file.getAbsoluteFile());
    }

    //设计一个方法,删除文件夹,跟遍历文件夹一样
    public void deleteFile(File file){
        File[] files = file.listFiles(); //获取子文件夹

        //如果是有内容的文件夹,说明执行需要等待里面的先执行完,才会执行自已
        if(files!=null && files.length!=0){
            for (File f:files){
                this.deleteFile(f);
            }
        }
        //如果是文件或文件夹,则删除,但是需要确认外面的有没有执行
        file.delete();
    }

    public static void main(String[] args) {
        TestFiles files = new TestFiles();
        File file = new File("E:\\test");
        files.showFile(file);
        files.deleteFile(file); //不会回收到回收站中,是直接删除
    }
}

遍历或删除文件夹执行流程

字节流

![Stream底层简图]{/medias/Java基础/Stream底层简图.png}

字节输出流

OutPutStream类是所有输出流的超类(父类)
OutputStream

以文件字节输出流为例,该 输出 指的是将 内存中的数据写入到硬盘中

FileOutPutStream类是关于文件的输出
FileOutputStream

package moe.io;

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

public class TestFileOutPutStream {
    public static void main(String[] args){

        FileOutputStream fileOutputStream = null;
        try {
            /*
                默认情况下(false),会在原来的基础上追加数据;如果将 append 参数设置为false,则会在原来的基础上追加数据
            */
            fileOutputStream = new FileOutputStream("D:\\Java项目\\fileoutput.txt");

            //write(int b),以该参数(数字)对应的字符输入到文件内
            fileOutputStream.write(97);
            fileOutputStream.write(97);

            //write(byte[] b),以字节数组的方式输入到文件内
            String str = "1+1=2";
            fileOutputStream.write(str.getBytes());

            //根据不同操作系统实现换行
            fileOutputStream.write("/r/n".getBytes());

            /*
                write(byte[] b, int off, int len),从索引为off 开始的长度len 写入文件中
                - 如果 len > 4,则出现异常 IndexOutOfBoundsException
            */
            fileOutputStream.write(str.getBytes(),1,4);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileOutputStream != null){
                try {
                    //关闭流操作,释放系统资源
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

字节输入流

InputStream

以文件输入流为例,该 输入将文件中的数据输入到内存

FileInputStream

{% note primary %}
循环读取单个字符

package moe.io;

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

public class TestFileInputStream {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream("D:\\Java项目\\fileoutput.txt");

            // read(),从流管道中读取一个字符,并且返回该字符对应的Unicode编码
            int read = fileInputStream.read();

            //读取不到字符时,返回-1
            while(read != -1){
                System.out.println(read);
                //读取完一个字符后,会自动读取下一个字符
                read = fileInputStream.read();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

读取多个字符

package moe.io;

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

public class TestFileInputStream {
    public static void main(String[] args) {
package moe.io;

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

public class TestFileInputStream {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream("D:\\Java项目\\fileoutput.txt");

            //读取一个字节,防止换行
            byte[] bytes = new byte[1024];

            //len:所读取到的字符数
            int len = fileInputStream.read(bytes);

            //当读取不到字符时,返回-1
            while(len!=-1){
                System.out.println(new String(bytes,0,len));
                len = fileInputStream.read(bytes);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

剩余方法

public class Test{
    public static void main(String[] args){
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("D:\\test\\Test.txt");

            //available()方法返回当前流管道剩余的字节数,在网络传输数据时可能会出现问题
            int count = fis.available(); //出现编译时异常:IOException
            System.out.println(count);

            //skip()方法表示跳过几个字节读取数据,返回跳过的字节数
            fis.skip(5);
            System.out.println((char)fis.read());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            //如果在创建对象时出现异常,则null条用方法,会出现空指针异常,故添加判断语句
            if(fis != null){
                try{
                    fis.close();//关闭流通道
                }catch(IOException e){
                    e.printStackTrace();
                }
            }

        }
    }
}

{% endnote %}

文件流的复制

{% note primary %}
复制文件

package moe.io;

import java.io.*;

//复制文件
public class FileCopy {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            /*
                文件读取到内存中
            */
            //文件必须存在,否则发生 编译时异常:FileNotFoundException
            fileInputStream =  new FileInputStream("D:\\Java项目\\fileoutput.txt");

            //参数设置成这样,则文件会在当前项目下创建;由于类的特性,如果文件不存在,则会自动创建一个
            fileOutputStream = new FileOutputStream("file.txt");

            byte[] bytes = new byte[1024];

            int len = fileInputStream.read(bytes);

            while(len != -1){
                //将内存中的数据写入到文件中
                fileOutputStream.write(bytes,0,len);

                //继续读取
                len = fileInputStream.read(bytes);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

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

复制图片

package moe.io;

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

public class ImageCopy {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            /*
                文件读取到内存中
            */
            fileInputStream =  new FileInputStream("D:\\Java项目\\朱雀院椿2.png");

            fileOutputStream = new FileOutputStream("D:\\朱雀院椿2.png");

            byte[] bytes = new byte[1024];

            int len = fileInputStream.read(bytes);

            while(len != -1){
                //将内存中的数据写入到文件中
                fileOutputStream.write(bytes,0,len);

                //继续读取
                len = fileInputStream.read(bytes);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

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

可以看出,文件、图片的复制都是一样的

文件夹复制

package testfile;

import java.io.*;

public class Test {
    //设计一个方法,进行文件夹的复制
    public void superCopyFile(File file,String path){
        //判断file是文件还是文件夹,listFiles()方法返回一个数组,如果为null,说明是文件,如果数组长度为0,说明是空文件夹
        File[] files = file.listFiles();
        //获取file文件的绝对路径
        String absolutePath = file.getAbsolutePath();
        String newPath = path + absolutePath.split(":")[1];
        File newFile = new File(newPath);
        if(files != null){ //文件夹
            newFile.mkdir(); //创建文件夹
            //遍历文件夹,找到文件
            if(files!= null && files.length!=0){
                for (File f : files){
                    this.superCopyFile(f,path);
                }
            }
        }else{ //文件,且创建文件和创建文件夹类似
            FileInputStream fileInputStream = null;
            FileOutputStream fileOutputStream = null;
            try {
                //创建一个字节文件输入流,获得file文件中的信息
                fileInputStream = new FileInputStream(file);
                //创建一个字节文件输出流,写入file文件中的信息
                fileOutputStream = new FileOutputStream(newFile);
                //读取文件信息
                byte[] bytes = new byte[1024]; //建议1kb-8kb
                int count = fileInputStream.read(bytes);
                //循环读取文件信息
                while(count != -1){
                    //写入信息
                    fileOutputStream.write(bytes,0,count);
                    fileOutputStream.flush();
                    count = fileInputStream.read(bytes);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    if(fileInputStream != null) {
                        fileInputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if(fileOutputStream != null) {
                        fileOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        //将D:\\baidu中的所有内容(包括 baidu这一文件夹)复制到aaa目录中
        test.superCopyFile(new File("D:\\baidu"),"C:\\aaa");
    }
}

{% endnote %}

缓冲流

读写数据时,数据在缓冲区能 减少系统实际对原始数据来源的存取次数,因此一次能做多个数据单位的操作。相较而言,对于从文件读取数据或将数据写入文件,比起缓冲区的读写要慢多了。所以使用缓冲区的 流,一般都会比没有缓冲区的流效率更高,拥有缓冲区的流别称为缓冲流,包括BufferedInputStream、BufferedOutputStream类和BufferedReader、BufferedWriter类。缓冲流把数据从原始流成块读入或把数据积累到一个大数据块后再成批写出,通过减少通过资源的读写次数来加快程序的执行。

{% note info %}
计算机访问外部设备或文件,要比直接访问内存慢的多如果我们每次调用read()方法或者writer()方法访问外部的设备或文件,CPU就要花上最多的时间是在等外部设备响应,而不是数据处理。
为此,我们开辟一个内存缓冲区的内存区域,程序每次调用read()方法或writer()方法都是读写在这个缓冲区中。当这个缓冲区被装满后,系统才将这个缓冲区的内容一次集中写到外部设备或读取进来给CPU。使用缓冲区可以有效的提高CPU的使用率,能提高整个计算机系统的效率。在字符流操作中,所有的字符都是在内存中形成的,在字符流输出前都将保存在内存中的缓冲区内。

字节缓冲流实例

package moe.io;

import java.io.*;
// 以输出流为例
public class TestBuffer {
    public static void main(String[] args) throws IOException {
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("HelloWrold\\file.txt"));

        bufferedOutputStream.write("hello".getBytes());

        // fiush()方法将在流管道中的数据推入文件中
        bufferedOutputStream.flush(); //如果没有此方法,数据在流管道中,因此不能写入文件,需要通过flush()将数据推进文件内

        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("HelloWrold\\\\file.txt"));

        byte[] bytes = new byte[1024];
        int read = bufferedInputStream.read(bytes);

        while(read!=-1){
            System.out.println(new String(bytes,0,read));
            read = bufferedInputStream.read(bytes);
        }
        bufferedInputStream.close();
        bufferedOutputStream.close(); //先刷新,后关闭系统资源
    }
}

字符缓冲流实例

package moe.io;

import java.io.*;
// 以输入流为例
public class TestInputStreamReader {
    public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        try {
            //默认编码(这里为IDEA的UTF-8)创建字符输入流对象
            bufferedReader = new BufferedReader(new FileReader("HelloWrold\\file.txt"));

            //read()读取一个字符,返回该字符的Unicode编码,并在下次读取下一个字符
            int read = bufferedReader.read();
            System.out.println((char)read); //h

            char[] chars = new char[1024];
            int read1 = bufferedReader.read(chars);
            while(read1 != -1){ //ello中国
                System.out.println(new String(chars,0,read1));
                read1 = bufferedReader.read(chars);
            }

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

字符流

计算机中存储的信息都是通过 二进制 表示的,我们所看到的中文、英文等都是二进制转换后的结果。

编码 :按照某种规则,将字符存储在计算机中;解码 :按照某种规则,将存储在计算机中的二进制数解析显示出来

使用什么规则进行编码,那么必须得使用这个规则进行解码,否则将出现 乱码 现象

{% note info %}
字符(Character)是 各种文字和符号的总称,包括 各国家文字、标点符号、图形符号、数字等。字符集(Character set)是多个字符的集合。字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,就需要进行字符编码,以便计算机能够识别和存储各种文字

字符串中的编码解码

package moe.io;

import java.io.UnsupportedEncodingException;
import java.sql.SQLOutput;
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        String str = "中国";

        /*
            编码
        */
        //getBytes()使用当前默认字符编码UTF-8:一个汉字表示三位
        byte[] bytes = str.getBytes();
        System.out.println(Arrays.toString(bytes));

        byte[] gbks = new byte[0];
        try {
            //getBytes()可以指定字符编码,其中GBK:一个汉字表示两位
            gbks = str.getBytes("GBK");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println(Arrays.toString(gbks));


        /*
            解码
        */
        //String类的构造方法以默认字符编码解析字节数组
        String s = new String(bytes);
        System.out.println(s);

        String s1 = null;
        try {
            //String类的构造方法也可以指定字符编码解析字节数组
            s1 = new String(gbks,"GBK");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        System.out.println(s1);

        //如果解码和编码对应的字符集不同,则会出现乱码
        String s2 = new String(gbks); //gbks是通过GBK编码成的,而String类的构造方法默认调用平台默认的字符编码(UTF-8)
    }
}

字符输入流

字符输入流的抽象基类
Reader类

与编码解码相关的
InputStreamReader类

常用方法

package moe.io;

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

public class TestInputStreamReader {
    public static void main(String[] args) {
        InputStreamReader streamReader = null;
        try {
            //创建一个默认编码的字符输入流对象,参数为文件字节输入流
            streamReader = new InputStreamReader(new FileInputStream("HelloWrold\\file.txt"));

            //read()读取一个字符,返回该字符的Unicode编码,并在下次读取下一个字符
            int read = streamReader.read();
            System.out.println((char)read); //h

            char[] chars = new char[1024];
            int read1 = streamReader.read(chars);
            while(read1 != -1){ //ello中国
                System.out.println(new String(chars,0,read1));
                read1 = streamReader.read(chars);
            }

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

字符输出流

字符输出流的抽象基类
Writer类

与编码解码相关的
OutputStreamWriter类

常用方法

package moe.io;

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

public class TestOutputStreamWriter {
    public static void main(String[] args) {
        //默认编码(这里为IDEA的UTF-8)创建字符输出流对象
        OutputStreamWriter streamWriter = null;
        try {
            streamWriter = new OutputStreamWriter(new FileOutputStream("HelloWrold\\file.txt",false));

            //write():写入一个字符
            streamWriter.write(97);

            //write(char[] cbuf):写入所有字符数组内容
            char[] chars = {'a','b','中','国'};
            streamWriter.write(chars);

            //write(char[] cbuf, int off, int len):从什么索引开始,写入多少字符
            streamWriter.write(chars,2,2);

            //write(String str):写入字符串
            String str = "hello中国";
            streamWriter.write(str);

            //write(String str, int off, int len):从什么索引开始,写入多少字符
            streamWriter.write(str,1,2);


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(streamWriter!=null){
                try {
                    //刷新后释放资源
                    streamWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

关于文件的字符输入输出,可以使用类 FileReaderFileWritrt

package moe.io;

import java.io.*;

public class TestInputStreamReader {
    public static void main(String[] args) {
        FileReader fileReader = null;
        try {
            //默认编码(这里为IDEA的UTF-8)创建字符输入流对象
            fileReader = new FileReader("HelloWrold\\file.txt");

            //read()读取一个字符,返回该字符的Unicode编码,并在下次读取下一个字符
            int read = fileReader.read();
            System.out.println((char)read); //h

            char[] chars = new char[1024];
            int read1 = fileReader.read(chars);
            while(read1 != -1){ //ello中国
                System.out.println(new String(chars,0,read1));
                read1 = fileReader.read(chars);
            }

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

{% note default %}
字符缓冲流特有方法

package moe.io;

import java.io.*;

public class TestBufferedReader {
    public static void main(String[] args) {
        BufferedWriter bufferedWriter = null;
        BufferedReader bufferedReader = null;
        try {
            bufferedWriter = new BufferedWriter(new FileWriter("HelloWrold\\file.txt"));

            bufferedReader = new BufferedReader(new FileReader("HelloWrold\\file.txt"));

            bufferedWriter.write(97);
            //写入一行分割符号(由当前系统定义)
            bufferedWriter.newLine();
            bufferedWriter.write(98);
            
            //读取每一行数据,对于类似换行的特殊符号不读取;当读取不到数据时,返回null
            String str = null;
            while((str = bufferedReader.readLine()) != null){
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(bufferedWriter!=null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

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

bufferedReader.readLine() 读取不到数据,返回null,检测后跟添加了 字符输出缓冲流 有关

特殊操作流

序列化流

对象序列化:把对象转换为字节序列的过程
ObjectOutputStream类

对象反序列化:把字节序列恢复为对象的过程
ObjectInputStream类

对象的序列化主要有两种用途

  1. 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中

  2. 在网络上传送对象的字节序列

序列化原因

  1. 在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中

  2. 当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象

{% note primary %}
序列化的对象

package moe.io;

import java.io.Serializable;

/*
    NotSerializableException:该类的对象序列化时 缺少实现序列化接口Serializable 时出现的异常
    InvalidClassException:默认情况下,每个对象都会有一个序列化id,当反序列化的id 与 序列化id 不匹配时则会出现的异常
*/
public class Student implements Serializable {
    private static final long serialVersionUID = 1247617872661789751L;
    private String name = null;
    // 关键字transient 可以使该成员变量不被序列化
    private transient Integer age = null;

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

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

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

    // 开始没有该方法,在进行序列化后,添加该方法,再进行反序列化,会出现 异常InvalidClassException
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Serializable类

对象的序列化和反序列化

package moe.io;

import java.io.*;

public class TestDuiXiang {
    public static void main(String[] args) {
        //创建对象序列化流对象
        ObjectOutputStream objectOutputStream = null;

        //创建反序列化流对象
        ObjectInputStream objectInputStream = null;
        try {
            objectOutputStream = new ObjectOutputStream(new FileOutputStream("HelloWrold\\file.txt"));

            objectInputStream = new ObjectInputStream(new FileInputStream("HelloWrold\\file.txt"));

            Student student = new Student("亚丝娜",17);
            //对象序列化
            objectOutputStream.writeObject(student);

            //对象反序列化
            Object o = objectInputStream.readObject();
            Student student1 = (Student) o;
            System.out.println(student1.getName() + "," + student1.getAge());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if(objectInputStream!=null){
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            if(objectOutputStream!=null){
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

{% endnote %}

{% note danger %}
EOFException异常

IDEA自动添加序列化id
{% note primary %}
设置
idea添加序列化id

添加
idea添加序列化id

序列化借鉴

Properties

该类 属于 Map集合,可以 保存到流中或从流中加载

Properties

package moe.io;

import java.util.Properties;
import java.util.Set;

public class TestProperties {
    public static void main(String[] args) {
        //该类没有泛型
        Properties properties = new Properties();

        properties.put(1,"亚丝娜");
        properties.put(2, "诗乃");
        properties.put(3,"绝剑");

        Set<Object> set = properties.keySet();
        for(Object key : set){
            Object value = properties.get(key);
            System.out.println(value);
        }
    }
}

特有方法

package moe.io;

import java.util.Properties;
import java.util.Set;

public class TestProperties {
    public static void main(String[] args) {
        //该类没有泛型
        Properties properties = new Properties();

        //参数都为字符串 setProperty(String key, String value)设置键值对
        properties.setProperty("1","亚丝娜");
        properties.setProperty("2","诗乃");
        properties.setProperty("3","绝剑");

        //getProperty(String key)根据键获取值
        String property = properties.getProperty("1");
        System.out.println(property);

        Set<String> set = properties.stringPropertyNames();
        for(String key : set){
            String value = properties.getProperty(key);
            System.out.println(value);
        }
    }
}

Properties类和 IO的结合
{% note primary %}
以字符流为例

package moe.io;

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

public class TestProperties {
    public static void main(String[] args) {
        //该类没有泛型
        Properties properties = new Properties();

        //参数都为字符串 setProperty(String key, String value)设置键值对
        properties.setProperty("1","亚丝娜");
        properties.setProperty("2","诗乃");
        properties.setProperty("3","绝剑");

        FileWriter fileWriter = null;
        FileReader fileReader = null;
        try {
            fileWriter = new FileWriter("HelloWrold\\file.txt");

            fileReader = new FileReader("HelloWrold\\file.txt");

            //将集合中的数据写入文件中
            properties.store(fileWriter,null);

            properties.load(fileReader);
            System.out.println(properties);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fileWriter != null){
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

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

写入的文件
file.txt

MVC设计思想

mvc

MVC分层架构思想

  1. V :View视图,用于向展示页面
  2. C :Controller控制,用于控制展示的数据
  3. M :Model模型(数据模型),用于数据处理(Service)、数据读写(Dao)、数据存储(JavaBean)
  4. D :Database数据,用于向数据库中存储数据

银行系统练习(目前主要是M层中的细分)
{% note primary %}
JavaBean:主要用于进行数据存储

package banksystem.bean;

import java.io.Serializable;

public class UserBean implements Serializable {

    //进行JavaBean时,最好实现序列化
    private static final long serialVersionUID = 1322406348010574710L;

    private String atmName;//账号
    private String atmPassword; //密码
    //以包装类来声明属性
    private Float atmBalance; //余额

    public UserBean() {}
    public UserBean(String atmName, String atmPassword, Float atmBalance) {
        this.atmName = atmName;
        this.atmPassword = atmPassword;
        this.atmBalance = atmBalance;
    }

    public String getAtmName() {
        return atmName;
    }

    public void setAtmName(String atmName) {
        this.atmName = atmName;
    }

    public String getAtmPassword() {
        return atmPassword;
    }

    public void setAtmPassword(String atmPassword) {
        this.atmPassword = atmPassword;
    }

    public Float getAtmBalance() {
        return atmBalance;
    }

    public void setAtmBalance(Float atmBalance) {
        this.atmBalance = atmBalance;
    }

    @Override
    public String toString() {
        return "UserBean{" +
                "atmName='" + atmName + '\'' +
                ", atmPassword='" + atmPassword + '\'' +
                ", atmBalance=" + atmBalance +
                '}';
    }
}

持久层:数据读写,主要用于操作数据

package banksystem.dao;

import banksystem.bean.UserBean;

import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
//该类属于持久层,主要进行数据的操作
public class BankDao {
    //添加一个Map集合,主要作为缓存,存储文件中的信息
    private HashMap<String, UserBean> userBox = new HashMap<String,UserBean>();
    //在对象被创建之前,将文件中的信息加载到userBox集合中
    {
        FileReader fileReader = null;
        BufferedReader bufferedReader = null;
        try {
            //创建一个输入流,读取文件中的信息
            //当参数为src\\banksystem\\user.txt找不到文件?
            File file = new File("C:\\Users\\blhorizon\\Desktop\\ideaProject\\bank\\src\\banksystem\\user.txt");
            fileReader = new FileReader(file);
            bufferedReader = new BufferedReader(fileReader);
            //遍历文件中的每一行信息
            String info = bufferedReader.readLine();
            while(info!=null){
                //拆分信息
                String[] splitInfo = info.split("-");
                //将信息存入userBox集合中
                userBox.put(splitInfo[0],new UserBean(splitInfo[0],splitInfo[1],Float.parseFloat(splitInfo[2])));
                //继续遍历下一条信息
                info = bufferedReader.readLine();
            }
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            //关闭IO流管道
            try {
                if(fileReader != null) {
                    fileReader.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
            try {
                if(bufferedReader != null) {
                    bufferedReader.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }

    //获取数据
    public UserBean selectOne(String atmName){
        return userBox.get(atmName);
    }
    //修改集合中的数据,并根据集合中的数据修改文件信息
    public void update(UserBean user){
        //修改集合中的信息,Map集合如果出现相同的key时,会进行覆盖
        userBox.put(user.getAtmName(),user);
        //根据修改后的集合修改文件中的信息
        this.commit();
    }
    //根据修改后的集合修改文件中的信息,不需要参数userBox,因为这个是属性
    public void commit() {
        boolean flag = false;
        FileWriter fileWriter = null;
        BufferedWriter bufferedWriter = null;
        try {
            //遍历userBox,获取信息,Map集合需要通过迭代器进行遍历
            Iterator<String> iterator = userBox.keySet().iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                //根据key获取信息
                UserBean user = userBox.get(key);
                File file = new File("C:\\Users\\blhorizon\\Desktop\\ideaProject\\bank\\src\\banksystem\\user.txt");
                if(!flag) {
                    fileWriter = new FileWriter(file);
                    flag = true;
                }else{
                    fileWriter = new FileWriter(file,true);
                }
                bufferedWriter = new BufferedWriter(fileWriter);
                //将信息拼接成字符串
                StringBuilder sub = new StringBuilder(user.getAtmName());
                sub.append("-");
                sub.append(user.getAtmPassword());
                sub.append("-");
                sub.append(user.getAtmBalance());
                bufferedWriter.write(sub.toString());
                bufferedWriter.newLine();
                bufferedWriter.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                if(fileWriter != null) {
                    fileWriter.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
            try {
                if(bufferedWriter!=null) {
                    bufferedWriter.close();
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }
}

服务层:主要进行数据处理,进行业务逻辑

package banksystem.service;

import banksystem.bean.UserBean;
import banksystem.dao.BankDao;

import java.io.*;
import java.util.*;
//设计一个类,该类主要进行银行业务操作,属于服务层,主要处理业务逻辑
public class BankService {

    private BankDao bankDao = new BankDao();

//    //添加一个Map集合,主要作为缓存,存储文件中的信息
//    private HashMap<String, UserBean> userBox = new HashMap<String,UserBean>();
//    //在对象被创建之前,将文件中的信息加载到userBox集合中
//    {
//        FileReader fileReader = null;
//        BufferedReader bufferedReader = null;
//        try {
//            //创建一个输入流,读取文件中的信息
//            File file = new File("C:\\Users\\blhorizon\\Desktop\\ideaProject\\bank\\src\\banksystem\\user.txt");
//            fileReader = new FileReader(file);
//            bufferedReader = new BufferedReader(fileReader);
//            //遍历文件中的每一行信息
//            String info = bufferedReader.readLine();
//            while(info!=null){
//                //拆分信息
//                String[] splitInfo = info.split("-");
//                //将信息存入userBox集合中
//                userBox.put(splitInfo[0],new UserBean(splitInfo[0],splitInfo[1],Float.parseFloat(splitInfo[2])));
//                //继续遍历下一条信息
//                info = bufferedReader.readLine();
//            }
//        }catch(FileNotFoundException e){
//            e.printStackTrace();
//        }catch(IOException e){
//            e.printStackTrace();
//        }finally{
//            //关闭IO流管道
//            try {
//                if(fileReader != null) {
//                    fileReader.close();
//                }
//            }catch(IOException e){
//                e.printStackTrace();
//            }
//            try {
//                if(bufferedReader != null) {
//                    bufferedReader.close();
//                }
//            }catch(IOException e){
//                e.printStackTrace();
//            }
//        }
//    }
//
//    //获取数据
//    public UserBean selectOne(String atmName){
//        return userBox.get(atmName);
//    }
//    //修改集合中的数据,并根据集合中的数据修改文件信息
//    public void update(UserBean user){
//        //修改集合中的信息,Map集合如果出现相同的key时,会进行覆盖
//        userBox.put(user.getAtmName(),user);
//        //根据修改后的集合修改文件中的信息
//        bankDao.commit();
//    }
//    //根据修改后的集合修改文件中的信息,不需要参数userBox,因为这个是属性
//    public void commit() {
//        FileWriter fileWriter = null;
//        BufferedWriter bufferedWriter = null;
//        try {
//            //遍历userBox,获取信息,Map集合需要通过迭代器进行遍历
//            Iterator<String> iterator = userBox.keySet().iterator();
//            while (iterator.hasNext()) {
//                String key = iterator.next();
//                //根据key获取信息
//                UserBean user = userBox.get(key);
//                File file = new File("C:\\Users\\blhorizon\\Desktop\\ideaProject\\bank\\src\\banksystem\\user.txt");
//                fileWriter = new FileWriter(file);
//                bufferedWriter = new BufferedWriter(fileWriter);
//                //将信息拼接成字符串
//                StringBuilder sub = new StringBuilder(user.getAtmName());
//                sub.append("-");
//                sub.append(user.getAtmPassword());
//                sub.append("-");
//                sub.append(user.getAtmBalance());
//                bufferedWriter.write(sub.toString());
//                bufferedWriter.newLine();
//                bufferedWriter.flush();
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }finally{
//            try {
//                if(fileWriter != null) {
//                    fileWriter.close();
//                }
//            }catch(IOException e){
//                e.printStackTrace();
//            }
//            try {
//                if(bufferedWriter!=null) {
//                    bufferedWriter.close();
//                }
//            }catch(IOException e){
//                e.printStackTrace();
//            }
//        }
//    }


    //设计一个业务,进行信息的登录
    //问题:每次用户登录系统,都会创建一个流管道,执行登录操作后,又要关闭流管道,这样降低系统的性能,那么应该如果解决?
    //加入一个缓冲机制,将文件中的信息都加载到内存中,以一个容器保存这些信息,那么用什么容器保存这些信息?
    //文件中的信息是动态改变的,所以数组不适合,那么使用长度动态改变的集合,则又需要使用什么集合?
    //使用Map集合,通过账户对应账户的信息(账户-密码-余额),那么账户的信息需要使用什么来存储?
    //可以使用String,但是需要进行拆分,所以不好;可以使用数组,但是数组的可读性不好,因为只看索引不知道代表什么,是账户,还是密码,或者余额;所以通过一个类的属性来保存这三个属性,并且属性类型可以不一样
    public String login(String atmName,String atmPassword){
        //UserBean user = userBox.get(atmName);
        UserBean user = bankDao.selectOne(atmName);
        if(user != null && user.getAtmPassword().equals(atmPassword)){
            return "登录成功";
        }
        return "用户名或密码错误";
//        FileReader fileReader = null;
//        BufferedReader bufferedReader = null;
//        try {
//            //创建一个输入流,读取文件中的数据
//            File file = new File("C:\\Users\\blhorizon\\Desktop\\ideaProject\\bank\\src\\banksystem\\user.txt");
//            //System.out.println(file.isFile());
//            fileReader = new FileReader(file);
//            bufferedReader = new BufferedReader(fileReader);
//            //循环读取文件中的每一行信息
//            String info = bufferedReader.readLine();
//            while(info!=null){
//                //如果info不为null,说明文件中有信息,此时进行登录判断
//                //需要将数组拆分为账号、密码、余额
//                String[] splitInfo = info.split("-");
//                //判断登录的账号和密码是否在文件中
//                if(splitInfo[0].equals(atmName)){
//                    if(splitInfo[1].equals(atmPassword)){
//                        //如果账号密码正确
//                        return "登录成功";
//                    }
//                }
//                info = bufferedReader.readLine(); //如果不写,循环会一直执行下去
//            }
//        }catch(FileNotFoundException e){
//            e.printStackTrace();
//        }catch(IOException e){
//            e.printStackTrace();
//        }
//        finally {
//            try {
//                if(fileReader != null) {
//                    fileReader.close();
//                }
//            }catch (IOException e){
//                e.printStackTrace();
//            }
//            try {
//                bufferedReader.close();
//            }catch(IOException e){
//                e.printStackTrace();
//            }
//        }
//        return "用户名或密码错误";
    }
    //---------------------
    //可以发现,底下的方法只有业务逻辑,对于数据的操作我们完全看不见,这些业务逻辑只有判断、查询、比较等等,所以我们需要将操作数据的部分与业务逻辑进行分开
    //设计一个业务,查询余额
    public Float getBalance(String atmName){
        //获取信息
        //UserBean user = userBox.get(atmName);
        UserBean user = bankDao.selectOne(atmName);
        return user.getAtmBalance();
    }

    //设计一个业务,进行存款
    public void depositMoney(String atmName,Float money){
        //此时可以看出,每次进行业务时,都需要获取数据,当获取数据的程序发生修改时,就需要修改很多地方,所以将这个获取数据的代码封装为一个方法
        UserBean user = bankDao.selectOne(atmName);
        //获取当前账号的余额,并增加money
        user.setAtmBalance(user.getAtmBalance()+money);
        //修改集合中的数据,并根据集合中的数据修改文件信息
        bankDao.update(user);
    }

    //设计一个业务,进行取款
    public void getMoney(String atmName,Float money){
        //获取信息
        UserBean user = bankDao.selectOne(atmName);
        //判断账户余额是否可以被取走
        if(user.getAtmBalance() > money){
            //说明账户余额足够被取款
            user.setAtmBalance(user.getAtmBalance() - money);
            bankDao.update(user);
        }else{
            System.out.println("对不起" + atmName + ",你目前账户余额为" + user.getAtmBalance() + ",不能取款");
        }
    }

    //设计一个业务,进行转账
    public void transMoney(String outAtmName,String inAtmName,Float transMoney){
        //获取需要转账的人信息
        UserBean outUser = bankDao.selectOne(outAtmName);
        //获取收钱人的信息
        UserBean inUser = bankDao.selectOne(inAtmName);
        //判断转账人余额足够不
        if(outUser.getAtmBalance() > transMoney){
            //说明余额足够转账
            outUser.setAtmBalance(outUser.getAtmBalance() - transMoney);
            inUser.setAtmBalance(inUser.getAtmBalance()  + transMoney);
            bankDao.update(outUser);
            bankDao.update(inUser);
        }else{
            System.out.println("对不起" + outAtmName + ",你目前账户余额为" + outUser.getAtmBalance() + "不能转账");
        }
    }
}

测试

package banksystem.test;

import banksystem.service.BankService;
import java.util.*;

public class Test {
    public static void main(String[] args) {
        //1.数据的存储地方,这里以文件的形式存储
        //1.1文件存储在哪里,例如如果存储在E盘中,如果甲方的操作系统没有E盘,那么数据就不存在了
        //1.2存储在当前工程目录下,与工程一起交给甲方,那么就不用了担心数据不存在,造成业务出现问题

        //测试登录业务
        BankService bank = new BankService();
        //控制台输入信息
        Scanner input = new Scanner(System.in);
        System.out.println("欢迎进入银行系统\n请输入你的账号:");
        String userName = input.nextLine();
        System.out.println("请输入密码:");
        String password = input.nextLine();
        if(bank.login(userName,password).equals("登录成功")){
            System.out.println("请输入你想要操作的业务:\n1.查询余额\n2.取款\n3.存款\n4.转账");
            String option = input.nextLine();
            switch(option){
                case "1" :
                    System.out.println(bank.getBalance(userName));
                    break;
                 //进入取款时,发现顺序没有按照文件的信息排序,原因在于Map集合是无序不重复的
                //但是文件中的信息却不能正好修改,而是进行覆盖,通过设置一个flag可解决
                case "2" :
                    System.out.println("请输入你要取款的金额:");
                    String balance = input.nextLine();
                    bank.getMoney(userName,Float.parseFloat(balance));
                    System.out.println("取款成功");
                    break;
                case "3" :
                    System.out.println("请输入你要存款的金额:");
                    String depositBalance = input.nextLine();
                    bank.depositMoney(userName,Float.parseFloat(depositBalance));
                    break;
                case "4" :
                    System.out.println("请输入你要转账的人:");
                    String user = input.nextLine();
                    System.out.println("请输入你需要转账的金额:");
                    String transBalance = input.nextLine();
                    bank.transMoney(userName,user,Float.parseFloat(transBalance));
                    break;
                default :
                    System.out.println("请输入正确的数字");
                    break;
            }
        }
    }
}

{% endnote %}

MVC分层架构思想的优点:每一个层次只负责处理自已的事情,层次内部改动其他所有层次不需要改动

线程与进程

进程(Process):计算机中的程序关于某数据集合上的一次运行活动,是 系统进行资源分配和调度的基本单位

线程(thread)操作系统能够进行运算调度的最小单位被包含在进程之中,是进程中的实际运作单位。单线程指的是进程中一个单一顺序的控制流,而多线程则是进程中多条执行路径的流

{% note info %}

  1. 主线程(系统线程),例如JVM

  2. 用户线程,例如main()方法

  3. 守护(精灵)线程,例如GC

线程属于操作系统级别,其运行时间或顺序我们不能控制,这些都 由CPU分配

实现多线程
{% note primary %}
继承Thread(线程类)

package thread01;
/*第一种方式*/
public class TestThread extends Thread{

    private String name;
    public TestThread(String name){
        this.name = name;
    }

    //重写Thread类的run()方法
    public void run(){
        for(int i = 1; i < 10000; i++)
        System.out.println(this.name +"跑了" + i + "米");
    }
}
package thread01;

public class Test {
    public static void main(String[] args){
       //创建线程对象
       TestThread th1 = new TestThread("亚丝娜");
       TestThread th2 = new TestThread("桐人");
       TestThread th3 = new TestThread("诗乃");

       //调用方法,使线程在就绪状态,由于当前的操作系统性能好,所以有时可能会看到一个线程快执行完,另一个线程才开始
       th1.start();
       th2.start();
       th3.start();

    }
}

实现接口Runnable类

package thread01;
/*假设TestRunnable类已经继承了一个类;由于Java单继承,所以不能再继承Thread类
  所以需要通过实现接口的方式来创建线程
*/
public class TestRunnable implements Runnable{
    private String name;
    public TestRunnable(String name){
        this.name = name;
    }

    public void run(){
        for(int i = 1; i < 10000; i++)
            System.out.println(this.name +"跑了" + i + "米");
    }
}
package thread01;

public class Test {
    public static void main(String[] args){
        TestRunnable th1 = new TestRunnable("亚丝娜");
        TestRunnable th2 = new TestRunnable("桐人");
        TestRunnable th3 = new TestRunnable("诗乃");
        //Runnable接口只有run()方法,只能通过创建Thread类来调用start()方法
        Thread thread1 = new Thread(th1);
        Thread thread2 = new Thread(th2);
        Thread thread3 = new Thread(th3);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

{% endnote %}

火车票练习

package thread01.practice;

/**
 * 类似Ticket类的容器,目的是为了增强代码的可读性,里面有属性,属性的数据类型可以不一致
 * 这种类被我们叫做POJO(平凡简单原始类)或JavaBean
 * 其中po叫做底层数据持久化(存储)对象,主要用来读取数据库信息
 */
public class Ticket {
    private String start; //起始站
    private String end; //终点站
    //使用包装类,当数据库中没有值时,可以接收到null值
    private Float price; //价格

    public Ticket(){}
    public Ticket(String start,String end,Float price){
        this.start = start;
        this.end = end;
        this.price = price;
    }

    public void setStart(String start){
        this.start = start;
    }
    public String getStart(){
        return this.start;
    }
    public void setEnd(String end){
        this.end = end;
    }
    public String getEnd(){
        return this.end;
    }
    public void setPrice(Float price){
        this.price = price;
    }
    public Float getPrice(){
        return this.price;
    }

    //重写toString()方法

    @Override
    public String toString() {
        return "Ticket{" +
                "start='" + start + '\'' +
                ", end='" + end + '\'' +
                ", price=" + price +
                '}';
    }
}
package thread01.practice;

import java.util.*;

public class System12306 {

    //系统只有一个,所以需要只有一个对象,使用单例模式,其中属性和方法需要特征修饰符static
    private System12306(){}
    private static System12306 system = null;
    public static System12306 getSystem(){
        if(system==null){
            system = new System12306();
        }
        return system;
    }

    //火车票的数目可能不一定,使用集合Vector。线程安全,多个线程并发操作同一个集合时不会抢夺资源
    private Vector<Ticket> tickets = new Vector<Ticket>();
    //初始化集合
    {
        for (int i = 1; i <= 100; i++){
            tickets.add(new Ticket("吉隆"+i,"梅州"+i,i*20F));
        }
    }

    //设计一个方法,进行售票
    public Ticket sellTicket(){
        //每次售出第一张车票,可能出现集合越界异常,即没有票了
        try {
            return tickets.remove(0);
        }catch(Exception e){
            return null;
        }
    }
}
package thread01.practice;

public class Window extends Thread{
    private String windowName; //窗口名称

    public Window(){}
    public Window(String windowName){
        this.windowName = windowName;
    }

    public void run(){
        //这里可以调用其他方法,如何都在这里写,则只能调用一种方法
        this.ticket();
    }
    public void ticket(){
        while(true) {
            //创建System对象
            System12306 system = System12306.getSystem();
            Ticket ticket = system.sellTicket();
            if(ticket == null){
                System.out.println("对不起,你所在的" + this.windowName + "火车票已卖完");
                break;
            }
            System.out.println(this.windowName + "售出:" + ticket);
        }
    }
}
package thread01.practice;

public class Test {
    public static void main(String[] args){
        Window window1 = new Window("吉隆东站");
        Window window2 = new Window("吉隆西站");
        Window window3 = new Window("吉隆南站");
        window1.start();
        window2.start();
        window3.start();
    }
}

线程的调度模型

  1. 分时调度模型:让所有线程轮流获得CPU使用权,并且平均分配每个线程占用CPU的时间片。

  2. 抢占式调度模型java虚拟机采用的线程调度模型。它是指优先让可运行池中优先级高的线程占用CPU,如果线程的优先级相同,就随机选择一个占用CPU

设置优先级

package com.qf.demo3;
/**
 * 优先级   : 只能反映 线程 的 中或者是 紧急程度 , 不能决定 是否一定先执行
 * setPriority()
 * 1~10   1最低  10最高    5是默认值
 */
public class Test {

    public static void main(String[] args) {
        MyThread thread = new MyThread("亚丝娜");
        thread.setPriority(1);
        MyThread thread2 = new MyThread("绝剑");
        thread2.setPriority(10);
        MyThread thread3 = new MyThread("诗乃");
        thread3.setPriority(3);
        thread.start();
        thread2.start();
        thread3.start();
    }
}

class MyThread extends Thread{

     public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            // currentThread()获取当前线程的引用,getName()获取当前线程名
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

获取线程优先级

public class JavaGetPriorityExp extends Thread  {    
    public void run()  {    
        System.out.println("running thread name is:"+Thread.currentThread().getName());    
    }

    public static void main(String args[])  {    
        JavaGetPriorityExp t1 = new JavaGetPriorityExp();
        JavaGetPriorityExp t2 = new JavaGetPriorityExp();

        // 获取线程默认优先级
        System.out.println("t1 thread priority : " + t1.getPriority());   
        System.out.println("t2 thread priority : " + t2.getPriority());

        t1.start();    
        t2.start();  
    }    
}

线程的 优先级在1到10的范围内。线程的 默认优先级为5

线程类的join()方法和sleep()方法

package thread03.join;

//线程1,需要先执行
public class ThreadOne extends Thread{

    public void run(){
        System.out.println("线程1开始执行");
        //为了先线程1先执行,创建线程2对象,并处于就绪状态
        ThreadTwo two = new ThreadTwo();
        two.start();

        try {
            //join(),线程2加入线程1中时,线程1需要等待线程2执行完毕后才会执行
            //join(2000),线程1只等待线程2执行2000毫秒,时间到后,将线程2剔除
            //假设线程1等待的时间到,需要将线程2剔除时,发现线程2不在了,这时线程1只能等待到发现线程2后剔除,并执行自已
            two.join(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程1执行完毕");
    }
}
package thread03.join;

//线程2,需要在线程1后执行,并加入线程1中
public class ThreadTwo extends Thread{
    public void run(){
        System.out.println("线程2开始执行");
        //创建线程3,并处于就绪状态
        ThreadThree three = new ThreadThree(this);
        three.start();
        //使线程2休息5000毫秒后
        try {
            //使当前线程休眠5000毫秒
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程2执行完毕");
    }
}
package thread03.join;

//线程3,在线程1剔除线程2的一刹那,将线程2锁住
public class ThreadThree extends Thread{

    //获取线程2对象
    private ThreadTwo two;
    public ThreadThree(ThreadTwo two){
        this.two = two;
    }

    public void run(){
        System.out.println("线程3开始执行");
        //锁住线程2
        synchronized(two){
            try {
                //使当前线程休眠10000毫秒
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程3执行完毕");
    }
}
package thread03.join;

//测试join()方法
public class Test {
    //join()方法,能使一个线程加入到其他线程中,使两个线程并行执行
    //创建线程1
    public static void main(String[] args) {
        ThreadOne one = new ThreadOne();
        one.start();
    }
}

{% note info %}

  1. join():使一个线程加入当前线程,使当前线程等待 (唯有加入的线程执行完毕才开始执行)。可设置参数,指定加入的线程只能执行多少时间

  2. sleep(long mills):使当前线程休眠,mills表示休眠时间

  3. setDaemon(boolean on):使当前线程被标记为 守护线程,当运行的线程都是守护线程时,java虚拟机将退出

线程的生命周期
thread.jpg

{% note primary %}

  1. 创建线程:通过new关键字创建对象

  2. 就绪状态:调用start()方法

  3. 执行状态:由CPU分配时间碎片,调用run()方法

  4. 等待/挂起状态:调用wait()方法

  5. 回到就绪状态,等待被CPU唤醒:调用notify()方法或notifyAll()方法

生产者与消费着

多个线程并发访问同一个集合时,不会抢夺资源

两个消费者线程同时抢夺仓库中的资源时,可能会发生如下情况:

多线程并发可能出现的问题

此时需要给仓库加上一把锁,当仓库中有线程时,其他线程不能进入仓库,必须等待

{% note primary %}
生产者

package thread02;

import java.util.*;

//仓库,存储数据
public class WareRoom {

    //仓库只能有一个,即对象只能有一个,所以使用单例模式,也可以生产者、消费者在创建对象时将同一个仓库对象通过构造方法赋予

    //数据保存的地方,使用集合
    private ArrayList<String> list = new ArrayList<String>();

    //设计一个方法,用于给生产者生产数据
    public synchronized void add(){
        //进行判断,当生产者生产数据到达一定数目时,停止生产
        if(list.size() > 20) {
            list.add("亚丝娜");
        }else{
            //return; //退出方法
            //让线程进行等待,并唤醒其他线程
            try {
           //如果方法没有添加关键字synchronized,会出现IllegalMonitorStateException时
                this.wait(); //调用对象不需要进行等待,而是访问对象的线程需要进行等待
                this.notifyAll(); //调用对象不需要进行唤醒,而是访问对象的线程被唤醒
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    //设计一个方法,用于给消费者获取数据
    public synchronized void get(){
        //进行判断,当数据不足时,消费者不再取数据
        if(list.size()>0) {
            list.remove(0);
        }else{
            //return; //退出方法
            //让线程进行等待,并唤醒其他线程
            try {
            //如果方法没有添加关键字synchronized,会出现IllegalMonitorStateException时
                this.wait(); 
                this.notifyAll();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
  1. wait():调用对象不需要进行等待,而是访问该对象的线程需要进行等待
  2. notify() 或 notifyAll(),调用对象不需要进行唤醒,而是访问对象的线程被唤醒

消费者

package thread02;

//消费者,取出数据
public class Consumer extends Thread{

    private WareRoom wareRoom;

    public Consumer(){}
    public Consumer(WareRoom wareRoom) {
        this.wareRoom = wareRoom;
    }

    //消费者与生产者类似
    public void run(){
        while(true) {
            wareRoom.get();
            System.out.println("消费者取出一个手办");
            try {
                Thread.sleep(300);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

测试

package thread02;

public class Test {
    public static void main(String[] args){
        //创建仓库
        WareRoom wareRoom = new WareRoom();
        //创建生产者和消费者
        Producer producer = new Producer(wareRoom);
        Consumer consumer1 = new Consumer(wareRoom);
        Consumer consumer2 = new Consumer(wareRoom);
        //设计线程抢夺资源的优先级,使线程尽可能先抢夺资源,参数在1-10
        producer.setPriority(10);
        //就绪状态
        producer.start();
        consumer1.start();
        consumer2.start();
    }
}

{% endnote %}

synchronized:解决线程安全问题
{% note primary %}
一个线程锁,锁定的是对象,主要放置地:

  1. 在方法的结构中,public synchronized void get(){}

  2. 在方法(也可以是程序块或构造方法)的内部,public void get(){synchronized(对象){代码}}

死锁

当多个线程同时 并发的抢夺资源 时,可能会 出现一个线程拿着另一个线程想要的资源,另一个线程又拿着这个线程想要的资源,此时只能僵持着

哲学家就餐模型

哲学家就餐模型
{% note primary %}
筷子

package thread03.zhexuejia;

//筷子
public class Chopstick {
    private int num; //筷子的编号
    //初始化编号属性
    public Chopstick(int num){
        this.num = num;
    }
    //获取筷子编号
    public int getNum(){
        return this.num;
    }
}

哲学家

package thread03.zhexuejia;

//哲学家
public class Philosopher extends Thread{
    private String name; //哲学家名字
    private Chopstick left; //哲学家左边的筷子
    private Chopstick right; //哲学家右边的筷子
    private long time; //解决死锁,使每个哲学家等待time时间后拿筷子

    //初始化属性
    public Philosopher(String name,Chopstick left,Chopstick right,long time){
        this.name = name;
        this.left = left;
        this.right = right;
        this.time = time;
    }

    //已经拿取的筷子,其他哲学家不能再拿去
    public void run(){
        //每个哲学家需要等待一些时间才能拿筷子
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //哲学家先拿去自已左边的筷子
        synchronized (left){
            System.out.println(this.name + "拿取了左边的筷子了");
            //然后哲学家拿去自已右边的筷子
            synchronized(right){
                System.out.println(this.name + "拿取了右边的筷子了");
            }
        }
        //这是哲学家可以就餐了
        System.out.println(this.name + "可以愉快的就餐了");
    }
}

测试

package thread03.zhexuejia;

public class Test {
    public static void main(String[] args) {
        /*此时产生了死锁*/
        //四个筷子
        Chopstick chopstick1 = new Chopstick(1);
        Chopstick chopstick2 = new Chopstick(2);
        Chopstick chopstick3 = new Chopstick(3);
        Chopstick chopstick4 = new Chopstick(4);
        //四个哲学家
        Philosopher philosopher1 = new Philosopher("哲学家a",chopstick2,chopstick1,0);
        Philosopher philosopher2 = new Philosopher("哲学家b",chopstick1,chopstick4,3000);
        Philosopher philosopher3 = new Philosopher("哲学家c",chopstick3,chopstick2,3000);
        Philosopher philosopher4 = new Philosopher("哲学家d",chopstick4,chopstick3,0);
        philosopher1.start();
        philosopher2.start();
        philosopher3.start();
        philosopher4.start();
    }
}

{% endnote %}

死锁的解决线程之间存在时间差,不会产生对象共用的问题

计时器

package thread03.timer;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

//设计一个计时器,每隔几秒发送消息
public class TestTimer {
    //存储用户
    private ArrayList<String> userList = new ArrayList<String>();
    //设置次数
    private int count=0;
    //初始化用户
    {
        userList.add("a");
        userList.add("b");
        userList.add("c");
        userList.add("d");
    }
    public void sendInfo() throws ParseException {
        //一个定时器
        Timer timer = new Timer();
        //schedule()方法。第一个参数为TimerTask对象,第二个为起始时间,第三个为每次间隔多少毫秒发送
        timer.schedule(new TimerTask() {
            public void run() {
                System.out.println("第"+ count++ +  "次发送信息");
                for(int i =0;i<userList.size();i++){
                    System.out.println(userList.get(i) + "发送一条信息");
                }
                System.out.println("发送完毕");
            }
        },new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2019-11-24 10:32:00"),1000);
    }
    public static void main(String[] args) throws ParseException {
        new TestTimer().sendInfo();
    }
}

网络编程

计算机网络:利用通信线路和通信设备,将地理位置不同的、功能独立的多台计算机互连起来,以功能完善的网络软件来实现 资源共享信息传递,就构成了计算机网络系统

网络编程:在网络通信协议下,实现 网络互连的不同计算机商运行的程序间可以进行数据交换

三要素

  1. ip地址:为每台计算机指定 一个标识号,来 指定要接收数据的计算机和识别发送的计算机,即让网络中的计算机能够互相通信

  2. 端口:在网络通信中,唯一标识计算机中的应用程序

  3. 网络通信协议:网络上所有设备(网络服务器、计算机及交换机、路由器、防火墙等)之间 通信规则的集合,它规定了通信时信息必须采用的格式和这些格式的意义

大多数网络都采用 分层 的体系结构,每一层都建立在它的下层之上,向它的上一层提供一定的服务,而把如何实现这一服务的细节对上一层加以屏蔽

ip地址

最初设计互联网络时,为了便于寻址以及层次化构造网络,每个IP地址包括两个标识码(ID),即网络ID和主机ID。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。IP地址根据网络ID的不同分为5种类型,A类地址、B类地址、C类地址、D类地址和E类地址。

ip地址的分类

ip地址每种类型的范围
ip地址每种分类的范围

两大类

  1. IPv4:通过 点分十进制表示法 给每个连接在计算机上的主机分配一个 32bit 的地址

  2. IPv6:为了解决网络地址资源数量不够的问题,采用128位地址长度,通过IPv6重新定义地址空间

ip地址的常用命令
{% note primary %}
ipconfig
ipconfig

ping (ip地址)
ping

127.0.0.1(locallhost),回送地址,可以代表本机地址,作为测试使用

Java中关于IP地址的操作类

InetAddress类

package moe.net;

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

public class TestInetAddress {
    public static void main(String[] args) throws UnknownHostException {
        //参数为 主机名称 或 ip地址(推荐),创建对象
        InetAddress address = InetAddress.getByName("192.168.101.8");

        //获取主机名称
        String hostName = address.getHostName();
        System.out.println("主机名称:" + hostName);

        //获取ip地址
        String hostAddress = address.getHostAddress();
        System.out.println("ip地址:" + hostAddress);
    }
}

端口

两个字节 表示的整数,取值范围为 0~65535

{% note default %}

  1. 0~1023 之间的端口好用于一些知名的1网络服务和应用。一般来说,普通的应用程序需要使用1024以上的端口号

  2. 当端口号被另一个 服务 或 应用所占用时,会导致当前程序启动失败

网络通信协议

UDP协议

UDP(User Datagram Protocol),用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。UDP提供了无连接通信,且 不对传送数据包进行可靠性保证适合于一次传输少量数据,UDP传输的可靠性由应用层负责

Java中的UDP
DatagramSocket

{% note primary %}
发送数据

package moe.net;

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

public class TestDatagramSocket {
    public static void main(String[] args) throws IOException {
        //构造一个数据报套接字并将其绑定到本地主机上的任何可用端口
        DatagramSocket socket = new DatagramSocket();

        /*发送数据报*/
        byte[] bytes = "刀剑神域第三季".getBytes();
        // 构造一个数据报包,用于将长度为 length的数据包发送到指定主机上的指定端口号
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.101.8"), 10086);
        socket.send(packet);

        //关闭发送端
        socket.close();
    }
}

接收数据

package moe.net;

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

public class TestRecelveData {
    public static void main(String[] args) throws IOException {
        //构造一个数据报套接字并将其绑定到本地主机上的指定端口
        DatagramSocket socket = new DatagramSocket(10086);

        /*接收数据*/
        //构造 DatagramPacket用于接收长度为 length数据包
        byte[] bytes = new byte[1024];
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
        socket.receive(packet);

        //解析数据
        byte[] data = packet.getData();
        String str = new String(data, 0, packet.getLength());
        System.out.println("接收到的数据:" + str);

        //关闭接收端
        socket.close();
    }
}

{% endnote %}

练习
{% note primary %}
只有当发送数据为886时才停止程序

package moe.practise.udp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
//发送端
public class SendData {
    public static void main(String[] args) throws IOException {
        //创建对象
        DatagramSocket socket = new DatagramSocket();

        //输入数据
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

       //发送数据
       String line = null;
       while((line = bufferedReader.readLine()) != null){
           if("886".equals(line)){ //跳出循环
               break;
           }
           //获取输入的字符串,将其转换为字节数组,封装为数据报
           byte[] bytes = line.getBytes();
           DatagramPacket packet = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("192.168.101.8"),10086);
           socket.send(packet);
       }
        //关闭发送端
        socket.close();
    }
}
package moe.practise.udp;

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

public class ReceiveData {
    public static void main(String[] args) throws IOException {
        //创建对象
        DatagramSocket datagramSocket = new DatagramSocket(10086);

        //接收数据
        byte[] bytes = new byte[1024];
        int count = 0;
        while (true){
            DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
            datagramSocket.receive(datagramPacket);

            //解析数据
            byte[] data = datagramPacket.getData();
            System.out.println(new String("数据" + count + ":" + data,0,datagramPacket.getLength()));
        }
    }
}

{% endnote %}

{% note info %}
UDP协议

TCP协议

TCP(Transmission Control Protocol 传输控制协议) 是一种面向连接(连接导向)的、可靠的、 基于IP的传输层协议

{% note primary %}
发送数据

package moe.net;

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

public class TestSocket {
    public static void main(String[] args) throws IOException {
        //创建流套接字并将其连接到指定主机上的指定端口号
        Socket socket = new Socket("192.168.101.8", 10000);

        //获取输出流对象,写入数据
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("刀剑神域".getBytes());

        //关闭发送端
        socket.close();
    }
}

客服端发送数据 到服务器端,需要经历三次:客服端发送连接请求服务器端响应请求客服端发送确认信息(判断是否连接成功)

接收数据

package moe.net;

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

public class TestServerSocket {
    public static void main(String[] args) throws IOException {
        //创建绑定到指定端口的服务器套接字
        ServerSocket serverSocket = new ServerSocket(10000);


        //监听客服端发送的请求
        Socket socket = serverSocket.accept();

        //获取输入流,读取数据
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int read = inputStream.read(bytes);
        while(read != -1){
            System.out.println(new String(bytes,0,read));
            read = inputStream.read(bytes);
        }

        //关闭接收端
        serverSocket.close();
    }
}

{% endnote %}

{% note info %}

  1. 运行客服端(仅发送数据)程序,会发生异常:ConnectException:表示没有连接的服务器,连接被拒绝了

  2. 当端口被占用时,会发生异常:BindException: Address already in use: NET_Bind

关闭当前被占用的端口
{% note success %}
端口占用解决

netstat -aon|findstr "端口"(查看指定端口的占用情况)tasklist|findstr "PID"(查看PID对应的进程)taskkill /f /t /im 进程(结束该进程)

{% note info %}
TCP协议

Lambda表达式

面向对象思想强调:必须通过对象的形式来做事情,而函数式思想则尽量忽略面向对象的复杂语法强调做什么,而不是以什么形式去做

启动线程,控制台输出线程已启动

package moe.lambda;

public class FirstLambda {
    public static void main(String[] args) {
        /*传统的做法*/
        //创建对象
       MyRunnable runnable = new MyRunnable();
       Thread thread = new Thread(runnable);
       thread.start();

        /*简化操作,通过匿名内部类,此时无需构建MyRunnable类*/
       new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("线程已启动");
           }
       }).start();

        /*继续简化操作,使用Lambda表达式*/
        new Thread(() -> {
            System.out.println("线程已启动");
        }).start();
    }
}
package moe.lambda;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("线程已启动");
    }
}

Lambda表达式 格式(形式参数) -> {方法体}

省略模式

package moe.lambda;

public interface TestLam {
    int add(int x,int y);
}
package moe.lambda;

public class Test {
    public static void main(String[] args) {
        /*
            第一种:可省略参数的数据类型,但不能只省略掉其中一个参数的数据类型
            第二种:当方法体只有 一条语句 时,可省略 {} 和 ; ,且如果包含 return ,则该关键字需要省略
        */
        add((x,y) -> x + y);
    }

    private static void add(TestLam lam){
        int add = lam.add(10, 20);
        System.out.println(add);
    }
}

{% note info %}
两个注意事项:

  1. 接口的 抽象方法只能有一个,才能像上面那样使用Lambda表达式

  2. 注意需要能 推导出上下文环境

根据 局部变量的赋值 得知Lambda表达式对应的接口:Runnable runnable = () -> System.out.println("表达式")
根据 调用方法的参数 得知Lambda表达式对应的接口:new Thread()(() -> System.out.println("表达式")).start()

匿名内部类和Lambda表达式的区别

  1. 匿名内部类 可用于 抽象类、具体类 和接口,而 Lambda表达式只能用于 接口

  2. Lambda表达式的 接口有且只有一个抽象方法,而匿名内部类可以有多个

  3. 在编译时,Lambda会 动态生成一个 字节码文件,而匿名内部类会创建一个字节码文件。即Lambad表达式 不会额外生成一个字节码文件

方法引用

通过 引用运算符(::),简化Lambda表达式

适用范围
{% note primary %}
引用类中的方法

package moe.lambda;

public interface TestMethod {
    void show(String s);
}
package moe.lambda;

public class TestInMethod {
    public static void main(String[] args) {
        //使用print()方法,该方法接收 Lambda表达式的形式参数,作为自已的参数
        show(System.out::print);
    }

    private static void show(TestMethod testMethod){
        testMethod.show("刀剑神域");
    }
}
  1. 该方法 可以是 静态方法,也可以是 普通的成员方法
  2. 该类 可以是 别人已经创建好的类,也可以是 自已创建的类

引用构造器

package moe.lambda;

public class Student {
    private String name = null;
    private Integer age = null;

    public Student() {
    }

    public Student(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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package moe.lambda;

public interface StudentBuilder {
    Student build(String name,Integer age);
}
package moe.lambda;

public class TestInMethod {
    public static void main(String[] args) {
        show1(Student::new);
    }

    private static void show1(StudentBuilder studentBuilder){
        Student student1 = studentBuilder.build("亚丝娜",17);
        System.out.println(student1);
    }
}

方法引用所接收的参数接口中对应方法的全部参数

函数式接口

使用 函数式接口 的前提:该接口有且只有一个抽象方法,一般可添加 注解@FunctionalInterface 标记(可省略)

package moe.lambda;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class TestFunction {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("a");
        arrayList.add("ccc");
        arrayList.add("bb");
        arrayList.add("dd");

        Collections.sort(arrayList,getComparator());

        System.out.println(arrayList);
    }

    private static Comparator<String> getComparator(){
        //使用Lambda表达式,通过比较长度进行排序
        return (s1,s2) -> s1.length()-s2.length();
    }
}

常用函数式接口
{% note success %}
生产型接口:Supplier

Supplier接口

package moe.lambda;

import java.util.function.Supplier;

public class TestSupplier {
    public static void main(String[] args) {
        //创建一个整型数组
        Integer[] array = {25,89,56,102,27};

        //使用Lambda表达式获取数组的最大值
        Integer maxValue = getMaxValue(() -> {
            int max = array[0];

            for (int i = 1; i < array.length; i++) {
                if(array[i] > max){
                    max = array[i];
                }
            }
            return max;
        });

        //输出最大值
        System.out.println("该数组的最大值:" + maxValue);
    }

    private static Integer getMaxValue(Supplier<Integer> supplier){
        // 将获取的结果返回
        return supplier.get();
    }
}

消费型接口:Consumer

Consumer接口

package moe.lambda;

import java.util.function.Consumer;

public class TestConsumer {
    public static void main(String[] args) {
        //创建一个数组
        String[] array = {"亚丝娜,17","朝田诗乃,16","优纪,15"};

        //返回 姓名:xx,年龄:xx 的形式
        operatorArray(array,str1 -> System.out.print("姓名:" + str1.split(",")[0]),
        str2 -> System.out.println(",年龄:" + str2.split(",")[1]));
    }

    private static void operatorArray(String[] array, Consumer<String> consumer1,Consumer<String> consumer2){
        for(String str : array){
            //andThen():参数 在 调用者后执行,accept():消费该参数
            consumer1.andThen(consumer2).accept(str);
        }
    }
}

判断型接口:Predicate

Predicate接口

package moe.lambda;

import java.util.ArrayList;
import java.util.function.Predicate;

public class TestPredicate {
    public static void main(String[] args) {
        //创建一个数组
        String[] array = {"亚丝娜,17","朝田诗乃,16","优纪,15"};

        //将 姓名的长度大于2 和 年龄大于16 的字符串 保存在集合中
        ArrayList<String> arrayList = filterString(array,str1 -> str1.split(",")[0].length()>2,
        str2 -> Integer.parseInt(str2.split(",")[1])>16);

        System.out.println(arrayList);
    }

    private static ArrayList<String> filterString(String[] array, Predicate<String> predicate1,Predicate<String> predicate2){
        ArrayList<String> arrayList = new ArrayList<>();

        for (String str : array) {
            /*
                and():短路与
                or():短路或
                test():判断条件是否满足
                negate():逻辑非
            */
            if(predicate1.and(predicate2).test(str)){
                arrayList.add(str);
            }
        }
        return arrayList;
    }
}

Function接口

Function

package moe.lambda;

import java.util.function.Function;

public class TestFun {
    public static void main(String[] args) {
        //一个字符串
        String str = "朝田诗乃,16";

        //操作
        operator(str,Integer::parseInt,number -> String.valueOf(number + 1));
    }

    private static void operator(String str, Function<String,Integer> function1,Function<Integer,String> function2){
        /*
            andThen():通过操作,调用者获取的结果 给参数进行操作
            apply():通过操作 一个数据 获取结果 
        */
        String s = function1.andThen(function2).apply(str.split(",")[1]);
        System.out.println(s);
    }
}

{% endnote %}

Stream流

配合 Lambad表达式 和 函数式编程 可以简化一些代码

中间操作
{% note primary %}
fitter():过滤

package moe.stream;

import java.util.ArrayList;
import java.util.stream.Stream;

public class TestFitter {
    public static void main(String[] args) {
        //一个 Collection集合 下的子类对象
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("亚丝娜");
        arrayList.add("朝田诗乃");
        arrayList.add("诗乃乃");

        //将该集合导入Stream流中
        Stream<String> stream = arrayList.stream();

        //进行中间操作:fitter(Predicate<T> predicate)
        Stream<String> stringStream = stream.filter(s -> s.startsWith("诗"));

        //终结操作
        stringStream.forEach(System.out::println);
    }
}

limit() 和 skip():获取某些元素

package moe.stream;

import java.util.HashMap;
import java.util.stream.Stream;

public class TestSkip {
    public static void main(String[] args) {
        // Map集合 获取Stream需要间接操作
        HashMap<String,String> hashMap = new HashMap<String,String>();
        hashMap.put("亚丝娜","17");
        hashMap.put("朝田诗乃","16");
        hashMap.put("优纪","15");
        hashMap.put("桐谷和人","16");

        //需间接获取Stream流
        Stream<String> stream = hashMap.keySet().stream(); //此处为key的Set集合

        /*
            limit(lone l):限制获取流中的元素,从第一个开始
            skip(long l):跳过几个流中的元素
        */
        stream.limit(3).skip(1).forEach(System.out::println);
    }
}

concat() 和 distinct():连接两个流

package moe.stream;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class TestConcat {
    public static void main(String[] args) {
        // Map集合 获取Stream需要间接操作
        HashMap<String,String> hashMap = new HashMap<String,String>();
        hashMap.put("亚丝娜","17");
        hashMap.put("朝田诗乃","16");
        hashMap.put("优纪","15");
        hashMap.put("桐谷和人","16");

        //获取第一个流
        //需间接获取Stream流
        Stream<String> stream = hashMap.values().stream();  //value所对应的Set集合

        //获取第二个流
        Stream<Map.Entry<String, String>> entryStream = hashMap.entrySet().stream(); //key=value所对应的Set集合

        //合并这两个流
//        Stream.concat(stream,entryStream).forEach(System.out::println);

        //根据equals()方法,去除重复的元素
        Stream.concat(stream,entryStream).distinct().forEach(System.out::println);
    }
}

被终结的Stream流不能再次被操作,否则将出现异常:java.lang.IllegalStateException: stream has already been operated upon or closed

sorted():排序

package moe.stream;

import java.util.stream.Stream;

public class TestSorted {
    public static void main(String[] args) {
        //数组
        String[] str = {"亚丝娜","结城明日奈","桐谷和人","朝田诗乃"};

        //获取Stream流
        Stream<String> stream = Stream.of(str); // 等价 Stream.of("亚丝娜","结城明日奈","桐谷和人","朝田诗乃")

        //自然排序
        //stream.sorted().forEach(System.out::println);

        //按照某种规则排序
        stream.sorted((s1,s2) -> s1.length()-s2.length()).forEach(System.out::println);
    }
}

map() 和 mapToInt()

package moe.stream;

import java.util.ArrayList;
import java.util.stream.Stream;

public class TestMap {
    public static void main(String[] args) {
        //以ArrayList集合为例
        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("12");
        arrayList.add("34");
        arrayList.add("56");

        //转换为Stream流
        Stream<String> stream = arrayList.stream();

        //map方法:返回由给定函数应用于此流的元素的结果
        // stream.map(Integer::parseInt).count(); //终结操作,返回当前流中的元素个数

        //sum()是 IntStream(int原始专业化Stream流 ) 特有的方法,能计算元素的总和
        int sum = stream.mapToInt(Integer::parseInt).sum(); //终结操作,统计当前流中元素的总和
        System.out.println(sum);
    }
}

集合、数组 中的元素才能到 Stream流中

Collectors类中的 toXxx()方法

package moe.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class TestTo {
    public static void main(String[] args) {
        /*将Stream流中的元素保存到List集合中*/
        //1.List集合
        ArrayList<String> list = new ArrayList<String>();
        list.add("亚丝娜");
        list.add("朝田诗乃");

        //2.将List集合放入Stream流中,并进行相关操作
        Stream<String> stream = list.stream().limit(1);

        //3.将流中的元素保存到List集合中
        List<String> collect = stream.collect(Collectors.toList());
        System.out.println(collect);
    }
}

其中,toSet()可以转换为Set集合,toMap()可以转换为Map集合

类加载

类的加载过程

类的加载器ClassLoader

package moe;

public class fanshe {
    public static void main(String[] args) {
        // 返回系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader); //jdk.internal.loader.ClassLoaders$ AppClassLoader @78308db1

        // 返回委托的父类加载器
        ClassLoader systemClassLoaderParent = systemClassLoader.getParent();
        System.out.println(systemClassLoaderParent); //jdk.internal.loader.ClassLoaders$ PlatformClassLoader @5fd0d5ae

        ClassLoader parent = systemClassLoaderParent.getParent();
        System.out.println(parent); //null
    }
}

Java运行时具有以下内置类加载器

  1. Bootstrap类加载器。 它是虚拟机的内置类加载器,通常表示为null ,并且没有父级。

  2. Platform class loader 。 平台类加载器可以看到所有平台类 ,它们可以用作ClassLoader实例的父ClassLoader 。 平台类包括Java SE平台API,它们的实现类以及由平台类加载器或其祖先定义的特定于JDK的运行时类。

  3. System class loader 。 它也称为应用程序类加载器 ,与平台类加载器不同。 系统类加载器通常用于在应用程序类路径,模块路径和JDK特定工具上定义类。 平台类加载器是系统类加载器的父级或祖先,所有平台类都是可见的

JVM的类加载机制

  1. 全盘负责:当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;

  2. 委托机制:先让parent(父)类加载器(而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。

  3. 缓存机制:如果cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。

反射

获取Class对象

package moe.fanshe;

public class TestClass {
    public static void main(String[] args) {
        /*第一种*/
        Class<TestClass> aClass = TestClass.class;
        Class<TestClass> testClassClass = TestClass.class;

        /*第二种*/
        Class<? extends TestClass> bClass = new TestClass().getClass();

        /*第三种*/
        Class<?> cClass = null;
        try {
            cClass = Class.forName("moe.fanshe.TestClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 都为 true
        System.out.println(aClass.equals(testClassClass));
        System.out.println(aClass.equals(bClass));
        System.out.println(aClass.equals(cClass));
        System.out.println(bClass.equals(cClass));
    }
}

获取构造方法和创建实例

package moe.fanshe;

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

public class TestConstructor {

    private String name = null;
    private Integer age = null;

    public TestConstructor() {
    }

    public TestConstructor(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;
    }

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

    //主方法:程序的入口,一个项目中只能有一个
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取该类Class对象
        Class<?> a = Class.forName("moe.fanshe.TestConstructor"); //抛出 异常:ClassNotFoundException

        /*获取该类的构造方法*/
        Constructor<?> constructor = a.getConstructor(String.class, Integer.class); //抛出 异常:NoSuchMethodException

        //创建实例
        Object instance = constructor.newInstance("亚丝娜", 17); //抛出 异常:IllegalAccessException, InvocationTargetException, InstantiationException
        System.out.println(instance);
    }
}

{% note default %}
获取构造方法的数组

  1. getConstructors():获取 公共 的构造方法,返回一个数组

  2. getDeclaredConstructors():获取 所有 的构造方法,返回一个数组

获取单个构造方法

  1. getConstructor(Class<?>... parameterTypes):获取一个 公共 的构造方法,其中参数为 对应顺序的数据类型的Class对象

  2. getDeclaredConstructor(Class<?>... parameterTypes):获取一个 所有 的构造方法,其中参数为 对应顺序的数据类型的Class对象

创建对象实例
newInstance(Object... initargs):创建实例,如果 构造方法为private时,则不能正常创建

setAccessible(boolean flag)值true表示反射对象在使用时应禁止检查Java语言访问控制,即此时可以通过私有的构造方法创建实例

成员变量的操作

package moe.fanshe;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class TestFiled {
    private String name = null;
    public Integer age = null;

    public TestFiled() {
    }

    private TestFiled(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;
    }

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

    //主方法:程序的入口,一个项目中只能有一个
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取该类的Class对象
        Class<?> field = Class.forName("moe.fanshe.TestFiled"); //抛出 异常:ClassNotFoundException

        //获取该类的成员变量
        Field name = field.getDeclaredField("age"); //抛出 异常:NoSuchFieldException

        Object instance = field.getConstructor().newInstance(); //抛出 异常:IllegalAccessException, InvocationTargetException, InstantiationException
        //IllegalArgumentException:不合法的参数
        name.set(instance, 17); 

        System.out.println(instance);
    }
}

{% note default %}
获取成员变量的数组

  1. getFields():获取 公共 的成员变量,返回一个数组

  2. getDeclaredFields():获取 所有 的成员变量,返回一个数组

获取单个成员变量

  1. getField(String name):获取一个 公共 的成员变量

  2. getDeclaredField(String name):获取一个 所有 的成员变量

set(Object obj, Object value):给obj对像的成员变量赋值(value)

获取成员方法

package moe.fanshe;

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

public class TestMehtod {

    private String name = null;
    public Integer age = null;

    public TestMehtod() {
    }

    private TestMehtod(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;
    }

    public void method(String str){
        System.out.println(str);
    }


    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取该类的Class对象
        Class<?> method = Class.forName("moe.fanshe.TestMehtod"); //抛出 异常:ClassNotFoundException

        //获取成员方法
        Method method1 = method.getMethod("method", String.class); //抛出 异常:NoSuchMethodException
        
        //调用成员方法
        Object instance = method.getConstructor().newInstance(); //抛出 异常:IllegalAccessException, InvocationTargetException, InstantiationException
        method1.invoke(instance,"亚丝娜");
    }
}

{% note default %}
获取成员方法的数组

  1. getMethods():获取 公共 的成员方法,包含继承的,返回一个数组

  2. getDeclaredMethods():获取 所有 的成员方法,返回一个数组

获取单个成员方法

  1. getMethod(String name):获取一个 公共 的成员方法

  2. getDeclaredMethod(String name):获取一个 所有 的成员方法

invoke(Object obj, Object... args):执行obj对象的成员方法