Java学习笔记-03(API阶段-1)

前言

目前我们看到的是Java基础部分的一个新的部分API,这是个啥,又能做啥呢?
其实可以概括成一句话:帮助我们站在巨人的肩膀上,实现更加高效的开发,那么我们来一探究竟吧~

API

API(Application Programming Interface,应用程序接口)是一些预先定义的函数。目的是提供应用程序与开发人员基于某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节.
API是一种通用功能集,有时公司会将API作为其公共开放系统,也就是公司制定自己的系统接口标准,当需要进行系统整合,自定义和程序应用等操作时,公司所有成员都可以通过该接口标准调用源代码.

Java.util包是java中的工具包,包含各种实用工具类/集合类/日期时间工具等各种常用工具包
import java.util.Scanner;
import java.util.Arrays;
 
java.lang包是java的核心,包含了java基础类
包括基本Object类/Class类/String类/基本数学类等最基本的类,这个包无需导入,默认会自动导入
import java.lang.Object;
import java.lang.String;
import java.lang.StringBuilder/StringBuffer;
正则表达式
包装类等等

那么接下来我们会学习一些常用的类,帮助大家完成"基建工程"

1. Object

1.1 概念

Object类是所有Java类的祖先,也就是说我们所说的”顶级父类”
它存在于java.lang.Object,这个包不需要我们手动导包
需要注意的是:每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法.
在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类.

1.2 常用方法介绍

1.2.1 toString()

本方法用于返回对应对象的字符串表示

1.2.2 hashCode()

本方法用于返回对应对象的哈希码值
小贴士:哈希码值的得出是通过一种算法,意在让不同的对象具有不同的哈希码值,用于区分不同的对象.
但是有时候也存在不同对象哈希码值相同的特殊情况,我们称之为”哈希碰撞”现象

1.2.3 equals()

本方法用于指示其他某个对象是否与当前对象”相等”

1.2.4 测试

package cn.tedu.api;

import java.util.Objects;

/*本类用于顶级父类Object的入门案例*/
//1.查API手册
//2.连点两下Shift打开IDEA的搜索,注意勾选"include non-Project items",再搜Object
//3.按住Ctrl点hashCode()
//4.在拓展库External Libraries找到jdk1.8->rt.jar->java.lang.Object
public class TestObject {
    public static void main(String[] args) {
        //4.创建学生类的对象做测试
        Student s = new Student();
        Student s1 = new Student("海绵宝宝",3);
        Student s2 = new Student("海绵宝宝",3);

        //5.测试hashCode()
        /*本方法的作用是返回对应对象的int类型的哈希码值
        * 本方法力求不同的对象返回的哈希码不同
        * 这样我们就可以根据哈希值区分不同的对象*/
        System.out.println(s.hashCode());
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        //6.测试toString()
        //打印s对象的是println(),这个方法会层层调用,一直到Object中的toString()
        /*Object中toString()的默认实现:对象的名字@十六进制的哈希码值
        * 子类重写了toString()以后:打印是对象的类型+属性+属性值*/
        //return getClass().getName() + "@" + Integer.toHexString(hashCode());
        System.out.println(s);
        System.out.println(s1);

        // 8.测试equals()
        /*Object中equals()的默认实现使用的是==比较
        * ==比较的是左右两边的值,如果是基本类型,比较的就是字面值,比如1和1,3.4和3.4
        * 如果是引用类型,比较的是引用类型变量保存的地址值
        * 子类重写了equals()与hashCode()以后,比较的就是对象的类型+属性+属性值*/
        System.out.println(s1.equals(s2));//false
        System.out.println(s.equals(s1));
    }
}
//1.创建一个学生类
class Student{
    //2.定义属性
    String name;//姓名
    int age;//年龄
    //3.1添加本类的无参构造
    public Student() {
        System.out.println("无参构造");
    }
    //3.2添加本类的全参构造
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("全参构造");
    }
    //7.在Student类中添加重写的toString()
    //右键->Generate->toString
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //9.添加重写的equals与hashCode()
    /*equals()与hashCode()逻辑要保持一致,要重写都重写,要不重写,都不重写
    * Object默认实现:hashCode()的哈希码值根据地址值生成
    *               equals()底层使用==比较两个对象的地址值
    * Student类重写后:hashCode()的哈希码值根据重写后传入的对象的属性生成
    *                equals()比较两个对象的类型+所有属性与属性值
    * */
    @Override
    public boolean equals(Object o) {
        //前提:this代表的是调用本方法对象s1 o代表的是传入的对象s2
        //1.比较的是两个对象的地址值,如果==为true,证明直接就是同一个对象
        //后续就不用比较了,直接返回true
        if (this == o) return true;
        //2.1如果传入的对象是null,说明实际上并没有对象,还是引用类型的默认值
        //2.2如果两个对象获取类型不一致,比如一个是Cat类型,一个是Car类型
        //以上两种情况只要满足任意一种,不符合条件,直接返回false
        if (o == null || getClass() != o.getClass()) return false;
        //3.传入对象的类型是Object,父类无法使用子类的特有属性,所以需要强转
        /*多态:向上造型:把子类型看作是父类型,花木兰替父从军 Animal a = new Cat();
        向下造型:之前转成父类型的子类对象,又想使用子类自己的特有功能了,可以向下转型
                 写法:Cat c = (Cat) a; 比如花木兰打仗完了想用自己的特有功能:化妆*/
        //向下造型:把父类型Object转回子类型Student
        Student student = (Student) o;
        //4.比较的是两个对象的属性与属性值
        //如果是基本类型,直接比较值,所以用==比较
        //如果是引用类型,比如String,还需要使用bjects.equals()做比较
        return age == student.age && Objects.equals(name, student.name);
    }
    @Override
    public int hashCode() {
        //重写后根据属性生成
        return Objects.hash(name, age);
    }
}

1.积累如上代码的重写过程思路
2.equals()与hashCode()逻辑要保持一致,要重写都重写,要不重写,都不重写

  • IDEA可以快速生成,右键->generate->equals and hashCode->next……

3.Object中toString()的默认实现:对象的名字@十六进制的哈希码值

  • 子类重写了toString()以后:打印是对象的类型+属性+属性值

2. String

2.1 概述

特点:

  1. String是一个封装char[]数组的对象
  2. 字符串不可变

通过下图中的底层实现可以看出:被final修饰,是常量
String str = “abc”; 等效于:char data[] = {‘a’, ‘b’, ‘c’};
在这里插入图片描述

2.2 创建String对象的方式

方式一:
String(char[] value) 分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
方式二: String str = “abc”;
在这里插入图片描述

如果是第一次使用字符串,java会在字符串堆中常量池创建一个对象。
再次使用相同的内容时,会直接访问堆中常量池中存在的对象。

2.3 常见方法

API总结
int hashCode() 返回此字符串的哈希码。
boolean equals(Object anObject) 将此字符串与指定的对象比较,比较的是重写后的串的具体内容
String toString() 返回此对象本身(它已经是一个字符串!)。

int length() 返回此字符串的长度。
String toUpperCase() 所有字符都转换为大写。
String toLowerCase() 所有字符都转换为小写
boolean startsWith(String prefix) 测试此字符串是否以指定的元素开头。
boolean endsWith(String suffix) 测试此字符串是否以指定的字符串结束。

char charAt(int index) 返回指定索引/下标处的 char 值/字符
int indexOf(String str) 返回指定字符在此字符串中第一次出现处的索引。
int lastIndexOf(String str) 返回指定字符在此字符串中最后一次出现处的索引。
String concat(String str) 将指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串
String[] split(String regex) 根据给定元素来分隔此字符串。

String trim() 返回去除首尾空格的字符串
byte[] getBytes() 把字符串存储到一个新的 byte 数组中
String substring(int beginIndex) 返回一个新子串,从指定下标处开始,包含指定下标
String substring(int beginIndex, int endIndex) 返回一个新子串,从执定下标开始,到结束下标为止,但不包含结束下标
static String valueOf(int i) 把int转成String

2.4 String常用方法练习

2.4.1 创建String对象

package cn.tedu.api;
/*本类用于练习String类的使用*/
public class TestString {
    public static void main(String[] args) {
        //1.创建String的方式一
        /*1.字符串类型底层维护的是char[],存在堆中*/
        char[] value = {'a','b','c'};
        String s1 = new String(value);//触发String(char[])的含参构造来创建对象
        String s11 = new String(value);//触发String(char[])的含参构造来创建对象
        //2.创建String的方式二
        String s2 = "abc";
        String s22 = "abc";
        String s3 = "ccc";
        //3.测试
        System.out.println(s1 == s2);//false,一个在堆里,一个在堆中常量池
        System.out.println(s1 == s11);//false,两个不同的对象,地址值不同
        System.out.println(s2 == s22);//true,都在堆中常量池,并且指向同一个,所以地址值相同
        System.out.println(s2 == s3);//false,都在堆中常量池,但是数据不同,指向两个地址

        /*Object类中equals()的默认实现是通过==来比较的。
        但是String类已经重写过了继承自Object中的equals()
        重写后,不再按照==比较,而是比较两个字符串的具体内容
        也就是说,不论创建方式,只要是串的内容一致,equals()就返回true
        * */
        System.out.println(s1.equals(s2));//true
        System.out.println(s1.equals(s11));//true
        System.out.println(s2.equals(s3));//false

    }
}

补充:
1.String常量池视频说明
2.String两种创建方式的不同

2.4.2 常用方法测试

package cn.tedu.api;

import java.util.Arrays;

/*本类用来测试String类的常用方法*/
public class TestString2 {
    public static void main(String[] args) {
        //1.创建字符串
        String s1 = "abc";

        char[] values = {'a','b','c'};
        String s2 = new String(values);

        //2.测试常用方法
        /*String重写了hashCode(),是根据字符串的内容生成哈希码值,而不是根据地址值生成
        * 所以虽然s1与s2一个在堆的常量池中,一个在堆中,它两的哈希码值一样*/
        System.out.println(s1.hashCode());//96354
        System.out.println(s2.hashCode());//96354

        System.out.println(s1.equals(s2));//true,重写了,比较的是具体内容

        System.out.println(s1.toString());//不需要写,底层会自动调用s1对象的toString()
        System.out.println(s1);//abc,String重写了toString(),直接打印的是串的具体内容

        System.out.println(s1.length());//3,查看当前字符串的长度
        System.out.println(s1.toUpperCase());//ABC,将本字符串转为全大写
        System.out.println(s1.toLowerCase());//abc,将本字符串转为全小写
        System.out.println(s1.startsWith("a"));//true,判断本字符串是否以指定元素a开头
        System.out.println(s2.endsWith("a"));//false,判断本字符串是否以指定元素a结尾

        System.out.println(s1.charAt(0));//a,根据下标获取本字符串中对应的元素

        String s3 = "abcbdbba";
        System.out.println(s3.indexOf("b"));//1,返回本字符串中指定元素第一次出现的下标
        System.out.println(s3.lastIndexOf("b"));//6,返回本字符串中指定元素最后一次出现的下标

        System.out.println(s2.concat("cxy"));//abccxy,将指定字符串拼接到本字符串的结尾
        System.out.println(s2);//abc,说明上面的拼接是临时的,不会改变原串的内容

        String s4 = s2.concat("aaa");//如果想要多次使用拼接后的结果,需要定义一个字符串来保存结果
        System.out.println(s4);//abcaaa

        String s5 = "afbfcfdfe";
        //返回值类型是String[],所以需要使用Arrays.toString()打印
        //以指定字符作为分割符,分割当前的字符串
        //我们只是直接打印了split()的结果,没有使用变量保存
        System.out.println(Arrays.toString(s5.split("f")));//[a, b, c, d, e]

        //由于split()的返回值类型是String[],所以我们执行这个方法,可以拿到这个方法的返回值
        //所以a数组就是这个方法执行以后得到的结果,只不过我们保存下来了
        String[] a = s5.split("f");
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }

        String s6 = "       hh hhh      ";//去除本字符串首尾两端的空格
        System.out.println(s6.trim());//hh hhh

        String s7 = "abcdefgh";
        System.out.println(s7.substring(3));//defgh,从指定下标处截取子字符串[3,结束]
        System.out.println(s7.substring(3,6));//def,从指定下标处截取子字符串[3,6)含头不含尾

        System.out.println(String.valueOf(10));//10,将int类型的参数10转为String类型
        System.out.println("20"+10);//2010,String与int拼接
        System.out.println(20+10);//30,int与int相加
        System.out.println(String.valueOf(80)+10);//8010,将int类型的参数80转为String类型,所以拼接

        byte[] bs = s7.getBytes();//将指定字符串转为byte[]
    }
}

3. StringBuffer/StringBuilder

3.1 概述

特点:
1.封装了char[]数组
2.是可变的字符序列
3.提供了一组可以对字符内容修改的方法
4.常用append()来代替字符串做字符串连接”+”
5.内部字符数组默认初始容量是16:super(str.length() + 16);
6.如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity =
value.length * 2 + 2;
7.StringBuffer 1.0出道线程安全,StringBuilder1.5出道线程不安全

3.2 常用方法

append()

3.3 测试字符串连接

package cn.tedu.api;
/*本类用于测试字符串的拼接*/
public class TestString3 {
    public static void main(String[] args) {
        //需求:将26个字母拼接10000次
        String s = "abcdefghijklmnopqrstuvwxyz";
        //method1(s);//使用方式一拼接字符串
        method2(s);//使用方式二拼接字符串
    }
    //使用方式二拼接字符串
    private static void method2(String s) {
        //1.创建工具类对象
        StringBuffer sb = new StringBuffer();
        StringBuilder sb2 = new StringBuilder();
        //5.添加一个计时的功能
        //5.1拼接之前获取系统当前时间作为开始时间
        long t1 = System.currentTimeMillis();
        //2.拼接10000次
        for (int i = 0; i < 10000; i++) {
            //3.使用工具类对象的append()进行拼接
            //sb.append(s);
            sb2.append(s);
        }
        //5.2拼接之后获取系统当前时间作为结束时间
        long t2 = System.currentTimeMillis();
        //4.打印拼接的效果
        //System.out.println(sb);
        System.out.println(sb2);
        //5.3打印拼接总共消耗的时间
        System.out.println(t2-t1);
    }

    //使用方式一完成字符串的拼接
    private static void method1(String s) {
        //1.创建一个变量用来保存拼接后的效果
        String result = "";
        //5.给程序添加一个计时的功能
        //5.1获取循环开始的系统当前时间
        long t1 = System.currentTimeMillis();
        //2.创建循环执行10000次
        for (int i = 0; i < 10000; i++) {
            //3.进行字符串的拼接
            result = result + s;
        }
        //5.2获取循环拼接之后的系统当前时间
        long t2 = System.currentTimeMillis();
        //4.打印拼接后的效果
        System.out.println(result);
        //5.打印花费的时间
        System.out.println(t2-t1);
        System.out.println(t2);
    }

}

获取系统当前时间
System.currentTimeMillis();

3.4 拓展

3.4.1 ==和equals的区别

1.当使用= =比较时,如果相比较的两个变量是引用类型,那么比较的是两者的物理地值(内存地址),如果相比较的两个变量都是数值类型,那么比较的是具体数值是否相等。
2.当使用equals()方法进行比较时,比较的结果实际上取决于equals()方法的具体实现
众所周知,任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String、integer等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,而在Object类的默认实现中,equals()方法的底层是通过==来实现的。

package cn.tedu.api;
/*本类用于测试字符串的拼接*/
public class TestString3 {
    public static void main(String[] args) {
        //需求:将26个字母拼接10000次
        String s = "abcdefghijklmnopqrstuvwxyz";
        //method1(s);//使用方式一拼接字符串
        method2(s);//使用方式二拼接字符串
    }
    //使用方式二拼接字符串
    private static void method2(String s) {
        //1.创建工具类对象
        StringBuffer sb = new StringBuffer();
        StringBuilder sb2 = new StringBuilder();
        //5.添加一个计时的功能
        //5.1拼接之前获取系统当前时间作为开始时间
        long t1 = System.currentTimeMillis();
        //2.拼接10000次
        for (int i = 0; i < 10000; i++) {
            //3.使用工具类对象的append()进行拼接
            //sb.append(s);
            sb2.append(s);
        }
        //5.2拼接之后获取系统当前时间作为结束时间
        long t2 = System.currentTimeMillis();
        //4.打印拼接的效果
        //System.out.println(sb);
        System.out.println(sb2);
        //5.3打印拼接总共消耗的时间
        System.out.println(t2-t1);
    }

    //使用方式一完成字符串的拼接
    private static void method1(String s) {
        //1.创建一个变量用来保存拼接后的效果
        String result = "";
        //5.给程序添加一个计时的功能
        //5.1获取循环开始的系统当前时间
        long t1 = System.currentTimeMillis();
        //2.创建循环执行10000次
        for (int i = 0; i < 10000; i++) {
            //3.进行字符串的拼接
            result = result + s;
        }
        //5.2获取循环拼接之后的系统当前时间
        long t2 = System.currentTimeMillis();
        //4.打印拼接后的效果
        System.out.println(result);
        //5.打印花费的时间
        System.out.println(t2-t1);
        System.out.println(t2);
    }

}

3.4.2 ==与equals测试

package cn.tedu.api;
/**本类用于测试==与equlas的区别*/
public class Test{
	public static void main(String[] args) {
		Person p1 = new Person("凹凸曼",16);
		Person p2 = new Person("凹凸曼",16);
		
		System.out.println(p1 == p2);//false,new了两个对象,p1和p2保存的地址值不同
		System.out.println(p1.name == p2.name);//true,name是String类型,保存在常量池中,值是同一个
		System.out.println(p1.age == p2.age);//true,age是int基本类型,保存的值都是18
		
		//第一次测试:结果false,使用的是Object默认的逻辑,也是用==来比较的
		//第二次测试:结果true,重写equals()后,就会使用子类重写后的功能,也就是说此时比较的不再是地址,而是类型&属性值
		System.out.println(p1.equals(p2));
	}
}

//1.创建Person类
class Person {
	String name;
	int age;
	
	public Person() {
		System.out.println("我是手动添加的无参构造");
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	//需求:比较两个对象的属性值,如果属性值都一样,就是"同一个对象",要求equals返回true
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
}

3.4.3 StringBuilder和StringBuffer的区别

1.在线程安全上 :
–StringBuffer是旧版本就提供的,线程安全的。@since JDK1.0
–StringBuilder是jdk1.5后产生,线程不安全的。@since 1.5
2. 在执行效率上,StringBuilder > StringBuffer > String
3.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题。

在这里插入图片描述

4. 正则表达式 Regex

4.1 概述

正确的字符串格式规则。
常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。

4.2 常见语法

在这里插入图片描述

4.3 String提供了支持正则表达式的方法

Matches(正则) : 当前字符串能否匹配正则表达式
replaceAll(正则,子串) : 替换子串
split(正则) : 拆分字符串

4.4 测试输入身份证号

package cn.tedu.api;

import java.util.Scanner;
/*本类用于正则表达式入门案例*/
//需求:接收用户输入的身份证号,并将判断的结果输出
public class TestRegex {
    public static void main(String[] args) {
        //1.编辑正则表达式
        //身份证号的规律:一共是18位,前17位是数子,第18位有可能是数字,也有可能是X
        //String regex = "[0-9]{17}[0-9X]";
        /*单个\在Java中有特殊的含义,表示转义符号,不认为是一个斜杠
         * 所以如果想要表示斜杠,需要在它的前面加一个用来转义的\
         * 也就是\\才表示成一个单纯的斜杠
         * \t -- 制表符  \r回车符 \n换行符*/
        String regex = "\\d{17}[0-9X]";

        //2.定义变量用来接收用户输入的身份证号:
        String input;

        //3.判断用户输入的数据是否符合正则表达式,如果不正确,继续输入
        do {
            System.out.println("请输入您的身份证号:");
            input = new Scanner(System.in).nextLine();
            if (input.matches(regex)) {//如果身份证号正确
                System.out.println("恭喜你!输入正确!");
                return;//结束本方法
            }
        } while (!input.matches(regex));//只要不符合正则表达式,就继续输入
    }
}

5. 包装类

5.1 概念

把基本类型进行包装,提供更加完善的功能。
基本类型是没有任何功能的,只是一个变量,记录值,而包装类可以有更加丰富的功能

5.2 与基本类型的对应关系

在这里插入图片描述

5.3 Number类(超类)

数字包装类的抽象父类。
提供了各种获取值的方式。

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

5.3.1 Integer类

创建对象
方式一: new Integer(5);
方式二: Integer.valueOf(5);
Integer类中包含256个Integer缓存对象,范围是 -128~127
使用valueOf()时,如果指定范围内的值,直接访问缓存对象不新建;如果指定范围外的值,直接新建对象。

常见方法
static int parseInt(String s) 将字符串参数作为有符号的十进制整数进行解析

5.3.1.1 测试
package cn.tedu.api;
/*本类用于测试包装类*/
public class TestNumber {
    //1.定义成员变量,注意要设置成静态的,因为静态只能调用静态
    static Integer i0;
    public static void main(String[] args) {
        //2.打印Integer的默认值进行测试
        System.out.println(i0);//默认值为null

        //3.创建int类型对应的包装类Integer类型的对象--方式一
        Integer i1 = new Integer(5);
        Integer i11 = new Integer(5);
        System.out.println( i1 == i11 );//false,==对于引用类型,比较的是地址值

        //4.创建int类型对应的包装类Integer类型的对象--方式二
        /*Integer有一个高效的效果,数据在:(-128~127)
        * 在此范围内,相同的数据只会存一次,后续再存都是使用之前存过的数据*/
        Integer i2 = Integer.valueOf(127);
        Integer i3 = Integer.valueOf(127);
        System.out.println(i1 == i2);//false
        System.out.println(i2 == i3);//true
        //满足高效效果的3个条件:Integer valueOf() -128~127
        Integer i4 = Integer.valueOf(300);
        Integer i5 = Integer.valueOf(300);
        System.out.println(i4 == i5);//false

    }
}

5.3.2 Double类

创建对象
new Double(3.14)
Double.valueOf(3.14)//和 new 没有区别
 
常用方法
Double.parseDouble();

5.3.2.1 测试
package cn.tedu.api;
/*本类用于测试基本类型的包装类*/
public class TestNumber {
    public static void main(String[] args) {
        //1.创建int包装类Integer对象的方式1
        Integer i1 = new Integer(100);
        Integer i11 = new Integer(100);
        System.out.println(i1 == i11);//false,new了两次,是两个不同的对象,地址值不同

        //2.创建int包装类Integer对象的方式2
        /*Integer有一个高效的效果,但是必须满足3个条件:
        * 1.是Integer类型
        * 2.使用valueOf()的创建方式
        * 3.数据在-128~127的范围内
        * 满足以上条件,相同的数据只会存一次,后续再使用都是以前存过的数据*/
        Integer i2 = Integer.valueOf(100);
        Integer i22 = Integer.valueOf(100);
        System.out.println(i2 == i22);//true

        Integer i3 = Integer.valueOf(300);
        Integer i33 = Integer.valueOf(300);
        System.out.println(i3 == i33);//false 超出高效的数据范围-128~127

        //3.创建double包装类Double对象的方式1
        Double d1 = new Double(3.14);
        Double d11 = new Double(3.14);
        System.out.println(d1 == d11);//false,创建两个不同的对象,地址值不同

        //4.创建double包装类Double对象的方式2
        /*只有Integer才有高效的效果Double是没有的*/
        Double d2 = Double.valueOf(3.14);
        Double d22 = Double.valueOf(3.14);
        System.out.println(d1 == d2);//false
        System.out.println(d2 == d22);

        //5.测试常用方法
        //这个方法的作用就是把传入的String类型的数据转成int
        /*对象是什么类型的,就可以使用这个类的所有资源
        i1是Integer类型的对象,所以可以使用parseInt()将String类型的数据转为int类型
        d1是Double类型的对象,所以可以使用parseDouble()将String类型的数据转为double类型*/
        System.out.println(i1.parseInt("800")+8);//808->int+int
        System.out.println(d1.parseDouble("2.2")+3.1);//5.300000000000001->double+double
    }
}

5.3.3 BigDecimal

BigDecimal:常用来解决精确的浮点数运算不精确的问题

5.3.3.1 创建对象

方式一 :
BigDecimal(double val)
将double转换为BigDecimal,后者是double的二进制浮点值十进制表示形式,有坑!
方式二 :
BigDecimal(String val)
将String类型字符串的形式转换为BigDecimal

5.3.3.2 常用方法

Add(BigDecimal bd) : 做加法运算
Subtract(BigDecimal bd) : 做减法运算
Multiply(BigDecimal bd) : 做乘法运算
Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常
Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用
setScale(保留位数,舍入方式) : 同上
pow(int n) : 求数据的几次幂

5.3.3.3 测试常用方法
package cn.tedu.api;

import java.math.BigDecimal;
import java.util.Scanner;

public class TestBigDecimal {
    public static void main(String[] args) {
        //f1();//使用普通的 +-*/ 四则运算,暴露出浮点数运算不精确的问题
        f2();//使用BigDecimal来解决浮点数运算不精确的问题
    }

    private static void f2() {
        //1.提示并接收用户输入的两个小数
        System.out.println("请输入您要计算的两个小数:");
        double a = new Scanner(System.in).nextDouble();
        double b = new Scanner(System.in).nextDouble();
        //2.创建工具类对象,把基本类型a和b交给工具类对象BigDecimal来保存
        /*1.最好不要用double作为构造函数的参数,不然还会有不精确的现象,有坑!!!*/
        /*2.最好使用重载的,参数类型是String的构造函数
        * double转String,直接拼个空串就可以*/
        BigDecimal bd1 = new BigDecimal(a+"");
        BigDecimal bd2 = new BigDecimal(b+"");

        //3.通过BigDecimal的对象来调用其方法,实现精确运算
        //3.1 定义BigDecimal类型的引用类型变量来保存结果
        BigDecimal bd3;
        //3.2 Add(BigDecimal bd) : 做加法运算
        bd3 = bd1.add(bd2);
        System.out.println(bd3);
        //3.3 Subtract(BigDecimal bd) : 做减法运算
        bd3 = bd1.subtract(bd2);
        System.out.println(bd3);
        //3.4 Multiply(BigDecimal bd) : 做乘法运算
        bd3 = bd1.multiply(bd2);
        System.out.println(bd3);
        //3.5 Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常
        /*3.除法运算,除不尽时会抛出异常ArithmeticException*/
        //方案一:(除不尽时有问题)
        //bd3 = bd1.divide(bd2);
        /*divide(m,n,o)
        m是要除以哪个对象,n指要保留几位,o指舍入方式(比如四舍五入)*/
        //方案二:
        bd3 = bd1.divide(bd2,3,BigDecimal.ROUND_HALF_UP);
        System.out.println(bd3);

    }

    private static void f1() {
        //1.提示并接收用户输入的两个小数
        System.out.println("请输入您要计算的两个小数:");
        double a = new Scanner(System.in).nextDouble();
        double b = new Scanner(System.in).nextDouble();

        //2.做运算
        System.out.println(a + b);//不精确
        System.out.println(a - b);//不精确
        System.out.println(a * b);//不精确
        System.out.println(a / b);//不精确
    }
}

5.3.3.4 拓展

舍入方式解析
ROUND_HALF_UP 四舍五入,五入 如:4.4结果是4; 4.5结果是5
ROUND_HALF_DOWN 五舍六入,五不入 如:4.5结果是4; 4.6结果是5
ROUND_HALF_EVEN 公平舍入(银行常用)
比如:在5和6之间,靠近5就舍弃成5,靠近6就进位成6,如果是5.5,就找偶数,变成6
ROUND_UP 直接进位,不算0.1还是0.9,都进位
ROUND_DOWN 直接舍弃,不算0.1还是0.9,都舍弃
ROUND_CEILING(天花板) 向上取整,取实际值的大值
朝正无穷方向round 如果为正数,行为和round_up一样,如果为负数,行为和round_down一样
ROUND_FLOOR(地板) 向下取整,取实际值的小值
朝负无穷方向round 如果为正数,行为和round_down一样,如果为负数,行为和round_up一样

6. 自动装箱和自动拆箱

6.1 概述

自动装箱:把 基本类型 包装成对应的 包装类型 的过程
Integer a = 5;//a是引用类型,引用了包装对象的地址。
编译器会完成对象的自动装箱:Integer a = Integer.valueOf(5);

自动拆箱:从包装类型的值,自动变成 基本类型的值
int i = a;//a现在是包装类型,没法给变量赋值,需要把5取出来。
编译器会完成自动拆箱:int i = a.intValue();

6.2 自动装箱与自动拆箱测试

package cn.tedu.api;
/*本类用于测试自动装箱和自动拆箱*/
public class TestBox {
    public static void main(String[] args) {
        //1.定义包装类型的数据
        //回顾:以前创建包装类型的两种方式
        Integer i1 = new Integer(127);
        Integer i2 = Integer.valueOf(127);
        //2.现在的方式:
        /*1.自动装箱:编译器会自动把基本类型int 5,包装成包装类型Integer
        * 然后交给i3来保存,自动装箱底层发生的代码Integer.valueOf(5);
        * valueOf()的方向: int --> Integer*/
        Integer i3 = 5;//不会报错,这个现象就是自动装箱
        /*2.自动拆箱:编译器会自动把包装类型的i1拆掉"箱子",变回基本类型数据127
        * 然后交给i4来保存,自动拆箱底层发生的代码:i1.intValue();
        * intValue()的方向:Integer -> int
        * */
        int i4 = i1;//不会报错,这个现象就是自动拆箱
    }
}

7. IO流

7.1 IO简介

7.1.1 流

在学习IO流之前,我们首先需要学习的概念就是Stream流
为了方便理解,我们可以把数据的读写操作抽象成数据在"管道"中流动,但需注意:
1.流只能单方向流动
2.输入流用来读取 → in
3.输出流用来写出 → out
4.数据只能从头到尾顺序的读写一次
所以以程序的角度来思考,In/out 相对于程序而言的输入(读取)/输出(写出)的过程.

在这里插入图片描述

7.1.2 IO流的继承结构

在java中,根据处理的数据单位不同,可以把流分为字节流和字符流
字节流 : 针对二进制文件
字符流 : 针对文本文件
再结合对应类型的输入和输出方向,常用的流有:

File
字节流:针对二进制文件
InputStream
FileInputStream
BufferedInputStream
ObjectInputStream
OutputStream
FileOutputStream
BufferedOutputStream
ObjectOutputStream
字符流:针对文本文件
Reader
FileReader
BufferedReader
InputStreamReader
Writer
FileWriter
BufferedWriter
OutputStreamWriter
PrintWriter一行行写出

7.2 File文件类

7.2.1 概述

封装一个磁盘路径字符串,对这个路径可以执行一次操作
可以封装文件路径、文件夹路径、不存在的路径

7.2.2 创建对象

File(String pathname)
通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例
new File(“d:/abc/a.txt”);
new File(“d:/abc”,”a.txt”);

7.2.3 常用方法

在这里插入图片描述

7.2.4 测试常用方法

package cn.tedu.file;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

/*本类用于测试文件类File*/
public class TestFile {
    public static void main(String[] args) throws IOException {
        //1.创建File类对象
        /*1.ready目录与1.txt需要自己手动创建
        * 2.File需要导包:import java.io.File;
        * 3.路径是String类型,必须写正确,不然找不到文件
        * 4.完整的文件名包含两部分:文件名+后缀名*/
        File file = new File("E:\\ready\\1.txt");

        //2.1测试File中的常用方法
        System.out.println(file.length());//3,获取文件的字节量
        System.out.println(file.exists());//true,判断文件是否存在
        System.out.println(file.isFile());//true,判断是否为文件
        System.out.println(file.isDirectory());//false,判断是否为文件夹
        System.out.println(file.getName());//1.txt获取文件名
        System.out.println(file.getParent());//E:\ready 获取父级路径
        System.out.println(file.getAbsolutePath());//E:\ready\1.txt 获取带盘符的完整路径:绝对路径

        //2.2 测试创建与删除
        /*new 只会帮你在内存中创建一个File类型的对象
        * 并不会帮你在磁盘中创建一个真实存在的2.txt文件*/
        file = new File("E:\\ready\\2.txt");

        //创建一个之前不存在的文件2.txt,如果创建成功,会返回true
        /*如果指定创建文件的路径不对,会抛出异常:java.io.Exception
        * 所以需要提前处理这个问题,我们暂时选择在main()上抛出
        * 这个IO异常是目前我们遇到的强制要求必须预先处理的异常
        * 如果不处理,方法的调用会报错,通不过编译*/
        System.out.println(file.createNewFile());//创建之前不存在的文件

        file = new File("E:\\ready\\m");
        System.out.println(file.mkdir());//创建之前不存在的单层文件夹

        file = new File("E:\\ready\\a\\b\\c");
        System.out.println(file.mkdirs());//创建之前不存在的多层文件夹

        System.out.println(file.delete());//c被删除,删除空文件夹或者文件

        file = new File("E:\\ready\\a");
        System.out.println(file.delete());//false,a文件夹不是空的,里面有内容

        file = new File("E:\\ready\\2.txt");
        System.out.println(file.delete());//2.txt被删除,可以删除文件

        //2.3测试展示文件列表
        file = new File("E:\\ready");
        String[] list = file.list();/*不常用*/
        System.out.println(Arrays.toString(list));
        //这句话会报错,因为这是一个String[],所以数组中每个元素都是String类型的
        //那么只能用String类中的方法,而isDirectory()是File类中的方法
        //System.out.println(list[0].isDirectory());

        File[] fs = file.listFiles();/*常用*/
        System.out.println(Arrays.toString(fs));
        System.out.println(fs[0].isDirectory());
    }
}

7.3 字节流

7.3.1 字节流读取

字节流是由字节组成的,字符流是由字符组成的.
Java里字符由两个字节组成.字节流是基本流,主要用在处理二进制数据
所以字节流是比较常用的,可以处理多种不同种类的文件,比如文本文档/音频/视频等等

7.3.1.1 InputStream抽象类

此抽象类是表示字节输入流的所有类的超类/抽象类,不可创建对象哦
常用方法:
abstract int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组,off表示存时的偏移量
void close() 关闭此输入流并释放与该流关联的所有系统资源

  • FileInputStream子类

直接插在文件上,直接读取文件数据

创建对象
1.FileInputStream(File file)—直接传文件对象
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定
2.FileInputStream(String pathname)—传路径
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定

  • BufferedInputStream子类

BufferedInputStream 为另一个输入流添加一些功能,在创建BufferedInputStream 时,会创建一个内部缓冲区数组(默认8k大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。

创建对象
BufferedInputStream(InputStream in)
创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。

7.3.1.2 案例测试
package cn.tedu.file;

import java.io.*;

/*本类用于练习字节输入流*/
public class TestIn {
    public static void main(String[] args) {
        //method();//字节流的读取
        method2();//高效字节流的读取
    }

    //本方法用于测试高效字节流的读取
    private static void method2() {
        //定义一个在本方法中都生效的局部变量in,注意手动初始化,值为null
        InputStream in = null;
        try {
            //1.创建高效字节输入流对象
//            InputStream in = new BufferedInputStream(
//                    new FileInputStream(new File("E:\\ready\\1.txt")));
              in = new BufferedInputStream
                      (new FileInputStream("E:\\ready\\1.txt"));
            //2.使用流进行读取
            int b;
            while ((b= in.read())!= -1){
                System.out.println(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {//关流操作写在finally{}中
            //3.流用完以后一定要关闭!!!
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

    //本方法用于测试字节流的读取
    private static void method() {
        //创建一个在本方法都生效的局部变量注意手动初始化
        InputStream in = null;
        try {
            //1.创建字节输入流对象用于读取
            //InputStream in = new InputStream();//报错原因:抽象类不可实例化
            //InputStream in = new FileInputStream(new File("E:\\ready\\1.txt"));
            in = new FileInputStream("E:\\ready\\1.txt");
            //2.开始读取
            /*read()每次调用都会读取一个字节,如果读到了数据的末尾,返回-1*/
//            System.out.println(in.read());
//            System.out.println(in.read());
//            System.out.println(in.read());
//            System.out.println(in.read());
            //需求:需要循环读取文件中的所有内容,直至读完
            //定义变量,记录读到的数据
            int b;
            while((b=in.read())!= -1){
                System.out.println(b);
            }
        } catch (Exception e) {
            e.printStackTrace();//打印错误信息
        /*try-catch结构中的第三个部分:finally{}
        * 这部分不论是否捕获到异常,是一定会被执行到的代码,常用于关流*/
        }finally {
            try {
                //3.释放资源,流资源用完必须释放!!!
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

7.3.2 字节流输出

7.3.2.1 OutputStream抽象类

此抽象类是表示输出字节流的所有类的超类.输出流接受输出字节并将这些字节发送到某个接收器.

常用方法:
Void close() 关闭此输出流并释放与此流相关的所有系统资源
Void flush() 刷新此输出流并强制写出所有缓冲的输出字节
Void write(byte[ ] b) 将b.length个字节从指定的byte数组写入此输出流
Void write(byte[ ] b,int off ,int len) 将指定byte数组中从偏移量off开始的len个字节写入输出流
Abstract void write(int b) 将指定的字节写入此输出流

  • FileOutputStream 子类

直接插在文件上,直接写出文件数据

构造方法(创建对象):
1.FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的文件输出流
2.FileOutStream(File file)
创建一个向指定File对象表示的文件中写入数据的文件输出流
3.FileOutStream(File file,boolean append)—如果第二个参数为true,表示追加,不覆盖
创建一个向指定File对象表示的文件中写入数据的文件输出流,后面的参数是指是否覆盖原文件内容

  • BufferedOutputstream 子类

该类实现缓冲的输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每次针对字节写出调用底层系统

构造方法(创建对象):
BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,用以将数据写入指定的底层输出流

7.3.2.2 案例测试
package cn.tedu.file;

import java.io.*;

/*本类用于测试字节输出流*/
public class TestOut {
    public static void main(String[] args) {
        method();//用于测试普通字节输出流
        //method2();//用于测试高效字节输出流
    }
    //创建一个用于测试高效字节输出流的方法
    private static void method2() {
        //1.创建一个在本方法都生效的局部变量,注意手动初始化
        OutputStream out = null;
        try{
            //2.创建高效字节输出流对象
//          out = new BufferedOutputStream(new FileOutputStream(new File("E:\\ready\\2.txt")));
            out = new BufferedOutputStream(new FileOutputStream("E:\\ready\\2.txt"));
            //3.使用流对象--进行写出操作
            out.write(97);
            out.write(97);
            out.write(97);
        }catch (Exception e){
            e.printStackTrace();
        }finally {//关流操作要放在finally{}中
            try {
                //4.关流
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //创建一个用于测试普通字节输出流的方法
    private static void method() {
        //1.创建一个在本方法中都生效的局部变量,注意手动初始化null
        OutputStream out = null;
        //2.创建try-catch-finally结构,因为IO操作可能会产生异常
        try{
            //3.创建普通字节输出流对象
            //out = new FileOutputStream(new File("E:\\ready\\2.txt"));
            //out = new FileOutputStream("E:\\ready\\2.txt");
            out = new FileOutputStream("E:\\ready\\2.txt",true);
            //4.使用流对象--进行写出操作
            out.write(99);//对应ASCII码表中的a
            out.write(99);//对应ASCII码表中的b
            out.write(99);//对应ASCII码表中的c
        }catch (Exception e){
            e.printStackTrace();
        }finally {//如果想要代码一定会执行,需要写在finally中
            try {
                //5.关流操作
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

7.4 字符流

7.4.1 字符流读取

常用于处理纯文本数据,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8

7.4.1.1 Reader抽象类

用于读取字符流的抽象类。

常用方法:
1.int read() 读取单个字符
2.int read(char[] cbuf) 将字符读入数组
3.abstract int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分
4.int read(CharBuffer target) 试图将字符读入指定的字符缓冲区
5.abstract void close() 关闭该流并释放与之关联的所有资源

  • FileReader子类

用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。

创建对象
1.FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader
2.FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader

  • BufferedReader子类

从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

创建对象
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流

7.4.1.2 案例测试
package cn.tedu.file;

import java.io.*;

/*本类用于测试字符流的读取*/
public class TestIn2 {
    public static void main(String[] args) {
        //method();//测试普通字符输入流
        method2();//测试高效字符输入流
    }
    //创建一个用于测试高效字符输入流的方法
    private static void method2() {
        //1.定义一个在本方法都生效的局部变量,手动初始化值null
        Reader in=null;
        try{
            //1.创建高效字符读取流对象
            //in = new BufferedReader(new FileReader(new File("E:\\ready\\1.txt")));
            in = new BufferedReader(new FileReader("E:\\ready\\1.txt"));
            //2.使用流对象
            int b;
            while((b=in.read())!=-1){
                System.out.println(b);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //3.关闭流对象
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //创建一个用于测试普通字符输入流的方法
    private static void method() {
        //1.1创建一个在本方法中都生效的局部变量,注意初始化值null
        Reader in = null;
        try {
            //1.2创建字符输入流对象,注意需要捕获异常
            //Reader in = new Reader();//报错原因:抽象父级不可实例化
            //in = new FileReader(new File("E:\\ready\\1.txt"));
            in = new FileReader("E:\\ready\\1.txt");
            //2.使用流对象
            //System.out.println(in.read());
            //需求:循环读取文件中的所有内容,只要不是-1,就说明还有数据,继续读取
            //3.1定义变量,记录读取到的数据
            int b;
            while((b = in.read())!= -1){
                System.out.println(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {//3.关流
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

7.4.2 字符流写出

7.4.2.1 Writer 抽象类

写入字符流的抽象类

常用方法:
1.Abstract void close() 关闭此流,但要先刷新它
2.Void write(char[ ] cbuf) 写入字符数组
3.Void write(int c) 写入单个字符
4.Void write(String str) 写入字符串
5.Void write(String str,int off,int len) 写入字符串的某一部分
6.Abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分

  • FileWriter 子类

用来写入字符文件的便捷类,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的.如果需要自己自定义这些值,可以先在FileOutputStream上构造一个OutputStreamWriter.

构造方法(创建对象):
1.FileWriter(String filename)
根据给定的文件名构造一个FileWriter对象
2.FileWriter(String filename,boolean append)
根据给定的文件名以及指示是否附加写入数据的boolean值来构造FileWriter2

  • BufferedWriter子类

将文本写入字符输出流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入.可以指定缓冲区的大小,或者接受默认的大小,在大多数情况下,默认值就足够大了

构造方法(创建对象):
BufferedWriter(Writer out)
创建一个使用默认大小输出缓冲区的缓冲字符输出流

7.4.2.2 案例测试
package cn.tedu.file;

import java.io.*;

/*本类用于测试字符输出流*/
public class TestOut2 {
    public static void main(String[] args) {
        //method();//用于测试普通字符输出流
        method2();//用于测试高效字符输出流
    }
    //创建一个用于测试高效字符输出流的方法
    private static void method2() {
        //1.创建一个在本方法都生效的局部变量,值为null,注意手动初始化!!!
        Writer out = null;
        //2.由于程序可能会抛出异常,所以需要写一个try-catch-finally结构
        try{//存放可能会抛出异常的代码
            //3.创建普通字符输出流对象
            //out = new BufferedWriter(new FileWriter(new File("E:\\ready\\2.txt")));
            //out = new BufferedWriter(new FileWriter("E:\\ready\\2.txt"));
            out = new BufferedWriter(new FileWriter("E:\\ready\\2.txt",true));
            //4.使用流对象
            out.write(100);
            out.write(100);
            out.write(100);
            out.write(100);
            out.write(100);
        }catch (Exception e){//匹配并捕获异常
            e.printStackTrace();//如果捕获到异常就打印错误信息
        }finally {//一定会被执行到的代码块,常用于关流
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //创建一个用于测试普通字符输出流的方法
    private static void method() {
        //1.创建一个在本方法都生效的局部变量,值为null,注意手动初始化!!!
        Writer out = null;
        //2.由于程序可能会抛出异常,所以需要写一个try-catch-finally结构
        try{//存放可能会抛出异常的代码
            //3.创建普通字符输出流对象
            //out = new FileWriter(new File("E:\\ready\\2.txt"));
            //out = new FileWriter("E:\\ready\\2.txt");
            out = new FileWriter("E:\\ready\\2.txt",true);
            //4.使用流对象
            out.write(98);
            out.write(98);
            out.write(98);
            out.write(98);
        }catch (Exception e){//匹配并捕获异常
            e.printStackTrace();//如果捕获到异常就打印错误信息
        }finally {//一定会被执行到的代码块,常用于关流
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

7.5 总结

7.5.1 IO的继承结构

1.主流分类

  • 按照方向进行分类:输入流 输出流(相对于程序而言,从程序写数据到文件中是输出)
  • 按照传输类型进行分类:字节流 字符流
  • 组合: 字节输入流 字节输出流 字符输入流 字符输出流

2.学习方法:在抽象父类中学习通用的方法,在子类中学习如何创建对象
3.字节输入流:
InputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法

--FileInputStream 子类,操作文件的字节输入流,普通类
--BufferedInputStream 子类,缓冲字节输入流,普通类

4.字符输入流:
Reader 抽象类,不能new,可以作为超类,学习其所提供的共性方法

--FileReader,子类,操作文件的字符输入流,普通类
--BufferedReader,子类,缓冲字符输入流,普通类

5.字节输出流:
OutputStream 抽象类,不能new,可以作为超类,学习其所提供的共性方法

--FileOutputStream 子类,操作文件的字节输出流,普通类
--BufferedOutputStream 子类,缓冲字节输出流,普通类

6.字符输出流:
Writer 抽象类,不能new,可以作为超类,学习其所提供的共性方法

--FileWriter,子类,操作文件的字符输出流,普通类
--BufferedWriter,子类,缓冲字符输出流,普通类

7.6 序列化与反序列化

7.6.1 概述

序列化是指将对象的状态信息转换为可以存储或传输形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象.

  • 序列化:利用ObjectOutputStream,把对象的信息,按照固定的格式转成一串字节值输出并持久保存到磁盘
  • 反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象
    在这里插入图片描述

7.6.2 特点/应用场景

  1. 需要序列化的文件必须实现Serializable接口,用来启用序列化功能
  2. 不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出
  3. 每一个被序列化的文件都有一个唯一的id,如果没有添加此id,编译器会自动根据类的定义信息计算产生一个
  4. 在反序列化时,如果和序列化的版本号不一致,无法完成反序列化
  5. 常用与服务器之间的数据传输,序列化成文件,反序列化读取数据
  6. 常用使用套接字流在主机之间传递对象
  7. 不需要序列化的数据也可以被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存

7.6.3 涉及到的流对象

7.6.3.1 序列化:ObjectOutputStream

ObjectOutputStream 将 Java 对象的基本数据类型写入OutputStream,通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

  • 构造方法:
    ObjectOutputStream(OutputStream out)
    创建写入指定 OutputStream 的 ObjectOutputStream
  • 普通方法:
    writeObject(Object obj)
    将指定的对象写入 ObjectOutputStream
7.6.3.2 反序列化:ObjectInputStream

ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化重构对象。

  • 构造方法:
    ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream
  • 普通方法:
    readObject() 从 ObjectInputStream 读取对象
7.6.3.3 代码实现序列化与反序列化
  1. 学生类Student
package cn.tedu.serializable;

import java.io.Serializable;

/**本类用来封装学生类*/
/**
 * 如果本类想要完成序列化,必须实现可序列化接口,否则会报错:
 * 报错信息:java.io.NotSerializableException: cn.tedu.serializable.Student
 * Serializable接口是一个空接口,里面一个方法都没有,作用是用来当做标志,标志这个类可以序列化/反序列化
 * */
public class Student implements Serializable{
	/**需要给每个进行序列化的文件分配唯一的UID值*/
	//The serializable class Student does not declare a static final serialVersionUID field of type long
	//private static final long serialVersionUID = 1L;
	private static final long serialVersionUID = -3193364654654535741L;
	
	//1.定义学生的相关属性 + private封装
	private String name;//姓名
	private int age;//年龄
	private String addr;//地址
	private char gender;//性别
	
	/**自动创建构造方法:右键-->Source-->Generate Constructor using Fields...*/
	//2.创建无参构造--必须手动提供无参构造,否则会被含参构造覆盖
	public Student() {
		System.out.println("我是Student的无参构造");
	}
	
	//3.创建全参构造
	public Student(String name,int age,String addr,char gender) {
		super();//默认调用父类的无参构造
		this.name = name;
		this.age = age;
		this.addr = addr;
		this.gender = gender;
		System.out.println("我是Student的全参构造");
	}
	
	//4.属性封装后,需要本类提供公共的属性访问与设置方式get()&set()
	/**自动创建get()&set(),右键-->Source-->Generate Getters and Setters...*/
	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

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

	public String getAddr() {
		return addr;
	}

	public void setAddr(String addr) {
		this.addr = addr;
	}

	public char getGender() {
		return gender;
	}

	public void setGender(char gender) {
		this.gender = gender;
	}

	//打印结果:cn.tedu.serializable.Student@4c873330-->地址值
	//想看对象的属性值,原因是想查看序列化后对象的属性,需要重写toString()
	//5.重写toString()
	//自动生成toString()--右键-->Source-->Generate toString()
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", addr=" + addr + ", gender=" + gender + "]";
	}
}
  1. 创建序列化测试类
package cn.tedu.serializable;

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

/**本类用于序列化与反序列化的测试类*/
//序列化:是指把程序中的java对象,永久保存在磁盘中,相当于是写出的过程,方向是out-->ObjectOutputStream
//反序列化:是指把已经序列化在文件中保存的数据,读取/恢复到java程序中的过程,方向是in-->ObjectInputStream
public class TestSerializable {
	public static void main(String[] args) {
		//method();//本方法用来完成序列化的功能
		method2();//本方法用于完成反序列化功能
	}
	
	/**反序列化方法*/
	public static void method2() {
		//声明在本方法内都生效的局部变量,局部变量需要初始化,默认值是null
		ObjectInputStream in = null;
		try {
			//1.创建ObjectInputStream流对象来完成反序列化
			in = new ObjectInputStream(new FileInputStream("D://ready//1.txt"));
			
			//2.通过流对象反序列化生成指定对象
			Student o = (Student)in.readObject();
			System.out.println(o);
			System.out.println("恭喜您!反序列化成功!");
			
		} catch (Exception e) {
			System.out.println("很抱歉!反序列化失败!");
			e.printStackTrace();
		} finally {//一定会执行的代码块写释放资源的代码
			try {
				//3.释放资源
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**序列化方法*/
	public static void method() {
		//声明在本方法内都生效的局部变量,局部变量需要初始化,默认值是null
		ObjectOutputStream out = null;
		try {
			//1.创建ObjectOutputStream流对象来完成序列化
			out = new ObjectOutputStream(new FileOutputStream("D://ready//1.txt"));
			
			//2.指定要序列化(输出)的对象
			Student obj = new Student("海绵宝宝",3,"大海底部",'男');
			//3.通过OOS流对象来序列化输出Student对象
			out.writeObject(obj);
			
			System.out.println("恭喜你!序列化成功!");
		} catch (IOException e) {
			System.out.println("很抱歉!序列化失败!");
			e.printStackTrace();
		}finally {//关流的操作要放在finally{}中--因为此代码块一定会执行
			try {
				//4.关流操作
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

7.6.4 拓展

7.6.4.1 常见错误

1)测试报错NotSerializableException:
在这里插入图片描述
报错原因:要序列化对象所在的类并没有实现序列化接口
解决方案:实现序列化接口

2)测试报错InvalidClassException:
在这里插入图片描述
报错原因:本次反序列化时使用的UID与序列化时的UID不匹配
解决方案:反序列化时的UID与序列化时的UID要保持一致,或者测试时一次序列操作对应一次反序列化操作,否则不匹配就报错

注:IDEA中设置自动生成serialVersionUID的提示:
在这里插入图片描述
在这里插入图片描述

补充:
为什么反序列化版本号需要与序列化版本号一致?
我们在反序列化时,JVM会拿着反序列化流中的serialVersionUID与序列化时相应的实体类中的serialVersionUID来比较,如果不一致,就无法正常反序列化,出现序列化版本不一致的异常InvalidClassException。
 
而且我们在定义需要序列化的实体类时,如果没有手动添加UID,
Java序列化机制会根据编译的class自动生成一个,那么只有同一次编译生成的class才是一样的UID。
 
如果我们手动添加了UID,只要这个值不修改,就可以不论编译次数,进行序列化和反序列化操作。

7.7 编码转换流

7.7.1 概述

编码转换流(InputStreamReader/OutputStreamWriter)
主要进行编码的转换,用来解决字符流读写乱码的问题

7.7.2 工具API学习

OutputStreamWriter :
1.OutputStreamWriter(OutputStream out)把传入的字节流转成字符流
2.OutputStreamWriter(OutputStream out ,String charsetName)把Unicode转成其他编码输出

InputStreamReader :
1.InputStreamReader(InputStream in) 把传入的字节流转成字符流
2.InputStreamReader(InputStream in,String charsetName)读取其他编码转成Unicode

7.7.3 常见字符编码表

在这里插入图片描述

补充:
想要了解更多字符集的前世今生请点我!!!!
JAVA采用Unicode中的utf-16编码

7.7.4 编码转换流测试

package cn.tedu.encoding;

import java.io.*;

/*本类用于测试编码转换流*/
public class TestEncode {
    public static void main(String[] args) {
        method1();//用来测试OutputStreamWriter
        method2();//用来测试InputStreamReader
    }

    private static void method2() {
        //1.创建一个在本方法中都生效的局部变量,并给变量初始化
        InputStreamReader in = null;
        //2.由于IO操作可能会抛出异常,所以需要try-catch-finally结构
        try{
            //3.创建针对于输入流的编码转换流对象
            in = new InputStreamReader(
                    new FileInputStream("1.txt"),"utf-8");
                    //new FileInputStream("1.txt"),"gbk");
                    //new FileInputStream("1.txt"),"iso-8859-1");
            //4.使用流读取数据,并将读取到的信息打印在控制台
            //read方法的返回值类型是int,打印的是编码
            //UTF-8-28023    gbk-23092   iso-8859-1-230
            //System.out.println(in.read());
            //拓展:将刚刚读到的数据,以中文的格式展示在控制台
            char[] ch = new char[8192];
            //Reader中的方法:read(char[] cbuf)将字符读入数组,返回读取到的字符的个数
            int len = in.read(ch);//len保存的是读取到的字符的个数
            //此处是String的构造函数,利用ch数组中的数据,构建一个字符串,并打印到控制台
            System.out.println(new String(ch,0,len));
            System.out.println("恭喜您!成功读取!");
        }catch (Exception e){
            System.out.println("很抱歉!读取失败!");
            e.printStackTrace();
        }finally {
            //5.关流
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void method1() {
        //1.创建一个在本方法中都生效的局部变量,并给变量初始化
        OutputStreamWriter out = null;
        //2.由于代码可能会产生IO异常,所以需要完成try-catch-finally结构
        try{
            //3.创建流对象
            out = new OutputStreamWriter(
                    new FileOutputStream("1.txt"),"utf-8");
                    //new FileOutputStream("1.txt"),"iso-8859-1");
                    //new FileOutputStream("1.txt"),"gbk");
            //4.使用流对象写出数据
            out.write("海绵宝宝与派大星");
            System.out.println("执行成功!");
        }catch (Exception e){
            System.out.println("执行失败!");
            e.printStackTrace();
        }finally {
            //5.关流
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:
写入和读取的字符集应当保持一致,否则会出现中文乱码现象!

7.7.5 拓展

7.7.5.1 常用字符集测试
package cn.tedu.encoding;

import java.io.*;
import java.util.Arrays;

/*本类用于测试常用编码转换*/
public class TestEncode {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "jsd海纳百川";
        byte[] arr = s.getBytes("utf8");
        System.out.println(Arrays.toString(arr));
//        String s2 = new String(arr,"gbk");中文乱码,读取与写入数据不一致
        String s2 = new String(arr,"utf8");
        System.out.println(s2);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值