网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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);
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!