Java02_中级


第11章 枚举

10.1 什么是枚举

对象是固定个数,可以穷举的类型是枚举

枚举类不能创建新的对象

枚举项之间用逗号隔开

枚举量要大写(final)

示例:

季节

性别

月份

10.2 如何定义枚举类型

枚举类型与其他类型一样,对象在堆空间中,栈针中的变量名指向枚举类的首地址

public class EnumTest1 {
    public static void main(String[] args) {
        TrafficSignal trafficSignal = TrafficSignal.CAUTION;
        System.out.println(trafficSignal);
    }
}
enum TrafficSignal{
    STOP,GO,CAUTION;//以在类外只能使用类名.枚举项。
}

10.2.1 原理说明

枚举类中,成员变量被final和static修饰,默认的,不用写

本质是类,很特殊的类

本省不能创建new对象,不能被继承

枚举类中写的都是==成员变量== ,本质属于类变量

10.2.2 知识补充 成员变量

成员变量只与类相关,与对象的创建无关,随着类的加载而加载,不在内存的空间中,在调用时最好使用类调用,放置误解

<1> 初始化成员变量

img

<2> 第一步类加载(加载成员变量)

image-20200810182333381

<3> 第二步创建类对象 成员变量归属于类内存

image-20200810182419379

参考:https://blog.csdn.net/weixin_37012881/article/details/82699089

10.3 枚举类的常用方法

enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Seriablizablejava.lang.Comparable 两个接口。

values(), ordinal() valueOf() 方法位于 java.lang.Enum 类中:

  • values() 返回枚举类中所有的值,获得枚举对象的数组。
  • ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
  • valueOf()方法返回指定字符串值的枚举常量。

image-20200810191351613

10.3.1 values() ordinal() 与 枚举数组的成员迭代 ==增强for==

public class Weekends {
    public static void main(String[] args) {
        Week[] weeks = Week.values();//Values()返回枚举数组
        for (Week week : weeks){//枚举数组的成员迭代方法
            int index = week.ordinal();//获取week枚举项的索引
            System.out.println(week + ": " + index);
        }
    }
}
enum Week{
    MON,TUE,WED,THU,FRI,SAT,SUN;
}

10.3.2 value of() 返回指定字符串的枚举常量

public class Weekends {
    public static void main(String[] args) {
        Week a = Week.valueOf("FRI");
            System.out.println(a);
    }
}
enum Week{
    MON,TUE,WED,THU,FRI,SAT,SUN;
}

10.4 在switch中使用枚举方法与==常量==

穷举变量可能的值. 变量的数据类型必须是byte, short, int , char, String, 枚举类

​ case 常量1 : 常量 : ==字面量(int之类)==和被==final修饰的量==
​ case 常量2 :

常量:内存空间的值不能刷新都常量

常量:字面量 5 “123” 和 被final修饰的量

如果不用break,则会直接full through倒底

<1> 常量:程序在执行过程中其值是不可以改变的量叫做常量。

<2> Java中的常量: Java中常量是分两种的一种是字面值常量一种是面向对象常量,今天我要记载的是字面值常量。

<3> 字面值常量:

  • 字符串常量 :双引号内包括的内容,特点是双引号内。
  • 整数常量 : 所有整数
  • 小数常量 :所有小数
  • 布尔常量 :其值比较特殊,只有两个值一个是true(正确的),一个是false(错误的),特点是单词不能拼写错误。
  • 空常量 :null
  • char字符常量 :单引号内包括的内容,只能是单个数字,单个字母或者单个字符
public class EnumTest1 {
    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        TrafficSignal[] tss = TrafficSignal.values();
        TrafficSignal sign = tss[n];

        switch (sign){
            case STOP:
                System.out.println("红灯停");
                break;
            case GO:
                System.out.println("绿灯行");
                break;
            case CAUTION:
                System.out.println("黄灯等一等");
                break;
        }
    }
}
enum TrafficSignal{
    STOP,GO,CAUTION;//以在类外只能使用类名.枚举项。
}

10.5 命令行参数的使用

执行命令带参数

args[0]命令行参数第一个

arguments 实参

parameter 形参

方法在调用时,方法的形参在调用时变成实参、

传递用命令行参数

public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
}
//接受参数

10.6 枚举的属性添加==重点==

给week枚举加上属性

String feel;

反编译的使用

enum Season {

    SPRING("绿色"), SUMMER("红色"), AUTUMN, WINTER;

    private String color = "黑色";

    private Season() {
    }
    private Season(String color) {
        this.color = color;
    }
    @Override
    public String toString() {
        return super.toString() + "Season{" +
                "color='" + color + '\'' +
                '}';
    }
}
public class SeasonTest {
    public static void main(String[] args) {
        Season summer = Season.SUMMER;
        System.out.println(summer);
    }
}

第11章 java常用类

11.1 object类

object是所有类的父类 root

11.1.1 方法equals()

1 equals不写成抽象方法的原因

不写成抽象方法有些子类不需要比较不用实现

,要想使用必须重写,这种称为==虚拟方法==

这是Object父类中的写法, 这个写法的代码很烂. 必须重写才能完成内容的比较

public boolean equals(Object obj) {
    return (this == obj);
}

重写之后的

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Point point = (Point) o;
    return x == point.x && y == point.y;

11.1.2 ==hashCode==

image-20200813102143834

哈希码 又称 散列码

@Override
public int hashCode() {
    return Objects.hash(x, y);
}

equals 是比较两个对象的内容是否相等

hashCode称为散列码, 又称为特征码, 特征码是根据内容计算的

如果两个对象的equals为true,说明两个对象内容相等, 内容相等,两个对象的特征码必须一致

如果两个对象的equals为false,说明两个对象内容不等, 内容不等,两个对象必须散列

如果两个对象的哈希码相等, 则两个对象的equals必须为true

如果两个对象的哈希码不等, 则两个对象的equals必须为false

<1> 原始的哈希码

这是原始的哈希码, 返回的码值是根据物理地址散列出来的, 必须重写

public native int hashCode();

绝对的散列不存在

<2> 代码演示

@Override
public int hashCode() {
    return Objects.hash(x, y);
    }
}

11.2 包装类

有了类的特点,就可以调用类中的方法

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

11.2.1 包装类的类型转换

<1> 基本数据类型–>包装类

Integer t1 = new Integer(i);//手动装箱
Integer t2 = 500;//自动装箱

<2> ==字符串参数==–>包装类

Float f = new Float("4.56");
Long l = new Long("782a");
//Exception in thread "main" java.lang.NumberFormatException: For input string: "789a"

<3> 包装类–>基本数据类型

拆箱

调用包装类中的方法:.xxxValue

boolean b = bObj.booleanValue();//手动拆箱
boolean n = bObj;//自动拆箱

<4> 字符串–>基本数据类型

通过包装类的构造器实现:

int i = new Integer("12");

通过包装类的parseXxx(String s)静态方法:

Float f = Float.parseFloat("12.1");

<5> 基本数据类型–>字符串

调用字符串重载的valueOf()方法:

String fstr = String.valueOf(2.34f);

更直接的方式:

String intStr = 5 + " ";

11.2.2 面试题目

public void method1() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//flase

Integer m = 1;
Integer n = 1;
System.out.println(m == n);//ture

Integer x = 128;
Integer y = 128;
System.out.println(x == y);//flase
}

11.3 String类

String 是内容不可以改变的 ==unicode 字符序列==

内部是用一个 char[] 数组来保存所有字符

public static void main(String[] args) {
        String str = "abc";
        str += "def";
        str += "ghi";
        System.out.println(str);
}

字符串的拼接在底部进行时,生成了 StringBuilder 的进行拼接

在底层任何对于字符串的修改都会产生新对象

适用于简单的应用场景

字符串 研究下标(偏移量)offset

11.3.1 字符串常量定义

双引号引起来的都是字面量,都是字符串常量,事物唯一的,保存在虚拟机的常量区当中,常量区在方法区里

方法区数据永不卸载,

String : 内容不可改变的Unicode字符序列. 所以任何对于字符串内容的修改都会导致产生新对象.

  • 内部使用一个char[]来保存所有字符, 所以每个字符都有下标.
  • 字符串常量 包括字面量, 都是默认保存在常量区中. 为的是提高效率,多次使用方便快捷.

    11.3.2 构造器创建字符串对象

    String类较常用构造方法
String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] value);
String s4 = new String(char[]value,int startIndex,int count);//可以通过构造器进行截断,把字符数组编程字符串

创建了新对象之后,在GC堆里

String重写了equals,全部换成了char数组,

11.3.3 String底层

image-20200811112817943

<1> String str = “abc”与 String str1 = new String(“abc”)==区别==

String str = “abc”;

只创建了一个对象 在常量池里

而String str1 = new String(”abc”)创建了两个对象

一个在常量池,一个在GC堆

graph LR
A[Str1]-->B[GC堆]
B--属性 charSequence-->C

==为假 地址一个GC堆里的地址

equals为真

<2> 内存解析

image-20200813132822046

image-20200813133053418

只要在变量的参与就会进去GC堆

11.3.4 String类字符串的==操作方法==

参考

String string = "  abcQYz12 我喜欢你,你喜欢我吗? 我不喜欢你, zzQQyy  ";

<1> length()

字符串的长度 字符个数

public int length()

<2> charAt(int index)

获取指定参数下标出的字符

public char charAt(int index)
char a = string.charAt(13);
char b = string.charAt(27);

<3> toCharArray

返回字符串内部char[]的副本

public char[] toCharArray()
char result[] = new char[value.length];
for (int i = 0; i < value.length; i++) {
	result[i] = value[i];
}
/*
系统中的复制操作
第一个参数value是源数组对象, 第二个参数0是源数组准备复制的开始下标
第三个参数result是目标数组对象, 第四个参数0是目标数组复制元素的开始下标.
第五个参数value.length是复制的元素的个数.
System.arraycopy(value, 0, result, 0, value.length);
*/

<4> equals

操作

public boolean equals(Object anObject)

<5> equals

操作忽略大小写

public int equalsIgnoreCase(String anotherString)

<6> compareTo

比较大小

public int compareTo(String anotherString)

<7> indexOf

返回参数中的子串s在当前字符串中首次出现的下标

public int indexOf(String s)
string.indexOf("喜欢");// => 12
string.indexOf("hello");// => -1

<8> index Of()

返回参数中的子串s在当前字符串中首次出现的下标, 但是是从startPoint下标开始搜索

public int indexOf(String s ,int startpoint)
string.indexOf("喜欢", 13)// => 17, 
string.indexOf("喜欢", 18)// => 25

<9> lastIndexOf()

返回回参数中的子串s在当前字符串中首次出现的下标, 但是是从右向左搜索的

public int lastIndexOf(String s)

<10> lastIndexOf(String s ,int startpoint)

返回参数中的子串s在当前字符串中首次出现的下标, 但是是从右向左搜索的,并且从指定下标开始

public int lastIndexOf(String s ,int startpoint)

<11> startsWith

判断当前字符串是否以参数中的子串为前缀

public boolean startsWith(String prefix)

<12> endsWith

判断当前字符串是否以参数中的子串为后缀

public boolean endsWith(String suffix)
 string.startsWith("  abc")//=> true
 string.endsWith("zzQQyy")//=> false

<13> substring

从当前字符串中获取子串, 从start开始(包含) ,到end结束(不包含) 注意:substring写法

public String substring(int start,int end)

<14> substring(..)

从当前字符串中获取子串, 从start开始(包含) 到末尾

public String substring(int startpoint)

<15> repalce

替换当前字符串中的所有oldChar字符为newChar字符. 替换char

public String replace(char oldChar,char newChar)

<16> replaceAll

替换字符串的所有old为new串 支持正则表达式 替换串

public String replaceAll(String old,String new)

<17> trim()

修剪首尾的空白字符, 码值小于等于32的所有字符.

功能有限,一些中文或者日文中的空格去不掉

public String trim()

<18> concat

拼接

public String concat(String str)

<19> toUpperCase

把所有小写字母变成大写字母

public String toUpperCase()

<20> toLowerCase

把所有大写字母变成小写字母

public String toLowerCase()

<21> split

以参数中的子串为切割器,把字符串切割成多个部分

分割的字符会舍弃掉

public String[] split(String regex)
public void test10() {
       String s = "abc,你和他,yyy,111,333";
       String[] split = s.split(",");
       for (int i = 0; i < split.length; i++) {
           System.out.println(split[i]);
       }

       String path = "C:\\Program Files\\Java\\jdk1.8.0_251\\bin;C:\\Program Files (x86)\\NetSarang\\Xftp 6\\;C:\\Program Files (x86)\\NetSarang\\Xshell 6\\;D:\\MyProgram\\Database\\Oracle\\MyOracle11G\\product\\11.2.0\\dbhome_1\\bin;C:\\MyProgram\\_MyBin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;D:\\MyProgram\\LLVM\\bin;D:\\MyProgram\\CMake\\bin;C:\\Program Files\\Git\\cmd;C:\\Program Files\\Bandizip\\";
       String[] split1 = path.split(";");
       for (int i = 0; i < split1.length; i++) {
           System.out.println(split1[i]);
       }
   }

<22> valueOf

把任意的数据转换成字符串

static String valueOf(...);

<23> intern()

image-20200811113735528

在参入变量时,仍然让数据保存在常量池内部,会导致大量数据保存在常量区

所以不要爆掉常量区

常量区一旦溢出。就会波及到类模板

JDK1.7 以后把类模板区拿出来,放在元空间,只放类模板

public void test3() {
        String s1 = "atguigu";
        String s2 = "java";
        String s4 = "java"; // 常量区
        String s3 = new String("java"); // gc
        System.out.println(s2 == s3); // false
        System.out.println(s2 == s4); // true
        System.out.println(s2.equals(s3)); // true

        String s5 = "atguigujava"; // 常量区
        // intern方法的作用就是把字符串强制保存在常量区中
        String s6 = (s1 + s2).intern(); // 字符串拼接时,如果有变量参与, 它一定生成在GC堆中.
        System.out.println(s5 == s6); // true
        System.out.println(s5.equals(s6)); // true
    }

11.3.5 字符串的复杂操作

<1> 自动装箱

@Test
    public void test1() {
        String str = "abc";
        str += 100;
        str += "qqq";
        System.out.println(str);

        Integer m = 1; // 自动装箱
        Integer n = 1;
        System.out.println(m == n);

    }

<2> 遍历字符串

for (int i = 0; i < string.length(); i++) {
    char c = string.charAt(i); // string[i]
    System.out.println(c);
}

<3> 反向遍历字符串

// 反向遍历字符串, 把字符串接在string2后面, 循环结束就OK.
 for (int i = string.length() - 1; i >= 0; i--) {
     char ch = string.charAt(i);
     string2 = string2 + ch;
}

<4> 反向拼接字符串

​ 可以直接倒着拼接即可

for (int i = 0; i < string.length(); i++) {
            //char ch = string.charAt(string.length() - 1  - i);
            //string2 += ch;
    char ch = string.charAt(i);
    string2 = ch + string2;
}

​ 分成char数组进行交换即可

char[] chars = string.toCharArray();
for (int i = 0; i < chars.length / 2; i++) {
    // i, length - 1 - i
    char tmp = chars[i];
    chars[i] = chars[chars.length - 1 - i];
    chars[chars.length - 1 - i] = tmp;
}
String s = new String(chars);

​ 反向加字符

public class StringTest {
    public static void main(String[] args) {
        String string = "  abcQYz12 我喜欢你,你喜欢我吗? 我不喜欢你, zzQQyy  ";
        String string2 = "";
        for (int i = string.length()-1; i >= 0; i--) {
            string2 += string.charAt(i);
        }
        System.out.println(string2);
            // 反向遍历字符串, 把字符串接在string2后面, 循环结束就OK.
    }
}

<5> 获取一个字符串在另一个字符串中出现的次数

public static void main(String[] args) {
        String s1 = "abkkcadkabkebfkabkskab";
        String s2 = "ab";

        int count = 0;
        int index = 0;
        while (true) {
            index = s1.indexOf(s2, index);
            if (index == -1) {
                break;
            } else {
                count++;
                index++;//从第一次找的下一步开始
            }
            System.out.println("count = " + count);
        }

<6> 将一个字符串进行反转。将字符串中指定部分进行反转。比如将“abcdefg”反转为”abfedcg”

@Test
public void exer3() {
    String string = "abcdefghijklmn";
    int begin = 2;
    int end = 8;
    String s1 = string.substring(0, begin);
    String s2 = string.substring(begin, end);
    String s3 = string.substring(end);
    // 反转中间的s2
        /*char[] chars = s2.toCharArray();
        for (int i = 0; i < chars.length / 2; i++) {
            char tmp = chars[i];
            chars[i] = chars[chars.length - 1 - i];
            chars[chars.length - 1 - i] = tmp;
        }*/
    String s4 = "";
    char[] chars = s3.toCharArray();
    for (int i = 0; i < chars.length; i++) {
          s4 = chars[chars.length-i-1] + s4;
    }
    String result = s1 + s4 + s3;
    System.out.println(result);
}

11.4 数据结构 之 ==链表==

使用特殊类node

11.4.1 内存图示

<1>链表的内存图 链接方法

image-20200811210836169

自己关联自己的新对象

<2>创建方法

image-20200811211101795

<3>最终形态

image-20200811211216982

11.4.2 代码演示

class Node {
    int value; // 数据域
    Node next; // 指针域
}
class Link {//定义link类
    //创建link类的两个属性
    private Node head;//创建头部引用 = null
    private Node tail;//创建尾部引用 = null
    
    public void add(int val) {//在链表中添加新值
        Node newNode = new Node();
        newNode.value = val; // 携带数据
        // 如果是第一次插入
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            // 让老尾的next指向新结点
            tail.next = newNode;
            // 刷新老尾为新结点.
            tail = newNode;
        }
    }
    public void travel() {
        Node tmp = head;//head是链表的第一个地址
        while (tmp != null) {
            System.out.println(tmp.value);
            tmp = tmp.next;//让引用变成下一个
        }
    }
    public int size() {
        Node tmp = head;
        int count= 0;
        while (tmp != null){
            tmp = tmp.next;
            count++;
        }
        return count;
    }

    public void remove(int val) {
        if (head.value == val) {
            head = head.next;
        } else {
            Node prev = head;
            while (prev.next != null) {
                if (prev.next.value == val) {
                    prev.next = prev.next.next;
                    break;//continue;
                    // 在这里删除节点
                    // 删除目标节点的next地址回刷给prev.next
                }
                prev = prev.next;
            }
        }
    }
}

public class LinkTest {

    public static void main(String[] args) {
        Link link = new Link();
        link.add(8);
        link.add(1);
        link.add(4);
        link.add(2);
        link.add(9);
        link.add(6);
        link.add(3);

        link.travel();
        System.out.println("count = " + link.size());
        System.out.println("----------------------------------");
        link.remove(4);
        link.travel();
        System.out.println("count = " + link.size());

    }
}

11.5 StringBuffer类

java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删。

很多方法与String相同,但StringBuffer是可变长度的。

StringBuffer是一个容器

11.5.1 StringBuffer类有三个构造方法

  1. StringBuffer() 初始容量为 16 的字符串缓冲区
  2. StringBuffer(int size)构造指定容量的字符串缓冲区
  3. StringBuffer(String str) 将内容初始化为指定字符串内容

11.5.2 StringBuffer的一些方法

  1. StringBuffer是内容可以改变的Unicode字符序列, 可以看成是一个无限量的字符容器.
  2. StringBuilder append(…) 在当前字符串末尾追加参数中的数据, 数据可以是任意类型
  3. StringBuilder insert(int index, …) 在指定下标处插入新内容
  4. StringBuilder delete(int begin, int end) 删除指定的区间
  5. void setCharAt(int index, char ch) 替换指定位置的字符
public class StringBufferTest {

    @Test
    public void test2() {
        StringBuilder stringBuilder = new StringBuilder(); // 初始容量是16, 内部数组长度也是16
        stringBuilder.append("abc").append(200).append(false).append(3.22).insert(3, "我是汉字").delete(7, 11).setCharAt(3, '大');
        System.out.println(stringBuilder);
    }

    @Test
    public void test1() {
        StringBuilder stringBuilder = new StringBuilder(); // 初始容量是16, 内部数组长度也是16
        stringBuilder.append("abc");
        stringBuilder.append(200);
        stringBuilder.append(false);
        stringBuilder.append(3.22);

        System.out.println(stringBuilder); // "abc200false3.22"

        stringBuilder.insert(3, "我是汉字");

        System.out.println(stringBuilder);// "abc我是汉字200false3.22"

        stringBuilder.delete(7, 11);

        System.out.println(stringBuilder);// "abc我是汉字alse3.22"

        stringBuilder.setCharAt(3, '大');

        System.out.println(stringBuilder);
    }
}

11.6 StringBuilder类

11.6.1 StringBuilder与StringBuffer的区别

String StringBuffer StringBuilder
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间 StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量 可变类,速度更快
不可变 可变 可变
线程安全 线程不安全
多线程操作字符串 单线程操作字符串

11.6.1 程序运行的执行时间判断

public class StringTest {
    public static void main(String[] args) {
        String text = "";
        long startTime = 0L;
        long endTime = 0L;
        StringBuffer buffer = new StringBuffer("");
        StringBuilder builder = new StringBuilder("");
        startTime = System.currentTimeMillis();//获取系统当前时间
        for (int i = 0; i < 200000; i++) {
            buffer.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();
        System.out.println("StartBuffer的运行时间:" + (endTime-startTime));

        startTime = System.currentTimeMillis();
        for (int i = 0; i < 200000; i++) {
            builder.append(String.valueOf(i));
        }
        endTime = System.currentTimeMillis();

        System.out.println("StartBuilder的运行时间:" + (endTime-startTime));
    }
}

运行结果

image-20200813154530913

11.6.2 StringBuilder的扩容机制

<1> 刚开始传输字符串

在传输一个字符串之后,还会留一个16的余量

image-20200812090613173

<2> 在容量不够时

扩容机制 是<<1 + 2 ;

11.6.3 StringBuilder的操作方法

<1> StringBuilder append(…)

在当前字符串末尾追加参数中的数据, 数据可以是任意类型

stringBuilder.append("abc");

<2> StringBuilder insert(int index, …)

在指定下标处插入新内容

stringBuilder.insert(3, "我是汉字");

<3> StringBuilder delete(int begin, int end)

删除指定的区间

stringBuilder.delete(7, 11);

<4> void setCharAt(int index, char ch)

替换指定位置的字符

stringBuilder.setCharAt(3, '大');

<5> 可以连续处理

因为处理之后依然返回自己,所以可以反复处理

@Test
public void test2() {
    StringBuilder stringBuilder = new StringBuilder(); // 初始容量是16, 内部数组长度也是16
 stringBuilder.append("abc").append(200).append(false).append(3.22).insert(3, "我是汉字").delete(7, 11).setCharAt(3, '大');
    System.out.println(stringBuilder);
}

11.7 日期类

11.7.1 Java.lang.System.currentTimeMillis()获取系统当前时间

public static long currentTimeMillis()

用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差

此方法用于计算时间差

<1> 计算世界时间的标准

计算世界时间的主要标准有:

  • UTC(Universal Time Coordinated)
  • GMT(Greenwich Mean Time)
  • CST(Central Standard Time)

11.7.2 日期格式化器

<1> SimpleDateFormat格式化类

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

定义格式标准化示例

<2> 关于==格式化器==的说明

写一个y也可以,y是用来补0,多余的字母是用来补0的

格式化器可以直接格式化显式currentTimeMillis()的时间

image-20200812132054170

<3> 可以将currentTimeMillis()格式化称当前时间

long time = System.currentTimeMillis(); 
    // 当前时间, 以1970-01-01 00:00:00.000为0, 距离当前时间的毫秒数
System.out.println(time);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//定义格式标准化示例
String format = simpleDateFormat.format(time);
System.out.println(format);

程序运行结果

image-20200813160059867

11.7.2 java.util.Date类

表示特定的瞬间,精确到毫秒

<1> 构造方法

Date( ) 使用Date类的无参数构造方法创建的对象可以获取本地当前时间。

Date(long date) 参数为当前一个L整数,代表毫秒,给换算出时间

public class StringTest {
    public static void main(String[] args) {
        Date date = new Date(2343214351245L);
        System.out.println(date);
    }
}

其余的构造方法

image-20200813160727055

<2> 常用方法

==getTime():==

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

==toString():==

把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Su0n, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。

public void test1() throws ParseException {
        long time = System.currentTimeMillis(); 
    // 当前时间, 以1970-01-01 00:00:00.000为0, 距离当前时间的毫秒数
        System.out.println(time);
        Date date = new Date();
        System.out.println(date);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//定义格式标准化示例
        String format = simpleDateFormat.format(date);
        System.out.println(format);

        String string = "1972-05-12 12:33:50";
        Date parse = simpleDateFormat.parse(string); //解析字符串
    // 解析转换字符串为日期对象
        System.out.println(parse);
        System.out.println(simpleDateFormat.format(time));
        Date date2 = new Date(2008, 8, 8);//赋值+1900
        System.out.println(simpleDateFormat.format(date2));//赋值会出问题
    }
}

<3> Date的问题

Date 缺点 : 显示不友好, 年有1900问题, 月是少1

image-20200812131254957

可以看见Date的构造器中年份给添加了1900

<4> 使用Date类写小时钟

public class  Clock{
	public static void main(String[] args) {
		java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss a");//定义格式化器
		java.util.Date date = new java.util.Date();
		for(;;){
			String format = sdf.format(date);
			System.out.print("\r" + format);
			date = new java.util.Date();
		}
	}
}

11.7.3 Calendar时间类

Calendar的本质把他的属性放置在数组里

calendar属性都放在数组里,获取的时候获取下标

Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。

<1> 获取Calendar实例的方法

使用Calendar.getInstance()方法

调用它的子类GregorianCalendar的构造器。

一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。

比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND

public void set(int field,int value)
public void add(int field,int amount)
public final Date getTime()
public final void setTime(Date date)

Calendar 缺点 : 显示不友好, 月有小1, 内部用数组保存数据不好. 致命缺点: 内容可以改变, 不好.

public void test4(){
      Calendar calendar = Calendar.getInstance(); //new Calendar(); // 不能直接new对象的时候.
      System.out.println(calendar);
        //int year = calendar.getYear();
      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);
      System.out.println(month);
      System.out.println(day);
        // 设置为1992,10,22
      calendar.set(Calendar.YEAR, 1992);
      calendar.set(Calendar.MONTH, 9);//设置月需求减1
      calendar.set(Calendar.DAY_OF_MONTH, 22);
      System.out.println(calendar.getTime()); // 转换成date对象
      calendar.add(Calendar.MONTH, 10); // 10个月后
      System.out.println(calendar.getTime());
      calendar.add(Calendar.DAY_OF_MONTH, -500); // 500天以前
      System.out.println(calendar.getTime());
}

11.7.4 ==LocalDate类== java8新增

Java 的日期与时间 API 问题由来已久,Java 8 之前的版本中关于时间、日期及其他时间日期格式化类由于线程安全、重量级、序列化成本高等问题而饱受批评。

Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。

<1> 新的 java.time 的包含

  1. 于时钟(Clock)
  2. 本地日期(LocalDate)
  3. 本地时间(LocalTime)
  4. 本地日期时间(LocalDateTime)
  5. 时区(ZonedDateTime)
  6. 持续时间(Duration)的类。

历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。

这些新增的本地化时间日期 API 大大简化了了日期时间和本地化的管理。

<2> localTime的操作

提供get-set方法,并且显示当前时间

LocalDate date = LocalDate.now();//new LocalDate();获取当前时间
System.out.println(date);
int year = date.getYear();//获取年份
int month = date.getMonthValue();//获取月份
int day = date.getDayOfMonth();//获取天数
System.out.println("year = " + year);
System.out.println("month = " + month);
System.out.println("day = " + day);

image-20200813163501819

提供with 设定,plus加法,minus减法

每一次运算都会产生新的对象

LocalDate localDate1 = date.withYear(1995).withMonth(10).withDayOfMonth(5);//with
System.out.println(localDate1);
        // 推算日期
LocalDate localDate2 = localDate1.minusDays(-200);//减法
LocalDate localDate3 = localDate1.plusDays(-200);//加法
System.out.println(localDate4);
}
方法 描述 示例
now() 静态方法,根据当前时间创建对象 LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.now();
of() 静态方法,根据指定日期/时间创建对象 LocalDate localDate = LocalDate.of(2016, 10, 26);
LocalTime localTime = LocalTime.of(02, 22, 56);
LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55);
plusDays,
plusWeeks,
plusMonths,
plusYears
向当前 LocalDate 对象添加几天、几周、几个月、几年
minusDays,
minusWeeks,
minusMonths,
minusYears
从当前 LocalDate 对象减去几天、几周、几个月、几年
plus,
minus
添加或减少一个 Duration 或 Period
withDayOfMonth,
withDayOfYear,
withMonth,
withYear
将月份天数、年份天数、月份、年份修改为指定的值并返回新的 LocalDate 对象
getDayOfMonth 获得月份天数(1-31)
getDayOfYear 获得年份天数(1-366)
getDayOfWeek 获得星期几(返回一个 DayOfWeek 枚举值)
getMonth 获得月份, 返回一个 Month 枚举值
getMonthValue 获得月份(1-12)
getYear 获得年份
until 获得两个日期之间的 Period 对象,或者指定 ChronoUnits 的数字
isBefore,
isAfter
比较两个 LocalDate booelan类型判断是前是后
isLeapYear 判断是否是闰年

<3>练习 设置生日,然后求百岁

public void test2() {
  LocalDate date1 = LocalDate.now();
  System.out.println(date1);
  System.out.println(date1.withYear(1995).withMonth(8).withDayOfMonth(10).plusDays(100));
}

11.8 Math类

java.lang.Math提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为double型。

abs绝对值

acos, asin, atan, cos,sin,tan 三角函数

sqrt 平方根

pow(double a,doble b) a的b次幂

log 自然对数

exp e为底指数

max(double a,double b)

min(double a,double b)

random() 返回0.0到1.0的随机数

long round(double a) double型数据a转换为long型(四舍五入)

toDegrees(double angrad) 弧度–>角度

toRadians(double angdeg) 角度–>弧度

静态方法称为工具方法

round

进行四舍五入

image-20200812142821639

11.8 BigInteger类

Integer类作为int的包装类,

能存储的最大整型值为2^31-1

BigInteger类的数字范围较Integer类的数字范围要大得多,可以支持任意精度的整数。

<1> 构造器

BigInteger(Stringval)

<2> 常用方法

public BigInteger abs() 绝对值

public BigInteger add (BigIntegerval) 添加

public BigInteger subtract(BigIntegerval) 减法

public BigInteger multiply(BigIntegerval) 乘法

public BigInteger divide(BigIntegerval) 除法

public BigInteger remainder(BigIntegerval) 余数

public BigInteger pow(int exponent) 次方幂

public BigInteger[] divideAndRemainder (BigIntegerval)返回数组 保存余数跟除数

11.9 BigDecimal类

import org.junit.Test;

import java.math.BigDecimal;
import java.math.BigInteger;

public class MathTest {

    @Test
    public void test2() {
        BigInteger bi1 = new BigInteger("847239847958230982385098235098235");
        BigInteger bi2 = new BigInteger("134234234229835675785098235098235");
        BigInteger multiply = bi1.multiply(bi2);
        System.out.println(multiply);

        BigDecimal bd1 = new BigDecimal("293842398472874208348273.239842394823482734829374927349827492873498274928734928374987234234234");
        BigDecimal bd2 = new BigDecimal("293842392342342348472874208348273.23234234234234234234239842394823482734829374927349827492873498274928734928374987234234234");
        BigDecimal add = bd1.add(bd2);
        System.out.println(add);

    }

    @Test
    public void test1() {
        System.out.println(Math.random());
        System.out.println((int)(Math.random() * 100));

        System.out.println(Math.round(9.5));
        System.out.println(Math.round(-9.5));
        System.out.println(Math.round(-9.6));
        System.out.println(0x7FFFFFFFFFFFFFFFL); // 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
    }
}

第12章 集合

数组 : 解决批量数据的存储问题

集合 : 解决批量对象的存储问题, 可以简单把集合看成是一个可变长的Object数组, Object意味着任意对象都可以保存

12.1集合的框架

PPT中集合分类

Collection接口, 表示的集合只能保存一个一个的对象. 特点 : 无序可重复
无序 : 不按添加顺序保存元素, 可重复 : 内容相等的元素可以重复放入

12.2 数据结构 之 ==二叉树==

12.2.1 内存分析图

12.2.2 代码演示图

package com.javase.collection;

class TreeNode {
    int value;
    TreeNode left;
    TreeNode right;
}

class Tree {

    TreeNode root;

    public void insert(TreeNode target, TreeNode newNode) {
        if (newNode.value < target.value) { // 向左走
            if (target.left == null) { // 左子为空, 新结点直接插入
                target.left = newNode;
            } else { // 左子非空, 把左子作为根当成子树进一步插入
                insert(target.left, newNode);
            }
        } else { // 向右走
            if (target.right == null) {
                target.right = newNode;
            } else {
                insert(target.right, newNode);
            }
        }
    }

    public void add(int val) {
        TreeNode newNode = new TreeNode();
        newNode.value = val;
        if (root == null) {
            root = newNode;
        } else {
            insert(root, newNode);
        }
    }

    public void view(TreeNode node) {
        if (node == null) {
            return;
        }
        view(node.left);
        System.out.println(node.value);
        view(node.right);
    }
    public void travel() {
        view(root);
    }
}


public class TreeTest {

    public static void main(String[] args) {
        Tree tree = new Tree();
        tree.add(10);
        tree.add(20);
        tree.add(5);
        tree.add(3);
        tree.add(30);
        tree.add(18);
        tree.add(15);
        tree.add(1);

        tree.travel();

    }

}

12.3 Collection接口

Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的==方法==既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合

集合继承分类树

JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。

在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;

从 Java5 增加了泛型以后,Java 集合可以记住容器中对象的数据类型

SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。

12.4 Set与List集合比较

12.4.1 特点

<1> List 有序,可重复

  • ArrayList
    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程不安全,效率高
  • Vector
    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程==安全==,效率低
  • LinkedList
    优点: 底层数据结构是链表,查询慢,增删快。
    缺点: 线程不安全,效率高

<2> Set 无序,唯一

  • HashSet
    底层数据结构是哈希表。(哈希与equals)
    (无序,唯一)
    如何来保证元素唯一性?
    依赖两个方法:hashCode()和equals()
  • LinkedHashSet
    底层数据结构是链表和哈希表。
    (FIFO插入有序,唯一)
    由链表保证元素==有序==
    由哈希表保证元素唯一
  • TreeSet
    底层数据结构是二树。(唯一,==有序==) 又称为二叉搜索树
    如何保证元素排序的呢?
    自然排序
    比较器排序
    如何保证元素唯一性的呢?根据比较的返回值是否是0来决定

12.4.2 数组的一些操作

<1> collection

image-20200813172857476

image-20200813173024817

<2> List 子接口

List 集合里添加了一些根据索引来操作集合元素的方法

void add(int index, Object ele);//在指定的index下标处插入新元素
boolean addAll(int index, Collection eles);
Object get(int index);//获得指定下标的元素,并返回该下标的对象
int indexOf(Object obj);
int lastIndexOf(Object obj);
Object remove(int index);//删除指定的index下标处的元素并返回元素
Object set(int index, Object ele);//替换指定的index下标处的元素为新ele元素, 被替换的老元素作为返回值返回
List subList(int fromIndex, int toIndex);//截取子字符串

12.5 List 实现类之一:ArrayList

ArrayList 是 List 接口的典型实现类

本质上,ArrayList是对象引用的一个变长数组

ArrayList 是线程不安全的,而 Vector 是线程安全的,即使为保证 List 集合线程安全,也不推荐使用Vector

Arrays.asList(…) 方法返回的 List 集合既不是 ArrayList 实例,也不是 Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合

12.6 List实现类之二:LinkedList

对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高

新增方法:

void addFirst(Object obj);
void addLast (Object  obj);
Object getFirst ();
Object getLast();
Object removeFirst();
Object removeLast();

12.7 List实现类之三:Vector

各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。

新增方法: (这些方法是冗余的,有替代方法,了解即可 )

void addElement(Object obj)

void insertElementAt(Object obj,int index)

void setElementAt(Object obj,int index)

void removeElement(Object obj)

void removeAllElements()

12.3.6 使用 Iterator 接口遍历集合元素

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素

所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。

Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。

12.8 使用泛型(Generic)

集合类中的元素使用Object类型,以允许不同的添加和获取类型。当获取集合中所需对象时,必须进行强制类型转型。

例如:

List list = new ArrayList();
list.add(Hello world”);
list.add(123);
String str1 = (String)list.get(0);
String str2 = (String)list.get(1);  //产生异常

泛型机制为编写代码提供了便利,同时提供了编译时的类型安全检查。

使用集合编程时,应结合使用泛型。例如:

ArrayList<String> list = new ArrayList<String>();//String[]

list.add(Hello world”);

String str = list.get(0);

泛型以< >形式呈现, < >中的类型约束了集合所能存储的对象类型。

这样在获取集合中对象时,不必再进行强制类型转型。

< >中的类称为泛型的类型参数

12.9 Set实现类之一: HashSet

HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。

HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。

12.19.1 HashSet 具有以下==特点==:

  • 不能保证元素的排列顺序
  • HashSet 不是线程安全的
  • 集合元素可以是 null

当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值决定该对象在 HashSet 中的存储位置。

HashSet 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等

12.10 Set实现类之二:LinkedHashSet

LinkedHashSet 是 HashSet 的子类

LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。

LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。

LinkedHashSet 不允许集合元素重复。

12.12 Set实现类之三:TreeSet

有序 不可重复

TreeSet 是 ==SortedSet==接口的实现类,TreeSet 可以确保集合元素处于排序状态。

Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)

TreeSet 两种排序方法:自然排序定制排序。默认情况下,TreeSet 采用自然排序。

12.9.1 自然排序

<1> 自然排序:

TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序排列

@org.junit.Test
    public void Test1(){
        Set set = new TreeSet();
        set.add(78);
        set.add(23);
        set.add(231);
        System.out.println(set);
        // treeSet涉及到排序,输入的内容要一致,存在自然排序;

[23, 78, 231] //程序运行结果按照自然排序进行

<2> 添加不同类型的对象

当继续使用其他类型的字符串型对象添加进TreeSet集合时,就会报错,因为无法进行比较,因为自然排序采用统一的compareTo进行排序

public void Test1(){
        Set set = new TreeSet();
        set.add(78);
        set.add(23);
        set.add(231);
        set.add("abc");
        System.out.println(set);
        // treeSet涉及到排序,输入的内容要一致,存在自然排序;
    }

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

<3> TreeSet放置字符串

全放置字符串就可以

在treeSet全部放置字符串

<4> treeSet放置对象

当使用treeSet直接放置对象时,会直接报错,因为自定义的对象没有实现comparable接口,没有比较大小的能力,无法通过二叉树进行数据存入

public class SetListTest{
    public static void main(String[] args) {
        Set set = new TreeSet();
        Students s1 = new Students("张飞",23,3000);
        set.add(s1);
    }
}

class Students{
    private String name;
    private int age;
    private int salary;
}

java.lang.ClassCastException

<5> 让对象实现comparable接口

public class SetListTest{
    public static void main(String[] args) {
        Set set = new TreeSet();
        Students s1 = new Students("张飞",23,3000);
        set.add(s1);
        System.out.println(set);
    }
}

如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。

image-20200814092715355

实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。

<6> treeSet的去重,判断对象相等,通过compareTo

image-20200814092520363

出现compareTo = 0时会失效

12.6.2 定制排序 Comparator第三方比较器(接口)

含义相近

image-20200814100148623

自定义比较器 写一个类,实现Comparator

<1> 实现comparator

image-20200814100743919

public class SetListTest{
    public static void main(String[] args) {
        Students s1 = new Students("张飞",23,3000);
        Students s2 = new Students("关羽",46,8000);
        Students s3 = new Students("马超",23,5000);
        MyComparator comparator = new MyComparator();
        Set set = new TreeSet(comparator);//将重写后的比较器直接传入形参使用
        set.add(s1); set.add(s2); set.add(s3);
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

class Students implements Comparable{
    private String name;
    private int age;
    private int salary;
    public Students() {
    }
    public Students(String name, int age, int salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    @Override
    public String toString() {
        return new StringJoiner(", ", Students.class.getSimpleName() + "[", "]")
                .add("name='" + name + "'")
                .add("age=" + age)
                .add("salary=" + salary)
                .toString();
    }
    @Override
    public int compareTo(Object o) {
        if (o instanceof Students){
            int num = this.salary - ((Students) o).salary;
            if (num == 0) num = 1;
            return num;
        }else{
            throw new RuntimeException("type exception");
        }
    }
}
class MyComparator implements Comparator {//实现比较器类
    @Override
    public int compare(Object o1, Object o2) {
        if (o1 instanceof Students && o2 instanceof Students) {
            int num = (((Students) o1).getSalary() - ((Students) o2).getSalary());
            if (num == 0) {
                num = 1;
            }
            return num;
        }else{
            throw new RuntimeException("type exception");
        }
    }
}

如果有比较器和对象,对象自己也会重写了comparable,以比较器为准

<2> String自己本身具有compareTo

匿名内部类 与 String的大小比较

unicode不靠谱 不会按照汉字的字母进行排序

image-20200814103012805

<3> Comparator的匿名内部类的使用

image-20200814102752185

还可以把匿名内部类可以直接放进集合构造器的形参列表

image-20200814103145878

public class SetListTest{
    public static void main(String[] args) {
        Students s1 = new Students("张飞",23,3000);
        Students s2 = new Students("关羽",46,8000);
        Students s3 = new Students("马超",23,5000);
        Comparator comparator = new Comparator() {

            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof Students && o2 instanceof Students) {
                    int num = (((Students) o1).salary - ((Students) o2).salary);
                    if (num == 0) {
                        num = 1;
                    }
                    return num;
                } else {
                    throw new RuntimeException("type exception");
                }
            }
        };
        Set set = new TreeSet(comparator);//将重写后的比较器直接传入形参使用
        set.add(s1); set.add(s2); set.add(s3);
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

class Students implements Comparable{
    private String name;
    private int age;
    int salary;
    public Students() {
    }
    public Students(String name, int age, int salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    @Override
    public String toString() {
        return new StringJoiner(", ", Students.class.getSimpleName() + "[", "]")
                .add("name='" + name + "'")
                .add("age=" + age)
                .add("salary=" + salary)
                .toString();
    }
    @Override
    public int compareTo(Object o) {
        if (o instanceof Students){
            int num = this.salary - ((Students) o).salary;
            if (num == 0) num = 1;
            return num;
        }else{
            throw new RuntimeException("type exception");
        }
    }
}

<4> Comparable的典型实现

BigDecimal、BigInteger 以及所有的数值型对应的包装类:按它们对应的数值大小进行比较

Character:按字符的 unicode值来进行比较

Boolean:true 对应的包装类实例大于 false 对应的包装类实例

String:按字符串中字符的 unicode 值进行比较

Date、Time:后边的时间、日期比前面的时间、日期大

向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。

因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象

对于 ==TreeSet==集合而言,它判断两个对象是否相等的==唯一标准==是:

  • 两个对象通过 compareTo(Object obj) 方法比较返回值

当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过 equals() 方法比较返回 true,则通过 compareTo(Object obj) 方法比较应返回 0

12.13 迭代器Iterator

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。

所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。

Iterator仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合。

image-20200814104152104

image-20200814104207202

在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出

//NoSuchElementException异常。
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
    Object obj = iterator.next();
    System.out.println(obj);
}

迭代器运行图

12.11.1 迭代器的使用

通过集合对象的iterator方法获取迭代器对象

while循环不断检测迭代器是否还有下一个元素

如果有,真的调用next()获取下一个元素,并同时移动游标

增强for本质上使用了迭代器

image-20200814104839027

<1> 迭代器的注意事项

  1. 不能new 必须向集合要迭代器
  2. 迭代器拿到之后尽快使用,不要再修改集合(set.add(100))
  3. 再循环中,next()方法只能调用一次
  4. 否则游标越界
  5. 迭代器只能使用一次,用过就不能再用

<2> 增强for

把数组跟集合整合在一起

增强for为了写法简单

<3> 注意事项

  • 迭代出来的元素都是原来集合元素的拷贝。
  • Java集合中保存的元素实质是对象的引用,而非对象本身。
  • 迭代出的对象也是引用的拷贝,结果还是引用。那么如果集合中保存的元素是可变类型的,那么可以通过迭代出的元素修改原集合中的对象。
Set set = new TreeSet(comparator);//将重写后的比较器直接传入形参使用
set.add(s1); set.add(s2); set.add(s3);
Iterator<Students> iterator = set.iterator();
while(iterator.hasNext()){
    Students i = iterator.next();
    if (i.salary < 5000){
        iterator.remove();
    }
}
System.out.println(set);

<4> 迭代器的remove的使用 如上

12.14 泛型

12.12.1 为什么要有泛型

  • 解决类型安全问题.
  • 解决获取数据元素时,需要类型强转的问题

当没有泛型时的集合应用

使用泛型之后

当使用泛型时集合的使用变得更加简便

image-20200814112459363

<1> 类型安全的说明

类型一旦使用泛型,集合中只能保存泛型中保存的元素

集合类中的元素使用Object类型,以允许不同的添加和获取类型。

当获取集合中所需对象时,必须进行强制类型转型。

例如:

List list = new ArrayList();
list.add("Hello world");
list.add(123);
String str1 = (String)list.get(0);
String str2 = (String)list.get(1);  //产生异常

12.15 Map接口

image-20200814203310215

Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value

Map 中的 key 和 value 都可以是任何引用类型的数据

Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法。

key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value。

Map接口的常用实现类:HashMap、TreeMap和Properties。

==HashMap==是 Map 接口使用频率最高的实现类。

允许使用null键和null值,与HashSet一样,不保证映射的顺序。

  • HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
  • HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。

12.15.1 Map的基本操作

key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value。

内部有2个子集合, 一个是Set保存所有键对象, 另一个是Collection保存所有值对象.

Object put(Object key,Object value)//写入词条

Object get(Object key)//查词典, 根据给定的键对象 获取它映射的值对象

Object remove(Object key)//删除词条

Set keySet()//返回保存所有键对象的Set子集合.

Set entrySet()//返回的是保存所有条目对象的Set集合

添加、删除操作:

Object put(Object key,Object value);
Object remove(Object key);
void putAll(Map t);
void clear();

元视图操作的方法:

Set  keySet();//返回键值对象数组
Collection values();
Set entrySet();

元素查询的操作:

Object get(Object key);
boolean containsKey(Object key);
boolean containsValue(Object value);
int size();
boolean isEmpty();
boolean equals(Object obj);

12.5.2 添加的覆盖

put具有覆盖效果,新值会覆盖老值

image-20200814120701343

12.16 Map实现类之一 HashMap

使用哈希算法实现的Map集合, 对所有键对象保存规则就是无序不可重复.

12.16.1 练习 创建HashMap并且使用泛型

public static void main(String[] args) {
    Map<Integer,Integer> map = new HashMap<Integer,Integer>();//使用泛型
    for (int i = 1; i <= 50; i++) {
        int radius = i;
        int area = (int)(i*i*3.14);
        map.put(radius,area);
    }
    Set set = map.keySet();
    Iterator iterator = set.iterator();
    while(iterator.hasNext()){
        Object next = iterator.next();
        System.out.print("半径为" + next);
        System.out.println(" 面积为" + map.get(next));
    }
        /*for (Object o :set){
            System.out.print("半径为" + o);
            System.out.println(" 面积为" + map.get(o));
        }*/

12.6.2 keySet()与entrySet()的应用(entrySet()需要理解)

Map<Integer,Integer> map = new HashMap<Integer,Integer>();
            for (int i = 1; i <= 50; i++) {
                int radius = i;
                int area = (int)(i*i*3.14);
                map.put(radius,area);
            }
            Set<Map.Entry<Integer,Integer>> entries = map.entrySet(); // 所有条目对象
            for (Map.Entry tmp : entries) {
                System.out.println(tmp.getKey() + " = " + tmp.getValue());
            }
Set<Integer> integers = map.keySet();
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()) {
    Integer key = iterator.next();
    String value = map.get(key); // 根据键对象动态获取值对象.
    System.out.println(key + " = " + value);
}
System.out.println("**********************************");
Set<Map.Entry<Integer, String>> entries = map.entrySet(); // 所有条目对象
for (Map.Entry tmp : entries) {
    System.out.println(tmp.getKey() + " = " + tmp.getValue());
}

12.17 Map实现类之二 TreeMap

TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。

TreeMap 的 Key 的排序:

自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException

定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口

12.18 Map实现类之三 LinkedHashMap

lLinkedHashMap 是 HashMap 的子类

l与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致

12.18 Map实现类之四 HashTable

Hashtable是个古老的 Map 实现类,线程安全。

与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value

与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序

Hashtable判断两个key相等、两个value相等的标准,与hashMap一致。

12.19 Map实现类之五 Properties

Properties 类是 Hashtable 的子类,该对象用于处理属性文件

由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型

存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

能够自动读取存储信息

image-20200814143742402

创建一个配置文件,用properties

12.20 工具类 Collections Arrays

Collections 是一个操作 Set、List 和 Map 等集合的工具类

Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

排序操作:(均为static方法)

reverse(List):反转 List 中元素的顺序

shuffle(List):对 List 集合元素进行随机排序

sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序

sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序

swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

<1> sort

可以提供比较器

image-20200814144141915

12.21 ArrayList集合内部==底层代码==

基于数组实现内部集合

数组的缺省容量是10

属性

image-20200815102457804

数组容量判断image-20200815102852453

计算所需容量

image-20200815103026663

返回参数10

扩容

image-20200815103212320

image-20200815103436384

有序可重复

扩容是1.5倍

删除的流程

前移补空洞

image-20200815104920016

迭代器原理

为啥修改错误

image-20200815105513924迭代器的内部类itr

今晚代码把代码找出来

第13章 泛型

泛型即为参数化类型

  1. 解决元素存储的安全性问题

  2. 解决获取数据元素时,需要类型强转的问题

泛型,JDK1.5新加入的,解决数据类型的安全性问题,其主要原理是在类声明时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。

这样在类声明或实例化时只要指定好需要的具体的类型即可。

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。

同时,代码更加简洁、健壮。

13.1 在集合中使用泛型

在集合中,可以在添加时固定类型,获取类型不要再造型

13.1.1 泛型的声明

interface List 和 class TestGen<K,V>

其中,T,K,V不代表值,而是表示类型。这里使

​ 用任意字母都可以。

常用T表示,是Type的缩写。

13.2.2 泛型的实例化

一定要在类名后面指定类型参数的值(类型)。如:

List<String>strList= new ArrayList<String>();

Iterator<Customer> iterator = customers.iterator();

能是类,不能用基本数据类型填充。

类型不安全

13.2.3 集合中泛型的深度理解

public class generic {
    public static void main(String[] args) {
        List<String> stringArrayList = new ArrayList<String>();
        List<Integer> integerArrayList = new ArrayList<Integer>();

        Class classStringArrayList = stringArrayList.getClass();
        Class classIntegerArrayList = integerArrayList.getClass();

        if(classStringArrayList.equals(classIntegerArrayList)){
            System.out.println("类型相同");
        }
    }
}

结果:类型相同

通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,==只在编译阶段有效。==

在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

13.2 在类中使用泛型

13.2.1 自定义泛型类

泛型类型只能对象相关, 和类无关, 所以不可以在静态方法中或静态属性中使用泛型类型

class Person<X> { // X就是这个类的泛型参数, 作用是表示某种类类型. 是一个形参, 此时并不知道X的具体类型是什么.
    // 在创建对象时由创建者指定X的具体类型. 所以X类型和对象相关.

    private String name;
    //private Object info; // 类型不安全.
    private X info; // X既然是数据类型, 就可以声明属性.

    public Person() {
    }

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

    public String getName() {
        return name;
    }

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

    public X getInfo() {
        return info;
    }

    public void setInfo(X info) {
        this.info = info;
    }

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

    /* 泛型类型只能对象相关, 和类无关, 所以不可以在静态方法中或静态属性中使用泛型类型
    public static void test(X x) {
    }*/
}

13.2.1 自定义泛型类的对象声明

public void test3() {
        Person<Integer> p1 = new Person<Integer>("张三", 30); // 类型安全, 因为在创建对象时指定了具体的类型
        Integer info1 = p1.getInfo(); // 类型安全了.
        //p1.setInfo("男");
        Person<String> p2 = new Person<String>("李四", "女"); // 类型安全, 指定了具体类型
        String info2 = p2.getInfo();

        Person<Boolean> p3 = new Person("王五", true); // 类型不安全, 因为没有指定, 所以只能用类型最模糊的Object.
        Boolean info3 = p3.getInfo();
    }

13.2.2 是否能够在静态方法中使用X(泛型)

泛型类型只能对象相关, 和类无关, 所以不可以在静态方法中或静态属性中使用泛型类型

成员变量(可以声明泛型)

类变量(静态变量)

局部变量(可以声明泛型)

13.3 泛型方法

在返回值左面使用<泛型类型A>, 和局部变量类似 ,

泛型类型A只能在本方法中使用, 所以是局部泛型

泛型方法必须通过参数来确定类型, 所以必须要有泛型类型的一个参数,

将来在调用时由调用者的实参的具体值进一步确定真正的类型.

==类 MyMethod==

// 泛型方法
class MyMethod {
    // 在返回值左面使用<泛型类型A>, 和局部变量类似 , 泛型类型A只能在本方法中使用, 所以是局部泛型
    // 泛型方法必须通过参数来确定类型, 所以必须要有泛型类型的一个参数, 将来在调用时由调用者的实参的具体值进一步确定真正的类型.
    public static <A> A test(A a) {
        A a2 = null;
        System.out.println("test()...");
        return a2;
    }
    public void test2() {
        //A a = null;
    }
}

实参避免传入null值。根据null值无法感知具体类型

public class GenericTest {
    @Test
    public void test4() {
        Integer test = MyMethod.test(200);
        String abc = MyMethod.test("abc");
        Object test1 = MyMethod.test(null); // 实参避免传入 null值, 因为根据null值无法它的具体类型.

通过传入的实参确认返回类型

13.4 泛型转数组 toArray 泛型方法

ArrayList的toArray(其他数组也具有这种方法)

ArrayList提供了一个将List转为数组的一个非常方便的方法toArray。toArray有两个重载的方法:

(1)list.toArray();

(2)list.toArray(T[] a);

对于第一个重载方法,是将list直接转为Object[] 数组;

第二种方法是将list转化为你所需要类型的数组,当然我们用的时候会转化为与list内容相同的类型。

image-20200814154958819

集合在转数组时必须进行对toArray(传入一个数组的类型,用来判断类型)

public class ToArrayTest {
    public static void main(String[] args) {
        List<Integer>list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add((int)(Math.random()*20));
        }
        Object[] objects = list.toArray();
        Integer[] arr = {2,3,4};
        Integer[] integers = list.toArray(arr);//必须传入一个可以参考的格式
        System.out.println(integers);
        for (int i = 0; i < integers.length; i++) {
            System.out.print(integers[i] + " ");
        }
        System.out.println();
        for(Integer integer:integers){
            System.out.print(integer + " ");
        }
        System.out.println();
        System.out.println(Arrays.toString(integers));
    }
}

如果这个数组能用,就用。如果传入数组不嗯能够用,我就不用

13.5 泛型与继承

泛型与继承的关系

子类在继承泛型父类时,没有明确泛型类型,父类中的泛型类型只能是最模糊的Object类型

class A<T> {
    private T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}
class B1 extends A {} // 子类在继承泛型父类时, 没有明确泛型类型, 父类中的泛型类型只能是最模糊的Object类型.
class B2 extends A<Integer> {} // 子类在继承泛型父类时, 直接写死泛型. 不够灵活, 但是使用简单.
class B3 extends A<String> {}
class B4<T> extends A<T> {} // 子类在继承时没有固定,也是继续泛型. 是最灵活的写法. 对于使用者要求高

13.6 通配符<?>的使用

权限不行,父类引用反而可以添加更多

public void test1() {
        List<Integer> list1 = new ArrayList<Integer>();
        for (int i = 0; i < 10; i++) {
            list1.add((int)(Math.random() * 20));
        }
        view(list1);

        List<String> list2 = new ArrayList<String>();
        list2.add("aflksj");
        list2.add("2384234");
        view(list2);

        //List<Number> list2 = new ArrayList<Integer>();
        //list2可以添加Number的其他子类对象, 而实际的List集合对象只能放Integer

        //List<Object> list = list1;
        //list = list2;

        

    }

13.6.1 <?>模糊类型的说明

// list集合因为类型的未知性, 使得它不便于添加元素. 但是可以获取类型模糊的元素.
        List<?> list = list1; // ? 表示未知. list里可以保存任意未知对象.
        //list = list2;

        list.add(200);//已知类型 报错
        list.add("abds");//已知类型 报错
        list.add(new Object());//已知类型 报错
        list.add(null);

        Object o = list.get(0);

已知类型不能放在<?>

image-20200814162507529

不适合于添加元素,但是适合于读取元素,放置用户随意添加删除

public class ListGenericTest {
    public static void main(String[] args) {
        ArrayList<Number> numbers = new ArrayList<>();
        numbers.add(3);
        numbers.add(23.2);
        numbers.add(219371L);
//        numbers.add("sadad");
        System.out.println(numbers);

        ArrayList list = new ArrayList();
        list.add(23);
        list.add('a');

        List<?> objects = list;//此时添加元素受到限制,所有已知类型的元素都会被限制加入
        for (int i = 0; i < objects.size() ; i++) {
            System.out.println(objects.get(i));
        }
        objects.remove(1);//但是可以删除元素
        System.out.println(objects);
    }
}

因为类型的未执行

获取时失去泛型

image-20200814162731128

可以获取类型模糊的元素

可以用于

获取元素

获取不同类型的集合

专门用来遍历

public void view(List<?> list) { // List<?>写法只适用于遍历集合, 只读的.
       for (int i = 0; i < list.size(); i++) {
           System.out.println(list.get(i));
       }
}

13.6.2 给通配符?+ 限定作用

<?>

允许所有泛型的引用调用

举例:

<? extends Number> (无穷小, Number]

只允许泛型为Number及Number子类的引用调用

<? super Number>   [Number ,无穷大)

只允许泛型为Number及Number父类的引用调用

<? extends Comparable>

只允许泛型为实现Comparable接口的实现类的引用调用

public void test2() {
        // list1集合中可以保存Number及其未知父类类型的对象.
        // <? super Number> 写法可以添加元素, 但是获取元素不方便.
        List<? super Number> list1 = new ArrayList<Number>();
        list1.add(100);
        list1.add(3.22);

        Object object = list1.get(0);

        // list2集合中可以保存Number及其未知子类类型的对象
        // <? extends Number> 写法不适合添加元素, 但是适合获取元素
        List<? extends Number> list2 = new ArrayList<Number>();
        //list2.add(200); // 200是已知类型
        Number num = list2.get(0);
}

13.7 泛型用于灵活的比较 实现Comparator Comparable接口

泛型在比较上的用途

public class CompareTest {
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<Integer>();
        Set<Double> list2 = new HashSet<>();
        int count = 0;
        while (count < 10){
            list1.add((int)(Math.random()*10));
            list2.add(Math.random()*10);
            count++;
        }
        System.out.println(CompareTools.Max(list1));
    }
}
class CompareTools{
    public static Comparable Max(Collection<? extends Comparable> list){
        //表示可以传入Comparable的子类
        Iterator<? extends Comparable> iterator = list.iterator();//传入的迭代器也要有相同的类型
        Comparable max = iterator.next();
        while (iterator.hasNext()){
            Comparable next = iterator.next();
            if(max.compareTo(next) < 0){
                max = next;
            }
        }
        return max;
    }
}

为了更好地兼容性

不写基于类型推断

通配符适用于可比较器用于比较

image-20200815095437294

用于找最大值,最小值

image-20200815101942881

第14章 IO流

输入跟输出

14.1 IO原理

IO流用来处理设备之间的数据传输。

Java程序中,对于数据的输入/输出操作以”流(stream)” 的方式进行。是指从==源节点到目标节点的数据流动==

源节点和目标节点可以是文件、网络、内存、键盘、显示器等等。

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

14.2 流的分类

img

按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
按数据流的流向不同分为:输入流,输出流

字节流 字符流
输入流 InputStream(基类) Reader(基类)
输出流 OutputStream (基类) Writer(基类)
流中的数据 二进制字节(8位) Unicode字符(16位)
  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。

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

14.3 fileReader 文本文件输入流 ==char==

无论是文本文件还是二进制文件,当需要读取文件数据时,需要完成以下步骤:

fileReader

返回第一次字符的unicode的码值

char

使用文件输入流打开指定文件

14.3.1 换行符

0d 0a是换行 0a 是10

\r 13 \n 10

14.4 使用IOfileWriter写文本文件

写文件会自动创建文件

image-20200815141341225

13 、\r 10 \t

记事本具有\r\t

14.4.1 提高效率使用缓冲区[] 100

最后一次会剩下一部分上一次已经处理过的数据

<1> 如果使用缓冲区读文本文件应该怎么做

image-20200815145301306

<2> 使用缓冲区写文件

image-20200815152248221

14.5 缓冲流

为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组

根据数据操作单位可以把缓冲流分为:

BufferedReader 和 BufferedWriter

BufferedInputStream 和 BufferedOutputStream

缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法, 增强了流处理能力.
对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush()将会使内存中的数据立刻写出

二进制文件不能使用字符流

节点流 处理流

高级流

低级流作为一个属性在高级流内,完成对象关联

只关闭高级流即可

image-20200815164031403

readline是最高级的方法

image-20200815164427900

ctri+alt + t

通常包装都是兑现关联

image-20200815164959321

14.6 处理流之六 对象流

image-20200815170507303image-20200815170810425

标识

class文件

image-20200815171245690

14.7 对象的序列化

序列化对象在GC堆中的数据

ObjectInputStream和OjbectOutputSteam

用于存储和读取对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

==序列化(Serialize)==:用ObjectOutputStream类将一个Java对象写入IO流中

==反序列化(Deserialize)==:用ObjectInputStream类从IO流中恢复该Java对象

ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

为了防止对象消失,从GC堆里写入硬盘

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象

序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原

序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础

如果需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:

  • Serializable
  • Externalizable

对象序列化是序列化对象在GC堆中的数据

要想实现序列化对象,这个对象所属的类必须实现序列化接口

serializable

类的全限定名称()

版本号

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

private static final long serialVersionUID;

serialVersionUID用来表明类的不同版本间的兼容性

如果类没有显示定义这个静态变量,它的值是Java运行时环境

根据类的内部细节自动生成的。若类的源代码作了修改,

serialVersionUID 可能发生变化。故建议,显示声明

显示定义serialVersionUID的用途

希望类的不同版本对序列化兼容,因此需确保类的不同版本具

有相同的serialVersionUID

不希望类的不同版本对序列化兼容,因此需确保类的不同版本

具有不同的serialVersionUID

静态属性不能序列化

public class PersonSerializedTest {
    public static void main(String[] args) {
        Set set = new HashSet();
        Person p1 = new Person("王平", 2, 5, 1000);
        Person p2 = new Person("廖化", 3, 7, 2000);
        Person p3 = new Person("陆机", 4, 2, 3000);
        Person p4 = new Person("张昭", 5, 8, 4000);
        Person p5 = new Person("陈宫", 6, 1, 5000);

        set.add(p1);
        set.add(p2);
        set.add(p3);
        set.add(p4);
        set.add(p5);
        FileOutputStream fps = null;
        BufferedOutputStream bps = null;
        ObjectOutputStream oos = null;
        try {//对对象序列化的意思就是把对象通过写出对象的方式写入文件
            fps = new FileOutputStream("对象序列化文件");
            bps = new BufferedOutputStream(fps);
            oos = new ObjectOutputStream(bps);
            oos.writeObject(set);

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

        FileInputStream fis = null;
        BufferedInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream("对象序列化文件");
            bis = new BufferedInputStream(fis);
            ois = new ObjectInputStream(bis);

            Set sets = (Set)ois.readObject();
            Iterator iterator = sets.iterator();
            for (Object person:sets){
                System.out.println(person);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

transient 短暂的

指针不能持久

image-20200817115152709

链表可以序列化

写几个read几个 数组的反序列化

集合的反序列化

image-20200817115638710

对象的获取途径

<1>第一种 是new

<2>第二种 工具方法 工厂方法 getBank

<3>反序列化

<4>反射

作业

保存50个随机数

写一个二进制文件,写入随机个随机数,如何正确的读取?

linkedlist的源码研究

14.8 链表的插入操作详解

image-20200817090747253

first = newnode;

image-20200817090007448

如果一个节点的prev为null头

next为null 尾

如何删除

image-20200817091055243

prev.next = target.next;
next.prev = target.prev;

image-20200817091752040

链表的删除就是用删除两个指针

数组当中的删除用的是 arraycopy 数组比较多

linkedlist的优点,添加或者删除元素都是非常快的

删除过程

.next = o.next
o.next  = null;.prev = o.prev
o.prev = null;

o.item = null;

ArrayList与Linkedlist的比较,优缺点

image-20200817093518520

image-20200817093537502

JDK内部底层用于红黑树,用于平衡

hashSet

使用hashCode特征码,设计存储数据的地址

对元素计算哈希码直接得到内存地址

散列表

内存必须连续

hashMap与hashSet一回事

14.9 UTF-8 编码与解码

Unicode是[字符集]

UTF-8是[编码规则]

其中

字符集:为每个[字符]分配一个唯一的ID(学名为码位/码点/Code Point)

编码规则:将[码位]转换为字节序列的规则(编码/解码 可以理解为加密/解密的过程)

UTF-8顾名思义,是一套以8位位一个编码单位的可边长编码,会将一个码位为1到4个字节

U+ 0000 ~ U+ 007F:0XXX XXXX;
U+ 0080 ~ U+ 07FF:110X XXXX 10XX XXXX;
U+ 0800 ~ U+ 007F:1110 XXXX 10XX XXXX 10XX XXXX; 
U+ 10000 ~ U+ 007F:1111 0XXX,10XX XXXX 10XX XXXX 10XX XXXX;

image-20200817192028232

image-20200817104637636

汉字三个字节(说明数据)

编码:为了让字符串保存在文件中,或者通过网络传输

字符串 -> 字节数组

解码:让文件中或网络中的编码数据还原成字符串

在java程序中,String字符串永远是unicode字符序列

GBK编码

的转换

码值按字母顺序

gbk优点:本土化,省空间

utf8: 国际化

14.10 二进制文件的解读 UTF8 chars 与 writeInt

writeInt的写入文件

public void test3() {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fos = new FileOutputStream("二进制文件");
            oos = new ObjectOutputStream(fos);

            oos.writeShort(5);
            oos.writeDouble(2.2);
            oos.writeBoolean(false);
            oos.writeLong(8);

            oos.writeUTF("abc我和你qqq");
            oos.writeChars("abc我和你qqq"); // 当成Unicode字符序列写文件.

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

程序结果

二进制文件解读

跟着项目走

image-20200817110734656

utf8编码

使用字符流可以自动进行解码???

image-20200817111538135

总结

unicode只是一个符号集,他只规定了符号的二进制代码,并没有规定这个二进制代码应该如何进行存储,简单的讲就是,unicode为所有字符提供一个唯一的编号,然后utf8只是将字符的unicode编号编码成相应的二进制代码进行存储或运算

所以相同的unicode编号若用不同的编码方式进行编码,最后产生不同的二进制代码,这就是有时候文件造成乱码的原因

14.11 处理流之二 转换流

转换流提供了在字节流和字符流之间的转换

Java API提供了两个转换流:
InputStreamReader和OutputStreamWriter

字节流中的数据都是字符时,转成字符流操作更高效。

==IOTEST2==

image-20200817141538090

fileReader不能用

不靠谱

还是utf8编码

image-20200817141816910

需指定编码方式

image-20200817142011016

要想写不同文本,就要写转换流

image-20200817142520266

书写数据的缓存机制

一般来说缓存的flush放置在close里,如果忘记关闭,会导致文件的写入的失败

调用write写在缓冲区里

flush,刷一下

把缓冲区的数据强刷到硬盘里

image-20200817145026758

所有的打印流可以自动刷新flush

14.12 在原来文件的基础上追加(true)

以追加的方式写文件

默认是

image-20200817145309484

是否追加,默认指定为false;

在OutPutStream的形参列表里加上true,表示追加

14.13 处理流之三 标准输入输出流 System.in System.out

流的一段链接键盘

处理输入的转换

2

问题:==bufferedreader从哪里来的==文件读取的缓冲

image-20200817145732009

键盘输入用流操作

System.in

 dsfsdf打印的

system.out

system.err

也是

发生错误,就是错误流

image-20200817154348612

双线程,并发,多个任务同时执行

system.in有时可以不用new,直接使用

image-20200817235023075

14.14 数据流(可以用object替代)

image-20200817154430168

对象流都能包含这些东西

14.15 adomAccessFile 类(了解)麻烦

image-20200817154824290

14.16 file类

File类的常见构造方法:
public File(String pathname)
以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
public File(String parent,String child)
以parent为父路径,child为子路径创建File对象。

File的静态属性String separator存储了当前系统的路径分隔符。
在UNIX中,此字段为‘/’,在Windows中,为‘\’

image-20200818224356767

import java.io.File;
import java.io.IOException;
public class FileTest {
    public static void main(String[] args) throws IOException {
        File file = new File("aaa/bbb/cc/dd/ee");//打开文件
        //file.createNewFile(); // 创建文件
        //file.mkdir(); // make directory, 单层目录
        file.mkdirs(); // make directories, 创建多层目录
        // 针对目录的操作
        File[] files = file.listFiles();// 列出file目录下的所有File子对象, 包括子目录和子文件
        for (int i = 0; i < files.length; i++) {
            System.out.println(files[i]);
            if (files[i].isFile()) {
                System.out.println("是文件");
            } else {
                System.out.println("是目录");
            }
            files[i].delete();
        }
        System.out.println(file.delete());
    }
    public static void main2(String[] args) {
        File file = new File("一个文本文件");

        // 针对文件的操作, 创建文件, 删除文件, 重命名文件....
        System.out.println("file.getAbsolutePath() : " + file.getAbsolutePath());
        System.out.println("file.canRead() : " + file.canRead());// 是否能读
        System.out.println("file.isFile() : " + file.isFile()); // 是不是文件
        System.out.println("file.isDirectory() : " + file.isDirectory()); // 是否是目录
        System.out.println("file.exists() : " + file.exists()); // 是否存在
        System.out.println("file.length() : " + file.length()); // 获取文件长度
        System.out.println("file.lastModified() : " + file.lastModified());
        System.out.println("file.getTotalSpace() : " + file.getTotalSpace());
        System.out.println("file.getFreeSpace() : " + file.getFreeSpace());
        System.out.println("file.getName() : " + file.getName());
        //System.out.println("file.delete() : " + file.delete());
    }
}

image-20200817162300801

递归删除文件

public class DeleteFileTest {
    public static void main(String[] args) {
        File file = new File("D:/classppt");
        File[] files = file.listFiles();
        DeleteFileTest.delete(files);

    }
    public static void delete(File[] files){
        for (int i = 0; i < files.length; i++) {
            if (files[i].isFile()){
                System.out.println("已删除文件:" + files[i].getName());
                files[i].delete();
            }
            else {
                System.out.println("已进入文件夹:" + files[i].getName());
                 File[] files1 = files[i].listFiles();
                 delete(files1);
            }
        }

    }
}

14.17 作业 之 目录的复制

第15 章 多线程

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程(process)是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。

如:运行中的QQ,运行中的MP3播放器

程序是静态的,进程是动态的

线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

若一个程序可同一时间执行多个线程,就是支持多线程的

多进程实现多任务

比较复杂

cpu实现多线程

线程间通信更容易,更方便

15.1 线程的创建与使用

1.写一个具体类, 实现Runnable接口, 并实现其中的run方法, 这个方法就称为线程体

image-20200818082556030

多线程就是多个栈

2 创建上面的具体类对象, 并以它为实参继续创建Thread线程对象

image-20200818083147865

创建线程的两种方式

实现的方式

继承的方式

image-20200817170046095

注意什么时候激活子栈

start压进栈里瞬间弹出

image-20200817170255281

静态方法,跟调用者没关系

跟那个栈有关系

修改名字

currentThread返回当前线程

是否能够改名字

并发

public class HelloRunnerTest {

    /*
     *  1) 实现的方式
     *      1) 写一个具体类, 实现Runnable接口, 并实现其中的run方法, 这个方法就称为线程体
     *      2) 创建上面的具体类对象, 并以它为实参继续创建Thread线程对象
     *      3) 调用线程对象的start()方法 启动线程.
     */
    public static void main(String[] args) {
        Runnable runner = new HelloRunner();
        Thread thread = new Thread(runner); // 创建子栈
        thread.start(); // start才能激活栈, 并把run压入子栈执行.
        thread.setName("子线程");

        Thread.currentThread().setName("主线程");

        for (int i = 0; i < 200; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}

15.2 hashMap的源码

15.2.1 hashSet

1.hashSet底层是hashMap

  • 底层使用hashMap的键
  • 值都是虚拟的普通对象
  • 只使用键值,保证不可重复性

image-20200818085707965

15.2.2 HashMap

1 容量

image-20200818090244256

2 加载因子 数组加载的什么程度就不加载了,数组的使用率 要散列就要有余量

image-20200818090357076

3 变树值

image-202008180906302604.当红黑树的值,小于此值时,变会链表

image-20200818090722734

5.最小变树容量

要求容量变成64以上的数组容量才会变树

image-20200818090744316

6.组成数组的元素类型是节点类型,其实就是继承了entry的node

这是最重要的,最核心的哈希表,Node类的数组

image-20200818091049242

计数器

image-20200818091326413

修改次数

image-20200818091356100

加载因子

image-20200818091414366

数组的使用门槛 数组长度*加载因子,计数器超过此值就会扩容

image-20200818091432970

添加元素

先装箱

put放入方法

求键对象哈希码值

image-20200818091746707

使得低于16位的数更加散列

putVal

tab是内部哈希表

resize调整容量

哈希值通过与最大容量-1 与hash取& 按位与

让哈希值落入数组范围之内,放置越界

image-20200818094120903

继续插入,位置相同时应该怎么做

image-20200818095139997

重复不为modcount++

15.2.3 练习题 讨论==????==

删除问题

image-20200818200708991

image-20200818102225459

image-20200818102549889

image-20200818102841893

15.3 继承类线程的共享

可以使用同一个runnable对象创建不同的thread对象

image-20200818134235332

15.4 线程的停止

stop直接把程序按死

使用boolean变量进行停止

控制线程的boolean

设置线程的停止

线程与线程的交互是柔和

避免Runnable可以实现多线程共享

15.5 线程的调度

时间片

image-20200818120539206

15.6 多线程的使用放置之二 继承

继承Thread类

  • 写一个类继承Thread并重写Thread类中的run方法
  • 创建Thread子类对象,即创建了线程对象。
  • 调用线程对象start方法:启动线程,调用run方法。

15.7 Thread方法

static void yield()

线程让步

暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法

join()

当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止

(在那个栈里发生,那个栈阻塞 join,a 阻塞)

低优先级的线程也可以获得执行

15.8 Thread方法 sleep and interrupt

static void sleep(long millis):(指定时间:毫秒)(取决于那个栈,当前线程)

image-20200818141404390

其他子线程可以可以打断主线程的睡觉

令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。

抛出InterruptedException异常

image-20200818142059193

并发

如果想要两者搭配,就要睡足够的时间

练习 sleep

image-20200818142909684

主线程编程观察者模式

image-20200818145319446

必须设定沉睡时间

image-20200818145615541

public class ThreadSleep {
    public static void main(String[] args) {
        ThreadPrint threadPrint = new ThreadPrint();
        Thread t = new Thread(threadPrint);
        t.start();
        /*try {
            while (threadPrint.flag){
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();*/
        while (threadPrint.flag){//观察程序
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        t.interrupt();
    }

}

子程序

public class ThreadPrint implements Runnable{
    Boolean flag = true;

    @Override
    public void run() {
        List list = new ArrayList();
        for (int i = 0; i < 100 ; i++) {
            int num = (int)(Math.random() * 100);
            try {
                Thread.sleep(num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + ":" + num);

            list.add(num);
        }
        Long start = 0L;
        Long end = 0L;
        try {
            System.out.println("开始睡眠30秒");
            flag = false;
            start = System.currentTimeMillis();
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            System.out.println("被打断了");
            end = System.currentTimeMillis();
        }
        System.out.println("线程运行完毕");
        System.out.println("在执行完随机数之后睡了" + (end - start));
        System.out.println(list);
    }
}

stop

image-20200818142622883

stop(): 强制线程生命期结束

boolean isAlive():返回boolean,判断线程是否还活着

state 线程的生命周期

JDK中用Thread.State枚举表示了线程的几种状态

要想实现多线程,必须在主线程中创建新的线程对象。

Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件

运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能(拿到了cpu执行权)

阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态

死亡:线程完成了它的全部工作或线程被提前强制性地中止

image-20200818151414290

总结

graph LR;
A[new]

线程的生命周期

public enum State{
}

wait - notify

线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程。

它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。

守护线程是用来服务用户线程的,通过在start()方法前调用

thread.setDaemon(true)可以把一个用户线程变成一个守护线程。

Java垃圾回收就是一个典型的守护线程。

若JVM中都是守护线程,当前JVM将退出。

守护线程

image-20200818152845267

15.9 线程的同步 与 对象互斥锁

问题的提出

  • 多个线程执行的不确定性引起执行结果的不稳定
  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。

image-20200818153042806

线程同步提出的代码

synchronized 同步

//需要被同步的代码

只要是对象都可以做锁

image-20200818160643704

多线程会带来效率问题

互斥锁 隐式锁

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

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

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

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

同步方法(非静态的)的锁为this。

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

线程保护安全机制

锁的力度

![锁的力度](images/image-20200818161446032.png

字符串常量是全程唯一

只要是全局唯一对象都能锁上

保证多个线程用的是同一个锁才有意义

用常量对象更方便一些

练习

image-20200818164701230

锁对象 线程同步,注意锁,每个对相同对象的锁都是锁

image-20200818172052703

放锁的操作

image-20200818172246932

线程的死锁问题

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

解决方法

专门的算法、原则

尽量减少同步资源的定义

image-20200818173003674

15.10 线程通信

第13章 反射机制

13.1 概述

Java Reflection

Reflection(反射)是被视为==动态语言==的关键

反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息

并能直接操作任意对象的内部属性及方法

Java反射机制提供的功能

在运行时判断任意一个对象所属的类

在运行时构造任意一个类的对象

在运行时判断任意一个类所具有的成员变量和方法

在运行时调用任意一个对象的成员变量和方法

生成动态代理

13.2 Java反射机制研究及应用

反射相关的主要API:

java.lang.Class:代表一个类

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

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

java.lang.reflect.Constructor:代表类的构造方法
。。。

13.3 Class类

在Object类中定义了以下的方法,此方法将被所有子类继承:

● public final Class getClass()

以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

对照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。

一个 Class 对象包含了特定某个类的有关信息。

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个类在 JVM 中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 通过Class可以完整地得到一个类中的完整结构

13.4 class类的常用方法

方法名 功能说明
static Class for Name (String name) 返回指定类名 name 的 Class 对象
Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例
String getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称
Class [] getInterfaces() 获取当前Class对象的接口
ClassLoader getClassLoader() 返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体的超类的Class
Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
Field[] getDeclaredFields() 返回Field对象的一个数组
Method getMethod(String name,Class … paramTypes) 返回一个Method对象,此对象的形参类型为paramType

image-20200819085408860

13.5 反射(课上笔记)

反射能够让编译时错误推迟到运行错误

  • 类不存在
  • 没有无参构造器
  • 私有化构造器

1597891037450

只要在运行时搞好

类名必须使用全限定

如何修改反射属性

public class reflectTest {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.practice.Student");
            Object object = clazz.newInstance();
            Field nameField = clazz.getField("name");//从类模板中获取属性
            nameField.set(object,"宋宋");//定义属性中的名称
            System.out.println(object);
            Object name = nameField.get(object);//获取名称
            System.out.println(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }
}

getField方法只能获得本类中的公共属性,也能获得父类中的公共属性

如何获得私有属性并进行更改

1597898663820

public class reflectTest {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.practice.Student");
            Object object = clazz.newInstance();
//            Field nameField = clazz.getField("name");//从类模板中获取属性
            Field nameField = clazz.getDeclaredField("name");//获取私有属性,
            nameField.setAccessible(true);//获取权限
            nameField.set(object,"宋宋");//定义属性中的名称
            System.out.println(object);
            Object name = nameField.get(object);//获取名称
            System.out.println(name);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }
}

获取类模板对象的几种途径

1 类的class属性

1)前提:若已知具体的类,通过类的class属性获取,该方法
最为安全可靠,程序性能最高
实例:Class clazz = String.class;

2 对象的getClass属性

2)前提:已知某个类的实例,调用该实例的getClass()方法获
取Class对象
实例:Class clazz = “www.atguigu.com”.getClass();

Teacher teacher = new Teacher("佟刚", 40, "男");
        Class clazz2 = teacher.getClass();
        System.out.println(clazz1 == clazz2);

通过类型的路径

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

4)其他方式(不做要求)

ClassLoader cl = this.getClass().getClassLoader();

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

类模板对象是绝对的单例

1597901839161

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。

1597902570295

1597902583944

类加载器与三个层次

类加载器是用来把类(class)装载进内存的。

JVM 规范定义了两种类型的类加载器:启动类加载器(bootstrap)和用户自定义加载器

(user-definedclass loader)。

JVM在运行时会产生3个类加载器组成的初始化加载器层次结构

如下图所示:

1597902660530

public void test8() {
       // 系统类加载器是最常用, 主要用于加载我们自定义类
       ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
       System.out.println(systemClassLoader);

       // 扩展类加载器, 负责加载jre/lib/ext目录下的所有jar
       ClassLoader extClassLoader = systemClassLoader.getParent();
       System.out.println(extClassLoader);

       // 引导类加载器, 是最底层最核心的类加载器, 负责加载jre/lib目录下的所有.jar.
       ClassLoader boostrapClassLoader = extClassLoader.getParent();
       System.out.println(boostrapClassLoader);

       // 双亲委派机制
       // 1) 首先由系统类加载器发起类加载请求.
       // 2) 把加载任务委托给父类加载器:扩展类加载器
       // 3) 扩展类加载器委托给引导类加载器, 此时引导类加载器要判断, 如果不是核心类, 驳回请求
       // 4) 扩展类加载器进一步也要判断, 是我该由加载, 如果也不该我加载, 驳回请求
       // 5) 系统类加载器发现全部被驳回后, 自己直接加载了.

       // 1) 首先由系统类加载器发起类加载请求.
       // 2) 把加载任务委托给父类加载器:扩展类加载器
       // 3) 扩展类加载器委托给引导类加载器, 此时引导类加载器要判断, 是核心类, 当仁不让, 立刻加载
       // 4) 扩展类加载器发现已经加载过了, 不加载并返回
       // 5) 系统类加载器发现已经加载过了, 不加载并返回.
   }

类加载器对源文件进行数据传输

rt.jar最重要

引导类加载器负责加载jre中所有的jar

使用类加载器获取资源

public void test9() throws IOException {
        // 类加载可以 加载各种资源, 只要是项目所包含的.jar文件或src目录下的所有资源.
        // 它可以加载classpath路径中的任意文件
        //InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/sun/corba/se/impl/logging/LogStrings.properties");
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("com/atguigu/javase/hello.properties");
        Properties properties = new Properties();
        properties.load(is);

        Iterator<Map.Entry<Object, Object>> iterator = properties.entrySet().iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        //"javax/sql/rowset/rowset.properties"
    }

哈希map的迭代器的使用

public class getClassLoaderTest {
    public static void main(String[] args) {
        Student student = new Student();
        InputStream is = student.getClass().getClassLoader().getResourceAsStream("com/practice/rowset.properties");
        Properties properties = new Properties();//map底下的性能器      //十分注意地址的写法
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //哈希map的迭代器的使用
        Iterator<Map.Entry<Object,Object>> iterator = properties.entrySet().iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

反射获取构造器 创建实体对象

创建实体对象

public void test10() {
        Class clazz = null;
        try {
            clazz = Class.forName("com.atguigu.javase.reflect.Teacher");
            //Object object = clazz.newInstance();
            //public Teacher(String name, int age, String gender) {
            // 找到指定的构造器, 参数中提供构造方法的形参类型列表
            Constructor constructor = clazz.getConstructor(String.class, int.class, String.class);
            // newInstance()调用时必须传递和形参一致的实参列表
            Object object = constructor.newInstance("宋宋", 30, "男"); // 相当于new Teacher("宋宋", 30, "男");
            System.out.println(object);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

反射调方法

public void test11() {
        try {
            Class clazz = Class.forName("com.atguigu.javase.reflect.Teacher");
            Object object = clazz.newInstance();
            //((Teacher)object).lesson("Mysql", "302", 2);
            // 必须提供方法名, 和方法的形参类型列表
            // getMethod()只能获取公共方法, 包括父类继承的.
            //Method m1 = clazz.getMethod("lesson", String.class, String.class, int.class);
            // getDeclaredMethod获取本类中声明的所有方法, 包括私有的.
            Method m1 = clazz.getDeclaredMethod("lesson", String.class, String.class, int.class);
            m1.setAccessible(true);
            // 必须再通过m1调用, 如果方法是void, 它的返回值就是null, 提供实参列表
            Object retValue = m1.invoke(object, "MySQL", "302", 3);//object.lesson("Mysql", "302", 2);
            System.out.println(retValue);

            Method hashCode = clazz.getMethod("hashCode");// 获取父类继承来的公共方法. 方法没有参数, 不需要提供参数列表
            System.out.println(hashCode.invoke(object)); // object.hashCode(), 方法没有参数, 也不需要提供实参列表.

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) { // 没有访问权限
            e.printStackTrace();
        } catch (InstantiationException e) { // 创建对象失败
            e.printStackTrace();
        } catch (NoSuchMethodException e) { // 没有找到方法.
            e.printStackTrace();
        } catch (InvocationTargetException e) { // 调用的目标方法出问题时.
            e.printStackTrace();
        }
    }

反射调静态的属性与方法

public void test12() {
        try {
            Class clazz = Class.forName("com.atguigu.javase.reflect.Teacher");
            Field school = clazz.getField("school");
            school.set(null, "尚硅谷"); // Teacher.school = "尚硅谷";, 第一个参数会被完全忽略
            System.out.println(school.get(null)); // 静态属性的访问不需要对象, 所以参数中数据被忽略

            Class superclass = clazz.getSuperclass(); // 获取父类
            System.out.println(superclass);

            Class[] interfaces = clazz.getInterfaces();
            for (int i = 0; i < interfaces.length; i++) {
                System.out.println(interfaces[i]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

第14章 网络编程

端口号 定位应用

ipv6的号码 16位

域名与ip转换有DNS转换 DNS相当于哈希map

1597972653071

1597974799989

4/5/6/9/14/18/19/28


文章作者: Jinxin Li
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jinxin Li !
  目录