华清远见—重庆中心——JAVA高级阶段知识点梳理

String字符串

String是一个类,属于数据类型中的引用类型。Java中所有使用""引起来的内容都是属于这个类的实例,称为字符串对象

字符串在定义后,值不可改变,是一个常量,实际是一个字符数组

创建字符串对象

方法一:使用""赋值创建

String str="abc";

方法二:通过构造方法

//创建一个空字符串对象
String str=new String();
//创建一个指定的字符串对象
String str2=new String("abc");

注意:

字符串比较时,不能用判断,因为判断的是存储的地址,如果要比较两个字符串,要使用String类中重写equals方法。且在比较两个字符串时,最好把已知的非空字符串放在equals前面,这样能避免空指针异常。

username.equals("adimin");//这样写,username可能为空,会出现空指针异常
"adimin".equals(username);//能避免空指针

字符串常用方法

方法名 返回值 作用
length() int 得到字符串的长度
toLowerCase() String 转换为小写
toUpperCase() String 转换为大写
trim() String 去除字符串首尾的所有空格
isEmpty() boolean 判断字符串是否为空白字符串""
getBytes() byte[] 将字符串转换为字节数组
toCharArray() char[] 将字符串转换为字符数组
equalsIgnoreCase(String str) boolean 忽略大小写判断两个字符串是否相同
equals(String str) boolean 判断两个字符串是否相同
charAt(int index) char 得到字符串指定索引上的字符
indexOf(String str) int 得到字符串中某个子字符串第一次出现的索引,如果不存在,返回-1
lastIndexOf(String str) int 得到字符串中某个子字符串最后一次出现的索引,如果不存在,返回-1
contains(字符序列) boolean 判断某个子字符串是否在原字符串中出现
concat(String str) String 将参数字符串拼接到原字符串末尾
startsWith(String str) boolean 判断是否以指定字符串开头
endsWith(String str) boolean 判断是否以指定字符串结尾
substring(int begin) String 从指定索引开始截取字符串至末尾
substring(int being,int end) String 截取[begin,end)区间内的字符串
split(String regex) String[] 按执行字符串或正则表达式切分原字符串。如果指定内容不再末尾,n个指定字符能得到n+1个子串;如果指定内容在末尾,n个指定字符能得到n个子串(不包含末尾的无效字符)
replace(char oldChar,char newChar) String 将原字符串中的所有指定字符替换为新字符
String.valueOf(参数) String 将任意参数转换为字符串。通常用于原始类型转换为字符串。
String.formart(String 格式,Object… obj) String 根据指定格式转换参数。常用于将浮点数保留小数。如String.format(“%4.2f”,10.0/3)表示将计算的结果四舍五入保留2位小数转换为字符串;如果最终数据所占位置小于4,原样输出,大于4在最前补充空格。

可变字符串

String字符串对象是一个常量。在定义后,值不可改变

如果使用String类的对象,对其进行频繁更新后,就会不停地创建新的字符串对象,不停地引给同一个变量。这时候就需要用到可变字符串。

StringBuilder类

用来表示可变字符串的一个类,是非线程安全,建议在单线程环境下使用。

StringBuffer类

用来表示可变字符串的一个类,是线程安全,建议在多线程环境下使用。

StringBuilder和StringBuffer类中的方法都一致,只不过StringBuffer中的方法使用了synchoronized关键字修饰,表示是一个同步方法,在多线程环境下不会出现问题。

构造方法

常用构造方法 作用
StringBuilder() 创建一个大小为16的字符串数组,表示一个空白字符。类似于String str=“”;
StringBuilder(String str) 创建一个str长度+16的字符数组后,将str添加到其中。类似于String str=“初始值”;

常用方法

常用方法 作用
append(Object obj) 将任意类型的参数添加到原可变字符串末尾
delete(int start,int end) 删除[start,end)区间内的字符
deleteCharAt(int index) 删除index索引上的字符
insert(int index,Object obj) 在索引index上插入obj
replace(int start,int end,String str) 将[start,end)区间内的字符替换为str
reverse() 反转字符串

注意

  • 以上表格中的方法都是在直接操作同一个字符串对象,每次调用方法后,原字符串都会发生变化

  • StringBuffer和StringBuilder并没有重写equals方法,所以可变字符串的值是否相同时,调用的是
    equals中原始的==判断。如果要判断两个可变字符串的值是否相同时,需要将其转换为String后调
    用equals判断

可变字符串与String之间的转换

String转换为可变字符串
String str="abc";
//使用构造方法把String字符串包装成可变字符串
StringBuilder sb=new StringBuilder(str);
可变字符串转换为String(任意类型对象转换为String)

方法一:String.valueOf(Object obj)方法

StringBuilder sb=new StringBuilder("abc");
//调用静态方法
String str=String.valueOf(sb);

方法二:对象.toString();

StringBuiilder sb=new StringBuilder("abc");
//调用toString()方法
String str=String.toString();

方法三:拼接一个空字符串

StringBuiilder sb=new StringBuilder("abc");
//使用一个空字符串+拼接起来
String str=sb+"";

可变字符串面试题

比较String、StringBuilder、StringBuffer的区别

相同点

  • 这三个类都可以表示字符串。都提供了一些操作字符串的方法。

  • 这三个类中有相同的方法,如charAt()、indexOf()等

  • 这三个类都是被final修饰的类,不能被继承

不同点

  • String定义的字符串是一个常量。可变字符串定义的字符串是一个变量

  • String类中的方法,调用后,不会改变原本字符串的值;可变字符串类中的方法,调用后,会改变原本字符串的值

  • StringBuilder是非线程安全的可变字符串类,StringBuffer是线程安全的可变字符串类,其中的方
    法被synchronized修饰

总结

在频繁使用同一个字符串时,一定要使用可变字符串的对象,不能使用String类的对象。

System类

其中包含了一些系统相关的信息和一些方法,其中的方法和属性都是静态的,可以直接通过类名调用,但该类不能创建对象,且不是抽象类,而是因为它的构造方法是私有的

常用属性和方法 作用
System.out 获取打印输出流PrintStream对象,用于控制台打印信息。
System.in 获取输入流InputStream对象,用于获取输入的信息
System.err 获取打印输出流PrintStream对象,用于控制台打印异常信息。
System.exit(int statues) 终止虚拟机运行,参数0表示正常终止。
System.currentTimeMillis() 获取从1970.1.1 0:0:0至今进过了多少毫秒。中国是UTC(+8),所以是从1970.1.1 8:0:0至今经过了多少毫秒。返回long类型。
System.arraycopy(原数组,原数组起始位置,目标数组,目标数组起始位置,原数组要复制的元素数量) 将原数组中指定长度的元素复制到新数组中

RunTime类

Runtime类的对象,表示程序运行时对象(程序运行环境对象)。

包含了程序运行环境相关的信息,常用于获取取运行环境信息(如虚拟机内存)或执行某个命令。

特点

这个类不是一个抽象类,但不能创建对象,因为它的构造方法是私有的。
这个类提供了一个静态方法getRuntime(),通过这个方法,可以获取一个Runtime类的对象。
这是Java中的一种设计模式–单例模式(一个类只能有一个创建对象)。

public class Runtime {//定义了私有的一个静态成员:当前类的对象
//由于静态成员只在类加载时执行一次,所以这里只会创建唯一一个当前类的对象
private static Runtime currentRuntime = new Runtime();
//定义了一个公共的静态方法,用于获取创建的唯一的当前类的对象
public static Runtime getRuntime() {return currentRuntime;
}
//构造方法是私有的,不能在当前类之外创建对象
private Runtime() {}
}

方法调用时传值问题

public class Test {/**  当方法的参数为原始类型,方法中对该参数做修改,不会影响实际参数* */public static void fun1(int i) {i = 123;System.out.println(i);}/**  当方法的参数为字符串时,方法中对字符串"重新赋值",实际是创建了一个新的字符串对象,不会影响实际参数* */public static void fun2(String str) {str = "new";System.out.println(str);}/**  如果参数为引用类型,方法中直接操作该参数,操作的就是实际参数的内存地址,会影响实际参数* */public static void fun3(Person p) {p.setName("吴彦祖");System.out.println(p.getName());}/** 如果参数为引用类型,方法中创建了一个新对象对其赋值,操作的是创建的新对象,不会影响实际参数* */public static void fun4(Person p) {p = new Person();p.setName("易烊千玺");System.out.println(p.getName());}/** 如果参数为数组,也属于引用类型,方法中直接操作数组,操作的是实参数组,会影响实际参数* */public static void fun5(int[] list) {list[0] = 123;System.out.println(list[0]);}public static void fun(char[] list,Person p){list[0]='m';//这里在直接操作实际参数,会影响实参p = new Person();//这里创建了一个新的对象,操作的是方法中的对象,不会影响实参p.setName("刘鑫");}public static void main(String[] args) {//方法参数为原始类型,方法中对参数做修改,不会改变实际参数int i = 0;fun1(i);//123System.out.println(i);//0//方法参数为字符串,方法中对字符串重新赋值,不会改变实际参数String str = "old";fun2(str);//newSystem.out.println(str);//old//方法参数为引用类型,方法中对参数直接修改,会改变实际参数Person p = new Person();p.setName("王海");fun3(p);System.out.println(p.getName());//方法参数为引用类型,方法中创建新对象后赋值给实际参数,操作的是方法中的对象,不会改变实际参数Person p1 = new Person();p1.setName("赵敏");fun4(p1);System.out.println(p1.getName());//方法参数为数组,属于引用类型,方法中对参数直接修改,会改变实际参数int[] list = {0,1,2};fun5(list);System.out.println(list[0]);//练习char[] list2={'a','b','c'};Person p2 = new Person();fun(list2,p2);System.out.println(list2[0]);//mSystem.out.println(p2.getName());//null}
}

总结

参数只有是引用类型时(类、接口、数组),并且方法中直接操作该参数时,才会对实际参数造成影响

fun3(Person p)参数为Person对象,方法中直接调用参数p的xxx方法,是在操作实际参数。

fun4(Person p)方法里面创建了一个新对象,操作赋值的是方法中的对象,所以不会改变实际的参数。

fun5(int[] list)参数为数组,方法中直接操作数组某个索引对应的元素,是在操作实际参数。

Date类

用于表示日期时间的类,位于java.util包下

构造方法

常用构造方法 说明
Date() 创建当前瞬间对应的日期对象
Date(long l) 创建指定瞬间对应的日期对象
Date(int year,int month,int day) 该构造方法已过时。创建指定年月日的日期对象(年是1900年起经过的年数,月用0-11表示1到12月)

常用方法

常用方法 作用
getTime() 得到对应Date对象表示的毫秒数
setTime(long l) 设置Date对象的毫秒数
after(Date when) 判断调用日期对象是否在when之后
before(Date when) 判断调用日期对象是否在when之前

SimpleDateFormat类

构造方法

常用构造方法 作用
SimpleDateFormat(String pattern); 创建一个指定日期模板的格式化日期对象

日期模板

特殊字符 作用
yyyy 年份
MM 月份
dd 日期
HH 小时
mm 分钟
ss
E 星期
以上两个字母都可以写成一个,如月份5 M:5,MM:05
yyyy/MM/dd HH:mm:ss E 2022/11/24 16:24:09 星期四

常用方法

常用方法 返回值 作用
format(Date date) String 将Date对象按日期模板转换为字符串
parse(String str) Date 将满足日期模板的字符串转换为Date对象

Calendar类

表示日期类,包含了很多日历相关的信息

是一个抽象类,无法创建对象,可以通过调用静态方法getInstance()获取类的一个实例。

//获取Calendar类的对象
Calendar cal=Calendar.getInstance();

日历字段

在Calendar类中,定义了很多被final修饰的常量,称为日历字段,是一个数字,用于获取指定信息

Calendar.YEAR 年份
Calendar.MONTH 月份(0-11表示1-12月)
Calendar.DATE 日期
Calendar.DAY_OF_WEEK 星期(1-7表示周天到周六)
Calendar.HOUR 12进制小时
Calendar.HOUR_OF_DAY 24进制小时
Calendar.MINUTE 分钟
Calendar.SECOND 秒
Calendar.DAY_OF_MONTH 本月第几天
Calendar.DAY_OF_YEAR 本年第几天
Calendar.WEEK_OF_MONTH 本月第几周
Calendar.WEEK_OF_YEAR 本年第几周

包装类

Java是纯面向对象语言,宗旨是将一切事物视为对象处理

但原始类型不属于对象,不满足面向对象的思想,但是原始类型在使用时无需创建对象,保存在栈中,效率高。

为了让元素类型也有对应的类类型,达到"万物皆对象"的理念,所以就有了包装类的概念。

包装类就是原始类型对应的类类型。包装类通常用于字符串与原始类型之间的转换

在web应用中,从浏览器页面中获取到后台的数据,全是String类型,所以一定要使用转换为原始类型的方法。

包装类 原始类型
Byte byte
Short short
Integer int
Long long
Float float
Double double
Character char
Boolean boolean

特点

  • 八个原始类型中,除了int和char,其余都是把首字母改成大写,int对应Integer,char对应Character

  • 包装类都是被final修饰的,不能被继承

  • 除了Character类之外,其余包装类都有两个构造方法:参数为原始类型或者String类型的构造方法。Character的构造方法只有一个,参数为char类型

  • 除了Character类,其余类都有静态方法parse原始类型(String str),用于将字符串转换为相应的原始类型

    • 数值类的包装类的parseXXX()方法,如果参数不是一个对应的数字,转换时就会抛出一个NumberFormat异常。如"123abc",或"123.4",在使用Integer.parseInt()时都会抛出异常

    • Boolen类型中的parseBoolean()方法,参数如果是"true"这四个字母,不区分大小写,都能转换为真正的boolean类型的true,只要不是"true",其他字符串都是"false",不会报错

  • 除了Boolean外,其余包装类都有MAX_VALUE和MIN_VALUE这两个属性,用于获取当前类型定义的最大值和最小值

  • 所有包装类都重写了toString()方法,用于将包装类对象转换为String对象

字符串与原始类型之间的转换

字符串转换为原始类型

使用原始类型对应的包装类,调用parse原始类型(字符串)方法

String num="123";
byte b=Byte.parseByte(num);
short s=Short.parseShort(num);
int i=Integer.parseInt(num);
long l=Long.parseLong(num);
float f=Float.parseFloat(num);
double d=Double.parseDouble(num);
boolean b=Boolean.parseBoolean(num);//false,只要值不是true,其他都是false

原始类型转换为字符串

  • 使用"+"拼接一个空白字符串
int num=123;
String str=num+"";
  • 将原始类型转换为包装类后,调用toString()方法
int num=123;
Integer integer=new Integer(num);
String str=integer.toString();
  • String.valueOf(原始类型数据)
int num = 123;
String str = String.valueOf(num);

装箱和拆箱

  • 所有包装类都有一个静态方法valueOf(原始类型),将某个原始数据类型转换为相应的包装类对象,这个过程称为装箱boxing
//手动装箱
int i=123;//定义原始类型
Integer integer=Integer.valueOf(i);//调用包装类Integer的静态方法value()将原始类型转换为包装类对象
  • 所有包装类都有一个原始类型Value()方法,用于将包装类对象转换为原始类型,这个过程称为拆箱unboxing
//手动拆箱
Integer integer =new Integer(123);
int i=integer.intValue();
  • 自动装箱和拆箱,在JDK1.5后,为了方便原始类型和包装类之间做转换,加入了自动装箱拆箱的概念,可以直接将原始数据类型和包装类之间互相赋值
//自动装箱
Integer anInt = 345;
//自动拆箱
int i = anInt;
  • 自动装箱缓冲区
public class Test3 {public static void main(String[] args) {//超过了128,new了新对象Integer i1=200;Integer i2=200;//自动装箱,如果值在byte范围内[-128,127],这个值会共享Integer i3=127;Integer i4=127;//i5和i6保存的是创建的不同对象的地址,==结果为falseInteger i5 = new Integer(100);Integer i6 = new Integer(100);System.out.println(i1 == i2);//falseSystem.out.println(i3 == i4);//trueSystem.out.println(i5 == i6);//false,new了一个对象,地址不同}
}

注意

如果通过构造方法创建的包装类对象,会有不同的内存地址,使用==判断结果为false

自动装箱方式创建包装类对象,赋值范围1在byte范围内,将这个值保存在缓冲区中,如果多个对象使用同一个数值,共享这个数据,使用同一个地址,使用判断结果为true,结果不在byte范围内,使用判断结果为false

引用类型比较值是否相同时,不要使用==,使用重写后的equals方法

异常

当程序没有按照开发人员的医院正常执行,中途出现错误导致程序中断,出现这种情况,就称为异常。

学习异常就是认识异常的种类,如何处理异常和避免异常。

异常的产生

异常在程序中以对象的形式存在,当代码执行过程中出现异常,虚拟机会自动创建一个异常对象,如果没有对该异常对象进行处理,就会导致程序中断,不再执行后续代码

异常的分类

异常在程序中以对象的形式存在,就有相应的类。

所有的异常类,组成了一个"异常家族"

Error错误

如果出现xxxxError,如StackOverflowError栈空间溢出,无法通过额外的代码解决,只能修改源码

Exception异常

如果出现xxxException,如NullPointerException空指针异常,叫做非编译异常,可以通过额外的代码解决

  • 运行时异常RuntimeException

如果一个异常类属于RuntimeException异常类的子类,称为运行时异常,可以通过编译,可以不用处理,不抛出异常,运行时可能抛出异常对象

常见运行时异常 说明 出现的情景
NullPointException 空指针异常 用空对象null调用属性或方法
ArrayIndexOutOfBounds 数组下标越界异常 使用数组时候下标超出范围
NumberFormatException 数字格式异常 使用包装类调用parse方法转换时,无法将参数转换。如String str=“123abc”;int i=Integer.parseInt();
InputMismatchException 输入不匹配异常 使用Scanner接收控制台输入时,如果不满足接收的类型。如int i=sc.nextInt();实际输入a
ClassCastException 对象转型异常 Person p=(Person) new Object();
ArithmeticException 算术运算异常 如0当分母
  • 编译时异常

如果一个异常类属于Exception异常类的子类,称为编译时异常,无法通过编译,必须处理异常后才能编译运行

常见编译时异常 说明 出现的情况
SQLException 数据库SQL相关异常 操作数据库时
IOException 输入输出流异常 使用流对象时
FileNotFoundException 文件未找到异常 方法的参数为文件时

处理异常

通常所说的处理异常,是指处理Exception类的子类异常。

处理异常的目的,就是保证程序正常执行

方式一:try-catch-finally语句

这种方式处理异常,无论会不会抛出异常,都能让程序正常执行

try{//可能出现异常的代码
}catch(异常类 异常对象){//如果出现异常对象,且与catch小括号内的异常匹配,就会执行这里的代码
}catch(异常类 异常对象){//如果出现异常对象,且与catch小括号内的异常匹配,就会执行这里的代码
}finally{//无论程序是否会抛出异常,都要执行的代码
}

try-catch使用时要注意

  • try、catch、finally都不能单独使用,try需要配合catch或finally或catch和finally一起一起用

  • 执行try中的内容时,当某行代码抛出异常,就不会执行try中该行代码后续的内容

  • 无论try中的代码是否会抛出异常,finally中的代码一起会执行

  • 如果代码会抛出多个异常,可以使用多个catch捕获异常,但是需要将异常子类放到异常父类之前

  • 在try中定义的内容,无法在try之外的地方使用

  • try中的return,不影响finally的执行,finally优先于return执行

方法二:throws关键字

这种方式,可以让编译时异常通过编译。

在定义方法的同时,通过该关键字声明可能抛出的异常

用法:方法的参数部分之后,添加"throws 异常类型1,异常类型2"

publlic class{public void fun()throws InterruptedException{//这时候该方法抛出异常//这句话直接写完后,会报错,因为sleep()方法可能会抛出InterruptedException,属于编译时异常,必须处理Tread.sleep(500)
}
}
throw和throws
  • throws表示用于声明方法有可能出现的异常。使用时写在方法的小括号之后
public void fun()throws InterruptedException{//这时候该方法抛出异常Tread.sleep(500)
}
  • throw用于手动抛出异常对象,使用时,写在方法体中。常用于满足某种情况时,强制中断程序

用法:throw 异常对象

public void fun2(){foe(int i=0;i<10;04i++){if(i==5){//手动抛出异常throw newNullPointerException;
}
}

自定义异常

如果需要在某种情况下中断程序时,可以自定义一个异常类,通过throw关键字手动抛出。

自定义异常步骤

1.定义一个类继承一个异常类

  • 如果继承的是RuntimeException类,表示自定义的异常属于运行时异常,该异常对象可以不用处理

  • 如果继承的是非RuntimeException类,表示该自定义异常属于编译时异常,必须要处理

2.可选操作。定义带参构造方法,参数为异常信息,调用父类中带参的构造方法

相关面试题

final、finally、finalize的区别

  • final是一个修饰符,被final修饰的属性称为常量,方法不能重写,类不能继承

  • finally是try-catch-finally结构中的关键字,无论师傅抛出异常,都会执行finally的代码块

  • finalize是Object类中的方法,finalize()是某个对象被回收前调用的方法

数组和集合(重点)

数组的特点

  • 数组中保存的元素都是有序的,可以通过下标快速访问
  • 数组中保存的数据都是同一种类型
  • 数组的长度在定义后,无法改变
  • 数组无法获取其中保存的元素实际数量

集合的特点

  • 能保存一组数据,可以有序可以无序
  • 集合的容量可变
  • 集合中可以保存不同类型的数据
  • 可以获取集合中保存的元素实际数量

集合框架

集合的根接口为Collection接口Map接口,位于java.util包中。

Collection接口

该接口有两个核心子接口:List和Set。

这两个接口都可以保存一组元素,List接口保存元素时,是有序可重复的;Set接口保存元素时,是无序不重复的。

常用方法

常用方法 返回值 作用
add(Object obj) boolean 将元素添加到集合中
size() int 获取集合中的元素数量
isEmpty() boolean 判断集合是否为空
clear() void 清空集合
contains(Object obj) boolean 判断集合中是否存在指定元素
remove(Object obj) boolean 移除集合中的指定元素
toArray() Object[] 将集合转换为数组
iterator() Iterator 获取集合的迭代器对象,用于遍历集合

List接口(有序可重复)

有序集合,元素可以重复,允许保存null,可以通过索引获取对应位置上的元素。
在该接口继承Collection接口的同时,又拓展了一些操作元素的方法,如添加到指定索引、根据索引删
除、获取指定索引的元素、截取子集合的方法等。

常用方法 返回值 作用
get(int index) Object 根据指定索引获取对应的元素
set(int index,Object obj) Object 使用obj替换index上的元素,返回被替换的元素
add(int index,Object obj) void 将obj添加到index上
remove(int index) Object 移除指定索引的元素
indexOf(Object obj) int 得到某元素第一次出现的索引,没有返回-1
lastIndexOf(Object obj) int 得到某元素最后一次出现的索引,没有返回-1
subList(int from,int to) List 截取[from,to)区间内的元素,返回子集合

ArrayList实现类(重点)

  • 采用数组实现的集合
  • 可以通过索引访问元素,可以改变集合大小。如果要在其中插入或删除元素时,会影响后续元素
  • 该集合中保存的都是引用类型,即便保存了数组123,也保存的是Integer类型的123,而不是int类型的123
  • 该集合查询效率高,中途增加和删除元素效率低
构造方法
常用构造方法 说明
ArrayList() 创建一个Object类型的空数组。在调用添加方法后,才会更改该数组大小为10
ArrayList(int initialCapacity) 创建一个指定容量的Object数组,如果参数为负,会抛出IllegalArgumentException异常
常用方法

ArrayList实现类的常用方法就是Collection接口和List接口中定义的方法。

LinkedList实现类

  • 采用双向链表实现的集合
  • 集合中保存的每个元素也称为节点,除首尾节点外,其余节点都保存了自己的信息外,还保存了其前一个和后一个节点的地址
  • 如果在双向链表的数据结构中插入和删除操作节点时,不会影响其他节点的位置。如添加时新节点时,只需要重写定义新节点的前后节点位置即可
  • 如果要查询某个节点时,需要从头结点或尾结点开始一步步得到目标节点的位置
  • 双向链表在中间插入和删除的效率高,随机读取的效率低
构造方法
常用构造方法 说明
LinkedList() 创建一个空链表
常用方法

由于LinkedList既实现了List接口,又实现了Deque接口,所以还有Deque接口中的一些方法

实现Deque接口的方法 说明
addFirst(Object obj) 添加头元素
addLast(Object obj) 添加尾元素
removeFirst() 移除头元素
removeLast() 移除尾元素
getFirst() 得到头元素
getLast() 得到尾元素
remove() 移除头元素
pop() 移除头元素
push(Object obj) 添加头元素
peek() 得到头元素
poll() 移除头元素
offer(Object obj) 添加尾元素

ArrayList和LinkedList的区别

  • 这两个类都是List接口的实现类,保存的元素有序可重复,允许保存null
  • ArrayList采用数组实现,随机读取效率高,插入删除效率低,适合用于查询
  • LinkedList采用双向链表实现,插入删除时不影响其他元素,效率高,随机读取效率低,适合用于频繁更新集合

Set接口(无序不重复)

无序集合,元素不可以重复,允许保存null,没有索引。
Set接口中没有自己定义的方法,都是继承于Collection接口中的方法

哈希表hash table

哈希表,也称为散列表,是一种数据结构,能更快地访问数据。

要保存的数据称为原始值,这个原始值通过一个函数得到一个新的数据,这个函数称为哈希函数,这个新数据称为哈希码,哈希码和原始值之间有一个映射关系,这个关系称为哈希映射,可以构造一张映射表,这个表称为哈希表。在哈希表中,可以通过哈希码快速地访问对应的原始值。

假设原本的数据为左侧的数组。

如果要查询10,需要遍历数组,效率不高。

通过一个特定的函数"原始值%5",得到一组新数据,让新数据重新对应元素,保存到“新数组”中,这个“新数组”称为哈希表。

这时如果要查询10,由于哈希函数是通过%5得到了0,所以直接查询哈希表中0对应的元素即可。

整个过程中,这个函数称为哈希函数,得到的新数据称为哈希码,新数组称为哈希表,对应关系称为哈希映射。

这个哈希函数,有一定的几率让多个原始值得到相同的哈希码,这种情况称为哈希冲突(哈希码一致,实际值不同),

为了解决哈希冲突,可以使用"拉链法",将2这个哈希码所在的位置向链表一样进行延伸。

哈希码的特点
  • 如果两个对象的hashCode不同,这两个对象一定不同
  • 如果两个对象的hashCode相同,这两个对象不一定相同
    • hashCode相同,对象不同,这种现象称为哈希冲突
    • 通话重地这两个字符串的hashCode相同,但是两个不同的对象

HashSet实现类

  • 采用哈希表实现
  • 元素不能重复,无序保存,允许保存一个null
  • 本质是一个HashMap对象
  • 使用HashSet集合时,通常要重写实体类中的equals和hashcode方法
构造方法
常用构造方法 说明
HashSet() 创建一个空集合,实际是创建一个HashMap对象。
常用方法

HashSet中没有属于自定义的方法,都是重写了父接口Set和Collection中的方法。这里参考Collection中的方法即可。

没有与索引相关的方法。

HashSet添加数据的原理

如果两个元素的hashCode相同且equals结果为true,视为同一个对象,不能添加。

每次向集合中添加元素时,先判断该元素的hashCode是否存在

  • 如果不存在,视为不同对象,直接添加
  • 如果存在,再判断equals方法的结果
    • 如果false,视为不同对象,可以添加
    • 如果true,视为同一对象,不能添加

由此可见,不能添加的条件是两个对象的hashCode相同且equals的结果为true。

如果每次只判断equals的话,由于equals方法通常重写时会判断很多属性,效率不高。

如果每次只判断hashCode的话,效率高,但有可能会有哈希冲突,

所以先判断hashCode,再判断equals,技能保证效率,又能保证不添加重复元素。

equals方法和hashCode的关系

  • 如果两个对象的equals方法结果为true,在没有重写equals方法的前提下,hashCode相同吗

    • 如果没有重写equals,默认是Object中使用==判断,如果结果为true,说明是同一个对象,hashCode一定相同
  • 如果两个对象的hashCode不同,在没有重写equals方法的前提下,equals方法的结果为?

    • hashCode不同,说明不是同一个对象,没有重写equals,说明使用Object中equals的==判断,结果为false
  • 如果两个对象的hashCode相同,equals方法的比较结果为?

    • 可能为true也可能为false
    String str1="hello";String str2="hello";//以上两个字符串使用同一个地址,hashCode相同,equals方法为trueString str3="通话";String str4="重地";//以上连个字符串是不同地址,但hashCode相同,因为哈希冲突,equals方法为false```

Map接口

Map称为映射,数据以键值对的形式保存。保存的是键与值的对应关系。

键称为Key,值称为Value,键不能重复,键允许出现一个null作为键,值无限制。

键和值都是引用类型。

如,yyds就是一个键key,代表了一个含义:“永远单身”即为值value。

常用方法 作用
size() 得到键值对的数量
clear() 清空所有键值对
put(Object key,Object value) 向集合中添加一组键值对
get(Object key) 在集合中根据键得到对应的值
remove(Object key)/remove(Object key,Object key) 根据键或键值对移除
keyset() 获取键的集合
values() 获取值的集合
containsKey(Object key) 判断是否存在某个键
containsValue(Object value) 判断是否存在某个值
entrySet() 得到键值对的集合

HashMap实现类(重点)

  • JDK1.8之后,HashMap采用"数组+链表+红黑树"实现

    • 当没有哈希冲突时,元素保存到数组中
    • 如果出现哈希冲突,在对应的位置上创建链表,元素保存到链表中
    • 如果链表的长度大于8,将链表转换为红黑树
  • 数据采用键值对key-value的形式保存,键不能重复,能用null作为键;值没有限制,键和值都是引用类型
  • 向HashMap集合中添加元素时,原理同HashSet

构造方法

常用构造方法 说明
HashMap() 创建一个空的映射集合,默认大小为16,加载因子为0.75

常用方法

常用方法参考Map中的方法

遍历集合中元素的方式

遍历List集合

ArrayList<String> nameList = new ArrayList();
nameList.add("Tom");
nameList.add("Jerry");
nameList.add("LiHua");
nameList.add("Danny");

方式一:普通for循环

System.out.println("使用普通for循环遍历");
//方式一:普通for循环
for (int i = 0; i < nameList.size(); i++) {//从0遍历到size()String name = nameList.get(i);//通过get(int index)获取指定索引的元素System.out.println(name);
}

方式二:增强for循环

System.out.println("使用增强for循环遍历");
//方式二:增强for循环
for (String name : nameList) {System.out.println(name);
}

方式三:迭代器

System.out.println("使用迭代器遍历");
//方式三:迭代器
//Collection类型的集合对象.iterator(),获取迭代器
Iterator<String> iterator = nameList.iterator();
// iterator.hasNext()判断集合中是否还有下一个元素
// iterator.next();获取下一个元素
while (iterator.hasNext()) {String name = iterator.next();System.out.println(name);
}

遍历Set集合

Set hs = new HashSet();
hs.add(123);
hs.add("hello");
hs.add(null);
hs.add(987);

方式一:增强for循环

for(Object o : hs){System.out.println(o);
}

方式二:迭代器

Iterator<Object> it = hs.iterator();while(it.hasNext()){System.out.println(it.next());
}

遍历Map集合

Map<Integer, User> hm = new HashMap<>();User u1 = new User("admin", "123123");
User u2 = new User("tom", "123123");
User u3 = new User("jerry", "123123");
hm.put(1001, u1);
hm.put(1002, u2);
hm.put(1003, u3);//遍历hashMap
for (Integer id : hm.keySet()) {//遍历键//根据键得到对应的值System.out.println(id + "\t" + hm.get(id).getUsername());
}//得到当前hashmap对象中的所有键值对的集合
Set<Map.Entry<Integer, User>> entries = hm.entrySet();
//遍历键值对
for (Map.Entry<Integer, User> entry : entries) {System.out.println(entry);
}

泛型

一种规范,常用于限制集合中元素的类型,省去遍历元素时判断是否为对应类型和转型的过程

//集合在定义后,默认可以添加任意类型的数据,但通常情况下,都是保存同一种类型
List  list = new ArrayList();
list.add(123);
list.add(null);
list.add("hello");//这时如果没有限制类型,使用增强for循环遍历集合中的元素时,就只能使用Object类型变量接收
for(Object o : list){}

用法

在定义集合遍历时,在类后面写上**<引用数据类型>**

集合类或接口<引用数据类型> 集合变量名 = new 集合实现类();

List<String> list = new ArrayList();
//当前集合只能保存String类型的元素
list.add("sdfsdf");
//list.add(123);//无法添加List<Integer> list2 = new ArrayList();
list2.add(123);

Collections集合工具类

  • Collection是集合的根接口,定义了集合操作元素的方法
  • Collections是集合的工具,定义了集合操作元素的静态方法

常用方法

常用方法 说明
Collections.shuffle(List list) 打乱List集合中元素的顺序
Collections.sort(List list) 对List集合中的元素进行排序,元素必须实现Comparable接口
Collections.swap(List list,int a,int b) 交换List集合中元素的索引
Collections.replaceAll(List list,Object oldObj,Object newObj) 替换List集合中的旧元素为新元素
Collections.reverse(List list) 将List集合中的元素反转
Collections.fill(List list , Object obj) 使用指定元素填充List集合
Collections.rotate(List list , int n) 将集合中最后n个元素放在最前
Collections.max(Collection col)/min(Collection col) 得到集合中的最大/最小值,集合中的元素必须实现Comparable接口

集合和数组之间的转换

  • 集合转换为数组:使用Collection接口中的toArray()方法

    Object[] obj = 集合对象.toArray();List<Integer> list = new ArrayList();
    list.add(123);
    list.add(63);
    list.add(3);Integer[] nums =(Integer[]) list.toArray();
    
  • 数组转换为集合

    //一个数组对象
    int[] nums ={11,2,66,3,6,21};
    //定义集合对象
    List list = new ArrayList();
    //遍历数组的同时添加到集合中
    for(int i:nums){list.add(i);
    }
    
  • 一组数据转换为集合:使用Arrays工具类中的asList(一组数据)方法

    //通常将数组中的数据直接作为参数
    List<String> strings = Arrays.asList("XX", "aa", "qq", "xx");
    

文件类File

Java中的File类,表示本地硬盘中的文件(文件和目录)的一个类。

通过这个类创建的对象,可以操作对应的文件。

构造方法

常用构造方法 说明
File(String pathName) 根据文件的完整路径创建File对象
File(String parent,String child) 根据文件的父目录路径和自身路径创建File对象
File(File parent,String child) 根据文件的父目录对应的File对象和自身路径创建File对象

常用方法

常用方法 说明
exists() 判断文件是否存在
isFile() 判断是否为文件
isDirectory() 判断是否为目录
getName() 获取文件名
getPath() 获取文件相对路径
getAbsolutePath() 获取文件绝对路径
getParent() 获取父目录的名称
getParentFile() 获取父目录对象
lastModified() 获取最后一次修改时间对应的毫秒数
length() 获取文件所占字节
isHidden() 判断文件是否隐藏
delete() 删除文件或空目录
renameTo(File newFile) 将原文件重命名且移动到指定目录
mkdir() 创建目录
list() 获取某个目录下的第一层子文件的名称的数组
listFiles() 获取某个目录下的第一层子文件对象的数组

IO

I:Input输入

O:Output输出

流Stream

在Java中,流用于表示计算机硬盘与内存之间传输数据的通道。

内存中的数据存入到硬盘中,称为写write,也称为输出Output

硬盘中的数据存入到内存中,称为读read,也称为输入Input

流的分类

Java中将流定义为类,以对象的形式表现流。流有"四大家族",是所有流的父类。

字节输入流InputStream

FileInpuStreamObjectInputStream

字节输出流OutputStream

FileOutputStreamObjectOutputStream

字符输入流Reader

FileReader、BufferedReader、OutputStreamWriter

字符输出流Writer

FileWriter、BufferedWriter、InputStreamReader

按方向分类

  • 输入流:InputStream、Reader

    • 将硬盘中的数据读取到内存中
  • 输出流:OutputStream、Writer

    • 将内存中的数据写入到硬盘中

按类型分

  • 字节流:InputStream、OutputStream

    • 读写非文本类型文件。如图片、音视频、其他文件等。
  • 字符流:Reader、Writer
    • 读写纯文本类型文件。如txt、md等

如要将硬盘中某个txt文件中的内容读取到程序中,使用Reader

如要将硬盘中的某个图片读取到程序中,使用InputStream

如要将程序中的文本写入到硬盘中为txt类型文件时,使用Writer

如要将程序中的数据写入到硬盘中为非文本文件时,使用OutputStream

流的四个父类的特点

  • 这四个父类都是在java.io包下,都是抽象类,不能直接创建其对象,使用其子类创建对象
  • 这四个父类中都定义了close()方法,用于关闭流对象,释放资源
  • 输入流(InputStream和Reader)都有read()方法读取数据到内存中,输出流都有write()方法写入数据到硬盘中
  • 输出流(OutputStream和Writer)都有flush()方法,用于将流中的数据冲刷到硬盘中
    • 在使用输出流对象时,一定要调用flush()或close()方法后,才能真正将数据写入到硬盘中
  • 所有的流中,以Stream结尾,都是字节流,数据以字节传输;以Reader或Writer结尾的,都是字符流,数据以字符传输
  • 读取硬盘中的数据,使用输入流,读取的文件必须存在;将数据写入到硬盘中,使用输出流,文件可以不存在,但父目录必须存在。
  • 读入或写入文本时,使用字符流;读取或写入非文本时,使用字节流

FileInputStream文件字节输入流(重点)

按字节读取硬盘中的文件。

构造方法

常用构造方法 说明
FileInputStream(String pathName) 根据文件名创建流对象
FileInputStream(File file) 根据文件对象创建流对象

常用方法

常用方法 说明
read() 读取一个字节,返回读取到的字节
read(byte[] bytes) 按字节数组读取,返回读取到的字节数量,读取到的内容保存在字节数组中
close() 关闭流对象

FileOutputStream文件字节输出流(重点)

按字节将内存中的数据写入到硬盘中。

构造方法

常用构造方法 说明
FileOutputStream(String pathname) 根据文件名创建输出流对象,写入时覆盖原内容
FileOutputStream(String pathname,boolean append) 根据文件名创建输出流对象,第二个参数为true,写入时追加在原内容之后
FileOutputStream(File file) 根据文件对象创建输出流对象,写入时覆盖原内容
FileOutputStream(File file,boolean append) 根据文件对象创建输出流对象,第二个参数为true,写入时追加在原内容之后

常用方法

常用方法 作用
write(int i) 写入一个指定字节
write(byte[] bytes) 写入一个字节数组
write(byte[] bytes,int off,int len) 写入字节数组中从off开始的len个字节
flush() 将流中的数据冲刷到硬盘中
close() 关闭流对象

使用FileInputStream和FileOutputStream读写时的注意事项

  • 在通过FileInputStream对象使用read(byte[] bytes)方法时,每次读取指定数组的字节,将读取到的字节保存在字节数组中,该方法返回读取到的字节数量。如果最后一次读取的字节数不足字节数组的大小时,只会将读取到内容覆盖数组中最前的几个元素。所以会导致读取到的内容多于实际内容。

  • 在通过FileOutputStream对象使用write(byte[] bytes)方法时,会将字节数组中的所有内容写入到输出流中,在最后一次写入时,可能会写入多余的内容。所以在写入时,最好使用write(byte[] bytes,int off,int lef)方法,表示将字节数组中的内容,从off开始写入len个。

    如有word.txt文件,其中保存aaabbbccc

    FileInputStream fis = new FileInputStream("d:/word.txt");
    FileOutputStream fos = new FileOutputStream("d:/copy.txt");byte[] bytes = new byte[4];
    //第一次读取4个字节,即aaab,count为4
    int count=fis.read(bytes);
    //写入数组中的全部内容
    fos.write(bytes);
    //第二次读取4个字节,即bbcc,count为4
    count=fis.read(bytes);
    //写入数组中的全部内容
    fos.write(bytes);
    //第三次读取1个字节c,覆盖数组中的第一个元素,即数组现在为cbcc,count为1
    count=fis.read(bytes);
    //写入数组中的全部内容
    fos.write(bytes);//最终会写入aaabbbcccbcc
    fos.write(bytes,0,count);//这样最后一次只会写入实际读取到的cfos.close();
    fis.close();
    

使用FileInputStream和FileOutputStream实现单文件的复制

package com.hqyj.IOTest;import java.io.*;public class CopyFile {public static void main(String[] args) throws IOException {//定义原文件和目标文件File source = new File("F:\\221001\\录屏\\FileInputStream和FileOutputStream.mp4");File target = new File("F:\\221001\\copy.mp4");//定义文件字节输入流,用于读取原文件FileInputStream fis = new FileInputStream(source);//定义文件字节输出流,用于写入文件FileOutputStream fos = new FileOutputStream(target);/*//调用无参的read()方法,表示读取一个字节,返回读取到的字节int read = fis.read();//如果能读取到内容while (read > -1) {//将读取到的内容写入到文件中fos.write(read);//继续读取read = fis.read();}*///定义一个字节数组,大小为8MBbyte[] bytes = new byte[1024 * 1024 * 8];//按字节数组读取,返回读取到的字节数量int count = fis.read(bytes);//循环读取写入while (count > -1) {//将读取的字节数组写入到文件中// fos.write(bytes);//如果调用该方法,最后一次会多写入上一次残留的数据fos.write(bytes,0,count);//如果调用该方法,实际读取到了多少字节就写入多少count = fis.read(bytes);}fis.close();fos.close();if (target.exists()) {System.out.println("复制成功");}}
}

文件夹的复制

package com.hqyj.IOTest;import java.io.*;public class CopyDirectory {public static void main(String[] args) {/*File source = new File("F:\\221001\\录屏\\流的基本概念.mp4");File target = new File("F:\\221001\\copy.mp4");copyFile(source, target);*/File source = new File("F:\\221001\\笔记");File target = new File("F:\\221001\\笔记副本");/** source    F:\221001\笔记* target    F:\221001\笔记副本* 1.调用copyDir方法,判断发现source是一个文件夹,创建目标文件夹target:“F:\221001\笔记副本”* 2.遍历source,如其中有xxx.md文件,即child* 此时的source是F:\221001\笔记\xxx.md,即child* 此时的target是F:\221001\笔记副本\xxx.md,用File(File parent,String child)构造方法表示这个目标文件* 所以创建File newTarget = new File(target,child.getName())** */copyDir(source, target);}/** 定义复制文件夹的方法* */public static void copyDir(File source, File target) {//如果是文件,调用单文件复制的方法if (source.isFile()) {copyFile(source, target);} else {//如果是文件夹//创建要复制的目标文件夹target.mkdir();//展开原文件夹for (File child : source.listFiles()) {//定义复制后的新目标文件//如source为F:\221001\笔记\day1.md时,递归调用的target为F:\221001\笔记副本\day1.mdFile newTarget = new File(target, child.getName());//这里使用File(File parent,String child)构造方法创建target对象//递归调用的原文件依然是当前遍历出来的子文件,目标文件就是最终复制的F:\221001\笔记副本\day1.mdcopyDir(child, newTarget);}}}/** 定义单文件复制的方法* */public static void copyFile(File source, File target) {FileInputStream fis = null;FileOutputStream fos = null;try {//创建用于输入输出的流对象fis = new FileInputStream(source);fos = new FileOutputStream(target);//定义字节数组byte[] bytes = new byte[1024 * 1024 * 8];//按数组读取int count = fis.read(bytes);while (count != -1) {fos.write(bytes, 0, count);count = fis.read(bytes);}} catch (FileNotFoundException e) {System.out.println("文件不存在" + e);} catch (IOException e) {System.out.println("读写异常" + e);} finally {try {if (fis != null) {fis.close();}if (fos != null) {fos.close();}} catch (IOException e) {System.out.println("关闭流对象异常" + e);}}}
}

断点调试执行细节

FileReader文件字符输入流

按字符读取文件。

构造方法

常用构造方法 说明
FileReader(String fileName) 根据文件名创建文件字符输入流对象
FileReader(File file) 根据文件对象创建文件字符输入流对象

常用方法

常用方法 作用
ready() 判断是否还有下一个字符
read() 读取下一个字符,返回读取到的字符
read(char[] chars) 按字符数组读取,返回读取到的字符数量,读取到的字符保存在字符数组中
close() 关闭流对象

FileWriter文件字符输出流

按字符写入文件。

构造方法

常用构造方法 作用
FileWriter(String fileName) 按文件名创建字符输出流对象,覆盖写入
FileWriter(String fileName,boolean append) 按文件名创建字符输出流对象,如果append为true,表示追加写入
FileWriter(File file) 按文件对象创建字符输出流对象,覆盖写入
FileWriter(File file,boolean append) 按文件对象创建字符输出流对象,如果append为true,表示追加写入

常用方法

常用方法 作用
write(String str) 按字符串写入
flush() 将流中的数据冲刷到硬盘中的文件,必须调用该方法或close方法后,才能真正写入
close() 关闭流对象

BufferedReader缓冲字符输入流(重点)

自带缓冲区(字符数组)的字符输入流。默认字符数组大小为8192,每次最多读取8192个字符。

在读取纯文本文件(txt或md)时,首选该类。

构造方法

常用构造方法 作用
BufferedReader(Reader in) 创建一个带有缓冲区(大小为8192的char数组)的字符输入流对象,参数为Reader类型对象,Reader是抽象类,所以实际参数为Reader的子类,如FileReader,在FileReader对象中定义要读取的文件
BufferedReader(Reader in,int size) 创建一个指定缓冲区(字符数组)大小的字符输入流对象

常用方法

常用方法 作用
ready() 判断是否还有字符
readLine() 读取整行字符
close() 关闭流对象

读取文本练习

package com.hqyj.ReaderAndWriter;import java.io.*;public class Test2 {public static void main(String[] args) throws IOException {/*File file = new File("F:\\221001\\笔记\\Java基础回顾.md");//FileReader(File file)Reader fr = new FileReader(file);//BufferedReader(Reader in)BufferedReader br = new BufferedReader(fr);*///创建带有缓冲区的字符输入流对象BufferedReader br = new BufferedReader(new FileReader("F:\\221001\\笔记\\Java基础回顾.md"));//循环判断是否还有字符while (br.ready()) {//读取整行System.out.println(br.readLine());}//关闭最大的流对象即可br.close();}
}

BufferedWriter缓冲字符输出流(重点)

自带缓冲区(字符数组)的字符输出流

构造方法

常用构造方法 说明
BufferedWriter(Writer writer) 创建一个自带缓冲区的字符输出流对象,参数为一个Writer对象,Writer是一个抽象类,实际参数为Writer的子类,如FileWriter,在FileWriter中定义要将输入写入的目标文件
BufferedWriter(Writer writer,int size) 创建一个指定缓冲区大小的字符输出流对象

常用方法

常用方法 作用
write(String str) 写入字符串
newLine() 换行
flush() 冲刷流中的数据到硬盘
close() 关闭流对象

写入文本练习

package com.hqyj.ReaderAndWriter;import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.SimpleFormatter;public class Test3 {public static void main(String[] args) throws IOException {File file = new File("221001.txt");//创建缓冲字符输入流对象,读取文本BufferedReader br = new BufferedReader(new FileReader(file));//创建集合,保存读取到的姓名ArrayList<String> list = new ArrayList<>();//循环读取文件中的所有字符while (br.ready()) {String name = br.readLine();list.add(name);}//关闭br.close();//打乱集合中的元素Collections.shuffle(list);//创建日期字符串String today = new SimpleDateFormat("yyyy.MM.dd").format(new Date());//创建缓冲字符输出流,用于写文本,文件名为"日期+作业情况.txt",如果每次都是新建,这样写// BufferedWriter bw = new BufferedWriter(new FileWriter(today + "作业情况.txt"));//如果要追加,在new FileWriter("文件名",true)设置BufferedWriter bw = new BufferedWriter(new FileWriter(today + "作业情况.txt",true));//写入字符串bw.write("姓名\t\t是否完成");//换行bw.newLine();Scanner sc = new Scanner(System.in);//随机3个人for (int i = 0; i < 3; i++) {String name = list.get(i);System.out.println(name + "完成情况:");String str = sc.next();//写入读取到的内容bw.write(name + "\t\t" + str);//换行bw.newLine();}bw.close();}
}

ObjectOutputStream对象字节输出流(序列化)(掌握)

序列化:将对象转换为文件的过程

被序列化的对象,必须要实现Serializable接口。

这个接口是一个特殊的接口,没有定义任何方法,只是给该类加上标记,表示该类可以被序列化

构造方法

构造方法 说明
ObjectOutputStream(OutputStream os) 创建一个对象字节输出流对象,参数为一个字节输出流对象,由于OutputStream是抽象类,所以使用其子类,如FileOutputStream对象,在其中定义要写入的文件

常用方法

常用方法 作用
writeObject(Object obj) 将一个对象写入到本地文件中
close() 关闭流对象

ObjectInputStream对象字节输入流(反序列化)(掌握)

反序列化:将文件转换为对象的过程

构造方法

常用构造方法 说明
ObjectInputStream(InputStream is) 创建一个对象字节输入流对象,参数为一个字节输入流对象,由于InputStream是抽象类,所以使用其子类,如FileInputStream对象,在其中定义要读取的文件

常用方法

常用方法 作用
readObject() 读取序列化后的文件,返回类型为Object
close() 关闭流对象

序列化和反序列化案例

Person类,实现Serializable接口

package com.hqyj.ObjectStream;import java.io.Serializable;
/*
* 如果希望该类的对象能序列化,写入对象到本地,必须要实现Serializable接口
* Serializable接口中没有任何方法,是一个标记接口,表示该类的对象可以被序列化
* */
public class Person implements Serializable {private String name;private int age;private String sex;//省略getter/setter和toString()
}

Main类

package com.hqyj.ObjectStream;import java.io.*;
import java.util.ArrayList;public class Test1 {public static void main(String[] args) throws IOException, ClassNotFoundException {Person p1 = new Person("王海", 22, "男");Person p2 = new Person("赵敏", 24, "女");Person p3 = new Person("刘涛", 21, "女");ArrayList<Person> list = new ArrayList<>();list.add(p1);list.add(p2);list.add(p3);//创建OutStream的实现类,设置写入的文件路径OutputStream os = new FileOutputStream("F:\\221001\\person.p");//创建对象输出字节流,参数为OutStream类型ObjectOutputStream oos = new ObjectOutputStream(os);//调用writeObject(Object obj)方法,将对象写入到硬盘中(序列化)oos.writeObject(list);oos.close();//创建对象输入字节流,将上一步保存的文件进行反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("F:\\221001\\person.p"));//使用readObject()方法,将写入的文件进行读取(反序列化)ArrayList<Person> pList = (ArrayList<Person>) ois.readObject();for (Person person : pList) {System.out.println(person);}ois.close();}
}

转换流

实际属于字符流,作用为将一个字节流对象转换为字符流对象

OutputStreamWriter

将字节输出流转换为字符输出流

InputStreamReader

将字节输入流转换为字符输入流

转换流的使用

如果只提供了一个字节流,但要向其中写入或读取字符时,就可以使用转换流将字节流转换为字符流。

使用字符流读写字符时比字节流更方便。

//假如只提供一个字节输出流对象
FileOutputStream fos = new FileOutputStream("文件路径");
//fos.write(97);//这时如果写入数据,只能按字节写入,不方便//使用转换流,将字节流对象fos转换为字符流对象
Writer writer = OutputStreamWriter(fos);
//将字符流对象writer包装成缓冲字符流对象
BufferedWriter bw = new BufferedWriter(writer);
bw.write("hello你好");
bw.newLine();bw.close();//只提供字节输入流对象
FileInputStream fis = new FileInputStream("221001.txt");// fis.read()每次只能读取一个字节
//将字节流转换为字符流
Reader reader = new InputStreamReader(fis);
//创建缓冲字符流,将字符流包装为缓冲流
BufferedReader br = new BufferedReader(reader);
//整行读取
while (br.ready()) {System.out.println(br.readLine());
}
br.close();

网络编程

InetAddress类

表示IP的一个类

package NetTest;import java.net.InetAddress;
import java.net.UnknownHostException;public class Test1 {public static void main(String[] args) throws UnknownHostException {//获取本机ip对象//InetAddress ip=InetAddress.getLocalHost();//getByName(域名) 得到域名对应的ip对象//localhost域名表示本机,对应的ip地址为127.0.0.1InetAddress ip = InetAddress.getByName("localhost");//获取域名System.out.println(ip.getHostName());//获取ip地址System.out.println(ip.getHostAddress());}
}

Socket类和ServerSocket类

都属于Socket(套接字)对象,表示网络中的某个断点

  • Socket指普通端点

  • ServerScocket指服务器端

进程和线程

进程Process

进程就是操作系统中执行的程序。一个程序就是一个执行的进程实体

每个运行中的进程,都要属于它的进程

线程Thread

线程是一个进程中的执行单元,一个进程中可以有多个线程。

多个线程,可以访问同一个进程中的资源。

每个线程都有一个独立的栈空间,这些线程所在的栈空间位于同一个进程空间中

多线程

如果一个进程中,同时执行多个线程,称为多线程

多线程可以提高程序执行效率。如多个窗口卖票,可以加快卖票效率

其实每个执行的java程序都是多线程执行,main方法称为主线程,还有gc线程(守护线程)在同时进行

如有一个工厂,工厂中有很多车间,每个车间有很多流水线

工厂就是内存,车间就是各个进程,每个流水线都是一个进程中的一个线程

并行和并发

并行

各个进程同时执行,称为并行。

并发

多个线程同时执行,称为并发

同步和异步

同步

所有的任务排队执行,称为同步执行

异步

在执行任务A的同时,执行任务B,称为异步执行

Java中的线程

java中,线程以对象的形式存在。

Thread表示线程类

获取线程对象

  • 获取当前正在运行的线程对象
Thread ct=Thread.currentThread();
  • 创建一个线程对象

构造方法

常用构造方法 说明
Thread() 创建一个默认的线程对象
Thread(String name) 创建一个指定名称的线程对象
Thread(Runnable target) 将一个Runnable对象包装为线程对象
Thread(Runnable target,String name) 将一个Runnable对象包装为线程对象同时设置线程名

线程常用方法

方法 作用
getId() 获取线程id
getName() 获取线程名
getPriority() 获取线程优先级
getState() 获取线程状态
setName(String str) 设置线程名
setProority(int priority) 设置线程优先级i,范围在1~10,值越大越先执行
setDaemon(boolean f) 参数为true表示设置线程为守护线程
start() 让线程进入就绪状态
run() 线程获得执行权时执行的方法
Thread.sleep(long m) 设置当前线程休眠m毫秒
Thread.currentThread() 获取当前执行的线程对象
Thread.yield() 线程让步

实现多线程

方法一:定义一个类,继承Thread类

  • 1.创建一个类,继承Thread类

  • 2.重写Thread类中的run方法

  • 3.创建自定义的线程子类,调用start()方法

自定义线程类

package Thread;/*
* 实现多线程步骤
* 1.成为Thread的子类
* 2重写run()方法
* */
public class MyThread extends Thread {public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {Thread ct = Thread.currentThread();for (int i=0;i<100;i++){System.out.println(ct.getName()+":"+i);}}}

main类

package Thread;public class Test2 {public static void main(String[] args) {//创建自定义线程对象MyThread t1= new MyThread();t1.setName("线程1");MyThread t2 = new MyThread("线程2");t1.start();t2.start();}
}

方拾二:实现Runnable接口

由于Java是单继承,如果某个类以及使用了extends关键字去继承另一个类,这时候就不能通过extends继承Thread实现多线程,就需要实现Runnable接口的方式实现多线程

  • 1.自定义一个类,实现Runnable接口

  • 2.重写run()方法,将多线程

  • 3.创建Runnable接口的实现类对象

  • 4…使用构造方法Thread(Runnable target)或Thread(Runnable target,String name将上一步的对象包装成Thread对象

package Thread;
/*
* 实现多线程步骤
* 1.成为Runnable的实现类
* 2.重写run()方法
* 3.
* */
public class MyThread2 implements Runnable{@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+":"+i);}}
}

main

  Runnable target = new MyThread2();//由于启动多线程必须通过Thread的start()方法,所以一定要创建Thread对象Thread t1 = new Thread(target);//这里使用Thread(Runnable target)创建Thread对象t1.start();

方式三:匿名内部类

线程的生命周期

线程的初始化到终止的整个过程,称为线程的生命周期

新生状态

当线程的对象被创建后,就进入了新生状态

就绪状态

当某个线程对象调用了start()方法后,就进入了就绪状态

在这个状态下,线程对象不会做任何事,只在等CPU调度

运行状态

当某个线程对象得到CPU时间片(CPU执行这个线程的机会所给的时间),则进入运行状态,开始执行run()方法

不会等待run()执行完毕。只会在指定的时间内尽可能执行run()方法。只要调用完run()方法后,就会再进入就绪状态。

阻塞状态

如果某个线程遇到了sleep()方法或wait()方法时,就会进入阻塞状态

sleep()方法会在指定时间后,让线程重新就绪

wait()方法只有在被调用notify()或notifyAll()方法唤醒后才能重新就绪

终止状态

当某个线程的run()方法中的所有内容都执行完,就会进入终止状态,意味着该线程的使命已经完成。

守护线程

如果将一个线程设置setDeamon(true),

多线程访问同一资源

可能出现的问题

如银行存款100,同一时刻在手机和ATM一起取出,如果用多线程模拟,可能会出现两个线程都取出100,与实际情况不符。要避免这种情况

如何解决

让线程同步(排队)执行即可。这样一来,某个线程执行run()方法的时候,让其他线程等待run()方法的内容执行完毕。

synchronized关键字

这个关键字可以修饰方法或代码块

修饰方法

写在方法返回值前,这时该方法就称为同步方法

 public synchronized void sell(){if (ticket>0){System.out.println(Thread.currentThread().getName()+"售出一张");ticket--;System.out.println(Thread.currentThread().getName()+"剩余"+ticket+"张");}else {System.out.println(Thread.currentThread().getName()+"已售罄");}}

修饰代码块

写在一个独立的{}前,这时该段内容称为同步代码块

synchronized(要同步的对象或this){//会排队执行的代码
}

原理

每个对象默认有一把锁,当某个对象运行到内sychronized修饰的方法时,该对象就会拥有这把锁,在拥有锁的过程中,其他线程不能同时访问该方法,只有等待其结束后,才会释放这把锁。

使用修饰后的锁称为“悲观锁”

方法被修饰后,称为同步方法,就会让原本的多线程变成了单线程(异步变同步)

多线程相关面试题

  • 实现多线程方式

    • 继承Thread类

    • 实现Runnable接口,包装为Thread对象

    • 匿名内部类

  • 为什么说StringBuilder或ArrayList、HashMap是非线程安全的

  • 什么叫死锁?怎么产生?如何解决?

如果两个人吃西餐,必须有刀和叉,此时只有一副刀叉

如果A拿到刀,B拿到叉,互相都在等另一个工具,但都不释放自己拥有的,这时候就会造成僵持的局面,这就叫死锁,既不结束,也不继续

死锁

模拟死锁出现的情况

定义两个线程类,线程A先获取资源A后,在获取资源B;线程B先获取资源B后,再获取资源A。

如果对资源A和资源B使用了synchronized进行同步,就会在线程A获取资源A的时候,线程B无法获取资源A,相反线程B在获取资源B的时候,线程A无法获取资源B,所以两个线程都不会得到另一个资源。

PersonA线程

package com.hqyj.deadlock;public class PersonA implements Runnable {//定义两个共享的成员变量,刀、叉private Object knife;private Object fork;public PersonA(Object knife, Object fork) {this.knife = knife;this.fork = fork;}/** 该线程执行run方法时,先获取knife对象,等待3s后获取fork对象** */@Overridepublic void run() {synchronized (knife) {System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (fork) {System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");}}}
}

PersonB线程

package com.hqyj.deadlock;public class PersonB implements Runnable {//定义两个共享的成员变量,刀、叉private Object knife;private Object fork;public PersonB(Object knife, Object fork) {this.knife = knife;this.fork = fork;}/** 该线程执行run方法时,先获取fork对象,等待3s后获取对象knife** */@Overridepublic void run() {synchronized (fork) {System.out.println(Thread.currentThread().getName() + "获取了fork,3s后获取knife");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (knife) {System.out.println(Thread.currentThread().getName() + "获取了knife,可以吃饭了");}}}}

死锁的解决方式

方式一

让两个线程获取资源的顺序保持一致。

如两个线程都先获取knife,再获取fork

@Override
public void run() {synchronized (knife) {System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (fork) {System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");}}
}

方式二

让两个线程在获取资源A和B之前,再获取第三个资源,对第三个资源使用synchronized进行同步,这样某个线程在获取第三个资源后,将后续内容执行完毕,其他线程才能开始执行。

如在获取knife和fork之前,先获取paper对象

@Override
public void run() {//先获取paper,再进行后续操作synchronized (paper) {synchronized (knife) {System.out.println(Thread.currentThread().getName() + "获取了knife,3s后获取fork");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (fork) {System.out.println(Thread.currentThread().getName() + "获取了fork,可以吃饭了");}}}
}

部分作业代码

package hardhomework;import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;public class Function {ArrayList<String> list = new ArrayList<>();//获取文件名public void fun(File file)  {if (file.isFile()){list.add(file.getName());}if (file.isDirectory()){for (File child : file.listFiles()) {fun(child);}}}//    返回编号集合public ArrayList<String> nolist(){ArrayList<String> list1=new ArrayList<>();for (String s : list) {int a=s.indexOf(" ");list1.add(s.substring(0, a));}return list1;}//    返回姓名集合public ArrayList<String> namelist(){ArrayList<String> list2=new ArrayList<>();for (String s : list) {int a=s.indexOf(" ");int b=s.indexOf(".");list2.add(s.substring(a,b));}return list2;}//添加学生对象public ArrayList<Student> addStudent(){ArrayList<Student> stulist = new ArrayList<>();for (int i=0;i<nolist().size();i++){stulist.add(new Student(nolist().get(i),namelist().get(i)));}return stulist;}}
package hardhomework;import java.io.Serializable;
import java.util.Objects;public class Student implements Serializable {private String no;private String name;public Student(String no, String name) {this.no = no;this.name = name;}@Overridepublic String toString() {return "Student{" +"no='" + no + '\'' +", name='" + name + '\'' +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return Objects.equals(no, student.no) &&Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(no, name);}public String getNo() {return no;}public void setNo(String no) {this.no = no;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package hardhomework;import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;public class Main {public static void main(String[] args) throws IOException, ClassNotFoundException {Main m = new Main();m.function();}public void function() throws IOException, ClassNotFoundException {Scanner sc = new Scanner(System.in);Function f = new Function();f.fun(new File("F:\\作业素材"));ArrayList<Student>  endlist=f.addStudent();System.out.println("1.存储学生对象\t2.查看学生对象");switch (sc.nextInt()){case 1:OutputStream os=new FileOutputStream("F:\\测试作业\\Student.p");ObjectOutputStream oos = new ObjectOutputStream(os);oos.writeObject(endlist);System.out.println("存储成功");os.close();oos.close();break;case 2:FileInputStream fis = new FileInputStream("F:\\测试作业\\Student.p");ObjectInputStream ois=new ObjectInputStream(fis);ArrayList<Student> s=( ArrayList<Student>)ois.readObject();for (Student stu : s) {System.out.println(stu);}fis.close();ois.close();break;}}
}

总结

Java高级部分也在这个星期结束了,高级部分主要还是学习定义好的类来实现我们所需要的的功能,其中的方法,属性等相当繁多,我会出现分不出来的情况,并且会出现一些问题不知道该用哪个类的哪个方法来解决,说到底还是代码量不够,就像老师说的,现目前学习的只是入门,想要有提升,就得自己去接着专研,这部分有许多知识点都是用的老师总结的,我自己也是边看总结边发现之前上课不是很懂或者没有注意到的,也有所新感悟和新发现,这也算一种学习吧,继续加油吧。

华清远见—重庆中心——JAVA高级阶段知识点梳理相关推荐

  1. 华清远见-重庆中心-JAVA高级阶段技术总结/个人总结

    目录 String字符串 定义 字符串对象的含义 关于字符串对象内存地址的问题 1.使用""赋值创建字符串对象 2.通过构造方法创建字符串对象 3.使用+拼接"" ...

  2. 华清远见-重庆中心-JAVA高级阶段技术总结/知识点梳理/面试题解析

    String字符串 String是一个类,属于数据类型中的引用类型. Java中一切使用""引起来的内容,都是这个类的实例,称为字符串对象. 字符串在定义后,值不可改变,是一个常量 ...

  3. 华清远见-重庆中心-JAVA高级阶段技术总结/知识点梳理/个人总结

    文章目录 String字符串 String类使用时注意 如何创建字符串对象 1.使用""赋值创建! 2.通过构造方法创建 不同方式创建字符串的过程 使用""赋值 ...

  4. 华清远见-重庆中心-JAVA高级阶段技术总结

    目录 String字符串 可变字符串 StringBuilder类 StringBuffer类 构造方法 普通方法 比较String.StringBuilder和StringBuffer的区别 Run ...

  5. 华清远见-重庆中心-Java高级阶段技术总结:

    Java容器分为Collection和Map两大类,其下又有很多子类. Collection还有父接口Iterable,但Iterable接口不算严格意义上的集合的根接口.它称为迭代器,是用于遍历集合 ...

  6. 华清远见-重庆中心-javaweb后端阶段知识点梳理

    JavaWeb 使用Java开发Web服务的技术,统称为JavaWeb. B/S与C/S模式 B/S:Browser/Server 浏览器/服务器模式 用户只需要一个浏览器即可访问服务器 C/S:Cl ...

  7. 华清远见-重庆中心-VSS前端阶段知识点梳理

    VSS 前端开发 1.html 1.1什么是html? Hyper Text Markup Language 超文本标记语言 超文本:有视频.音频.图片等,超越文本 标记语言:有一套标签 2.网页的基 ...

  8. 华清远见-重庆中心-JAVA基础阶段技术总结

    系列文章目录 第一章 华清远见--重庆中心-JAVA基础阶段技术总结 第二章 文章目录 系列文章目录 文章目录 前言 一.关于java 1.发展历程 2.编程开发 3.java架构 4.java的特点 ...

  9. 华清远见-重庆中心-JAVA面向对象阶段技术总结

    华清远见-重庆中心-JAVA面向对象阶段技术总结 面向对象和面向过程的编程思想 面向对象(OOP) 通过创建(new)对象,赋予对象对应的行为和特征,让这些对象相互配合来解决问题 面向过程(POP) ...

最新文章

  1. python3基础知识点总结_python基础知识点总结
  2. ECMAScript 和 JavaScript 的未来
  3. 支付宝人脸数据被共享?李开复道歉
  4. mysql5.6.24配置日志_windows下mysql5.6.x的日志正确配置方法(my.ini) (网上的都是5.6之前的版本)...
  5. React Native 和ionic2 你选择哪一个?
  6. numpy支持比python更多的数据类型_NumPy数据类型
  7. Java中用三种方法输出字符串_java中两个字符串连接的三种方法
  8. Android Media Playback 中的MediaPlayer的用法及注意事项(一)
  9. 13. 面向对象的概述
  10. vue—点击换一批就更换一批内容_Vue.js点击切换按钮改变内容的实例讲解
  11. 《C#图解教程》 总览
  12. c语言打印三角形//洛谷
  13. 从C++11到C++23(一) C++20圆周率、常数e和常见对数
  14. allegro中Externally Determined Violation错误标记去掉的skill
  15. Centos 修改镜像源为阿里云
  16. EtherCAT--01简介
  17. 内连接、外连接、全连接图示语法
  18. 如何识别64位和32位CPU
  19. node环境端口号被占用导致Vue报错:Error listen EADDRINUSE: address already in use 127.0.0.1:8888
  20. 狡猾的老鼠-循环C语言,狡猾的老鼠

热门文章

  1. LCP 05 发 LeetCoin
  2. LCP 18.早餐组合
  3. 学习技能速成课-观看历史以及观后感
  4. 安全合规--49--基于国内法律法规的企业数据合规体系建设经验总结(七)
  5. 学内核之十八:纸上得来终觉浅,绝知此事要躬行
  6. java actor_十分钟理解Actor模式
  7. 计算机八股文:操作系统总结
  8. vue.esm.js?efeb:571 [Vue warn]: Property or method subItem is not defined on the instance but refe
  9. webstorm mac破解版安装
  10. Cucumber的简单讲解