(超详细笔记整理)动力节点_老杜 JavaSE进阶 【P486之后】_动力节点老杜(1)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

        System.out.println();
        for (int j = 0; j< a[i].length; j++){
            System.out.print(a[i][j] + " ");
        }
    }
}

}


7.动态初始化二维数组



package array;
/*
动态初始化二维数组
*/
public class Test12 {
public static void main(String[] args){
int[][] a = new int[3][4];
printArray(a);
printArray(new int[][]{{1,2,3},
{4,5,6},
{7,8,9,10}});
}
public static void printArray(int[][] a){
for (int i = 0; i< a.length; i++){
System.out.println();
for (int j = 0; j< a[i].length; j++){
System.out.print(a[i][j] + " ");
}
}
}
}


#### 总结:


##### 1.1、数组的优点和缺点,并且要理解为什么。


​ 第一:空间存储上,内存地址是连续的。  
 ​ 第二:每个元素占用的空间大小相同。  
 ​ 第三:知道首元素的内存地址。  
 ​ 第四:通过下标可以计算出偏移量。  
 ​ 通过一个数学表达式,就可以快速计算出某个下标位置上元素的内存地址,  
 ​ 直接通过内存地址定位,效率非常高。


​ 优点:检索效率高。  
 ​ 缺点:随机增删效率较低,数组无法存储大数据量。  
 ​ 注意:数组最后一个元素的增删效率不受影响。


##### 1.2、一维数组的静态初始化和动态初始化


​ 静态初始化:  
 ​ int[] arr = {1,2,3,4};  
 ​ Object[] objs = {new Object(), new Object(), new Object()};  
 ​ 动态初始化:  
 ​ int[] arr = new int[4]; // 4个长度,每个元素默认值0  
 ​ Object[] objs = new Object[4]; // 4个长度,每个元素默认值null


##### 1.3、一维数组的遍历


​ for(int i = 0; i < arr.length; i++){  
 ​ System.out.println(arr[i]);  
 ​ }


##### 1.4、二维数组的静态初始化和动态初始化


​ 静态初始化:  
 ​ int[][] arr = {  
 ​ {1,2,34},  
 ​ {54,4,34,3},  
 ​ {2,34,4,5}  
 ​ };


​ Object[][] arr = {  
 ​ {new Object(),new Object()},  
 ​ {new Object(),new Object()},  
 ​ {new Object(),new Object(),new Object()}  
 ​ };  
 ​ 动态初始化:  
 ​ int[][] arr = new int[3] [4];  
 ​ Object[][] arr = new Object[4] [4];  
 ​ Animal[][] arr = new Animal[3] [4];  
 ​ // Person类型数组,里面可以存储Person类型对象,以及Person类型的子类型都可以。  
 ​ Person[][] arr = new Person[2] [2];  
 ​ …


##### 1.5、二维数组的遍历


​ for(int i = 0; i < arr.length; i++){ // 外层for循环负责遍历外面的一维数组。  
 ​ // 里面这个for循环负责遍历二维数组里面的一维数组。  
 ​ for(int j = 0; j < arr[i].length; j++){  
 ​ System.out.print(arr[i][j]);  
 ​ }  
 ​ // 换行。  
 ​ System.out.println();  
 ​ }


##### 1.6、main方法上“String[] args”参数的使用(非重点,了解一下,以后一般都是有界面的,用户可以在界面上输入用户名和密码等参数信息。)


##### 1.7、数组的拷贝:System.arraycopy()方法的使用


​ 数组有一个特点:长度一旦确定,不可变。  
 ​ 所以数组长度不够的时候,需要扩容,扩容的机制是:新建一个大数组,  
 ​ 将小数组中的数据拷贝到大数组,然后小数组对象被垃圾回收。


##### 1.8、对数组中存储引用数据类型的情况,要会画它的内存结构图。


#### 数组工具类


算法实际上在java中不需要精通,因为java中已经封装好了,要排序就调用方法就行。例如:java中提供了一个数组工具类:  
 java.util.Arrays  
 Arrays是一个工具类。  
 其中有一个sort()方法,可以排序。静态方法,直接使用类名调用就行。


#### 数据常见算法


排序算法:  
 冒泡排序算法  
 选择排序算法


​ 查找算法:  
 ​ 二分法查找  
 ​   
 ​ 以上算法在以后的java实际开发中我们不需要使用的。  
 ​ 因为java已经封装好了,直接调用就行。  
 ​ 只不过以后面试的时候,可能会有机会碰上。


##### 冒泡排序


参与比较的数据:9 8 10 7 6 0 11  
 第1次循环:


8 9 10 7 6 0 11 (第1次比较:交换)  
 8 9 10 7 6 0 11 (第2次比较:不交换)  
 8 9 7 10 6 0 11 (第3次比较:交换)  
 8 9 7 6 10 0 11 (第4次比较:交换)  
 8 9 7 6 0 10 11 (第5次比较:交换)  
 8 9 7 6 0 10 11 (第6次比较:不交换)


最终冒出的最大数据在右边:11


参与比较的数据:8 9 7 6 0 10  
 第2次循环:  
 8 9 7 6 0 10(第1次比较:不交换)  
 8 7 9 6 0 10(第2次比较:交换)  
 8 7 6 9 0 10(第3次比较:交换)  
 8 7 6 0 9 10(第4次比较:交换)  
 8 7 6 0 9 10(第5次比较:不交换)


参与比较的数据:8 7 6 0 9  
 第3次循环:  
 7 8 6 0 9(第1次比较:交换)  
 7 6 8 0 9(第2次比较:交换)  
 7 6 0 8 9(第3次比较:交换)  
 7 6 0 8 9(第4次比较:不交换)


参与比较的数据:7 6 0 8  
 第4次循环:  
 6 7 0 8(第1次比较:交换)  
 6 0 7 8(第2次比较:交换)  
 6 0 7 8(第3次比较:不交换)


参与比较的数据:6 0 7  
 第5次循环:  
 0 6 7(第1次比较:交换)  
 0 6 7(第2次比较:不交换)


参与比较的数据:0 6  
 第6次循环:  
 0 6 (第1次比较:不交换)



for (int i = 0;i<arr.length-1;i++){
for (int j = 0;j<arr.length-1-i;j++){
}
}



package array;
/*
冒泡算法
原始:3 2 7 6 8
拿着3和右边的2进行比较。如果左边>右边,交换位置
第一次:2 3 7 6 8
拿着上一次比较结果之后的右边较大的数据和后续的数据继续比较
第二次:2 3 7 6 8
第三次:2 3 6 7 8
第四次:2 3 6 7 8

*/
public class BubbleSort {
public static void main(String[] args){
int[] arr = {9,8,10,7,6,0,11};
int temp;
//经过冒泡排序算法,进行排序
for (int i = 0;i<arr.length-1;i++){
for (int j = 0;j<arr.length-1-i;j++){
if (arr[j]>arr[j+1]) {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
for (int i = 0;i<arr.length;i++){
System.out.print(arr[i]);
}

}

}


##### 选择排序


选择排序比冒泡排序的效率高。  
 高在交换位置的次数上。  
 选择排序的交换位置是有意义的。


循环一次,然后找出参加比较的这堆数据中最小的,拿着这个最小的值和  
 最前面的数据“交换位置”。


参与比较的数据:3 1 6 2 5 (这一堆参加比较的数据中最左边的元素下标是0)  
 第1次循环之后的结果是:  
 1 3 6 2 5


参与比较的数据:3 6 2 5 (这一堆参加比较的数据中最左边的元素下标是1)  
 第2次循环之后的结果是:  
 2 6 3 5


参与比较的数据:6 3 5 (这一堆参加比较的数据中最左边的元素下标是2)  
 第3次循环之后的结果是:  
 3 6 5


参与比较的数据:6 5 (这一堆参加比较的数据中最左边的元素下标是3)  
 第4次循环之后的结果是:  
 5 6


注意:5条数据,循环4次。


#### 二分法查找


第一:二分法查找建立在排序的基础之上。  
 第二:二分法查找效率要高于“一个挨着一个”的这种查找方式。  
 第三:二分法查找原理?  
 10(0下标) 23 56 89 100 111 222 235 500 600(下标9) arr数组


​ 目标:找出600的下标  
 ​ (0 + 9) / 2 --> 4(中间元素的下标)  
 ​   
 ​ arr[4]这个元素就是中间元素:arr[4]是 100  
 ​ 100 < 600  
 ​ 说明被查找的元素在100的右边。  
 ​ 那么此时开始下标变成:4 + 1


​ (5 + 9) / 2 --> 7(中间元素的下标)  
 ​ arr[7] 对应的是:235  
 ​ 235 < 600  
 ​ 说明被查找的元素在235的右边。  
 ​   
 ​ 开始下标又进行了转变:7 + 1  
 ​ (8 + 9) / 2 --> 8  
 ​ arr[8] --> 500  
 ​ 500 < 600  
 ​ 开始元素的下标又发生了变化:8 + 1  
 ​ (9 + 9) / 2 --> 9  
 ​ arr[9]是600,正好和600相等,此时找到了。



package array;

/*
二分法查找
10 11 12 13 14 15 16 17 18 19
通过二分法找出18这个元素的下标:
(0+10)/5------> 中间元素的下标

拿着中间这个元素和目标元素进行对比:
中间元素是:arr[5] —> 15
15 < 18 被查找的元素在中间元素的右边
所以开始元素的小标从0变成5+1

在重新计算中间元素下标:
5+1+10 / 2 = 8
8下标对应的元素为18,找到了中间元素和被找的元素相等,下标为8

二分法查找元素算法是基于排序的基础之上,没有排序的数据是无法查找的
*/
public class TwoSaerch {

public static void main(String[] args){
    int[] arr = {100,200,300,235,600,1000,2000,9999};
    //二分法查找200
    int index = binarySearch1(arr,2000);
    System.out.println(index == -1? "该元素不存在":"该元素下表为:" + index);
}

private static int binarySearch1(int[] arr, int n) {
    boolean flag = false;
    int start = 0;
    int end = arr.length - 1;
    while(start <= end){
        int mid = (start + end) / 2;
        if(arr[mid] == n){
            return mid;
        }else if(arr[mid] < n){
            start = mid +1;
        }else if (arr[mid] > n){
            end = mid -1;
        }
    }
    return -1;
}

}


#### 介绍一下java.util.Arrays工具类


所有方法都是静态的,直接用类名调用  
 主要使用的是两个方法:  
 二分法查找,排序  
 以后要看文档,不要死记硬背。



package array;

import java.lang.reflect.Array;
import java.util.Arrays;

/**

  • SUN公司已经为我们写好了一个数组工具类
  • java.util.Array

*/
public class ArrayTest02 {
public static void main(String[] args){
int arr[] = {1,5,7,4,3,4};
Arrays.sort(arr);
for (int i = 0; i< arr.length ; i++){
System.out.print(arr[i] + " ");

    }
    System.out.println("----------------");
    //二分法查找
    int index = Arrays.binarySearch(arr,4);
    System.out.println(index == -1? "该元素不存在":"该元素下标为:" + index);

}

}


### 常用类


#### String类


##### String为什么不可变?


String类中有一个byte[]数组,这个byte[]数组采用了final修饰,因为数组一旦创建长度不可变,并且被final修饰的引用指向某个对象之后,不可能再指向其他对象,所以String是不可变的!


##### String在内存方面的理解


1.String表示字符串类型,属于引用数据类型,不属于基本数据类  
 2.在Java随便使用双引号括起来的都是String对象。例如:”abc“,“def”,“hello world”,这三个String对象  
 3.Java中规定,双引号括起来的字符串是不可变的,也就是说”abc“从开始到最后,不能变成”abcd“  
 4.**在JDK当中双引号括起来的字符串,都是直接存储在”方法区“的“字符串常量池“当中的**  
 为什么sun公司把字符串存储在一个”字符串常量池“当中呢。因为字符串在实际开发中使用太频繁。为了执行效率,所以把字符串放到了  
 方法区的字符串常量池当中。


![001-String的内存图](https://img-blog.csdnimg.cn/img_convert/625d459ec842393de9abb6ce4bff30b0.png)



package javaseString;
/*
关于String
1.String表示字符串类型,属于引用数据类型,不属于基本数据类
2.在Java随便使用双引号括起来的都是String对象。例如:”abc“,“def”,“hello world”,这三个String对象
3.Java中规定,双引号括起来的字符串是不可变的,也就是说”abc“从开始到最后,不能变成”abcd“
4.在JDK当中双引号括起来的字符串,都是直接存储在”方法区“的“字符串常量池“当中的
为什么sun公司把字符串存储在一个”字符串常量池“当中呢。因为字符串在实际开发中使用太频繁。为了执行效率,所以把字符串放到了
方法区的字符串常量池当中。
*/
public class Test01 {
public static void main(){
String s1 = “abcd”;
String s2 = “abcd” +“xy”;
//使用new方法来构建字符串对象
//凡是双引号括起来的都在字符串常量池中有一份,凡是new出来的对象都在堆内存中开辟空间
String s3 = new String(“xy”);

}

}



package javaseString;

public class UserTest {
public static void main(String[] args){
User user = new User(110,“x”);

}

}
class User{
private int id;
private String name;
public User(){

}

public User(int id, String name) {
    this.id = id;
    this.name = name;
}

public int getId() {
    return id;
}

public String getName() {
    return name;
}

public void setId(int id) {
    this.id = id;
}

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

}


![002-String类型的引用中同样也是保存了对象的内存地址](https://img-blog.csdnimg.cn/img_convert/00bb976dc518fd496363521dc5303932.png)



package javaseString;

public class Test02 {
public static void main(String[] args){
String s1 = “hello”;
//"hello"是存储在方法区中的字符串常量池中
//所以这个”hello“不会重建
String s2 = “hello”;
System.out.println(s1 == s2);//true

    String x = new String("xy");
    String y = new String("xy");
    System.out.println(x==y);//false
    //String类已经重写了equals方法
    System.out.println(x.equals(y));//true

    String k = new String("Test");
    //"Test"是一个是String对象,只要是对象都可以调用方法
    System.out.println("Test".equals(k));//建议使用这种方式,可以避免空指针异常
    System.out.println(k.equals("Test"));
}

}


![003-String相关面试题](https://img-blog.csdnimg.cn/img_convert/be6bb63b3ad6aa65aa38146c669353eb.png)



package javaseString;
/*
分析一下程序创建了几个对象
*/
public class Test03 {
public static void main(String[] args){
/*
一共三个对象:
字符串常量池有一个对象
堆内存中有两个String对象
*/
String s1 = new String(“hello”);
String s2 = new String(“hello”);
}
}


##### 关于String类中的构造方法


* String s = new String("");
* String s = “”; 最常用
* String s = new String(char数组);
* String s = new String(char数组,起始下标,长度)
* String s = new String(byte数组);
* String s = new String(byte数组,起始下标,长度)



package javaseString;

/**
* 关于String类中的构造方法
* String s = new String(“”);
* String s = “”; 最常用
* String s = new String(char数组);
* String s = new String(char数组,起始下标,长度)
* String s = new String(byte数组);
* String s = new String(byte数组,起始下标,长度)
*/
public class Test04 {
public static void main(String[] args){
//创建字符串最常用的方式
//这里只掌握常用的构造方法
String s1 = “hello world”;
//String类已经重写了toString方法
System.out.println(s1);
byte[] bytes = {97,98,99};//97a 98b 99c
String s2 = new String(bytes);//支持传入byte数组
//输出一个引用的时候会自动toString方法,默认Object的话,会自动输出对象的内存地址
System.out.println(s2);//abc
//通过输出结果可以得到一个结论,String类已经重写了toString方法。
//输出字符串对象的话,输出的不是对象的内存地址,而是字符串本身。

    //String(字节数组,起始位置,长度)
    //将bytes数组中的一部分转换
    String s3 = new String(bytes,1,2);//1为开始下标,2为长度
    System.out.println(s3);

    //将char数组全部转换为字符串
    char[] chars = {'1','2','3','4'};
    String s5 = new String(chars);
    System.out.println(s5);
    String s4 = new String(chars,1,2);
    System.out.println(s4);

}

}


##### String类常用的21个方法


1.char charAt(int index)


2.compareT(String aontherString) 返回一个int


3.boolean contains(CharSequence s)


​ //判断前面的字符串是否包含后面的子字符串


4.boolean endsWith(String suffix)


​ //判断当前字符串是否以某个字符串结尾


5.boolean equals(String anotherString)


//equals方法是否调用compareTo JDK新版中没有调用compareTo()方法


6.boolean equalsIgnoreCase(String anotherString)


//判断两个字符串是否相等,忽略大小写


7.btye[] getBytes()


​ //将字符串对象转换为字节数组


8.int indexOf(String str)


//判断某个子字符串在当前字符串中第一次出现处的索引


9.boolean isEmpty()


//判断某个字符串是否为空  
 //底层源代码调用字符的length方法


10.int length()


​ //面试题:判断数组长度和判断字符串长度不一样  
 ​ //判断数组长度是length属性,判断字符串长度是length方法


11.lastIndexOf(int ch)


​ //字符出现的最后一次索引位置


12.String replace(CharSequence traget,CharSequence replacement)


​ //替换指定的字符串  
 ​ //String的父接口就是CharSequence


13.String[] split(String regex)


​ //拆分字符串


14.boolean startsWith(String perfix)


​ //判断某个字符串是否以某个子字符串开始的


15.String substring(int beginIndex)


​ //截取字符串


16.String substring(int beginIndex,int endIndex)//左闭右开


17.char[] toCharArray()


​ //将字符串转换为char数组


18.String toLowerCase()


​ 转换为小写


19.String toUpperCase()


20.String trim()


​ 去除字符串前后空白


21.String中只有一个方法是静态的不需要new对象



package javaseString;

import javax.crypto.spec.PSource;
import java.nio.charset.StandardCharsets;
import java.util.Locale;

public class Test05 {
public static void main(String[] args){
//String类当中的常用方法
//1.char charAt(int index)
char c= “中国人”.charAt(1);
System.out.println©;//输出国

    //2.compareT(String aontherString) 返回一个int
    System.out.println("abc".compareTo("abc"));//0 相等
    System.out.println("abcd".compareTo("abce"));//-1 前小后大
    System.out.println("abce".compareTo("abcd"));//1 前大后小
    System.out.println("xyz".compareTo("yxz"));//-1

    //3.boolean contains(CharSequence s)
    //判断前面的字符串是否包含后面的子字符串
    System.out.println("HelloWorld.java".contains(".java"));//true
    System.out.println("1111111".contains("2"));//false

    //4.boolean endsWith(String suffix)
    //判断当前字符串是否以某个字符串结尾
    System.out.println("test.txt".endsWith(".txt"));//true
    System.out.println("test.txt".endsWith(".java"));//false

    //5.boolean equals(String anotherString)
    System.out.println("11".equals("11"));//true
    //equals方法是否调用compareTo JDK新版中没有调用compareTo()方法

    //6.boolean equalsIgnoreCase(String anotherString)
    //判断两个字符串是否相等,忽略大小写
    System.out.println("abc".equalsIgnoreCase("ABC"));//true

    //7.btye[] getBytes()
    //将字符串对象转换为字节数组
    byte[] b = "abcdf".getBytes(StandardCharsets.UTF_8);
    for (int i = 0;i<b.length;i++){
        System.out.print(b[i] +" ");
    }
    System.out.println();

    //8.int indexOf(String str)
    //判断某个子字符串在当前字符串中第一次出现处的索引
    System.out.println("abcd".indexOf("b"));

    //9.boolean isEmpty()
    //判断某个字符串是否为空
    //底层源代码调用字符的length方法
    System.out.println("11".isEmpty());//false
    System.out.println("".isEmpty());//true

    //10.int length()
    //面试题:判断数组长度和判断字符串长度不一样
    //判断数组长度是length属性,判断字符串长度是length方法
    String s = "a";
    System.out.println(s.length());//字符串.length()方法

    //11.lastIndexOf(int ch)
    //字符出现的最后一次索引位置
    System.out.println("12345996789".lastIndexOf("9"));

    //12.String replace(CharSequence traget,CharSequence replacement)
    //替换指定的字符串
    //String的父接口就是CharSequence
    String newString = "11112222".replace("1111","2222");
    System.out.println(newString);//22222222

    //13.String[] split(String regex)
    //拆分字符串
    String[] s1 = "1998-1-0".split("-");//将1998-1-0以-进行拆分
    for (int i =0;i<s1.length;i++){
        System.out.print(s1[i] + " ");//1998 1 0
    }
    System.out.println();

    //14.boolean startsWith(String perfix)
    //判断某个字符串是否以某个子字符串开始的
    System.out.println("111111223334456".startsWith("11"));//true
    System.out.println("45678911".startsWith("11"));//flase

    //15.String substring(int beginIndex)
    //截取字符串
    System.out.println("123456789987".substring(7));//参数是起始下标

    //16.String substring(int beginIndex,int endIndex)
    System.out.println("123456789987456".substring(7,10));//左闭右开 包含7 不包含10

    //17.char[] toCharArray()
    //将字符串转换为char数组
    char[] chars = "我是中国人".toCharArray();
        for (int i =0;i<chars.length;i++){
            System.out.print(chars[i] + " ");//我 是 中 国 人
        }
    System.out.println();


    //18.String toLowerCase()
    //转换为小写
    System.out.println("ABCDEF".toLowerCase(Locale.ROOT));//abcdef

    //19.String toUpperCase()
    System.out.println("abcdfe".toUpperCase(Locale.ROOT));//ABCDFE

    //20.String trim()
    //去除字符串前后空白
    System.out.println(" 123 456 789 ".trim());//123 456 789

    //21.String中只有一个方法是静态的不需要new对象
    //valueOf
    //将非字符串转换为字符串
    String s2 = String.valueOf(true);//true 字符串
    System.out.println(s2);
    String s3 = String.valueOf(100);//100 字符串
    System.out.println(s3);

    String s4 = String.valueOf(new C());
    System.out.println(s4);//obj.toString 没有重写toString方法之前是对象的内存地址

    //println源代码
    //底层调用了toString方法
    Object obj = new Object();
    System.out.println(obj);
    /\*

String s = String.valueOf(x);

public static String valueOf(Object obj) {
return (obj == null) ? “null” : obj.toString();
}
*/

    //本质上System.out.println(),这个方法在输出任何数据的时候都是先转换成字符串,在进行输出
    System.out.println();

}

}
class C{

//重写toString
public String toString(){
    return "vip";
}

}


#### StringBuffer和StringBuilder


##### StringBuffer和StringBuilder为什么是可变的?


StringBuiler和StringBuffer内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuilder和StringBuffer的初始化容量为16,当存满之后会进行扩容,底层调用了数组拷贝的方法System.arrarycopy()。所以StringBuilder/StringBuffer适合于字符串频繁拼接操作。



在实际开发当中,频繁的拼接字符串会出现在什么问题?

  • java的字符串是不可变的,每一次拼接都会产生一个新的字符串,这样会占用大量的方法去内存,造成内存空间的浪费
  • String s = “abc”;
  • s += “hello”
  • 以上两行代码,就导致在方法区字符串常量池中创建了3个字符串对象

1.StringBuffer/StringBuilder可以看做可变长度字符串。  
 2.StringBuffer/StringBuilder初始化容量16.  
 3.StringBuffer/StringBuilder是完成字符串拼接操作的,方法名:append  
 4.StringBuffer是线程安全的。StringBuilder是非线程安全的。  
 5.频繁进行字符串拼接不建议使用“+”



package StringBuffer;
/**
* 在实际开发当中,频繁的拼接字符串会出现在什么问题?
* java的字符串是不可变的,每一次拼接都会产生一个新的字符串,这样会占用大量的方法去内存,造成内存空间的浪费
* String s = “abc”;
* s += “hello”
* 以上两行代码,就导致在方法区字符串常量池中创建了3个字符串对象
*/

/**
* 如果以后想要进行字符串的拼接,那么建议使用JDK中自带的:
* java.lang.StringBuffer/StringBuilder
* 优化StringBuffer性能
* 在创建StringBuffer的时候,尽量给定一个初始化容量
* 最好减少底层数组的扩容次数,预估计一下
* 关键点:给定一个合适的初始化容量,可以提高程序的效率
*
*/
public class Test01 {
public static void main(String[] args) {
//直接使用+=的方法会给方法区中的字符串常量池带来很大的压力
//创建一个初始化容量为16个byte[]数组
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(“a”);
stringBuffer.append(“b”);
stringBuffer.append(“c”);
System.out.println(stringBuffer);
//StringBuffer会自动扩容

    //初始化指定容量的StringBuffer

    StringBuffer stringBuffer1 = new StringBuffer(100);
    stringBuffer1.append("1");
    stringBuffer1.append("2");
    stringBuffer1.append("3");
    stringBuffer1.append("4");
    System.out.println(stringBuffer1);


}

}



package StringBuffer;

/**

  • StringBuilder

  • StringBuffer和StringBuilder的区别

  • StringBuffer中的方法,都有关键字synchronized修饰,表示StringBuffer在多线程环境下运行是安全的

  • StringBuilder中的方法都没有关键字synchronized修饰,表示StringBuilder在多线程环境下运行是不安全的。
    *

  • StringBuffer是线程安全的

  • StringBuilder是非线程安全的
    */
    public class Test02 {
    public static void main(String[] args) {
    //扩容次数越少,效率越高
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(“1”);
    stringBuilder.append(“2”);
    stringBuilder.append(“3”);
    stringBuilder.append(“4”);
    System.out.println(stringBuilder);

    }
    }


#### 八种基本数据类型对应的包装类


##### 1.java中为8种基本数据类型有对应准备了8中包装类型。


8中包装类属于引用数据类型,父类是Object


##### 2.为什么提供8中包装类?


因为8种基本数据类型不够用


##### 3.基本数据类型和包装类的对应


基本数据类型:byte、short、int、long、floot、double、boolean、char  
 8种包装类型:Byte、Short、Integer、Long、Floot、Double(父类都是Number)、Boolean、Character(父类都是Object)


##### 4.8中包装类种其中6个都是数字对应的包装类,他们的父类都是Number,可以先研究一下Number中公共方法:


​ Number是一个抽象类,无法实例化对象  
 ​ Number类中有这样的方法:  
 ​ byte byteValue()以 byte 形式返回指定的数值。  
 ​ abstract double doubleValue() 以 double 形式返回指定的数值。  
 ​ abstract float floatValue()以 float 形式返回指定的数值。  
 ​ abstract int intValue()以 int 形式返回指定的数值。  
 ​ abstract long longValue()以 long 形式返回指定的数值。  
 ​ short shortValue() short 形式返回指定的数值。  
 ​ 这些方法是负责拆箱的[把引用数据类型都转换成基本数据类型]


##### 5.装箱和拆箱



package Integer;
/*
关于Integer类的构造方法,有两个:
Integer(int)
Integer(String)

*/
public class Test02 {
public static void main(String[] args) {
//将数字100转换为Integer类型
Integer x = new Integer(100);
System.out.println(x);
//将字符串123转换为Integer类型
Integer y = new Integer(“123”);
System.out.println(y);

    Double d = new Double(1.23);
    System.out.println(d);
    Double e = new Double(1);
    System.out.println(e);
}


package Integer;
/*
在JDK1.5之后支持自动拆箱和自动装箱
*/
public class Test04 {
public static void main(String[] args) {
Integer x = 100;//自动装箱 int类型自动转换为Integer
int y = x;//自动拆箱 Integer自动转换为int
}
}


##### 整数型常量池


java中为了提高程序执行效率,将-128-127之间的所有包装对象提前创建好  
 放到方法区整数型常量池当中,目的是只要用这个区间数据不需要在new,直接从整数型常量池中取出


![004-Integer的内存结构](https://img-blog.csdnimg.cn/img_convert/fde7f88241ad645573a93aa164573821.png)


##### 所遇到异常的总结


空指针异常:NullPointerException  
 类型转换异常:ClassCastException  
 数组下标越界异常:ArraryIndexOutOfBoundsException  
 数组格式化异常:NumberFormatException



package Integer;
/*
总结所遇到的异常:
空指针异常:NullPointerException
类型转换异常:ClassCastException
数组下标越界异常:ArraryIndexOutOfBoundsException
数组格式化异常:NumberFormatException
*/
public class Test05 {
public static void main(String[] args) {
Integer x = 100;
int y = x;
System.out.println(y);
Integer z = new Integer(“123”);//字符串必须为一串数字,否则会出现数组格式化异常:NumberFormatException
System.out.println(z);

    //重点方法
    //static int parseInt(String s)
    //静态方法,传参String 返回int
    int retValue = Integer.parseInt("123");//String ---> int
    System.out.println(retValue);

    double retValue2 = Double.parseDouble("3.14");
    System.out.println(retValue2);

    float retValue3 = Float.parseFloat("1.0");
    System.out.println(retValue3);
}

}


##### String int Integer类型转换


![005-String Integer int三种类型的互相转换](https://img-blog.csdnimg.cn/img_convert/453e9e1a81a5f885f21667622609382e.png)



package Integer;

import java.io.StringWriter;

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

    String s1 = "100";
    int i1 = Integer.parseInt(s1);//String --> int
    System.out.println(i1);
    String s2 = i1 + ""; //int --> String
    System.out.println(s2 +1);

    Integer x = 1000;
    int y = x; //自动装箱和自动拆箱

    String s3 = "123";
    Integer k = Integer.valueOf(s3); //Integer ---> String

    Integer l = 123;
    String s4 = String.valueOf(l);//String --> Integer

}

}


#### 日期类


##### 1.怎么获取当前系统时间


##### 2.Date --> String


##### 3.String --> Date



package Data;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/*
java中对日期的处理
1.怎么获取当前系统时间
2.Date --> String
3.String --> Date
*/
public class Test01 {
public static void main(String[] args) throws ParseException {
//获取当前系统的时间
Date nowTime = new Date();
System.out.println(nowTime); //Mon Aug 09 09:44:49 CST 2021

    //日期格式化,将日期按照指定的格式转换
    //将日期按照指定格式进行转换:Date ---> String
    //SimpleDateFormat专门负责日期格式化
    /\*\*

* yyyy 年
* MM 月
* dd 日
* HH 时
* mm 分
* ss 秒
* SSS 毫秒
* 注意:在日期格式中,除了y M d H m s S这些字符不能随便写之外,剩下的符号格式自己随意组织
*/
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH-mm-ss-SSS”);
String nowTimeStr = sdf.format(nowTime);
System.out.println(nowTimeStr);

    //日期字符串String,怎么转换成Date
    String time = "2020-01-01 00:08:08:888";
    //注意:字符串的日期格式和SimpleDateFormat对象指定的日期要相同,不然会出现异常PareException
    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
    Date dateTime = sdf2.parse(time);
    System.out.println(dateTime);

}

}


##### 统计方法的执行时长



package Data;
/*
自1970年1月1日 00:00:000到当前系统时间的总毫秒数
简单总结一下System类的相关属性和方法:
System.out out是System的静态变量
System.out.println() println()方法不是System类的,是PrintStream类的方法
System.gc() 建议启动垃圾回收器
System.currentTimeMillis() 自1970年1月1日 00:00:000到当前系统时间的总毫秒数
System.exit(0) 退出JVM
*/
public class Test02 {
public static void main(String[] args) {
long nowTimeMillis = System.currentTimeMillis();
System.out.println(nowTimeMillis);

    //统计一个方法的耗时
    //在调用目标方法之前记录一个毫秒数
    long begin = System.currentTimeMillis();
    System.out.println(begin);
    print();
    //执行完调用一个毫秒数
    long end = System.currentTimeMillis();
    System.out.println(end - begin);
}

//需求:统计一个方法执行所耗费的时长
public static void print(){
    for (int i = 0; i < 1000; i++){
        System.out.println("i = " + i);
    }

}

}



package Data;
import java.text.SimpleDateFormat;
import java.util.Date;
/*

*/
public class Test04 {
public static void main(String[] args) {
//这个时间是:1970-01-01 00:00:00:001
Date time = new Date(1);//注意参数是1毫秒4
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”);
String strTime = sdf.format(time);
System.out.println(strTime);

    //获取昨天此时的时间
    Date time1 = new Date(System.currentTimeMillis() - (1000 \* 60 \*60 \*24));
    String strTime2 = sdf.format(time1);
    System.out.println(strTime2);

}

}


#### 数字类


##### DecimalFormat 数字格式化



package Numbers;
import java.text.DecimalFormat;
public class DecimalFormatTest01 {
public static void main(String[] args) {
//专门负责数字格式化
//DecimalFormat df = new DecimalFormat(“数字格式”);
/**
* 数字格式有哪些
* #代表任意数字
* ,代表千分位
* .代表小数
*
* ###,###,##
* 表示:加入千分位,保留2个小数
*/
DecimalFormat df = new DecimalFormat(“###,###.##”);
String s = df.format(1234.56);
System.out.println(s);
DecimalFormat df2 = new DecimalFormat(“###,###.0000”);//保留4个小数,不够补0
String s2 =df2.format(1234.56);
System.out.println(s2);

}

}


##### BigDecimal 属于大数据,精度极高


财务软件的设计上使用精度高的BigDecimal



package Numbers;

import java.math.BigDecimal;

/*
1.BigDecimal 属于大数据,精度极高。不属于基本数据类型,属于java对象(引用数据)
这是SUN提供的一个类。专门用在财务软件中
2.注意:财务软件中Double是不够用的
财务数据是java.math.BigDecimal
*/
public class BigDecimalTest01 {
public static void main(String[] args) {
//这个100不是普通的100,是精度极高的100
BigDecimal v1 = new BigDecimal(100);
BigDecimal v2 = new BigDecimal(200);
//求和
//v1 + v2 都是引用数据类型不可以使用这个语法
//调用方法求和
BigDecimal v3 =v1.add(v2);
System.out.println(v3);

    BigDecimal v4 = v2.divide(v1);
    System.out.println(v4);
}

}


##### Random生成随机数


###### 怎么产生int类型随机数。


​ Random r = new Random();  
 ​ int i = r.nextInt();


###### 怎么产生某个范围之内的int类型随机数。


​ Random r = new Random();  
 ​ int i = r.nextInt(101); // 产生[0-100]的随机数。



package Numbers;

import java.util.Random;
import java.util.Arrays;

/*
编写程序,生成5个不重复的随机数。重复的话重新生成
最终生成的5个随机数,放入数组里面,要求这五个随机数不重复
*/
public class RandomTest02 {
public static void main(String[] args) {
//准备一个长度为5的一维数组
int[] arr = new int[5];
for (int i =0; i< 5 ; i++){
arr[i] = -1;
}
int index = 0;
//循环
Random random = new Random();
while(index < arr.length){
int res = random.nextInt(101);
boolean flag = flag(arr,res);
if (!flag){
arr[index++] = res;

        }
    }
    for (int i =0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
}
public static  boolean flag(int[] arr,int res) {
    /\*

这个方法存在bug,有一半的数据不可以用
//排序
Arrays.sort(arr);
//二分法查找 元素所在的下标
int index = Arrays.binarySearch(arr, res);

return index >0 ;//证明存在
*/

    for (int i = 0;i<arr.length;i++){
        if (arr[i] ==res){
            return true;
        }
        return false;
    }
    return false;
}

}


##### Enum枚举类型


###### 枚举是一种引用数据类型。


###### 枚举编译之后也是class文件。


###### 枚举类型怎么定义?


​ enum 枚举类型名{


​ 枚举值,枚举值2,枚举值3  
 ​ }


###### 枚举的使用情况


当一个方法执行结果超过两种情况,并且是一枚一枚可以列举出来的时候,建议返回值类型设计为枚举类型。



package enumlei;//关键字不可以做标识符
/*
分析以下程序的缺陷,没有使用枚举类型
以下程序可以编译运行,程序本身没有问题
设计缺陷:这个方法的返回值类型不恰当,这个方法只是返回成功和失败,那么最好返回布尔类型,返回值已经偏离了需求。

*/
public class Test01 {
public static void main(String[] args) {
boolean res = divide(10,2);
System.out.println(res);

   boolean res2 = divide(10,0);
   System.out.println(res2);
}

/\*\*

* 以下程序计算两个int类型的商,计算成功返回1,计算失败返回0
* @param a
* @param b
* @return 返回1表示成功,0表示失败
*/
/*
public static int divide(int a,int b){
try{
int c= a/b;
return 1;//表示执行成功
}catch (Exception e){
return 0;//表示执行失败
}
}*/
public static boolean divide(int a,int b) {
try {
int c = a / b;
return true;//表示执行成功
} catch (Exception e) {
return false;//表示执行失败
}
}
/*
思考:以上的方法设计没毛病,但是在以后的开发过程中,有可能遇到一个方法的执行结果可能包括几种情况
这时布尔类型就无法满足需求,这是需要使用java中的枚举类型
*/
}



package enumlei;
/*
枚举:
1.枚举是一种引用数据类型
2.枚举怎么定义
enum 枚举类型名{
枚举值1,枚举值2
}
3.只有两种类型建议使用布尔类型,超过两种的建议使用枚举类型
*/
public class Test02 {
public static void main(String[] args) {
Result r = divide(10,2);
System.out.println(r == Result.SUCCESS? “Sucess”:“Fail”);

}

/\*\*

*
* @param a
* @param b
* @return 返回枚举类型,SUCCESS表示成功,FAIL表示失败
*/
public static Result divide(int a,int b ){
try{
int c = a/b;
return Result.SUCCESS;
}catch(Exception e){
return Result.FAIL;
}
}
}
//枚举
enum Result{
//枚举编译成功之后也是生成class文件
//枚举也是一种引用数据类型
//枚举中的每一个值,可以看作是常量
//SUCCESS是枚举类中的一个值
//FAIL 也是枚举中的一个值
SUCCESS,FAIL
}


### 异常类


#### 异常处理机制


​ 0.1、java中异常的作用是:增强程序健壮性。  
 ​ 0.2、java中异常以类和对象的形式存在。


##### 1.什么是异常,java提供异常处理机制有什么用?


​ 以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做异常  
 ​ java语言是十分完善的,提供了异常处理方式,以下程序出现了不正常的情况,java把该异常信息打印,  
 ​ 输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加健壮


##### 2.以下程序执行控制台出现了:


​ Exception in thread “main” java.lang.ArithmeticException: / by zero  
 at exception.Test01.main(Test01.java:7)  
 这个信息我们称为:异常信息,这个信息是JVM打印的



package exception;
/*
1.什么是异常,java提供异常处理机制有什么用?
以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做异常
java语言是十分完善的,提供了异常处理方式,以下程序出现了不正常的情况,java把该异常信息打印,
输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加健壮
2.以下程序执行控制台出现了:
Exception in thread “main” java.lang.ArithmeticException: / by zero
at exception.Test01.main(Test01.java:7)
这个信息我们称为:异常信息,这个信息是JVM打印的
*/
public class Test01 {
public static void main(String[] args) {
/* int a =10;
int b =0;
int c= a/b; // 实际上JVM执行到此处时会new一个异常对象
System.out.println©;*/

}

}


##### 3.Java语言中异常是以什么形式存在的


​ 异常在Java中以类的形式存在,每一个异常类都可以创建异常对象



package exception;

import lei.NullPointerTest;

/*
Java语言中异常是以什么形式存在的
异常在Java中以类的形式存在,每一个异常类都可以创建异常对象

*/
public class Test02 {
public static void main(String[] args) {
NumberFormatException nfe = new NumberFormatException(“数字格式化异常”);
//也会调用toString
System.out.println(nfe);
//通过异常类,创建异常对象
NullPointerException npe = new NullPointerException(“空指针异常”);
System.out.println(npe);
}
}


##### 4.java的异常的继承结构


异常在java中以类和对象的形式存在。那么异常的继承结构是怎样的?  
 我们可以使用UML图来描述一下继承结构。  
 画UML图有很多工具,例如:Rational Rose(收费的)、starUML等…  
 Object  
 Object下有Throwable(可抛出的)  
 Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)  
 Exception下有两个分支:  
 **Exception的直接子类:编译时异常**(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。  
 **RuntimeException:运行时异常。**(在编写程序阶段程序员可以预先处理,也可以不管,都行。)


![image-20210816082831942](https://img-blog.csdnimg.cn/img_convert/6cdde228243e5826aad519d9be05eb1c.png)


##### 5.编译时异常和运行时异常


**编译时异常和运行时异常**,**都是发生在运行阶段**。**编译阶段异常是不会发生的**。


###### 编译时异常因为什么而得名?


因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new异常对象。


###### 编译时异常和运行时异常的区别


编译时异常一般发生的概率比较高。对于一些发生概率较高的异常,需要在运行之前对其进行预处理。


运行时异常一般发生的概率比较低。


**假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常**,所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果呢?  
 首先,如果这样的话,程序肯定是绝对的安全的。但是程序员编写程序太累,代码到处都是处理异常的代码。


编译时异常还有其他名字:受检异常:CheckedException 受控异常  
 运行时异常还有其它名字:未受检异常:UnCheckedException 非受控异常


##### 6.java对异常处理的两种方式


第一种方式:在方法声明的位置上,使用throws关键字抛给上一级。  
 谁调用我,我就抛给谁。抛给上一级。


第二种方式:使用try…catch语句进行异常的捕捉,这件事发生了,谁也不知道,因为我给抓住了。


举个例子:  
 我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,  
 “损失1000元”这可以看做是一个异常发生了。我有两种处理方式,  
 第一种方式:我把这件事告诉我的领导【异常上抛】  
 第二种方式:我自己掏腰包把这个钱补上。【异常的捕捉】


思考:  
 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有**这两种处理方式**。


**注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。**



package exception;

public class Test03 {
public static void main(String[] args) {
System.out.println(100/0);
/*
程序执行到此处,发生了一个ArithmeticException异常,底层new了一个ArithmeticException异常对象,然后抛出了,由于是main方法调用了100/0,
所以这个异常ArithmeticException抛给了main方法,main方法没有处理,直接抛给了JVM,JVM最终终止了程序的执行。

ArithmeticException继承了RuntimeException,属于运行时异常,在编写阶段不需要进行异常的预先处理
*/
System.out.println(“Hello Word”);
}
}


###### 编译时异常的报错



package exception;
/*
以下代码报错的原因是什么?
doSome方法声明位置上使用了:throws ClassNotFoundExcption,ClassNotFoundExcption是编译时异常,必须在编写代码时进行异常处理,否则报错。
*/
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException {
//main方法中直接调用了doSome方法
//因为doSome方法声明位置上有:throws ClassNotFoundExcption
//我们调用doSome方法的时候必须对这种异常进行预先的处理,如果不处理,编译器报错
doSome();
}

/\*\*

* doSome方法在方法声明的位置上使用了:throws ClassNotFoundExcption
* 这个代码表示doSome()方法执行过程中,有可能会出现ClassNotFoundExcption异常
* @throws ClassNotFoundException 类没有找到异常,父类为Exception,所以该异常属于编译时异常
*/
public static void doSome() throws ClassNotFoundException{
System.out.println(“doSome”);
}
}


###### 两种处理方法



package exception;

public class Test05 {
//第一种处理方式:在方法声明的位置上继续使用:throws,来完成异常的上抛。抛给调用者
/*public static void main(String[] args) throws ClassNotFoundException {

doSome();
}*/
//第二种处理方式:try catch
//捕捉意味着把异常拦下了,把异常真正的解决了
public static void main(String[] args) {
try {
doSome();
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}

public static void doSome() throws ClassNotFoundException{
    System.out.println("doSome");
}

}



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

/*
处理异常的第一种方式:在方法声明的位置上使用throws关键字抛出,谁调用这个方法,我就抛给谁
*/
public class Test06 {
/*
一般不建议在main方法上使用throws,因为这个异常如果真的发生了,一定会抛给JVM,那么JVM会终止程序的执行
异常处理机制的作用就是增强程序的健壮性,异常发生了也不会影响程序的执行,所以一般main方法中的异常建议使用try catch进行捕捉
main就不会继续上抛
*/
public static void main(String[] args) {
System.out.println(“main begin”);
try {
m1();
//出异常,这里不会执行
System.out.println(“Hello Word”);
} catch (FileNotFoundException e) {
System.out.println(e);
System.out.println(“文件不存在”);
}
System.out.println(“main end”);
}
public static void m1() throws FileNotFoundException{
System.out.println(“m1 begin”);
m2();
//出异常,这里不会执行
System.out.println(“m2 end”);
}
//抛FileNotFoundException的父类IOException,这样可以,因为IOException包括FileNotFoundException。
//throws可以抛出多个异常,可以使用逗号隔开
public static void m2() throws FileNotFoundException{
System.out.println(“m2 begin”);
m3();
//第二种方法进行捕捉
/*try {
m3();
} catch (FileNotFoundException e) {
System.out.println(“该文件不存在”);
}*/

    //出异常,这里不会执行
    System.out.println("m2 end");

}
//第一种方法继续上抛
public static void m3() throws FileNotFoundException {
    System.out.println("m3 begin");
    /\*

编译报错的原因是什么?
第一:这里调用了一个构造方法:FileInputStream
第二:这个构造方法的声明位置上有:throws FileNotFoundException
第三:通过类的继承结构看到:FileNotFoundException 的父类为IOException,IOException的父类为Exception最终得知,
FileNotFoundException是编译时异常

编译时异常,要求程序员必须在编译过程中对他进行处理。
*/
//FileInputStream fileInputStream = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\11.txt”);
FileInputStream fileInputStream = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\1.txt”);
//出异常这里不会执行
System.out.println(“m3 end”);
}
}


###### try catch


1.catch语句块后面()可以写本身的类型,也可以写父类型  
 2.catch可以写多个,建议catch的时候一个一个精确的处理  
 3.catch写多个的时候,必须遵循从上到下,从小到大  
 4.catch (FileNotFoundException | ArithmeticException e) JDK8的新特性



package exception;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
try catch
1.catch语句块后面()可以写本身的类型,也可以写父类型
2.catch可以写多个,建议catch的时候一个一个精确的处理
3.catch写多个的时候,必须遵循从上到下,从小到大
4.catch (FileNotFoundException | ArithmeticException e) JDK8的新特性
*/
public class Test07 {
public static void main(String[] args) {
try {
FileInputStream fileInputStream = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\11.txt”);
fileInputStream.read();//需要处理IO异常
System.out.println(100/0);//运行时异常,编译时可以处理可以不处理
} catch (FileNotFoundException | ArithmeticException e) {
e.printStackTrace();
System.out.println(“文件不存在”);
}catch (IOException e){
e.printStackTrace();
}
/*try {
FileInputStream fileInputStream = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\11.txt”);
} catch (IOException e) {//多态
e.printStackTrace();
System.out.println(“文件不存在”);
}*/
System.out.println(“后续代码可以执行”);
}
}


###### 异常的两个方法



package exception;
/*
异常对象有两个非常重要的方法:
获取异常简单的描述信息:
String msg = exception.getMessage();

打印异常追踪的堆栈信息:
exception.printStackTrace();

*/
public class Test08 {
public static void main(String[] args) {
//这里只是new了异常对象,没有将异常对象抛出,JVM认为这只是一个普通的Java对象
NullPointerException e = new NullPointerException(“空指针异常”);
//获取异常信息,这个信息实际上就是构造方法
System.out.println(e.getMessage());

    //打印异常堆栈信息
    //两个线程来分别进行下面两个指令
    e.printStackTrace();
    System.out.println("可以正常执行");
}

}


###### finally语句块


关于try catch中的finally子句:  
 1.在finally子句中的代码是最后执行的,是一定会执行的,即使try语句块中的代码出现了异常,finally语句必须和try一起出现,不能单独编写


​ 2.finally语句通常使用在哪些情况下?  
 ​ finally语句块中完成资源的释放/关闭  
 ​ 因为finally中的的代码比较有保障。  
 ​ 即使try语句块中的代码出现异常,finally中的代码块也会正常执行



package exception;

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

/*
关于try catch中的finally子句:
1.在finally子句中的代码是最后执行的,是一定会执行的,即使try语句块中的代码出现了异常
finally语句必须和try一起出现,不能单独编写
2.finally语句通常使用在哪些情况下?
finally语句块中完成资源的释放/关闭
因为finally中的的代码比较有保障。
即使try语句块中的代码出现异常,finally中的代码块也会正常执行
*/
public class Test10 {
public static void main(String[] args) throws IOException {
//设置为全局变量
FileInputStream fileInputStream = null;
try {
//创建输入流对象
//这里的fileInputStream是一个局部变量
fileInputStream = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\11.txt”);
String s= null;
s.toString(); //会出现空指针异常
System.out.println(“3”);
//即使以上程序出现异常,流也必须关闭,放在当前位置有可能不会关闭
//fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}catch (NullPointerException e){
e.printStackTrace();
}finally {
System.out.println(“1”);
//流的关闭放在这比较保险
if (fileInputStream != null){
//close方法有异常采用捕捉的方式
try {
fileInputStream.close();
}catch (IOException e){
e.printStackTrace();
}
System.out.println(“2”);
}
}

}

}


**return,之后的finally也会执行**



package exception;

public class Test11 {
public static void main(String[] args) {
/*
try和finally,没有catch
try不能单独使用
try finally可以联合使用
*/
try{
System.out.println(“try”);
return;
}finally {
//有return,finally也会执行
System.out.println(“finally”);
}

    //如果有return
    //这里不能写语句,这个代码是无法执行到的
    //System.out.println("1");
}

}


**退出exit后,finally不执行**



package exception;

public class Test12 {
public static void main(String[] args) {
try{
System.out.println(“try”);
System.exit(0);
//退出JVM finally不执行
}finally{
System.out.println(“finally”);
}
}
}


###### 面试题finally



package exception;
/*
finally面试题
*/
public class Test13 {
public static void main(String[] args) {
int res = m();
System.out.println(res);//100
}
/*
java语法规则(有一些规则是不能破坏的)
java中有一条这样的规则:
方法体中的代码必须遵循自上而下顺序依次逐行执行
*/
public static int m(){
int i = 100;
try{
//这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
// return语句还必须保证是最后执行的,一旦执行,整个方法结束
return i;
}finally {
i++;
}
}
}
/*
反编译之后的效果
public static int m(){
int i = 100;
int j = i;
i ++;
return j;
Exceptrion exception;
exception;
i++;
throw exception;
}
*/


###### finally final finalize 的区别


###### final 关键字


​ final修饰的类无法继承  
 ​ final修饰的变量无法修改  
 ​ final修饰的方法无法覆盖


###### finally 关键字


​ finally和try连用  
 ​ finally语句块中的代码必须执行


###### finalize 方法


​ 是Object类中的一个方法名  
 ​ 这个方法是由垃圾回收器GC负责调用的



package exception;
/*
final 关键字
final修饰的类无法继承
final修饰的变量无法修改
final修饰的方法无法覆盖
finally 关键字
finally和try连用
finally语句块中的代码必须执行
finalize 方法
是Object类中的一个方法名
这个方法是由垃圾回收器GC负责调用的
*/
public class Test14 {
public static void main(String[] args) {
//final是一个关键字,表示最终的不变的
final int i =100;

    //finally也是一个关键字,和try联合使用,使用在异常处理机制中
    //finally语句块中的代码是一定会执行的
    try{

    }finally {
        System.out.println("finally");
    }

    //finalize()是Object类中的一个方法。作为方法名出现
    //所以finalize是标识符

}

}


#### 自定义异常类


1.可以进行自定义异常  
 2.自定义异常的步骤:  
 第一步:编写一个类继承exception或者RuntimeException  
 第二步:提供两个构造方法,一个无参数的,一个带有String参数的



package exception;
/*
1.可以进行自定义异常
2.自定义异常的步骤:
第一步:编写一个类继承exception或者RuntimeException
第二步:提供两个构造方法,一个无参数的,一个带有String参数的
*/
public class Test15 {
public static void main(String[] args) {
//创建异常对象(没有手动抛出异常)
MyException myException = new MyException(“no”);
//打印异常信息
myException.printStackTrace();
String msg = myException.getMessage();
System.out.println(msg);
}
}
class MyException extends Exception{
public MyException() {
}

public MyException(String message) {
    super(message);
}

}


#### 异常在实际开发中的应用


![image-20210819155820925](https://img-blog.csdnimg.cn/img_convert/f21afb7e16bcf4997c88b9ca399636b6.png)


![image-20210819155848654](https://img-blog.csdnimg.cn/img_convert/84f78c345a707f003534a9ffb529c6d6.png)



package exception;
//测试改良之后的栈
public class Test16 {
public static void main(String[] args) {
Mystack mystack = new Mystack();
//压栈
try {
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
} catch (MyStackOperationException e) {
System.out.println(e.getMessage());
}
//弹栈
try {
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
} catch (MyStackOperationException e) {
e.printStackTrace();
}

}

}
package exception;
//测试改良之后的栈
public class Test16 {
public static void main(String[] args) {
Mystack mystack = new Mystack();
//压栈
try {
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
mystack.push(new Object());
} catch (MyStackOperationException e) {
System.out.println(e.getMessage());
}
//弹栈
try {
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
mystack.pop();
} catch (MyStackOperationException e) {
e.printStackTrace();
}

}

}


#### 方法覆盖时的异常处理



package exception;
/*
之前在讲解方法覆盖的时候,遗留了一个问题
重写之后的方法不能比重写之前的方法抛出更多的异常,可以更少
*/
public class Test17 {
}
class A{
public void doSome(){}
public void doOtger() throws Exception{}
}
class B extends A{
/*public void doSome() throws Exception{ //父类没有抛出异常,子类不可以抛
}*/

/\*编译正常

public void doOther(){ //不抛出异常不报错

}*/

/\*编译正常

public void doOther() throws Exception{

}*/

/\*编译正常

public void doOther() throws NullPointerException{

}
*/

}


#### 总结异常关键字


总结异常的关键字:  
 异常捕捉:  
 try  
 catch  
 finally  
 throws 在方法声明位置上使用,表示上报异常信息给调用者  
 throw 手动抛出异常


### 集合


#### 通过集合的构造方法可以完成类型的转换


#### 集合的概述


##### 1.什么是集合?有什么用?


数组起始就是一个集合,集合实际上就是一个容器,可以用来容纳其他类型的数据


集合为什么说在开发中使用较多?


集合是一个容器,是一个载体,可以一次容纳多个对象。在实际开发中,假设连接数据库,数据库当中有十条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个Java对象,然后将10个Java对象放到某一个 集合当中,将集合传到前端,然后遍历,将一个个数据展现出来


##### 2.集合中存储的为引用数据类型


集合不能直接存储基本数据另类。另外集合也不能直接存储Java对象,集合当中存储的是Java对象的内存地址(或者说集合存储的是引用)


注意:集合在Java中本身是一个容器,引用对象


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1DyNs7od-1634041323688)(C:/Users/77/Downloads/Documents/09-JavaSE进阶每章课堂画图/05-集合/001-集合中存储的是对象的内存地址.png)]


##### 3.不同的集合对饮不同的数据结构


在Java中每一个不同的集合,**底层会对应不同的数据结构,往不同的集合中存储元素**,**等于将数据放到了不同的数据结构当中**,什么是数据结构?数据存储的结构就是数据结构,不同的数据结构,数据存储方式不同。例如:数组,二叉树,链表,哈希表,以上都是常见的数据结构。


你往集合c1中放数据,可能是放到数组上了。


你往集合c2中放数据,可能是放到二叉树上了。  
 …


你使用不同的集合等同于使用了不同的数据结构


你在java集合这一章节,你需要掌握的不是精通数据结构。java中已经将数据结构实现了,已经写好了这些常用的集合类,你只需要掌握怎么用?在什么情况下选择哪一种合适的集合去使用即可。


new ArrayList(); 创建一个集合,底层是数组。  
 new LinkedList(); 创建一个集合对象,底层是链表。  
 new TreeSet(); 创建一个集合对象,底层是二叉树。  
 …


##### 4.集合在java JDK中哪个包下?


java.util.\*; 所有的集合类和集合接口都在java.util下


##### 5.集合的继承结构图


一类是单个方式存储元素:  
 **单个方式存储元素,这一类集合中超级父接口:java.util.Collection;**


一类是以键值对儿的方式存储元素  
 **以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;**


![image-20210821094827860](https://gitee.com/nian_xiaoqi/xiaoqi/raw/master/img/20210821094828.png)
###### Map接口


![image-20210821095203155](https://img-blog.csdnimg.cn/img_convert/145cc5b0534354b4fda34ea73408605f.png)


###### 总结 :


ArrayList:底层是数组


LinkedList:底层是双向链表


Vector:底层是数组,线程安全的,效率较低,使用较少


HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合Key部分了


TreeSet:底层是TreeMap,放到TreeMap结合中的元素等同于放到TreeMap集合key部分


HashMap:底层是哈希表数据结构


HashTable:底层也是哈希表,只不过效率较低,使用较少


Properties:是线程安全的,并且key和value只能存储字符串


TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序


**List集合存储元素的特点**:有序可重复


​ 有序:存进去的顺序和取出的顺序相同,每一个元素都有下标


​ 可重复:存进去1,还可以存进去1


**Set集合存储元素的特点**:无序不可重复


​ 无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标


​ 不可重复


**SortedSet(SortedMap)**:无序不可重复,但是SortedSet中的元素是可排序的


Map集合中的key,就是一个Set集合


往Set集合中放数据,就是放到Map集合的key部分


#### Collection接口中的常用方法


##### 1.Collection中能存放什么元素?


​ 没有使用泛型之前,Collection中可以存储Object的所有子类型  
 ​ 使用“泛型”之后,Collection中只能存储某个具体类型  
 ​ 集合后期我们会学习“泛型”,Collection中什么都能存,只要Object子类型都行  
 ​ (集合中不能直接存储基本数据类型,也不能存储Java对象,只存储Java对象的内存地址)


##### 2.Collection中的常用方法


boolean add(Object e) add方法,向集合中添加元素  
 int size() 获取集合中元素的个数  
 void clear() 清空集合  
 boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false  
 boolean remove(Object o) 删除集合中某个元素  
 boolean isEmpty() 判断集合中元素的个数是否为0  
 Object[] toArray() 调用这个方法可以把集合转换成数组  
 Iterator iterator() 返回在此 collection 的元素上进行迭代的迭代器。



package collection;

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

/*
关于collection接口中的常用方法
1.Collection中能存放什么元素?
没有使用泛型之前,Collection中可以存储Object的所有子类型
使用“泛型”之后,Collection中只能存储某个具体类型
集合后期我们会学习“泛型”,Collection中什么都能存,只要Object子类型都行
(集合中不能直接存储基本数据类型,也不能存储Java对象,只存储Java对象的内存地址)
2.Collection中的常用方法
boolean add(Object e) add方法,向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中某个元素
boolean isEmpty() 判断集合中元素的个数是否为0
Object[] toArray() 调用这个方法可以把集合转换成数组
Iterator iterator() 返回在此 collection 的元素上进行迭代的迭代器。

*/
public class Test01 {
public static void main(String[] args) {
//创建一个集合对象
//Collection c = new Collection(); 接口是抽象的,无法实例化
Collection c = new ArrayList();//多态
//add方法,向集合中添加元素
c.add(1200);//自动装箱,实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200);
c.add(3.14);
c.add(new Object());
c.add(true);

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

    System.out.println("------------");
    //清空集合
    c.clear();
    System.out.println(c.size());

    c.add("hell0"); //hello对象的内存地址放入了集合当中
    c.add("world");
    c.add("h");
    c.add("lv");
    c.add(1);

    //判断集合中是否包含
    boolean flag = c.contains("h");
    System.out.println(flag);

    boolean flag2 = c.contains("1h");
    System.out.println(flag);

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

    System.out.println("------------");
    c.remove(1);
    System.out.println(c.size());

    System.out.println("------------");
    //判断集合是否为空
    System.out.println(c.isEmpty());
    c.clear();
    System.out.println(c.isEmpty());

    System.out.println("------------");
    c.add("abc");
    c.add(1);
    c.add(2);
    c.add("world");
    Object[] objs = c.toArray(); //把集合转换为数组
    for (int i = 0; i<objs.length;i++){
        System.out.println(objs[i]);
    }


}

}


##### 3.关于集合遍历/迭代器


![004-迭代集合的原理](https://img-blog.csdnimg.cn/img_convert/4b63b5b5ad507f0a933a720b9555f5bd.png)


第一步:获取集合的迭代器对象Iterator


第二步:通过以上的迭代器对象开始迭代/遍历集合



package collection;

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

/**
* 关于集合遍历/迭代器
*
*/
public class Test02 {
public static void main(String[] args) {
//以下遍历方式/迭代方式,是所有的Collection通用的一种方式
//在Map集合中不能使用,在所有Collection以及子类中使用
//创建集合对象
Collection c = new ArrayList();
//添加元素
c.add(“abc”);
c.add(10);
c.add(2);
c.add(“world”);
//遍历/迭代
//第一步:获取集合的迭代器对象Iterator
Iterator iterator = c.iterator();
//第二步:通过以上的迭代器对象开始迭代/遍历集合
/*
以下方法是迭代器Iterator中的方法
boolean hasNext()
如果仍有元素可以迭代,则返回 true。
E next()
返回迭代的下一个元素。
*/
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
}


![003-迭代原理](https://img-blog.csdnimg.cn/img_convert/c18c8b5da75104663687e8105796944e.png)


4.contains方法


boolean contains(Object o)  
 判断集合是否包含某个对象o  
 如果包含返回true,如果不包含返回flase



package collection;

import java.util.ArrayList;
import java.util.Collection;

/*
深入Collection集合的contains方法
boolean contains(Object o)
判断集合是否包含某个对象o
如果包含返回true,如果不包含返回flase
*/
public class Test04 {
public static void main(String[] args) {
//创建集合
Collection c = new ArrayList();
//向集合中存储元素
String s1 = new String(“abc”);
String s2 = new String(“def”);
c.add(s1);
c.add(s2);

    System.out.println(c.size());
    //String类已经重写了equals
    String x= new String("abc");
    System.out.println(c.contains(x));
    //contains 底层调用了equals方法 ,String的equals方法已经重写了



}

}


##### **4.放在集合里的类,需要重写equals方法**


###### contains也重写equals方法



package collection;

import java.util.ArrayList;
import java.util.Collection;

/*
测试contains方法
*/
public class Test05 {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();
User u1 = new User(“j”);
User u2 = new User(“h”);
c.add(u1);
c.add(u2);
//这里的class类没有重写equals方法
//所以contains方法为false
//重写了equals方法之后,返回true
User u3 = new User(“j”);
boolean flag = c.contains(u3);
System.out.println(flag);
}
}
class User{
private String name;

public User() {
}

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

//重写equals方法
//这个equals的比较原理是,String name一样就可以
@Override
public boolean equals(Object o) {
    if (o == null || ! (o instanceof User))return false;
    if (o == this) return true;
    User user = (User) o;
    return  user.name == this.name;

}

}


###### remove中也重写了equals方法



package collection;

import java.util.ArrayList;
import java.util.Collection;

/*
测试contains方法
测试remove方法
*/
public class Test05 {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();
User u1 = new User(“j”);
User u2 = new User(“h”);
c.add(u1);
c.add(u2);
//这里的class类没有重写equals方法
//所以contains方法为false
//重写了equals方法之后,返回true
User u3 = new User(“j”);
boolean flag = c.contains(u3);
System.out.println(flag);

    Collection collection = new ArrayList();
    String s1 = new String("C");
    String s2 = new String("C");
    collection.add(s1);
    //remove底层也调用了equals方法
    //s1和s2是相同的
    collection.remove(s2);
    System.out.println(collection.size());

}

}
class User{
private String name;

public User() {
}

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

//重写equals方法
//这个equals的比较原理是,String name一样就可以
@Override
public boolean equals(Object o) {
    if (o == null || ! (o instanceof User))return false;
    if (o == this) return true;
    User user = (User) o;
    return  user.name == this.name;
}

}


##### 5.remove方法中的迭代器


重点:当集合的结构发生改变时,迭代器必须重新获取,如果不重新获取迭代器那么就会出现异常ConcurrentModificationException。



Collection c = new ArrayList();
//此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器
//一定要注意:集合结构只要发生改变,迭代器就必须重新获取
//Iterator iterator = c.iterator();
c.add(1);
c.add(2);
c.add(3);
//迭代器没有重新获取那么会出现:ConcurrentModificationException异常


重点:在迭代集合元素过程中,不能调用集合对象的remove方法,删除元素;会出现ConcurrentModificationException。



Iterator iterator1 = c2.iterator();
while(iterator1.hasNext()){
Object o = iterator1.next();
//c2.remove(o); 直接通过集合去删除元素,没有通知迭代器(导致迭代器的快照和原集合状态不同)
//删除元素之后,集合的结构发生了变化,应该重新去获取迭代器,
//但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常
//使用迭代器来删除,不会出现异常
//迭代器删除时,会自动更新迭代器并且更新集合(删除集合中的元素)
iterator1.remove();//删除的一定是迭代器当前指向的元素
System.out.println(o);
}



package collection;

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

/*
关于集合的remove
重点:当集合的结构发生改变时,迭代器必须重新获取,如果不重新获取迭代器
那么就会出现异常ConcurrentModificationException。
重点:在迭代集合元素过程中,不能调用集合对象的remove方法,删除元素;
会出现ConcurrentModificationException。
*/
public class Test06 {
public static void main(String[] args) {
Collection c = new ArrayList();
//此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器
//一定要注意:集合结构只要发生改变,迭代器就必须重新获取
//Iterator iterator = c.iterator();
c.add(1);
c.add(2);
c.add(3);
//迭代器没有重新获取那么会出现:ConcurrentModificationException异常

    Iterator iterator = c.iterator();
    while(iterator.hasNext()){
        //编写代码是next方法必须返回Object
        Object i = iterator.next();
        System.out.println(i);
    }
    System.out.println("-----");
    Collection c2 = new ArrayList();
    c2.add("a");
    c2.add("b");
    c2.add("c");

    Iterator iterator1 = c2.iterator();
    while(iterator1.hasNext()){
        Object o = iterator1.next();
        //c2.remove(o); 直接通过集合去删除元素,没有通知迭代器(导致迭代器的快照和原集合状态不同)
        //删除元素之后,集合的结构发生了变化,应该重新去获取迭代器,
        //但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常
        //使用迭代器来删除,不会出现异常
        //迭代器删除时,会自动更新迭代器并且更新集合(删除集合中的元素)
        iterator1.remove();//删除的一定是迭代器当前指向的元素
        System.out.println(o);
    }
    System.out.println(c2.size());
}

}


重点:在迭代元素的过程中,一定要使用迭代器去删除元素, iterator1.remove();


#### List接口中常用方法


1.list集合存储元素特点:有序可重复  
 有序:有下表  
 2.list既然是Collection接口的子接口,那么一定有自己特有的方法:  
 以下只列出list接口特有的常用的方法:  
 void add(int index, E element)  
 在列表的指定位置插入指定元素(可选操作)。  
 E get(int index)  
 返回列表中指定位置的元素。  
 int indexOf(Object o)  
 返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。  
 int lastIndexOf(Object o)  
 返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。  
 E remove(int index)  
 移除列表中指定位置的元素(可选操作)。  
 E set(int index, E element)  
 用指定元素替换列表中指定位置的元素(可选操作)。



package collection;

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

/*
List接口中的常用方法
1.list集合存储元素特点:有序可重复
有序:有下表
2.list既然是Collection接口的子接口,那么一定有自己特有的方法:
以下只列出list接口特有的常用的方法:
void add(int index, E element)
在列表的指定位置插入指定元素(可选操作)。
E get(int index)
返回列表中指定位置的元素。
int indexOf(Object o)
返回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。
int lastIndexOf(Object o)
返回此列表中最后出现的指定元素的索引;如果列表不包含此元素,则返回 -1。
E remove(int index)
移除列表中指定位置的元素(可选操作)。
E set(int index, E element)
用指定元素替换列表中指定位置的元素(可选操作)。
*/
public class ListTest01 {
public static void main(String[] args) {
//创建list类型的集合
List l1 = new ArrayList<>();

    l1.add("A");//默认都是向集合末尾添加元素
    l1.add("b");
    l1.add("c");
    l1.add("d");
    l1.add("A");
    l1.add("A");
    //这个方法使用不多,因为对于Arraylist集合来说效率比较低
    l1.add(1,"king");//index 为下标,向指定位置添加元素

    Iterator iterator = l1.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }

    //根据元素下标获取元素
    System.out.println(l1.get(0));
    System.out.println("-------list集合有自己特殊的遍历方式--------");
    //list集合有自己特殊的遍历方式
    for (int i =0;i<l1.size();i++){
        System.out.println(l1.get(i));
    }

    System.out.println("-------获取指定对象,第一次出现处的索引--------");
    //获取指定对象,第一次出现处的索引
    System.out.println(l1.indexOf("king"));

    System.out.println("-------获取指定对象,最后一次出现处的索引--------");
    //获取指定对象,最后一次出现处的索引
    System.out.println(l1.lastIndexOf("A"));

    System.out.println("-------删除指定下标元素--------");
    //删除指定下标元素
    l1.remove(0);
    System.out.println(l1.size());

    System.out.println("-------修改指定位置的元素--------");
    //修改指定位置的元素
    l1.set(2,"s2");
    for (int i =0;i<l1.size();i++){
        System.out.println(l1.get(i));
    }
}

}


##### ArrayList集合


##### 常用的方法


1.默认初始化容量10,(底层先创建了一个长度为0的数组,当添加一个元素的时候,初始化容量为10)  
 2.集合底层是一个Object[]数组  
 3.构造方法:  
 new ArrayList();  
 new ArrayList(20);  
 4.ArrayList的集合扩容:  
 原容量的1.5倍  
 ArrayList集合底层是数组,尽可能减少扩容,因为数组扩容效率比较低,建议在使用ArrayList的时候预估计初始化容量。  
 5.数组的优点:检索效率比较高  
 6.数组的缺点:随机增删效率比较低,数组无法存储大数据量(很难找到一块非常巨大的连续空间)  
 7.数组末尾添加元素的效率很高,不受影响  
 8.这么多的集合中,用那个最多?  
 ArrayList,因为往末尾添加元素,效率不受影响。另外我们检索/查找某个元素的操作比较多、


9.Arraylist不是非线程安全的



package collection;

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

/*
ArrayList集合:
1.默认初始化容量10,(底层先创建了一个长度为0的数组,当添加一个元素的时候,初始化容量为10)
2.集合底层是一个Object[]数组
3.构造方法:
new ArrayList();
new ArrayList(20);
4.ArrayList的集合扩容:
原容量的1.5倍
ArrayList集合底层是数组,尽可能减少扩容,因为数组扩容效率比较低,建议在使用ArrayList的时候预估计初始化容量。
5.数组的优点:检索效率比较高
6.数组的缺点:随机增删效率比较低,数组无法存储大数据量(很难找到一块非常巨大的连续空间)
7.数组末尾添加元素的效率很高,不受影响
8.这么多的集合中,用那个最多?
ArrayList,因为往末尾添加元素,效率不受影响。另外我们检索/查找某个元素的操作比较多
*/
public class ArrayListTest01 {
public static void main(String[] args) {
//默认初始容量为10
List list1 = new ArrayList();
System.out.println(list1.size());
//指定初始容量为20
List list2 = new ArrayList(20);
System.out.println(list2.size());
//size方法是获取当前集合元素的个数,不是获取集合的容量

    list1.add(1);
    list1.add(2);
    list1.add(3);
    list1.add(4);
    list1.add(5);
    list1.add(6);
    list1.add(7);
    list1.add(8);
    list1.add(9);
    list1.add(10);
    System.out.println(list1.size());

    list1.add(11);
    System.out.println();
}

}


##### 构造方法


1.默认初始化



List mylist1 = new ArrayList();


2.给定初始化容量



List myList2 = new ArrayList(100);


3.初始化容量可以为其他的集合



Collection c = new HashSet();
List mylist3 = new ArrayList©;


#### 链表


**Linkedlist创建的时候底层是双向的**


##### 单链表


###### 单链表中的节点


节点是单链表中基本的单元。  
 每一个节点Node都有两个属性:  
 一个属性:是存储数据  
 一个属性:是下一个节点的内存地址


###### 添加节点


![添加节点](https://gitee.com/nian_xiaoqi/xiaoqi/raw/master/img/20210822102728.png)
![](https://img-blog.csdnimg.cn/img_convert/6f667854cfbb2efb1634dc1a70133e46.png)


###### 删除节点


![删除节点](https://gitee.com/nian_xiaoqi/xiaoqi/raw/master/img/20210822102909.png)
**链表的优点:**  
 由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素的位移,因此随即增删效率较高  
 在以后的开发中,如果遇到随即增删集合中元素的业务比较多时,建议使用linkedlist


**链表的缺点**:  
 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头结点开始遍历,知道找到为止,所以linkedlist集合检索/查找的效率比较低


**Arraylits:把检索发挥到极致**  
 **linkedlist:把随即增删发挥到极致**  
 加元素往往从末尾开始添加,所以Arraylist末尾添加的效率还是比较高的


**Linkedlist 集合底层也有下标**  
 注意:Arraylist的检索效率比较高,不是单纯因为下标的原因,是因为底层数组发挥的作用  
 linkedlist集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历


##### 双向链表


![006-双向链表](https://img-blog.csdnimg.cn/img_convert/fe4dd41f66739580d1d64e90686681a7.png)


###### 源码



private void linkFirst(E e) {
final Node f = first;
final Node newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}

/**

  • Links e as last element.
    */
    void linkLast(E e) {
    final Node l = last;
    final Node newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
    first = newNode;
    else
    l.next = newNode;
    size++;
    modCount++;
    }

![007-LinkedList内存图](https://img-blog.csdnimg.cn/img_convert/4cbd4db657f28e903f2838e6a559eefb.png)


###### linkedlist集合没有初始化容量


最初这个链表中没有任何元素,first和last的引用都是null,不管是linkedlist还是Arraylist,以后写代码时不需要关心具体是哪一个集合,因为我们要面向接口编写,调用的方法都是接口中的方法


##### Vector


1.底层是一个数组


2.初始化容量为:10


3.扩容之后为原容量的二倍。10->20->40


4.ArrayList扩容是1.5倍 10->15->22.5


5.Vector中所有的方法都是线程同步的是线程安全的,效率比较低,使用较少


6.如何将Arraylist转换成线程安全的Vector  
 使用集合工具类:  
 java.util  
 Collections.synchronizedList 变成线程安全的


#### 泛型机制


##### 介绍


1.泛型这种语法机制,只在程序编译阶段起作用,只是给编译器擦参考的(运行阶段泛型没有用)


2.使用泛型的好处:  
 第一:集合中存储的元素类型统一了  
 第二:从集合中取出的元素是泛型指定的类型。不需要进行大量的”向下类型转换“


3.使用泛型的缺点:  
 导致结合中存储的元素缺乏多样性  
 大多数业务中,集合中元素的类型还是统一的,这种机制还是被认可的


##### 使用泛型和不使用泛型



package collection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/*
1.泛型这种语法机制,只在程序编译阶段起作用,只是给编译器擦参考的(运行阶段泛型没有用)
2.使用泛型的好处:
第一:集合中存储的元素类型统一了
第二:从集合中取出的元素是泛型指定的类型。不需要进行大量的”向下类型转换“
3.使用泛型的缺点:
导致结合中存储的元素缺乏多样性
大多数业务中,集合中元素的类型还是统一的,这种机制还是被认可的
*/
public class GenericTest01 {
public static void main(String[] args) {
//不适用泛型机制分析程序存在的缺点
/*List mylist= new ArrayList();
C c = new C();
B b = new B();
mylist.add©;
mylist.add(b);*/

    //遍历
    //迭代器
    /\*Iterator iterator = mylist.iterator();

while(iterator.hasNext()){
A obj = (A) iterator.next();
if (obj instanceof C){
C x = © obj;
x.move();
x.catchM();
}else if (obj instanceof B){
B x = (B) obj;
x.move();
x.fiy();
}
}*/
//利用泛型
//使用泛型list之后,表示list集合只允许存储Animal类型的数据
//用泛型来指定集合中的数据类型
//指定类为A,那么其他类型就会编译报错
//这样用了泛型之后,集合中元素的数据类型就更加统一的
List mylist = new ArrayList();
C c = new C();
B b = new B();
mylist.add©;
mylist.add(b);
///表示迭代器迭代的是A类型
Iterator iterator = mylist.iterator();
while(iterator.hasNext()){
//使用泛型迭代的数据,每一次返回的数据类型都是A
//这里不需要强制类型的转换
A a = iterator.next();
a.move();
//调用子类型特有的方法还是需要强制类型转换的
if (a instanceof C){
C C = ©a;
C.catchM();
}else {
B B = (B)a;
B.fiy();
}
}

}

}

class A{
public void move(){
System.out.println(“move”);
}

}
class C extends A{
public void catchM(){
System.out.println(“catch”);
}

}
class B extends A{
public void fiy(){
System.out.println(“fly”);
}

}


##### 自动类型推断机制



package collection;

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

/*
JDK8之后引入了:自动类型推断机制
*/
public class GenericTest02 {
public static void main(String[] args) {
//ArrayList<这里的类型会自动推断>()
//自动类型推断
List mylist = new ArrayList<>();
mylist.add(new A());
mylist.add(new C());
mylist.add(new B());

    //遍历
    Iterator<A> iterator = mylist.iterator();
    while(iterator.hasNext()){
        iterator.next().move();
    }
    List<String> strlits = new ArrayList<>();
    strlits.add("abc");
    strlits.add("def");

    Iterator<String> iterator1 = strlits.iterator();
    while(iterator1.hasNext()){
        System.out.println(iterator1.next());
    }
}

}


##### 自定义泛型


自定义泛型的时候,<>尖括号中的是一个标识符,随便写  
 java源代码中常出现的是< E >和< T >  
 E:是element首字母  
 T:是Type的首字母



package collection;
/*
自定义泛型:
自定义泛型的时候,<>尖括号中的是一个标识符,随便写
java源代码中常出现的是和
E:是element首字母
T:是Type的首字母
*/
public class GenericTest03/只是一个标识符随便写/ {
public void doSome(AA o){
System.out.println(o);
}
public static void main(String[] args) {
GenericTest03 gt = new GenericTest03<>();
gt.doSome(“abc”);

    GenericTest03<Integer> gt2 = new GenericTest03<>();
    gt2.doSome(1);

    MyIterator<String> mi = new MyIterator<>();
    String s = mi.get();//类型返回为String

    MyIterator<A> mi2 = new MyIterator<>();
    A a = mi2.get();
}

}
class MyIterator{
public T get(){
return null;
}
}


##### 增强for循环



package collection;
/*
增强for循环
*/
public class ForEachTest01 {
public static void main(String[] args) {
int[] arr = {1,55,4,7,3,14};
for (int i = 0; i< arr.length ; i++){
System.out.println(arr[i]);
}
System.out.println(“-------------”);
//增强for
//缺点:没有下标,在需要使用下标的循环中,建议不使用
for(int e: arr){
System.out.println(e);
}
}
}



package collection;

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

/*
集合使用增强foreach
*/
public class ForEachTest02 {
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(“1”);
list.add(“2”);
list.add(“3”);
//遍历 使用迭代器的方式
Iterator it= list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println(“----------”);
//使用下标的方式,只针对于有下标的集合
for (int i = 0; i< list.size(); i++){
System.out.println(list.get(i));
}
System.out.println(“----------------”);
//使用foreach
for (String i :list){
System.out.println(i);
}

}

}


#### Set接口


##### HashSet


###### 特点


1.存储顺序和取出顺序不同  
 2.不可重复  
 3.放到HashSet集合中的元素实际上是放到HashMap集合的key部分了



package collection;

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

/*
HashSet集合:
无序不可重复
*/
public class HashsetTest01 {
public static void main(String[] args) {
Set str = new HashSet<>();
//添加元素
str.add(“11”);
str.add(“55”);
str.add(“44”);
str.add(“22”);
str.add(“11”);
str.add(“33”);

    /*
    11
    33
    44
    22
    55
    1.存储顺序和取出顺序不同
    2.不可重复
    3.放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
     */

    for (String i : str){
        System.out.println(i);
    }
}

}


##### TreeSet


###### 特点


1.无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:排序集合  
 2.无序,这里指的是存进去的顺序和取出来的顺序不同,并且没有下标



package collection;

import java.util.Set;
import java.util.TreeSet;

/*
TreeSet集合存储元素的特点:
1.无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:排序集合
2.无序,这里指的是存进去的顺序和取出来的顺序不同,并且没有下标
*/
public class HashsetTest02 {
public static void main(String[] args) {
Set strs = new TreeSet<>();
strs.add(“11”);
strs.add(“23”);
strs.add(“45”);
strs.add(“22”);
strs.add(“45”);
/*
11
22
23
45
从小到大自动排序
*/
for (String i : strs) {
System.out.println(i);
}
}
}


#### Map集合


##### 常用的方法


**1.Map和Collection没有继承关系**


**2.Map集合以key和value的方式存储数据:键值对**  
 key和value都是引用数据类型  
 key和value都是存储对象的内存地址  
 key起主导作用,value是key的一个附属品


**3.Map接口中常用的方法**


​ V put(K key, V value) 向Map集合添加键值对


​ void clear() 清空Map


​ V get(Object key) 通过key获取value


​ boolean containsKey(Object key) 判断Map中是否包含某个key


​ boolean containsValue(Object value) 判断Map中是否包含某个value


​ boolean isEmpty() 判断Map集合中元素个数是否为0


​ Set < K > keySet() 获取Map集合中所有的key (所有的键是一个Set集合)


​ V remove(Object key) 通过key删除键值对


​ int size() 获取Map集合中所有的键值对


​ Collection values() 获取所有的value


​ Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合  
 ​ map集合  
 ​ key value  
 ​ 1 z  
 ​ 2 s  
 ​ 3 w


​ Set set = map.entrySet();  
 ​ set集合  
 ​ 1=z 【注意:Map集合通过wntrySet()方法转换成的Set集合,Set集合中的元素类型是Map.Entry<K,V> ,Map.Entry<K,V>是Map中的静态内部类】  
 ​ 2=s  
 ​ 3=w



package collection;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/*
java.util.Map的常用方法:
1.Map和Collection没有继承关系
2.Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型
key和value都是存储对象的内存地址
key起主导作用,value是key的一个附属品
3.Map接口中常用的方法
V put(K key, V value) 向Map集合添加键值对
void clear() 清空Map
V get(Object key) 通过key获取value
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
Set keySet() 获取Map集合中所有的key (所有的键是一个Set集合)
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中所有的键值对
Collection values() 获取所有的value
Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合
map集合
key value
1 z
2 s
3 w

Set set = map.entrySet();
set集合
1=z 【注意:Map集合通过wntrySet()方法转换成的Set集合,Set集合中的元素类型是Map.Entry<K,V> ,Map.Entry<K,V>是Map中的静态内部类】
2=s
3=w

*/
public class MapTest01 {
public static void main(String[] args) {
//创建Map对象,向Maop集合中添加键值对
Map<Integer,String> map = new HashMap<>();
//添加键值对
map.put(1,“zz”); //1在这里进行了自动装箱
map.put(2,“ss”);
map.put(3,“ww”);
System.out.println(“-----通过key来获取value-------”);
//通过key来获取value
String s = map.get(2);
System.out.println(s);
System.out.println(“-----获取键值对的数量-------”);
//获取键值对的数量
System.out.println(“键值对数量:”+map.size());
//通过key删除key-value
System.out.println(“-----通过key删除key-value-------”);
map.remove(1);
System.out.println(“键值对数量:”+map.size());
//判断是否包含某个key
//contains方法底层调用的都是equals方法进行比对的,所以自定义的类型都需要重写equals方法
System.out.println(“-----判断是否包含某个key-------”);
System.out.println(map.containsKey(1));
//判断是否包含某个value
System.out.println(“-----判断是否包含某个value-------”);
System.out.println(map.containsValue(“ww”));
//获取所有的value
System.out.println(“-----获取所有的value-------”);
Collection c= map.values();
for (String e:c){
System.out.println(e);
}
//清空集合
System.out.println(“-----清空集合-------”);
map.clear();
System.out.println(map.size());
//判断是否为空
System.out.println(“-----判断是否为空-------”);
System.out.println(map.isEmpty());

}

}


###### 关于内部类的用法



package collection;

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

public class Myclass {
//声明一个静态内部类
private static class InnerClass{
public static void m1(){
System.out.println(“静态方法执行”);
}
public void m2(){
System.out.println(“静态内部类实例方法执行”);
}
}

public static void main(String[] args) {
    Myclass.InnerClass.m1();//外部类 内部类.m1
    Myclass.InnerClass mi  = new Myclass.InnerClass();//创建静态内部类对象
    mi.m2();

    //给一个Set集合,存储这个静态内部类
    Set<InnerClass> set = new HashSet<>();
    Set<MyMap.Myentry> set1 = new HashSet<>();
}

}
class MyMap{
public static class Myentry<k,v>{

}

}


##### Map集合的遍历


第一种方式:获取所有的key,来遍历value


第二种方式:Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合  
 把Map集合全部转换成Set集合  
 Set集合中元素的类型是:Map.Entry<K,V>



package collection;

import java.util.*;

/*
Map集合的便利
第一种方式:获取所有的key,来遍历value
第二种方式:Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合
把Map集合全部转换成Set集合
Set集合中元素的类型是:Map.Entry<K,V>
*/
public class MapTest02 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,“zz”);
map.put(2,“ss”);
map.put(3,“ww”);

    //第一种遍历
    Set<Integer> keys = map.keySet();
    //遍历key
    //迭代器 foreach
    Iterator<Integer> iterator = keys.iterator();
    System.out.println("------迭代器---------");
    while(iterator.hasNext()){
        Integer key = iterator.next();
        String value = map.get(key);
        System.out.println(key + "=" + value);
    }
    System.out.println("------foreach---------");
    //foreach
    for (Integer key:keys){
        System.out.println(key + "=" + map.get(key));
    }

    //第二种方式
    System.out.println("-----Set<Map.Entry<K,V>>--------");
    Set<Map.Entry<Integer,String>> set = map.entrySet();
    //遍历set集合每一次取出node
    Iterator<Map.Entry<Integer,String>> iterator1 = set.iterator();
    while(iterator1.hasNext()){
        Map.Entry<Integer,String> entry = iterator1.next();
        Integer key = entry.getKey();
        String value = entry.getValue();
        System.out.println(key + "=" +value);
    }
    System.out.println("-----Set<Map.Entry<K,V>> foreach-------");
    for (Map.Entry<Integer,String> e:set){
        System.out.println(e.getKey() + "=" + e.getValue());
    }

}

}


##### 哈希表的数据结构


##### HashMap集合:


**1.HashMap集合底层是哈希表/散列表的数据结构**


**2.哈希表是一个数组和单向链表的结合体。**  
 数组:在查询方面效率很高,随机增删效率很低  
 单向链表:随机增删效率很高,在查询方面效率很低  
 哈希表将两种数据结合在一起,充分发挥它们各自的优点


**3.HashMap集合底层的源代码:**  
 public class HashMap{


​ //HashMap底层实际上是一个一维数组  
 transient Node<K,V>[] table;


​ //静态内部类  
 ​ static class Node<K,V> implements Map.Entry<K,V>{  
 ​ final int hash; //哈希值,哈希值是key的hashCode()方法的执行结果,hash值通过哈希算法/函数,可以转换存储数组的下标  
 ​ final K key; //存储到Map集合中的那个key  
 ​ V value; //存储到Map集合中的那个value  
 ​ Node<K,V> next; //下一节点的内存地址  
 ​ }   
 ​ }  
 哈希表/散列表:一维数组,这个数组中每一个元素都是一个单向链表(数组和链表的结合体)


**4.最主要掌握的是:**  
 map.put(k,v)  
 v = map.get(k)  
 以上这两个方法的实现原理,是必须掌握的


![008-哈希表或者散列表数据结构](https://gitee.com/nian_xiaoqi/xiaoqi/raw/master/img/20210824145256.png)
**5.HashMap集合key部分特点**:  
 无序不可重复  
 为什么无序?因为不一定挂到哪一个单向链表上  
 不可重复怎么保证?equals方法来保证HashMap集合的key不可重复  
 如果key重复,value会覆盖


放在HashMap集合key部分的元素其实就是放到HashMap集合中了  
 所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法


**6.HashMap使用不当时无法发挥性能**  
 假设所有的hashCode()方法返回为某个固定的值,那么会导致底层哈希表变成纯单项链表。这种情况我们称为:**散列分布不均匀**


什么是散列分布均匀?  
 **假设有100个元素,10个单向链表,那么每个单向列表上有10个节点,这是最好的,是散列均匀的**


​ 假设将所有的hashCode()返回值都设定为不一样的值,这样的话会导致底层哈希表就会成为一维数组,就没有链表的概念了,也是散列分布不均匀


​ 散列分布均匀需要你重写hashCode方法时有一定的技巧。


**7.重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。**


**8.HashMap集合默认初始化容量为16,默认加载因子为0.75**  
 这个默认加载因子是当HashMap底层集合数组的容量达到75%的时候开始扩容  
 重点:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为可以提高存取效率,所以是必须的


##### equals和hashCode的重写


**1.向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法**  
 equals方法可能调用,也可能不调用  
 拿put(k,v)举例,  
 k.hashCode()方法返回哈希值,哈希值经过哈希算法转换为数组下标,数组下标位置上如果是null,equals不需要执行  
 拿get(k,v)举例  
 如果数组下标为null,不需要执行;如果数组单向链表上有元素时,就需要equals


**2.注意:如果一个类的equals方法重写了,那么hashCode方法也需要重写,并且如果equals方法返回true,hashCode方法返回值必须一样**


**3.重写方法直接使用IDEA方法生成,这两个方法要同时生成**


\*\*4.结论:\*\*放在HashMap集合key部分,以及放在HashSet集合中的元素,需要同时从写hashCode方法和equals方法


**5.对于哈希表数据结构来说:**  
 如果o1和o2的**hash值相同**,一定是放在一个单向链表上  
 当然如果o1和o2的hash值不同,但是由于**哈希算法执行结束之后转换的数组下标可能相同**,这时候会发生\*\*“哈希碰撞”\*\*


**6.扩容:扩容容量是原容量的二倍**



package collection;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/*
1.向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法
equals方法可能调用,也可能不调用
拿put(k,v)举例,
k.hashCode()方法返回哈希值,哈希值经过哈希算法转换为数组下标,数组下标位置上如果是null,equals不需要执行
拿get(k,v)举例
如果数组下标为null,不需要执行;如果数组单向链表上有元素时,就需要equals

2.注意:如果一个类的equals方法重写了,那么hashCode方法也需要重写,并且如果equals方法返回true,hashCode方法返回值必须一样
3.重写方法直接使用IDEA方法生成,这两个方法要同时生成
4.结论:放在HashMap集合key部分,以及放在HashSet集合中的元素,需要同时从写hashCode方法和equals方法
*/
public class HashMapTest02 {
public static void main(String[] args) {
Student student1 = new Student(“zz”);
Student student2 = new Student(“zz”);
System.out.println(student1.equals(student2)); //重写之后相等
System.out.println(student1.hashCode()); //2003749087
System.out.println(student2.hashCode()); //1324119927 重写之后都是3935

    //student1.equals(student2)结果以及是true了,表示student1和student2是一样的,那么往HashSet集合中放的话,
    //只能放进去一个

    Set<Student> set = new HashSet<>();
    set.add(student1);
    set.add(student2);
    System.out.println(set.size()); // 2
}

}
class Student{
private String name;

public Student() {
}

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

public String getName() {
    return name;
}

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

//hashCode

//equals
/\*public boolean equals(Object obj){

if(obj == null || !(obj instanceof Student))return false;
if (obj == this) return true;
//类型转换
Student s = (Student) obj;
if (this.name == s.name)return true;
return false;
}*/

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

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

}


**JDK8:如果哈希表单向链表中的元素超过8个,单向链表这种数据结构会变成红黑树数据结构,当红黑树上的节点雄安与6时,会重写把红黑树变成单向链表数据结构**


##### HashTable


**HashMap集合key和value部分允许为null**


**HashTable的key和value不允许为null**


HashTable方法都带有synchronized:线程安全的,线程安全有其他方案,这个HashTable对线程的处理导致效率较低,使用较少了


HashTable和HashMap一样,底层都是哈希表数据结构,Hashtable的初始化容量是11,默认加载因子是0.75f,Hashtable的扩容是原容量 \* 2+ 1


##### Properties


目前只需要掌握Properties属性类对象的相关方法即可.


Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型,Properties是属性类对象



package collection;

import java.util.Properties;

/*
目前只需要掌握Properties属性类对象的相关方法即可
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型
Properties是属性类对象
*/
public class PropertiesTest01 {
public static void main(String[] args) {
//创建一个Properties对象
Properties properties = new Properties();
//需要掌握Properties的两个方法,一个存,一个取
properties.setProperty(“url”,“http://”);
properties.setProperty(“driver”,“com.mysql.jac.Dirver”);
properties.setProperty(“uername”,“root”);
properties.setProperty(“password”,“123”);
//通过key获取value
String s1 = properties.getProperty(“url”);
String s2 = properties.getProperty(“driver”);
String s3 = properties.getProperty(“username”);
String s4 = properties.getProperty(“password”);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
}
}


##### TreeSet集合


1.TreeSet集合底层实际上是一个TreeMap


2.TreeMap集合底层是一个二叉树


3.放到TreeSet集合中的元素,等同于放到TreeMap集合key部分


4.Treeset集合中的元素,:无序不可重复,但是可以按照元素的大小顺序自动拍戏。称为可排序集合


###### 第一种排序方式



package collection;

import java.util.TreeSet;

/*
1.TreeSet集合底层实际上是一个TreeMap
2.TreeMap集合底层是一个二叉树
3.放到TreeSet集合中的元素,等同于放到TreeMap集合key部分
4.Treeset集合中的元素,:无序不可重复,但是可以按照元素的大小顺序自动拍戏。称为可排序集合
*/
public class TreeSetTest01 {
public static void main(String[] args) {
//创建一个TreeSet集合
TreeSet ts = new TreeSet<>();
ts.add(“zz”);
ts.add(“za”);
ts.add(“aa”);
ts.add(“wa”);
ts.add(“sd”);
for (String s:ts){
System.out.println(s);
}
TreeSet ts1 = new TreeSet<>();
ts1.add(100);
ts1.add(200);
ts1.add(45);
ts1.add(78);
ts1.add(150);
for (int i : ts1){
System.out.println(i);
}
}
}


###### 对自定义类型来说,TreeSet可以排序吗?


​ 以下程序中对于Person类型来说,无法排序,因为没有指定排序规则  
 ​ 以下程序出现了这个异常:  
 ​ java.lang.ClassCastException: class collection.  
 ​ Person cannot be cast to class java.lang.Comparable



package collection;

import java.util.TreeSet;

/*
对自定义类型来说,TreeSet可以排序吗?
以下程序中对于Person类型来说,无法排序,因为没有指定排序规则
以下程序出现了这个异常:
java.lang.ClassCastException: class collection.
Person cannot be cast to class java.lang.Comparable
*/
public class TreeSetTest02 {
public static void main(String[] args) {
Person p1 = new Person(20);
Person p2 = new Person(20);
Person p3 = new Person(20);
Person p4 = new Person(20);
Person p5 = new Person(20);
TreeSet treeSet = new TreeSet<>();
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
treeSet.add(p4);
treeSet.add(p5);
for (Person p : treeSet){
System.out.println§;
}
}
}
class Person{
int age;

public Person(int age) {
    this.age = age;
}
public String toString(String s){
    return "Person[age:" + age+"]";
}

}


###### TreeSet的第二种排序方式


TreeSet集合元素可排序的第二种方式:使用比较器  
 结论:放在TreeMap或者TreeMap结合key部分的元素想要做到排序,包括两种方式:  
 第一种:放到集合java.lang.Comparable接口  
 第二种:再够在TreeMap或者TreeSet集合的时候给他传一个比较器对象


Comparator和Comparable怎么选择  
 当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议使用Comparable接口。  
 如果比较规则有多个,并且需要多个比较规则频繁切换,建议使用Comparator接口  
 Comparator接口符合OCP原则



package collection;

import java.util.Comparator;
import java.util.TreeSet;

/*
TreeSet集合元素可排序的第二种方式:使用比较器
结论:放在TreeMap或者TreeMap结合key部分的元素想要做到排序,包括两种方式:
第一种:放到集合java.lang.Comparable接口
第二种:再够在TreeMap或者TreeSet集合的时候给他传一个比较器对象
Comparator和Comparable怎么选择
当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议使用Comparable接口。
如果比较规则有多个,并且需要多个比较规则频繁切换,建议使用Comparator接口
Comparator接口符合OCP原则
*/
public class TreeSetTest06 {
public static void main(String[] args) {
//创建TreeSet的集合的时候,需要使用这个比较器
TreeSet w = new TreeSet<>(new Wcomparator());
w.add(new W(100));
w.add(new W(101));
w.add(new W(10));
w.add(new W(800));
w.add(new W(2000));
w.add(new W(1000));
for (W s :w){
System.out.println(s);
}

    //可以使用匿名内部类的方式(这个类没有名字直接new接口)
    TreeSet<W> w1 = new TreeSet<>(new Comparator<W>(){
        public int compare(W o1, W o2) {
            //按照年龄排序
            return o1.age - o2.age;
        }
    });

}

}
//单独编写一个比较器
//实现Comparator接口

class Wcomparator implements Comparator {

@Override
public int compare(W o1, W o2) {
    //按照年龄排序
    return o1.age - o2.age;
}

}
class W {
int age;

public W(int age) {
    this.age = age;
}
public String toString(){
    return "W," + "age:" + age;
}

}


**改变比较器可以修改比较规则**



package collection;

import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;

public class CollectionTest02 {
public static void main(String[] args) {
TreeSet ts = new TreeSet<>(new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
ts.add(1);
ts.add(100);
ts.add(150);
for (int i :ts){
System.out.println(i);
}

}

}


#### 自平衡二叉树


**自平衡二叉树遵循左小右大的原则存放**


**中序遍历:左根右**


![010-自平衡二叉树](https://img-blog.csdnimg.cn/img_convert/1264f2bdac3b8feb1156b7a5a4608634.png)


#### Collections工具类


**java.util.Collection 集合接口**  
 **java.util.Collections 集合工具类**


**collections.sort如何进行排序**



package collection;

import java.util.*;

/*
java.util.Collection 集合接口
java.util.Collections 集合工具类
*/
public class CollectionTest1 {
public static void main(String[] args) {
List list = new ArrayList<>();
//变成线程安全的
Collections.synchronizedList(list);
//排序
list.add(“avf”);
list.add(“abc”);
list.add(“qw”);
list.add(“wes”);
System.out.println(“------------------”);
Collections.sort(list);
for (String s:list){
System.out.println(s);
}
List list1 = new ArrayList<>();
list1.add(new W2(200));
list1.add(new W2(250));
list1.add(new W2(400));
list1.add(new W2(500));
list1.add(new W2(700));
//对list集合元素排序,需要保证list结合中的元素实现:Comparable接口
Collections.sort(list1);
for (W2 w2 :list1){
System.out.println(w2.age);
}
System.out.println(“------------------”);
Set set = new HashSet<>();
set.add(“king”);
set.add(“king1”);
set.add(“king2”);
set.add(“king32”);
set.add(“king4”);
//将Set集合转换为List
List mylist = new ArrayList<>(set);
Collections.sort(mylist);
for (String s:mylist){
System.out.println(s);
}

    //这种方式也可以排序
    //Collections.sort(list,比较器)
}

}
class W2 implements Comparable {
int age;

public W2(int age) {
    this.age = age;
}
public String toString(){
    return "W," + "age:" + age;
}

@Override
public int compareTo(W2 o) {
    return this.age - o.age;
}

}


#### 总结


day30课堂笔记


**1、掌握Map接口中常用方法。**


**2、遍历Map集合的两种方式都要精通。**  
 第一种:获取所有key,遍历每个key,通过key获取value.  
 第二种:获取Set<Map.Entry>即可,遍历Set集合中的Entry  
 调用entry.getKey() entry.getValue()


**3、了解哈希表数据结构。**


**4、存放在HashMap集合key部分和HashSet集合中的元素需要同时重写hashCode和equals。**


**5、HashMap和Hashtable的区别。**  
 HashMap:  
 初始化容量16,扩容2倍。  
 非线程安全  
 key和value可以为null。


​ Hashtable  
 ​ 初始化容量11,扩容2倍+1  
 ​ 线程安全  
 ​ key和value都不能是null。


**6、Properties类的常用两个方法。**  
 setProperty  
 getProperty


**7、了解自平衡二叉树数据结构。**  
 左小右大原则存储。  
 中序遍历方式。


**8、TreeMap的key或者TreeSet集合中的元素要想排序,有两种实现方式:**  
 第一种:实现java.lang.Comparable接口。  
 第二种:单独编写一个比较器Comparator接口。


**9、集合工具类Collections:**  
 synchronizedList方法  
 sort方法(要求集合中元素实现Comparable接口。)


**集合这块最主要掌握什么内容?**  
 1.1、每个集合对象的创建(new)  
 1.2、向集合中添加元素  
 1.3、从集合中取出某个元素  
 1.4、遍历集合  
 1.5、主要的集合类:  
 **ArrayList**  
 **LinkedList**  
 **HashSet (HashMap的key,存储在HashMap集合key的元素需要同时重写hashCode + equals)**  
 **TreeSet**  
 **HashMap**  
 **Properties**  
 **TreeMap**


### I/O流


#### 对I/O流的理解


![001-什么是IO](https://img-blog.csdnimg.cn/img_convert/511a1d3f28f07b386b0ad800aea1674f.png)


I:Input


O:Output


通过IO可以完成硬盘文件的读和写


#### IO流的分类?


有多种分类方式:


​ 一种方式按照流的方向进行分类:


​ 以内存作为参照物,往内存中去,叫做输入。或者叫做读;从内存中出来,叫做输出,或者叫做写


​ 一种方式是按照读取数据方式不同进行分类:


​ \*\*有的流是按照字节的方式读取数据,叫做字节流,一次读取一个字节byte,\*\*等同于一次读取8个二进制位,这种流是万能的,什么类型的文件都可以读取,包括:文本文件,图片,声音文件,视频文件等…


​ 假设文件file1.txt,采用字节流的话是这样读的:  
 ​ a中国bc张三fe  
 ​ 第一次读:一个字节,正好读到’a’  
 ​ 第二次读:一个字节,正好读到’中’字符的一半。  
 ​ 第三次读:一个字节,正好读到’中’字符的另外一半。


​ \*\*有的按照字符的方式读取数据,一次读取一个字符,\*\*叫做字符流,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件,只能读取存文本文件,连word文件都无法读取。


​ 假设文件file1.txt,采用字符流的话是这样读的:  
 ​ a中国bc张三fe  
 ​ 第一次读:'a’字符('a’字符在windows系统中占用1个字节。)  
 ​ 第二次读:'中’字符('中’字符在windows系统中占用2个字节。)


​ 综上所述:流的分类


​ 输入流、输出流、字节流、字符流


#### java中的I/O


Java中的I/O流都是写好的,程序员不需要关心,我们主要还是掌握在java中以及提供了哪些流,每个流的特点是什么,每个流对象上的常用方法有哪些


Java中所有的流都在:java.io.\*; 下


java中主要还是研究:  
 怎么new流对象。  
 调用流对象的哪个方法是读,哪个方法是写。


#### java IO流四大家族


​ 四大家族的首领:  
 ​ java.io.InputStream 字节输入流


​ java.io.OutputStream 字节输出流


​ java.io.Reader 字符输入流


​ java.io.Writer 字符输出流


​ **四大家族的首领都是抽象类。(abstract class)**


注意:在java中只要“类名”**以Stream结尾的都是字节流**。以\*\*“Reader/Writer”结尾的都是字符流\*\*。


##### close()和flush()方法


所有的流都实现了:  
 java.io.Closeable接口,**都是可关闭的,都有close()方法。**  
 流毕竟是一个**管道**,**这个是内存和硬盘之间的通道**,**用完之后一定要关闭**,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。


所有的输出流都实现了:  
 **java.io.Flushable接口,都是可刷新的,都有flush()方法。**  
 养成一个好习惯,输出流在最终输出之后,**一定要记得flush()刷新一下**。这个刷新表示将通道/管道当中剩余未输出的数据,强行输出完(清空管道!)刷新的作用就是清空管道。  
 注意:如果没有flush()可能会导致丢失数据。


#### java.io包下需要掌握的流有16个:


**文件专属:**  
 java.io.FileInputStream(掌握)  
 java.io.FileOutputStream(掌握)  
 java.io.FileReader  
 java.io.FileWriter


**转换流:**(将字节流转换成字符流)  
 java.io.InputStreamReader  
 java.io.OutputStreamWriter


**缓冲流专属:**  
 java.io.BufferedReader  
 java.io.BufferedWriter  
 java.io.BufferedInputStream  
 java.io.BufferedOutputStream


**数据流专属:**  
 java.io.DataInputStream  
 java.io.DataOutputStream


**标准输出流:**  
 java.io.PrintWriter  
 java.io.PrintStream(掌握)


**对象专属流:**  
 java.io.ObjectInputStream(掌握)  
 java.io.ObjectOutputStream(掌握)


##### java.io.FileInputStream


​ 1.文件字节输入流,万能的,任何类型的文件都可以采用这个流来读  
 ​ 2.字节的方式,完成输入操作,完成读的操作(硬盘---->内存)



创建文件字节流文件
//以下都是采用了绝对路径的方式
//文件路径:C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记 会制动把\编程\,因为java中的\代表转义
//也可以写成C:/Users/77/Downloads/Documents/07-JavaSE进阶每日复习与笔记



package streamsIO;

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

/*
java.io.FileInputStream
1.文件字节输入流,万能的,任何类型的文件都可以采用这个流来读
2.字节的方式,完成输入操作,完成读的操作(硬盘---->内存)
*/
public class FileInputTest01 {
public static void main(String[] args) {
FileInputStream fis = null;
//创建文件字节流文件
//以下都是采用了绝对路径的方式
//文件路径:C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记 会制动把\编程\,因为java中的\代表转义
//也可以写成C:/Users/77/Downloads/Documents/07-JavaSE进阶每日复习与笔记
try {
fis = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\11.txt”);
int readData = fis.read();
System.out.println(readData);//这个方法的返回值是:读取到的字节本身
readData = fis.read();
System.out.println(readData);
readData = fis.read();
System.out.println(readData);
readData = fis.read();
System.out.println(readData);
readData = fis.read();
System.out.println(readData);//读取到末尾了,返回值为-1
//文件中为:abcd

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //finally语句块中确保流一定关闭。流是null的时候没必要关闭,避免空指针异常
        if (fis!=null) {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

}


###### 对程序进行改造,while循环



package streamsIO;

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

/*
对第一个程序进行改进,循环方式

一次读取一个字节,这样内存和硬盘交互太频繁了,基本上时间、资源都耗费了
*/
public class FileInputTest02 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\11.txt”);
/*while(true){
int readDate = fis.read();
if (readDate == -1){
break;
}
System.out.println(readDate);
}*/
//改进while
int readDate = 0;
while((readDate = fis.read())!=-1){
System.out.println(readDate);
}

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

}


###### 继续改造,一次读取多个字节



package streamsIO;

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

/*
int read(byte[] b)
一次最多读取b.length个字节
减少硬盘和内存的交互,提高程序的执行效率
往byte[]数组当中读
*/
public class FileInputTest03 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
//使用相对路径
//IDEA当前的默认路径为,工程Project的根就是IDEA的默认当前路径
fis = new FileInputStream(“myfile.txt”);
//开始读,采用byte数组,一次读取多个字节,最多读取“数组.length”个字节
byte[] bytes = new byte[4];//一次最多读取四个字节
//xiaoqi
int readCount = fis.read(bytes);//4
System.out.println(readCount);
//以下方法是把byte数组全部转换成了字符串
//System.out.println(new String(bytes));
//不应该全部都转,应该是读取了多少个字节,转换多少个
System.out.println(new String(bytes,0,readCount));

        //第二次只能读到两个字节
        readCount = fis.read(bytes);//2
        System.out.println(readCount);
        //System.out.println(new String(bytes));
        System.out.println(new String(bytes,0,readCount));

        //最后读不到数据返回-1
        readCount = fis.read(bytes);//-1
        System.out.println(readCount);

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

}


###### 最后的改进



package streamsIO;

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

/*
最后的版本
*/
public class FileInputTest04 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream(“myfile.txt”);
//准备一个数组
byte[] bytes = new byte[4];
/*while(true){
int readCount = fis.read(bytes);
if (readCount==-1){
break;
}
System.out.println(new String(bytes,0,readCount));
}*/
int readCount = 0;
while((readCount = fis.read(bytes))!= -1){
System.out.println(new String(bytes,0,readCount));
}

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

}


###### 其他常用的两个方法


int available() 返回流当中剩余的没有读到的字节数量  
 long skip(long n) 跳过几个字节不读



package streamsIO;

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

/*
其他常用的两个方法:
int available() 返回流当中剩余的没有读到的字节数量
long skip(long n) 跳过几个字节不读
*/
public class FileInputeTest05 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream(“myfile.txt”);
//读一个字节
//int readByte = fis.read();//还剩下五个字节
//这个方法有什么用
//不需要循环了,直接读一次就可以
/*System.out.println(“字节数量:” + fis.available());
byte[] bytes = new byte[fis.available()];//这种方式不太适合太大的文件,因为bytes[]数组不能太大
int readCount = fis.read(bytes);
System.out.println(new String(bytes));*/

        //跳过几个字节不读取,这个方法以后可能也会用到
        fis.skip(3);
        System.out.println(fis.read());


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

}


##### java.io.FileOutput


文件字节输出流,负责写(从内存到硬盘)


###### 写入方式



package streamsIO;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/*
文件字节输出流,负责写
从内存到硬盘
*/
public class FileOutputTest01 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
//文件不存在时会自动创建新的文件
//这种方式需要谨慎使用,因为他会把源文件清空
//以追加的方式在文件末尾写入。不会清空源文件内容
fos = new FileOutputStream(“myfile.txt”,true);
//开始往文件中写
byte[] bytes = {97,98,99,100};
//将byte数组全部写出
fos.write(bytes);//abcd
//将bytes数组的一部分写出
fos.write(bytes,0,2);//写出ab
String s= “china”;
byte[] ss = s.getBytes();//将字符串转换为byte数组
fos.write(ss);
//写完之后一定要刷新
fos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fos != null)
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


###### 文件的拷贝


使用Fileinput和FileOutput完成文件的拷贝  
 **拷贝的过程应该是一边读一边写,**  
 使用以上的字节流拷贝文件的时候,**文件类型随意,万能的**,什么样的文件都能拷贝


###### 002-文件的复制原理



package streamsIO;

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

/*
使用Fileinput和FileOutput完成文件的拷贝
拷贝的过程应该是一边读一边写,
使用以上的字节流拷贝文件的时候,文件类型随意,万能的,什么样的文件都能拷贝
*/
public class Copy1 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(“C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记\day34课堂笔记.txt”);
fos = new FileOutputStream(“D:\学习\day34课堂笔记.txt”);
//最核心的一边读一边写
byte[] bytes = new byte[1024*1024];
int readCount = 0;
while((readCount = fis.read(bytes)) != -1){
fos.write(bytes,0,readCount);
}
//输出流最后要刷新
fos.flush();
} catch (Exception e) {
e.printStackTrace();
}finally {
if (fos != null)
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
if(fis != null)
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


##### FileReader


文件字符输入流,只能读取普通文本。读取文本内容时,比较方便,快捷



package streamsIO;

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

/*
FileReader:
文件字符输入流,只能读取普通文本。
读取文本内容时,比较方便,快捷
*/
public class FileReaderTest01 {
public static void main(String[] args) {
FileReader reader = null;
try {
reader = new FileReader(“myfile.txt”);
char[] chars = new char[4];//一次读取四个字符
reader.read(chars);
for (char c : chars){ //按照字符的方式读取,第一次a 第二次b 第三次 小
System.out.println©;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (reader!=null)
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


##### FileWriter


文件字符输出流—写,只能输出普通文本



package streamsIO;

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

/*
FileWriter:
文件字符输出流,写
只能输出普通文本
*/
public class FileWriterTest01 {
public static void main(String[] args) {
FileWriter out = null;
try {
//创建文件字符输出流对象
out = new FileWriter(“file”,true);

        //开始写
        char[] chars = {'1','x','3','小','七'};
        out.write(chars);
        out.write(chars,3,2);
        out.write("\n");//换行
        out.write("廿七小七");


        //需要刷新
        out.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (out != null)
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}

}


###### 拷贝普通文本文件


使用FileReader和FileWriter进拷贝的话,只能拷贝普通文本文件



package streamsIO;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/*
使用FileReader和FileWriter进拷贝的话,只能拷贝普通文本文件
*/
public class Copy02 {
public static void main(String[] args) {
FileReader in = null;
FileWriter out = null;
try {
in = new FileReader(“file”);
out = new FileWriter(“file1”);
char[] chars = new char[1024 * 512];//1M
int readCount = 0;
while((readCount = in.read(chars))!= -1){
out.write(chars,0,readCount);
}

        out.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if(in!=null)
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        if(out!=null)
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }

}

}


##### BufferedReader


带有缓冲区的字符输入流,使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲



package streamsIO;

import lei.T;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/*
BufferReader:
带有缓冲区的字符输入流
使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组,自带缓冲
*/
public class BuggeredReaderTest01 {
public static void main(String[] args) {
FileReader reader = null;
BufferedReader buf = null;
try {
reader = new FileReader(“myfile.txt”);
//当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流
//外部负责包装的流,叫做包装流,还有一个名字叫做:处理流
//像当前这个程序来说,FileReader就是一个节点流,BufferReader就是处理流
buf = new BufferedReader(reader);
/*//读一行
String firstLine = buf.readLine();
System.out.println(firstLine);
//在读一行
String SecondLIne = buf.readLine();
System.out.println(SecondLIne);//读到最后一行的时候返回null*/

        String s = null;
        //readline方法读取文本的一行,但是不带换行符
        while((s = buf.readLine())!=null){ // 没有读出来换行
            System.out.println(s);
        }

        //关闭流
        //对于包装流来说,只需要关闭包装流,里面的流会自动关闭

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

}


###### 字符流转换为字符



package streamsIO;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;

public class BufferReadTest02 {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream(“file”);
//FileInputStream是一个字节流,不是一个字符流,BufferedReader里面需要传一个字符流
//BufferedReader buf = new BufferedReader(fileInputStream);
//fileInputStream是节点流,inputStreamReader是包装流
/*InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);//将节点流转换为字符流
//inputStreamReader是节点流,bufferedReader是包装流
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);*/
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
//合并
String line = null;
while((line = bufferedReader.readLine())!=null){
System.out.println(line);
}
//关闭,关闭最外层
bufferedReader.close();
}
}


##### BufferedWriter


带有缓存的字符输出流



package streamsIO;

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

/*
BufferWriter:带有缓存的字符输出流
*/
public class BufferWriterTest01 {
public static void main(String[] args) throws IOException {
BufferedWriter out = new BufferedWriter(new FileWriter(“file2”));
out.write(“xiaoqi”);
out.write(“\n”);
out.write(“1111”);
out.flush();
out.close();
}
}


##### PrintStream


标准的字节输出流。默认输出到控制台


###### 改变流的方向setOut



package streamsIO;

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

/*
标准的字节输出流。默认输出到控制台
*/
public class PrintStreamTest {
public static void main(String[] args) throws FileNotFoundException {
//联合起写
System.out.println(“hello word”);
//分开写
PrintStream ps = System.out;
ps.println(“hello l”);
ps.println(“hello l”);
ps.println(“hello a”);

    //标准输出流不需要手动关闭
    //改变便准输出流的输出方向
    /\*

这些是之前System类的使用过的方法和属性
System.gc();
System.currentTimeMillis();
PrintStream ps1 = System.out;
System.exit();
*/
//标准输出流不再指向控制台,指向myfile文件
PrintStream printStream= new PrintStream(new FileOutputStream(“myfile”));
System.setOut(printStream);
System.out.println(“11”);
System.out.println(“222”);
System.out.println(“3333”);
}
}


###### 日志工具类的使用



package streamsIO;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/*
日志工具
*/
public class LogUtil {

public static void log(String msg){
    PrintStream out = null;
    /\*

记录日志的方法
*/
try {
out = new PrintStream(new FileOutputStream(“log.txt”,true));
//改变输出方向
System.setOut(out);
//日期当前时间
Date date = new Date();
//格式化
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”);
String strtime= sdf.format(date);
System.out.println(strtime + “:” + msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}



package streamsIO;

public class LogTest {
public static void main(String[] args) {
//测试工具类是否好用
LogUtil.log(“调用了System类的gc方法,建议启动垃圾回收器”);
LogUtil.log(“调用了UserService的doSome()方法”);
LogUtil.log(“用户尝试进行登录,验证失败”);
}
}


##### ObjectOutputStream


###### 序列化和反序列化


![003-对象的序列化和反序列化](https://img-blog.csdnimg.cn/img_convert/97d0e58d2528082f943b5f329d5c5cca.png)


###### 序列化


1.NotSerializableException: streamsIO.S: S类不支持序列化


2.参与序列化和反序列化的对象,必须实现Serializable这个接口


3.注意:通过源代码发现,Serializable接口只是一个标志接口  
 public interface Serializable{  
 }  
 这个接口当中什么代码也没有  
 他起到一个标志的作用,Java虚拟机,看到这个类实现了这个接口,可能会对这个类进行特殊待遇  
 java虚拟机看到了这个接口之后,会为该类自动生成一个序列化版本号。


###### 序列化版本号:


如果更改了源代,在进行反序列会报错  
 java.io.InvalidClassException: streamsIO.S; local class incompatible:  
 stream classdesc serialVersionUID = 1419743757676022108,(之前的序列号)  
 local class serialVersionUID = -7501560671412578309(之后的序列号)


Java语言中是采用什么机制来区分类的?  
 第一:首先通过类名进行对比,如果类名不一样肯定不是同一个类  
 第二:如果类名一样,靠序列化号版本进行区分


**序列化版本号是用来区分类的**


不同的人编写了同一个类,但是这两个类不是同一个人编写的,这时候序列化就起作用了  
 对于Java虚拟机来说,Java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serialaizeble接口  
 都有默认的序列化版本号,他们的序列化版本号不一样,所以区分开了(这是自动生成序列化版本号的好处)


自动生成的序列化版本号的缺陷:**这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改**,因为只要修改,必然要重新编译,  
 此时会生成全新的序列化版本号,这时候Java虚拟机会认为这是一个全新的类,这样是不好的


\*\*结论:\*\*凡是一个类实现了Serializable接口,**建议给该类提供一个固定的序列化版本号**,**这样即使这个类的代码被修改了,但是版本号不变,java虚拟机会认为是同一个类**


建议将序列化版本号手动的写出来,不建议自动生成



package streamsIO;

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

/*
1.NotSerializableException: streamsIO.S: S类不支持序列化
2.参与学序列化和反序列化的对象,必须实现Serializable这个接口
3.注意:通过源代码发现,Serializable接口只是一个标志接口
public interface Serializable{
}
这个接口当中什么代码也没有
他起到一个标志的作用,Java虚拟机,看到这个类实现了这个接口,可能会对这个类进行特殊待遇
java虚拟机看到了这个接口之后,会为该类自动生成一个序列化版本号。
4.序列化版本号:
*/
public class ObjectOutputStreamTest01 {
public static void main(String[] args) {
S s = new S(111,“zz”);
ObjectOutputStream objectOutputStream = null;
//序列化
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(“data”));
objectOutputStream.writeObject(s);
objectOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (objectOutputStream!=null){
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

}
//继承一个可序列化接口
class S implements Serializable {
private int no;
private String name;

public S() {
}

public S(int no, String name) {
    this.no = no;
    this.name = name;
}

public int getNo() {
    return no;
}

public String getName() {
    return name;
}

public void setNo(int no) {
    this.no = no;
}

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

@Override
public String toString() {
    return "S{" +
            "no=" + no +
            ", name=" + name +
            '}';
}

}


序列化多个集合——list集合



package streamsIO;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/*
一次序列化多个对象,可以把对象放到序列化集合当中
*/
public class ObjectOutputStreamTest02 {
public static void main(String[] args) throws Exception {
List list = new ArrayList<>();
list.add(new U(1,“z”));
list.add(new U(2,“x”));
list.add(new U(3,“P”));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“users”));
//序列化一个集合,一个集合有多个对象
oos.writeObject(list);
oos.flush();
oos.close();

}

}
class U implements Serializable {
private int number;
private String name;

public U() {
}

public U(int number, String name) {
    this.number = number;
    this.name = name;
}

public int getNumber() {
    return number;
}

public String getName() {
    return name;
}

public void setNumber(int number) {
    this.number = number;
}

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

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

}


###### 反序列化



package streamsIO;

import java.io.*;

public class ObjectInputeStreamTest01 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(“data”));
//反序列化
Object obj = objectInputStream.readObject();
//反序列化回来是一个学生对象的toString方法
System.out.println(obj);
objectInputStream.close();
}
}


反序列化多个集合——list集合



package streamsIO;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

public class ObjectInputStreamTest02 {
public static void main(String[] args) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“users”));
/*Object obj = ois.readObject();
System.out.println(obj instanceof List);*/
List list = (List) ois.readObject();//向下类型转换
for (U u:list){
System.out.println(u);
}
ois.close();
}
}


###### transient关键字


transient定义的变量,不参与序列化


#### File类


1.File类和四大家族没有关系,所有File类不能完成文件的读写


2.File对象代表,文件和路径名的抽象表示形式  
 C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记 是File对象、  
 一个File对象可能是目录也可能是文件  
 File只是一个路径名的抽象表示形式


3.掌握File类的常用方法


##### 常用方法


boolean exists(); 测试此抽象路径名表示的文件或目录是否存在。


boolean createNewFile(); //以文件形式创建


boolean mkdir(); 创建此抽象路径名指定的目录。


boolean mkdirs(); 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。


String getAbsolutePath(); 返回此抽象路径名的绝对路径名字符串。


String getParent(); 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 `null`。


String getName() 返回由此抽象路径名表示的文件或目录的名称。


boolean isDirectory(); 测试此抽象路径名表示的文件是否是一个目录。


boolean isFile(); 测试此抽象路径名表示的文件是否是一个标准文件


long length(); 返回由此抽象路径名表示的文件的长度。


long lastModified(); 返回此抽象路径名表示的文件最后一次被修改的时间。



package streamsIO;

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

/*
File
1.File类和四大家族没有关系,所有File类不能完成文件的读写
2.File对象代表,文件和路径名的抽象表示形式
C:\Users\77\Downloads\Documents\07-JavaSE进阶每日复习与笔记 是File对象、
一个File对象可能是目录也可能是文件
File只是一个路径名的抽象表示形式
3.掌握File类的常用方法

*/
public class FileTest01 {
public static void main(String[] args) throws IOException {
File f1 = new File(“D:\file”);
System.out.println(f1.exists());//判断是否存在的

    //可以创建多重目录
    File f2 = new File("D:\\file\\11");

    //如果D盘先的file不存在 ,则以文件的形式创建出来
    if(!f1.exists()){
        //以文件形式创建
        //f1.createNewFile();
        f1.mkdir();//以目录的形式创建
    }
    if (!f2.exists()){
        f2.mkdirs();
    }
    File f3 = new File("D:\\java\\javaSE\\log.txt");
    String parentPath = f3.getParent();
    System.out.println(parentPath);//D:\java\javaSE
    File parentFile = f3.getParentFile();
    System.out.println("获取绝对路径" + parentFile.getAbsolutePath());//获取绝对路径D:\java\javaSE

    File f4 = new File("myfile");
    System.out.println("绝对路径"+ f4.getAbsolutePath());//绝对路径D:\java\javaSE\myfile
}

}



package streamsIO;

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

/*
File类的常用方法
*/
public class FileTest02 {
public static void main(String[] args) {
File f1 = new File(“myfile”);
//或取名文件
System.out.println(f1.getName());

    //判断是否是一个目录
    System.out.println(f1.isDirectory());

    //判断是否是一个文件
    System.out.println(f1.isFile());

    //获取文件最后一次的修改时间
    System.out.println(f1.lastModified());//返回的long是一个毫秒
    //将毫秒转换为日期
    Date time = new Date(f1.lastModified());
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
    String strtime = sdf.format(time);
    System.out.println(strtime);

    //获取文件大小的
    System.out.println(f1.length());
}

}


File[] listFiles(); 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。



package streamsIO;

import java.io.File;

/*
File中的listFiles方法
File[] listFiles()
返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
*/
public class FileTest03 {
public static void main(String[] args) {
//File[] listFile()
//获取当前目录下的所有子文件
File f = new File(“D:\”);
File[] files = f.listFiles();
for (File file:files){
System.out.println(file.getAbsoluteFile());//所有子文件
}
}
}


##### 拷贝整个目录下的文件



package streamsIO;

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

public class Copy03 {
public static void main(String[] args) {
//拷贝源和拷贝目标
//调用方法拷贝
File f1 = null;
File f2 = null;

    f1 = new File("D:\\java\\javaSE");
    f2 = new File("c:\\java");
    CopyDir(f1,f2);

}

/\*\*

* 拷贝目录
* @param src
* @param dest
*/
private static void CopyDir(File src, File dest) {
if (src.isFile()){
//src是一个文件的话,递归结束
//是文件的时候需要拷贝
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileInputStream = new FileInputStream(src);
byte[] bytes = new byte[1024*1024];
String path = dest.getAbsolutePath().endsWith(“\”)?dest.getAbsolutePath(): dest.getAbsolutePath() + “\” + src.getAbsolutePath().substring(3);
fileOutputStream = new FileOutputStream(path);
int readCount = 0;
while((readCount = fileInputStream.read(bytes))!= -1){
fileOutputStream.write(bytes,0,readCount);
}
fileOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
}finally {
if (fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null){
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
//获取源下面的子目录
File[] files = src.listFiles();
for (File file: files){
//System.out.println(file.getAbsolutePath());//获取所有文件的绝对路径
//可能是文件、可能是目录
if (file.isDirectory()){
String srcDir = file.getAbsolutePath();//获取源目录的绝对路径
//System.out.println(srcDir);
String destDir = dest.getAbsolutePath().endsWith(“\”)?dest.getAbsolutePath(): dest.getAbsolutePath() + “\” + srcDir.substring(3);
//System.out.println(destDir);
File newFile = new File(destDir);
if (!newFile.exists()){
newFile.mkdirs();
}
}
CopyDir(file,dest);
}
}
}


#### IO和Properties联合使用


**IO流:文件的读和写。**  
 **Properties:是一个Map集合,key和value都是String类型。**


非常好的设计理念:以后经常改变的数据,可以单独写到一个文件中,使用程序动态的读取,将来只需要修改这个文件的内容,Java代码不需要改动,不需要重新编译,服务器也不需要重新重启,就可以拿到动态的信息。类似于以上机制的这种文件被称为**配置文件**  
 并且当配置文件中的内容格式是:  
 key = value  
 的时候,我们把这种配置文件叫做属性配置文件  
 java规范中要求,**属性配置文件建议以properties结尾**,但是不是必须的  
 这种以properties对象是专门存放属性配置文件内容的一个类



package streamsIO;

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

/*
IO和Properties联合使用
非常好的设计理念:以后经常改变的数据,可以单独写到一个文件中,使用程序动态的读取
将来只需要修改这个文件的内容,Java代码不需要改动,不需要重新编译,服务器也不需要重新重启,就可以拿到动态的信息

类似于以上机制的这种文件被称为配置文件
并且当配置文件中的内容格式是:
key = value
的时候,我们把这种配置文件叫做属性配置文件
java规范中要求,属性配置文件建议以properties结尾,但是不是必须的
这种以properties对象是专门存放属性配置文件内容的一个类
*/
public class IOPropertiesTest01 {
public static void main(String[] args) throws Exception {
/*
Properties是一个Map集合,key和value都是String类型。
想将userinfo文件中的数据加载到Properties对象当中。
*/
//新建一个输入流对象
FileReader fileReader = new FileReader(“demo\src\streamsIO\userinfo.properties”);
//新建一个Map集合
Properties properties = new Properties();
//调用Properties对象的load方法将文件中的数据加载到Map集合当中
properties.load(fileReader);//文件中的数据顺着管道,加载到Map集合中,其中=左边作为key,右边作为value username = admin

    //通过key来获取value
    System.out.println(properties.getProperty("username"));
    System.out.println(properties.getProperty("password"));
}

}


###### 属性配置文件



username=admin
password=123
##########在属性配置文件中#是注释#############
#属性配置文件的key重复的话,value会覆盖
#属性配置文件中可以有空格,但是最好不要有空格
#不建议中间使用:
#建议中间使用=的格式


### 多线程


#### 线程和进程


进程是一个应用程序(1个进程是一个软件)。线程是一个**进程中的执行场景/执行单元**。  
 **一个进程可以启动多个线程。**


对于java程序来说,当在DOS命令窗口中输入:  
 java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。  
 JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。  
 最起码,现在的java程序中至少有两个线程并发,  
 **一个是垃圾回收线程,一个是执行main方法的主线程。**


#### 进程和线程的关系


进程可以看做是现实生活当中的公司,线程可以看做是公司当中的某个员工。


注意:  
 **进程A和进程B的内存独立不共享。**(阿里巴巴和京东资源不会共享的!)  
 魔兽游戏是一个进程  
 酷狗音乐是一个进程  
 这两个进程是独立的,不共享资源


线程A和线程B呢?  
 在java语言中:  
 **线程A和线程B,堆内存和方法区内存共享。**  
 **但是栈内存独立,一个线程一个栈。**


​ 假设启动**10个线程,会有10个栈空间**,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是**多线程并发**。多线程并发可以提高效率,java中之所以有多线程机制,目的就是为了提高程序的处理效率。


使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。**main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。**


![004-一个线程一个栈](https://img-blog.csdnimg.cn/img_convert/84b954ba8528b0a458609e56ac1b64d4.png)


#### 单核CPU和多核CPU


对于单核的CPU来说,真的可以做到真正的多线程并发吗?


对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执。


**什么是真正的多线程并发?**  
 t1线程执行t1的。  
 t2线程执行t2的。  
 t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。


单核的CPU表示只有一个大脑:  
 不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。  
 对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于**CPU的处理速度极快**,\*\*多个线程之间频繁切换执行,\*\*跟人来的感觉是:多个事情同时在做!!!!!  
 线程A:播放音乐  
 线程B:运行魔兽游戏  
 线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,  
 给我们的感觉是同时并发的。



package thread;

/*
分析程序有几个线程,除垃圾回收之外,有几个线程
一个线程(因为程序只有一个栈)
一个main方法中调用m1,m1调用m2,m2调用m3 都在一个栈中
*/
public class ThereadTest01 {
public static void main(String[] args) {
System.out.println(“main begin”);
m1();
System.out.println(“main over”);
}

private static void m1() {
    System.out.println("m1 begin");
    m2();
    System.out.println("m1 over");
}

private static void m2() {
    System.out.println("m2 begin");
    m3();
    System.out.println("m3 over");
}

private static void m3() {
    System.out.println("m3 exceute");
}

}


![001-ThreadTest01对应的内存图](https://img-blog.csdnimg.cn/img_convert/fa3154781f4b7e9df07e2743ab169fa2.png)


#### 实现线程的两种方式


##### 第一种方式


**第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。**



// 定义线程类
public class MyThread extends Thread{
public void run(){
}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程。
t.start();



package thread;
/*
实现线程的第一种方式:
编写一个类,直接继承java.lang。Thread,重写run方法
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

如何创建线程? new就可以了
怎么启动线程? 调用线程对象的short()方法
*/
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法。这里的代码属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
myThread.start();
//这里的代码还是运行在主线程中

}
class MyThread extends Thread{
public void run(){
//编写程序,这段程序运行在分支线程中
}
}


###### 直接调用run方法的时候栈


![002-线程的run](https://img-blog.csdnimg.cn/img_convert/6ed884610580ba209a4a0ad3d6491573.png)


###### 调用start方法的时候栈


![003-线程的start](https://img-blog.csdnimg.cn/img_convert/716dbb8e06d4eb80f5907c17c1d6cdcf.png)



package thread;
/*
实现线程的第一种方式:
编写一个类,直接继承java.lang。Thread,重写run方法
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

如何创建线程? new就可以了
怎么启动线程? 调用线程对象的short()方法

注意:
方法体当中的代码永远都是自上而下的顺序依次逐行执行的

以下程序的输出结果有这样的特点:
有先有后,有多有少
*/
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法。这里的代码属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
//myThread.run(); //如果只调用run方法不会启动线程,不会分配新的分支栈(这种方式就是单线程)
//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码瞬间就结束
//这段代码的任务只是为了开启新的栈空间,只要新的栈空间开出来了,start()方法就结束了,线程就启动成功了。
//启动成功的线程就自动调用run方法,并且run方法在分支栈的底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的
myThread.start();
//这里的代码还是运行在主线程中
for (int i = 0; i<1000;i++){
System.out.println(“主线程 —>” + i);
}
}
}
class MyThread extends Thread{
public void run(){
//编写程序,这段程序运行在分支线程中
for (int i = 0; i<1000;i++){
System.out.println(“分支线程 —>” + i);
}
}
}


##### 第二种方式(建议使用)


第二种方式:实现java.lang.Runnable接口,实现run方法



package thread;
/*
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
*/
public class ThreadTest03 {
public static void main(String[] args) {
//创建一个可运行的对象
//MyRunnable myRunnable = new MyRunnable();
//将可运行的对象封装成一个线程对象
Thread thread = new Thread(new MyRunnable());//合并之后的代码
//启动线程
thread.start();
for (int i = 0; i<1000;i++){
System.out.println(“主线程 —>” + i);
}
}
}
//这并不是一个线程类,是一个可运行的类,他还不是一个线程
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i<1000;i++){
System.out.println(“分支线程 —>” + i);
}
}
}


这种方式比较常用,一个类实现了接口,他还可以去继承其他的类,更加的灵活


###### 使用匿名内部类来创建线程



package thread;

import lei.T;

/*
采用匿名内部类
*/
public class ThreadTese04 {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类的方式
Thread thered = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i<1000;i++){
System.out.println(“分支线程 —>” + i);
}
}
});
//启动线程
thered.start();
for (int i = 0; i<1000;i++){
System.out.println(“主线程 —>” + i);
}
}
}


#### 线程生命周期


新建状态,就绪状态,运行状态,死亡状态


![image-20210903075942185](https://gitee.com/nian_xiaoqi/xiaoqi/raw/master/img/20210903075942.png)
#### 1.怎么获取当前线程对象


​ Thiead t = Thread.currentThread();  
 ​ 返回值t就是当前线程


#### 2.获取线程对象的名字


​ 线程对象。getName()


#### 3.修改线程对象的名字


​ 线程对象。setName(“线程名字”)


#### 4.线程的默认名字


当没有设置线程名称的时候,线程的默认名字为:


​ Thread-0  
 ​ Thread-1



package thread;
/*
1.怎么获取当前线程对象
Thiead t = Thread.currentThread();
返回值t就是当前线程
2.获取线程对象的名字
线程对象。getName()
3.修改线程对象的名字
线程对象。setName(“线程名字”)
4.当没有设置线程名称的时候,线程的默认名字为
Thread-0
Thread-1
*/
public class ThreadTest05 {
public static void main(String[] args) {
//current就是当前线程
//这个方法出现在主线程中,这就是主线程
Thread current = Thread.currentThread();
System.out.println(current.getName());//main 主方法名字
//创建线程对象
M m = new M();
M m2 = new M();
//设置线程的名字
m.setName(“m1”);
m2.setName(“m2”);
//获取线程的名字
System.out.println(m.getName());//默认名字Thread-0
System.out.println(m2.getName());
//启动线程
m.start();
m2.start();
}
}
class M extends Thread{
@Override
public void run() {
for (int i = 0; i<10;i++){
//current就是当前线程
//当m线程执行run方法,当前线程就是m
//当m2线程执行,当前线程就是m2
Thread current = Thread.currentThread();
//System.out.println(current.getName() + " "+ i);
//System.out.println(super.getName() + " "+ i);
System.out.println(this.getName() + " "+ i);
}
}
}


#### 5.sleep方法


##### **关于线程的sleep方法:**


​ static void slep(long millis)  
 ​ 1.静态方法 Thread.sleep(1000);


​ 2.参数是毫秒


​ 3.作用:让当前线程进入休眠状态,进入休眠状态,放弃占有cpu时间片,让给其他线程使用出现在那个线程中,就让那个线程休眠


​ 4.Thread.sleep() 可以达到这种效果: 间隔一定的时间,去执行一段特定的代码。



package thread;

import lei.T;

/*
关于线程的sleep方法:
static void slep(long millis)
1.静态方法 Thread.sleep(1000);
2.参数是毫秒
3.作用:让当前线程进入休眠状态,进入休眠状态,放弃占有cpu时间片,让给其他线程使用
出现在那个线程中,就让那个线程休眠
4.Thread.sleep() 可以达到这种效果: 间隔一定的时间,去执行一段特定的代码。
*/
public class ThreadTest06 {
public static void main(String[] args) {
/*try {
//让当前线程进入休眠
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后执行
System.out.println(“hello word”);*/
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName() + " — >" + i);
//睡眠一秒,执行一次
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}



package thread;

/*
关于sleep方法
*/
public class ThreadTest07 {
public static void main(String[] args) {
M1 m = new M1();
m.setName(“m”);
m.start();

    //调用sleep方法
    try {
       m.sleep(1000);//在执行的时候还是会转换成 Thread.sleep() 这行代码的作用是当前线程进入休眠,main方法进入休眠
                            //这行代码出现在main方法中,main线程休眠
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("hello world");
}

}
class M1 extends Thread{
@Override
public void run() {
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}


##### 唤醒正在睡眠的线程


**t.interrupt();**



package thread;

/*
唤醒一个正在睡眠的线程,这个不是中断线程的执行,是终止线程的睡眠
*/
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName(“t”);
t.start();

    //希望5秒之后t线程醒来
    try {
        Thread.sleep(1000\*5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //中断t线程的睡眠(这种中断睡眠方式,依靠了java异常的处理机制)
    t.interrupt();//干扰
}

}
class MyRunnable2 implements Runnable{

@Override
public void run() {
    System.out.println(Thread.currentThread().getName() + "--> begin");
    //这块只能try catch 子类不能比父类抛出更多的异常
    try {
        Thread.sleep(1000\*60\*60);
    } catch (InterruptedException e) {
        //打印异常信息
        //e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "---> end");

    //调用doOther
    try {
        doOther();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//其他的方法可以throws
public void doOther() throws Exception{

}

}


#### 6.终止线程


##### stop方法不建议使用



package thread;

/*
在java中强行终止一个线程
这种方式有很大的缺点:容易丢失数据,,因为这种方式是直接将线程杀死
线程没有保存的数据会丢失,不建议使用
*/
public class ThreadTest09 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable3());
thread.setName(“t”);
thread.start();
//模拟5秒睡眠
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后强行终止t线程
thread.stop();//已过时
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName() + “–>” + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


##### 布尔方法建议使用



package thread;

/*
合理终止线程
*/
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 myRunnable4 = new MyRunnable4();
Thread thread = new Thread(myRunnable4);
thread.setName(“t”);
thread.start();
//模拟五秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
//想要什么时候终止t的执行,修改run为false
myRunnable4.run = false;
}
}
class MyRunnable4 implements Runnable{
boolean run = true;//设置一个布尔标记
@Override
public void run() {
for (int i =0; i<10;i++) {
if (run) {
System.out.println(Thread.currentThread().getName() + “—>” + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
//return就结束了,在结束之前还有什么没有保存的,在这里就可以保存
//save
return;
}
}
}
}


#### 关于线程的调度


##### 常见的调度模式


抢占式调度模型:  
 那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。  
 java采用的就是抢占式调度模型。


均分式调度模型:  
 平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。  
 平均分配,一切平等。  
 有一些编程语言,线程调度模型采用的是这种方式


##### 关于线程调度的方法


实例方法:  
 void setPriority(int newPriority) 设置线程的优先级  
 int getPriority() 获取线程优先级  
 最低优先级1  
 默认优先级是5  
 最高优先级10  
 优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)


静态方法:  
 static void yield() 让位方法  
 暂停当前正在执行的线程对象,并执行其他线程  
 yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。  
 yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。  
 注意:在回到就绪之后,有可能还会再次抢到。



实例方法:
void join() ,合并线程



class MyThread1 extends Thread {
public void doSome(){
MyThread2 t = new MyThread2();
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
}
}

class MyThread2 extends Thread{
}


#### 线程安全(重点)


在以后的开发中,我们的项目都是运行在服务器当中,二服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。最重要的是,编写的代码需要放到一个多线程的环境下运行,更需要关注的是这些数据在多线程并发的环境下是否是安全的。


##### 多线程并发的问题


![005-多线程并发对同一个账户进行取款](https://img-blog.csdnimg.cn/img_convert/fde0c1cc34f17ff96df5d3745c8e698f.png)


##### 什么时候出现线程安全问题


​ 三个条件:  
 ​ **条件1:多线程并发。**  
 ​ **条件2:有共享数据。**  
 ​ **条件3:共享数据有修改的行为。**


​ 满足以上3个条件之后,就会存在线程安全问题。


##### 解决线程安全问题


在多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全的问题,解决线程的安全问题:


​ 线程排队执行(不能并发)


​ 这种排队执行解决线程安全问题


​ 这种机制被称为:线程同步机制


解决线程安全问题:使用线程同步机制


线程同步就是线程排队了,线程排队就会牺牲一部分效率,数据安全第一位,只有数据安全了,才可以提高效率


##### 涉及到的专业术语


异步编程模型:


​ 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型


​ 其实就是:多线程并发,效率高


​ 异步就是并发


同步编程模型:


​ 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2执行的时候必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型,效率低,线程排队执行


​ 同步就是排队


#### 线程安全的例子


##### 线程不安全



package TgreadSAFE;
/*
不使用线程同步机制,多线程对同一个账户进行取款,出现了线程安全问题
*/
public class Account {
private String acton;//账户
private double balance;//余额

public Account() {
}

public Account(String acton, double balance) {
    this.acton = acton;
    this.balance = balance;
}

public String getActon() {
    return acton;
}

public double getBalance() {
    return balance;
}

public void setActon(String acton) {
    this.acton = acton;
}

public void setBalance(double balance) {
    this.balance = balance;
}
//取款方法
public void withdraw(double money){
    //取款之前的余额
    double before = this.balance;
    //取款之后的余额
    double after = before - money;
    //模拟一下网络延迟,100%初出问题
    try {
        Thread.sleep(1000\*5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //更新余额
    this.setBalance(after);

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!


try {
m.sleep(1000);//在执行的时候还是会转换成 Thread.sleep() 这行代码的作用是当前线程进入休眠,main方法进入休眠
//这行代码出现在main方法中,main线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“hello world”);
}
}
class M1 extends Thread{
@Override
public void run() {
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}


##### 唤醒正在睡眠的线程


**t.interrupt();**



package thread;

/*
唤醒一个正在睡眠的线程,这个不是中断线程的执行,是终止线程的睡眠
*/
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName(“t”);
t.start();

    //希望5秒之后t线程醒来
    try {
        Thread.sleep(1000\*5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //中断t线程的睡眠(这种中断睡眠方式,依靠了java异常的处理机制)
    t.interrupt();//干扰
}

}
class MyRunnable2 implements Runnable{

@Override
public void run() {
    System.out.println(Thread.currentThread().getName() + "--> begin");
    //这块只能try catch 子类不能比父类抛出更多的异常
    try {
        Thread.sleep(1000\*60\*60);
    } catch (InterruptedException e) {
        //打印异常信息
        //e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "---> end");

    //调用doOther
    try {
        doOther();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//其他的方法可以throws
public void doOther() throws Exception{

}

}


#### 6.终止线程


##### stop方法不建议使用



package thread;

/*
在java中强行终止一个线程
这种方式有很大的缺点:容易丢失数据,,因为这种方式是直接将线程杀死
线程没有保存的数据会丢失,不建议使用
*/
public class ThreadTest09 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable3());
thread.setName(“t”);
thread.start();
//模拟5秒睡眠
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后强行终止t线程
thread.stop();//已过时
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName() + “–>” + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


##### 布尔方法建议使用



package thread;

/*
合理终止线程
*/
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 myRunnable4 = new MyRunnable4();
Thread thread = new Thread(myRunnable4);
thread.setName(“t”);
thread.start();
//模拟五秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
//想要什么时候终止t的执行,修改run为false
myRunnable4.run = false;
}
}
class MyRunnable4 implements Runnable{
boolean run = true;//设置一个布尔标记
@Override
public void run() {
for (int i =0; i<10;i++) {
if (run) {
System.out.println(Thread.currentThread().getName() + “—>” + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
//return就结束了,在结束之前还有什么没有保存的,在这里就可以保存
//save
return;
}
}
}
}


#### 关于线程的调度


##### 常见的调度模式


抢占式调度模型:  
 那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。  
 java采用的就是抢占式调度模型。


均分式调度模型:  
 平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。  
 平均分配,一切平等。  
 有一些编程语言,线程调度模型采用的是这种方式


##### 关于线程调度的方法


实例方法:  
 void setPriority(int newPriority) 设置线程的优先级  
 int getPriority() 获取线程优先级  
 最低优先级1  
 默认优先级是5  
 最高优先级10  
 优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)


静态方法:  
 static void yield() 让位方法  
 暂停当前正在执行的线程对象,并执行其他线程  
 yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。  
 yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。  
 注意:在回到就绪之后,有可能还会再次抢到。



实例方法:
void join() ,合并线程



class MyThread1 extends Thread {
public void doSome(){
MyThread2 t = new MyThread2();
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
}
}

class MyThread2 extends Thread{
}


#### 线程安全(重点)


在以后的开发中,我们的项目都是运行在服务器当中,二服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。最重要的是,编写的代码需要放到一个多线程的环境下运行,更需要关注的是这些数据在多线程并发的环境下是否是安全的。


##### 多线程并发的问题


![005-多线程并发对同一个账户进行取款](https://img-blog.csdnimg.cn/img_convert/fde0c1cc34f17ff96df5d3745c8e698f.png)


##### 什么时候出现线程安全问题


​ 三个条件:  
 ​ **条件1:多线程并发。**  
 ​ **条件2:有共享数据。**  
 ​ **条件3:共享数据有修改的行为。**


​ 满足以上3个条件之后,就会存在线程安全问题。


##### 解决线程安全问题


在多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全的问题,解决线程的安全问题:


​ 线程排队执行(不能并发)


​ 这种排队执行解决线程安全问题


​ 这种机制被称为:线程同步机制


解决线程安全问题:使用线程同步机制


线程同步就是线程排队了,线程排队就会牺牲一部分效率,数据安全第一位,只有数据安全了,才可以提高效率


##### 涉及到的专业术语


异步编程模型:


​ 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型


​ 其实就是:多线程并发,效率高


​ 异步就是并发


同步编程模型:


​ 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2执行的时候必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型,效率低,线程排队执行


​ 同步就是排队


#### 线程安全的例子


##### 线程不安全



package TgreadSAFE;
/*
不使用线程同步机制,多线程对同一个账户进行取款,出现了线程安全问题
*/
public class Account {
private String acton;//账户
private double balance;//余额

public Account() {
}

public Account(String acton, double balance) {
    this.acton = acton;
    this.balance = balance;
}

public String getActon() {
    return acton;
}

public double getBalance() {
    return balance;
}

public void setActon(String acton) {
    this.acton = acton;
}

public void setBalance(double balance) {
    this.balance = balance;
}
//取款方法
public void withdraw(double money){
    //取款之前的余额
    double before = this.balance;
    //取款之后的余额
    double after = before - money;
    //模拟一下网络延迟,100%初出问题
    try {
        Thread.sleep(1000\*5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //更新余额
    this.setBalance(after);

[外链图片转存中…(img-G4Os0RZP-1715585315051)]
[外链图片转存中…(img-LIhBhpAH-1715585315052)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值