JAVA高级知识点和常用类小结
String字符串
String是一个类,属于引用类型。
Java程序中一切使用""引起来的内容,都是这个类的实例,称为字符串对象。
字符串定义后是一个常量,值不可更改。字符串实际是一个字符数组。
String类使用时注意
如果要频繁更改字符串中的值,建议使用StringBuilder类或StringBuffer类
如何创建一个字符串对象
1.使用""赋值创建
String str="hello你好";
2.通过构造方法创建
常用构造方法 | 说明 |
---|---|
String() | 创建一个空白字符串对象,实际创建一个空字符数组 |
String(String str) | 创建一个指定的字符串对象,实际是创建一个形参字符串的副本 |
String(char[] list) | 创建一个指定字符数组的字符串对象。 |
String(byte[] list) | 按默认编码格式创建一个指定字节数组的字符串对象。 |
String(byte[] list,String charsetName) | 按指定的编码格式创建一个指定字节数组的字符串对象。 |
``
字符串常用方法
方法名 | 返回值 | 作用 |
---|---|---|
length() | int | 获取字符串的长度 |
trim() | String | 去除字符串首尾的所有空格 |
toLowerCase() | String | 转换字符串为小写 |
toUpperCase() | String | 转换字符串为大写 |
isEmpty() | boolean | 判断是否为一个空字符串 |
getBytes() | byte[] | 按默认编码格式将字符串转换为字节数组 |
toCharArray() | char[] | 将字符串转换为字符数组 |
equalsIgnoreCase(String str) | boolean | 忽略大小写比较指定字符串是否相同 |
equals(String str) | boolean | 判断字符串是否相同 |
charAt(int index) | char | 获取index位置上的字符串 |
indexOf(String str) | int | 获取str第一次出现的位置,如果没有返回-1 |
lastIndexOf(String str) | int | 获取str最后一次出现的位置,如果没有返回-1 |
contains(字符序列) | boolean | 判断指定的字符序列(字符串)是否存在于原字符串中 |
concat(String str) | String | 将str拼接到原字符串末尾 |
startsWith(String str) | boolean | 判断是否以指定字符串开头 |
endsWith(String str) | boolean | 判断是否以指定字符串结尾 |
substring(int index) | String | 截取原字符串在[index,数组长度)区间内的字符。(从指定位置截取至末尾,包含指定位置) |
substring(int from,int to) | String | 截取原字符串在[from,to)区间内的字符。(从from截取至to,包含from不包含to) |
split(String reg) | String[] | 按指定字符串或正则表达式切分原字符串。如果指定内容不在末尾,n个指定字符能得到n+1个子串;如果指定内容在末尾,n个指定字符能得到n个子串(不包含末尾的无效字符)。 |
String.valueOf(参数) | String | 将一个参数转换为字符串,参数可以是原始类型,也可以是任意对象。 |
replace(char oldChar,char newChar) | String | 使用newChar替换oldChar |
可变字符串
String字符串对象是一个常量,在定义后其值不可改变。
如果使用String类的对象,对其频繁更新时,就会不停地创建新的对象,重新引用。
所以如果要执行1000次重新赋值的过程,就要创建1000个字符串对象,花费很多时间和内存空间,所以效率很低。这时就需要使用可变字符串类。
StringBuilder类
用于表示可变字符串的一个类,是非线程安全的,建议在单线程环境下使用,效率略高于StringBuffer。
StringBuffer类
用于表示可变字符串的一个类,是线程安全的,建议在多线程环境下使用,效率略低于StringBuilder。
StringBuilder和StringBuffer中的方法作用都一致,只不过StringBuffer中的方法使用了synchronized关键字修饰,表示一个同步方法,在多线程环境下不会出现问题。
所以这里以StringBuilder为例。
构造方法
常用构造方法 | 作用 |
---|---|
StringBuilder() | 创建一个大小为16的字符数组。类似于String str=“”; |
StringBuilder(int capacity) | 创建一个指定大小的字符数组 |
StringBuilder(String str) | 创建一个str长度+16的字符串数组后,将str添加到其中。类似于String str=“初始值”; |
普通方法
方法 | 作用 |
---|---|
append(Object obj) | 将指定内容添加到原可变字符串对象末尾 |
delete(int start,int end) | 删除[start,end)范围内的字符 |
deleteCharAt(int index) | 删除指定索引的字符 |
insert(int index,Object obj) | 将obj添加到index位置上 |
replace(int start,int end,String str) | 将[start,end)范围内的字符替换为str |
reverse() | 翻转原字符串 |
注意
-
以上方法都是在直接操作原字符串,每个方法调用后,原字符串都会发生变化。
-
StringBuilder或StringBuffer中并没有重写equlas方法,所以要比较两个可变字符串对象的值是否相同时,需要将可变字符串对象转换为String对象后,调用equals方法比较。
可变字符串与不可变字符串之间的转换
有些方法如indexOf()、charAt()等,在String和StringBuilder中都存在,可以不用转换。
但有些方法如getBytes()、contains()等,只能通过String调用,这时就需要进行转换。
不可变字符串转换为可变字符串
通过创建一个可变字符串对象,将不可变字符串作为参数实现转换
//定义一个不可变字符串对象
String str="hello";
//创建一个可变字符串对象,将不可变字符串对象作为参数
StringBuilder sb = new StringBuilder(str);
可变字符串转换为不可变字符串
通过调用可变字符串的toString()方法实现转换
//创建一个可变字符串对象
StringBuilder sb = new StringBuilder("hello");
//调用toString()转换为String类型
String str=sb.toString();
比较String、StringBuilder、StringBuffer这三个类的区别
相同点
- 这三个类都可以用于表示或操作一个字符串
- 这三个类中有公共的方法,如indexOf()、charAt()等、
- 这三个类都是被final修饰的类,不能被继承
不同点
- String定义的字符串是一个常量,不可变;StringBuilder和StringBuffer定义的字符串是一个变量,可以改变。
- String类中的方法调用后,会创建字符串副本,不会影响原字符串;可变字符串对象调用的方法,直接操作原字符串,会影响。
- StringBuilder是非线程安全的,StringBuffer是线程安全的,其中的方法使用synchronized修饰为同步方法。在多线程情景下,需要使用StringBuffer。StringBuilder效率略高于StringBuffer。
总结
在程序中,如果要频繁操作某个字符串,一定使用可变字符串类StringBuilder或StringBuffer创建对象。
System类
这个类包含了一些系统相关 的信息和操作数组的方法。其中的方法和属性都是静态的。
常用属性和方法 | 作用 |
---|---|
System.in | 获取系统输入流对象,通常用于获取输入信息 |
System.out | 获取系统打印输出流对象,通常用于打印普通信息 |
System.err | 获取系统打印输出流对象,通常用于打印异常信息 |
System.exit(int statues) | 终止虚拟机运行,0表示正常结束 |
System.getenv(String key) | 获取系统指定的环境变量信息 |
System.arraycopy(原数组,原数组起始位置,目标数组,目标数组起始位置,原数组要赋值的元素数量) | 将原数组中指定长度的元素复制到新数组中 |
System.currentTimeMills() | 获取从1970.1.1 0:0:0(UTC)至今经过了多少毫秒。中国是UTC(+8)所以是从1970.1.1 8:0:0至今经过了多少毫秒,返回long类型 |
Runtime类
Runtime类的对象,用于表示程序运行时对象(程序运行环境对象)。
包含了程序运行环境相关的信息。常用于获取运行环境信息(如虚拟机内存)或执行某个命令。
特点
这个类不是一个抽象类,但不能创建对象,因为其构造方法是私有的。
但是它提供了一个静态方法getRuntime(),通过这个方法,可以获取一个Runtime类的对象。
简单使用
Runtime类的对象,可以通过exec()方法执行一个指令,如notepad指令用于打开记事本,
或执行一个存在于硬盘中的.exe可执行文件。该方法的返回值是一个Process进程对象,
通过Process进程对象调用destory()方法可以销毁当前进程。
package com.hqyj;
import java.io.IOException;
public class Test4 {
public static void main(String[] args) throws IOException, InterruptedException {
//通过静态方法getRuntime()获取一个Runtime类的对象
Runtime runtime = Runtime.getRuntime();
//获取程虚拟机空闲内存,单位为字节
System.out.println("当前虚拟机空闲内存" + runtime.freeMemory() / 1024 / 1024 + "MB");
//获取虚拟机总内存
System.out.println("当前虚拟机最大内存" + runtime.totalMemory() / 1024 / 1024 + "MB");
//获取虚拟机支持的最大内存
System.out.println("当前虚拟机支持的最大内存" + runtime.maxMemory() / 1024 / 1024 + "MB");
//让系统运行某个指令或程序,返回当前运行的进程对象
Process mspaint = runtime.exec("mspaint");//打开画图工具
Thread.sleep(3000);
//通过进程对象调用destroy()销毁进程,关闭程序
mspaint.destroy();
runtime.exec("calc");//打开计算器
runtime.exec("notepad");//打开记事本
//打开某个可执行文件
runtime.exec("D:\\xiaopw84in1111\\disland.xiaopw84in1\\smynesc.exe");
//300s后关机
runtime.exec("shutdown -s -t 300");
//取消关机任务
runtime.exec("shutdown -a");
}
}
包装类
Java是纯面向对象语言,宗旨是将一切事物视为对象处理。
但原始类型不属于对象,不满足面向对象的思想,但原始类型使用时无需创建对象,保存在栈中,效率更改。
为了让原始类型也有对象的类类型,达到"万物皆对象"的思想,所以就有了包装类的概念。
包装类就是原始类型对应的类类型包装类通常用于将字符串转换为对应的原始类型。
在web应用中,从浏览器中获取到后台的数据,全是String类型,一定要使用转换的方法。
包装类 | 原始类型 | 将字符串转换为原始类型 |
---|---|---|
Byte | byte | Byte.parseByte(String str) |
Short | short | Short.parseShort(String str) |
Integer | int | Integer.parseInt(String str) |
Long | long | Long.parseLong(String str) |
Float | float | Float.parseFloat(String str) |
Double | double | Double.parseDouble(String str) |
Boolean | boolean | Boolean.parseBoolean(String str) |
Character | char | 无 |
特点
- 八个原始类型中,除了int和char之外,其余类型的包装类,都是将首字母改为大写。int为Integer,char为Character。
- 除了Character类之外,其余类都有至少两个构造方法:参数为原始类型或字符串的构造方法。Character的构造方法只有一个,参数为char变量。
- 除了Character类之外,其余类都有静态方法parse原始类型(String str),用于将字符串转换为相应的原始类型
- 数值型的包装类的parseXXX()方法,如果不是一个真正的对应类型的数,转换时会抛出NumberFormatException异常。如"123abc"、"123.456"都不能使用Integer.parseInt()转换
- Boolean类型中的parseBoolean()方法,参数如果是"true"这个单词,无论大小写,都能转换为真正的boolean值的true,只要不是"true"这个单词,转换结果都为false
- 除了Boolean类之外,其余类都有MAX_VALUE和MIN_VALUE这两个静态属性,用于获取对应原始类型支持的最大最小范围
- 所有包装类中都有一个**compareTo(参数1,参数2)**方法,用于比较两个参数
- 如果是数值型,参数1>参数2返回1,参数1<参数2返回-1,相同返回0
- 如果是Boolean型,两个参数相同返回0,不同时,如果参数1为true返回1,否则返回-1
- 如果是Character型,返回参数1-参数2的值。
- 所有包装类中都有toString()方法,用于将包装类对象转换为String字符串对象
装箱和拆箱
-
所有包装类都有一个静态方法valueOf(原始类型),将某个原始类型的数据转换为相应的包装类对象,这个过程称为装箱。
//手动装箱 int i=123;//定义一个原始类型的数据 Integer aInteger=Integer.valueOf(i);//调用包装类的valueOf()方法将原始类型转换为包装类对象
-
所有包装类都有一个原始类型Value()方法,用于将包装类对象转换为原始类型,这个过程称为拆箱
//手动拆箱 Integer aInteger=new Integer(123);//创建一个包装类对象 int i = aInteger.intValue();//调用包装类的"原始类型Value()"方法将其转换为原始类型
-
自动装箱拆箱。在jdk1.5之后,加入了自动装箱拆箱的特性,可以直接在原始类型和对应的包装类中互相赋值
//自动装箱 Integer aInteger=123; //自动拆箱 int i=aInteger;
字符串与原始类型之间的转换
原始类型转换为字符串
-
拼接一个空字符串
int num=123; String str=num+"";
-
String.valueOf(原始类型参数);
int num=123; String str=String.valueOf(num); System.out.println(str.length());
-
将原始类型转换为包装类后,调用toString()方法
Integer num=123; String str = num.toString();
字符串转换为原始类型
使用原始类型对应的包装类,调用其parseXXX(字符串)方法
String num="123";
int i=Integer.parseInt(num);
Date类
用于表示日期时间的类,位于java.util包下
构造方法
常用构造方法 | 说明 |
---|---|
Date() | 创建当前瞬间的日期对象 |
Date(long l) | 创建指定毫秒数对应的日期对象。(从1970.1.1起经过了的毫秒数) |
Date(int year,int month,int date) | 该构造方法已过时。创建指定年月日的日期对象。(年从1900年起经过的年数,月用0-11表示1-12月) |
常用方法
常用方法 | |
---|---|
getTime() | 得到对应Date对象指定的毫秒数。 |
setTime(long l) | 设置毫秒数 |
after(Date when) | 判断调用日期对象是否在when之后 |
before(Date when) | 判断调用日期对象是否在when之前 |
SimpleDateFormat类
是一个用于格式化日期的类。
构造方法
//参数为一个日期模板
SimpleDateFormat(String pattern);
//如定义这样的一个模板对象
SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
//创建一个Date对象
Date now = new Date();
//调用格式化方法,参数为一个Date对象
String res=sdf.formart(now);
//输出的形式为2022/07/28
System.out.println(res);
常用方法
常用方法 | 返回值 | 作用 |
---|---|---|
format(Date date) | String | 将Date对象按日期模板转换为字符串 |
parse(String str) | Date | 将满足日期模板的字符串转换为Date对象 |
Calendar类
用于表示日历的类。包含了很多日历相关的信息。
是一个抽象类,无法直接创建对象,可以通过该类的静态方法getInstance()获取该类的实例。
//获取Calendar类的实例
Calendar c = Calendar.getInstance();
日历字段
该类中定义了很多被final和static修饰的日历字段,用于获取指定信息
值 | 说明 |
---|---|
Calendar.YEAR | 获取年份 |
Calendar.MONTH | 获取月份(0-11表示1-12月) |
Calendar.DATE | 获取日期 |
Calendar.DAY_OF_WEEK | 获取星期(1-7表示周天到周六) |
Calendar.HOUR_OF_DAY | 获取二十四小时制的小时 |
Calendar.MINUTE | 获取分钟 |
Calendar.SECOND | 获取秒 |
Calendar.DAY_OF_MONTH | 本月中的第几天 |
Calendar.DAY_OF_YEAR | 本年中的第几天 |
Calendar.WEEK_OF_MONTH | 本月中的第几周 |
Calendar.WEEK_OF_YEAR | 本年中的第几周 |
常用方法
常用方法 | |
---|---|
get(日历字段) | 获取指定日历字段实际的值 |
getTime() | 获取对应的Date对象(Calendar转换为Date) |
getMaximum(日历字段) | 获取指定日历字段支持的最大值。如Calendar.DATE支持最大值为31 |
getActualMaximum(日历字段) | 获取指定日历字段在实际时间下的最大值。如4月下Calendar.DATE实际最大值为30 |
getTimeZone() | 获取当前日历在实际时间下的时区信息 |
set(日历字段,值) | 将指定日历字段设置为指定值 |
set(年,月,日) | 设置指定的年月日,没有设置的部分沿用当前日期。 |
异常
当程序没有按程序员的意愿正常执行,中途出错导致程序中断,出现这种情况,就称为异常。
学习异常就是认识异常的种类和如何避免异常出现。
为了让程序能正常执行,就需要学会解决异常。
异常的产生
异常在程序中以对象的形式存在。当代码执行时出现异常,虚拟机会自动创建一个相应的异常对象,如果没有对该异常进行处理,就会导致程序中断。
异常的分类
异常在程序中以对象的形式存在,所以异常有对应的类。
常见异常
Error错误
如果出现XXXXXError,如StackOverflowError栈空间溢出时,无法通过额外的代码去解决,只能修改源码。
Exception异常
如果出现XXXXException,如NullPointerException空指针异常时,可以通过额外代码去避免。
运行时异常和非运行时异常
如果一个异常属于RuntimeException异常类的子类,称为运行时异常,可以通过编译,运行时可能抛出异常对象。
如果一个异常属于Exception异常类的子类,称为非运行时异常,无法通过编译,只有处理后才能编译运行。
- 运行时异常(RuntimeException)
可以通过编译,可以不用处理,但在运行时有可能会抛出异常。
常见运行时异常 | 说明 | 出现的情景 |
---|---|---|
NullPointerException | 空指针异常 | 用空对象null调用方法或属性 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 | 使用数组时,下标超出范围 |
ClassCastException | 对象转型异常 | Dog dog=(Dog)new Person();(Person类和Dog类不是继承关系) |
InputMismatchException | 输入类型不匹配异常 | 如需要int类型的数据,输入String类型的数据。 |
ArithmeticException | 算术运算异常 | 0当分母 |
IllegalArgumentException | 非法参数异常 | 调用方法的参数不合理 |
- 非运行时异常(除RuntimeException)
无法通过编译,必须要处理后才能编译运行。
常见非运行时异常 | 说明 | 出现的情景 |
---|---|---|
IOException | 输入输出流异常 | 当使用流对象 |
SQLException | 数据库SQL相关异常 | 操作数据库时 |
FileNotFoundException | 文件未找到异常 | 方法的参数为文件 |
处理异常Exception
只要处理Exception异常类的子类时,都称为处理异常。处理异常的目的就是为了保证程序正常运行,不要中断。
方式一:try-catch-finally语句
这种方式能成功地处理异常,无论会不会抛出异常,都能让程序保证正常执行。
try{
//可能出现异常的代码
}catch(异常类 异常对象){
//如果出现异常对象,且与catch中的异常类匹配,则执行
}catch(异常类 异常对象){//后续的catch中的异常类型只能与之前的异常类型同级或是父类
//如果出现异常对象,且与catch中的异常类匹配,则执行
}finally{
//无论程序是否会抛出异常,一定要执行的代码
}
执行流程:先执行try中的内容,当出现异常,与后续的catch进行匹配,如果匹配对应的异常类型或异常父类型,则执行大括号中的内容,最终一定执行finally中的内容
try-catch注意
- 执行try中的内容时,当某行代码抛出异常后,不再执行try中该行代码后续的内容。
- 无论try中是否会抛出异常,finally中的内容一定执行。通常finally用于释放资源。
- 如果有多个catch,需要将异常子类放在最前,异常父类放在最后
- try、catch、finally都不能单独使用,try需要配合catch或finally或catch和finally一起使用
- try中定义的内容,无法在try之外的地方使用
- try中如果有return,不影响finally的执行,并且finally优先于return执行
方式二:throws关键字
这种方式,可以让非运行时异常通过编译,定义方法的时候,声明可能抛出的异常。
用法:方法的参数小括号之后,添加"throws 异常类型1,异常类型2…"
public class Test{
public void fun() throws InterruptException{//在定义方法时,声明可能抛出的异常类型
//如果直接写这句话,会报错,因为sleep()方法会抛出一个非运行时异常,必须要处理
Thread.sleep(5000);
}
}
throw和throws
-
throws表示用于方法声明异常。使用时写在方法的小括号之后
//用于声明方法的异常 public void fun() throws Exception{ }
-
throw用于手动抛出异常对象。使用时写在{}中,通常用于满足某种条件时,强制中断程序。
public void fun(){ //用于手动抛出一个异常对象 RuntimeException e = new RuntimeException(); throw e; }
自定义异常
可以自定义异常,在满足某种条件下,手动通过throw关键字抛出异常,人为中断程序。
自定义异常步骤
-
定义一个类,继承某个异常类。
如果继承的是RuntimeException,表示自定义的异常类属于运行时异常,该异常对象可以不用处理。
如果继承的是非RuntimeException,表示自定义的异常类属于非运行时异常,该异常对象必须要处理。
-
[可选操作]定义一个无参数的构造方法,调用父类中无参的构造方法,定义一个带字符串参数的构造方法,调用父类带字符串参数的构造方法。
数组与集合
数组的特点
-
数组中保存的元素都是有序的,可以通过下标快速访问
-
数组中保存的数据都是同一种类型
-
数组的长度在定义后,无法改变
-
数组无法获取其中保存的实际元素数量
集合的特点
- 能保存一组数据,可以有序也可以无序
- 集合的容量可变
- 集合中可以保存不同类型的数据
- 可以获取集合中实际的元素数量
集合框架(集合家族)
Iterable接口并不算严格意义上的集合的"根",它称为迭代器,用于遍历集合元素的一个工具接口。
所以集合的根接口为:Collection接口和Map接口,位于java.util包中
Collection接口
核心的两个子接口:Set和List。
这两个接口都可以保存一组数据,Set接口保存数据时,是无序不重复的;List接口保存数据时,是有序可重复的。
List接口(有序可重复)
有序集合,元素可以重复,允许保存null,可以通过索引获取对应位置上的元素。
在接口中定义了一些操作元素的方法,如获取元素数量、添加、删除、替换、截取等。
ArrayList实现类(掌握)
- 采用数组实现的集合
- 可以通过索引访问元素、可以改变集合大小。如果要在其中插入或删除元素时,会影响其余元素。该集合查询效率高、增删中间元素效率低。
- 该集合对象中保存的元素,都是引用类型(对象的内存地址)。即使保存了123,其实不是保存的int类型的123,而是Integer类型的123.
构造方法
常用构造方法 | 说明 |
---|---|
ArrayList() | 创建一个无参的ArrayList对象。实际是创建了一个空的Object数组。在调用添加元素方法时,才会初始化该数组大小为10。 |
ArrayList(int capacity) | 创建一个指定容量的ArrayList对象,实际是创建一个指定大小的Object数组,如果参数为负,会抛出IllegalArgumentException异常 |
常用方法
常用方法 | 作用 | 返回值 |
---|---|---|
add(Object obj) | 添加obj到集合的末尾 | boolean |
add(int index,Object obj) | 添加obj到index索引上 | void |
get(int index) | 得到指定index索引的元素 | Object |
set(int index,Object obj) | 使用obj覆盖index索引的元素,返回被覆盖的元素 | Object |
size() | 获取集合中的元素数量 | int |
isEmpty() | 判断集合是否为空 | boolean |
clear() | 清空集合 | void |
remove(Object obj) | 删除指定obj对象 | boolean |
remove(int index) | 删除指定Index索引的元素,返回被删除的元素 | Object |
contains(Object obj) | 判断是否存在obj元素 | boolean |
indexOf(Object obj) | 得到元素obj第一次出现的索引 | int |
lastIndexOf(Object obj) | 得到元素obj最后一次出现的位置 | int |
subList(int from,int to) | 得到[from,to)区间内的元素 | List |
toArray() | 将集合转换为数组 | Object[] |
LinkedList实现类
-
采用双向链表实现的集合
-
集合中保存的每个元素也称为节点,除首尾节点外,每个节点即保存了自己的数据,也保存了其前一个和后一个节点的地址
-
如果在其中进行插入和删除的操作时,不影响其他元素的位置,只需要重新定义新节点的前后节点位置即可。
-
如果要查询某个节点的索引,需要从头结点或尾结点开始一步步得到目标节点位置
-
所以中间进行插入和删除的效率高,随机读取的效率低
构造方法
常用构造方法 | 说明 |
---|---|
LinkedList() | 创建一个空链表 |
常用方法
由于LinkedList实现了List接口,所有这里省略List接口中常用的方法,如add(Object obj)、Remove(Object obj)等。参考ArrayList中的方法
常用方法 | |
---|---|
addFirst() | 添加头结点 |
addLast() | 添加尾结点 |
getFirst() | 得到头结点 |
getLast() | 得到尾结点 |
remove() | 删除头结点 |
removeFirst() | 删除头结点 |
removeLast() | 删除尾结点 |
push(Object obj) | 添加头结点(入栈) |
pop() | 得到并移除头结点(出栈) |
peek() | 得到头结点 |
poll() | 删除头结点 |
offer(Object) | 添加尾结点 |
ArrayList和LinkedList的区别
- 两者都是List接口的实现类,保存的元素有序可重复,允许保存null,拥有一些公共的方法,如size(),isEmpty(),subList(int from,int to)等
- ArrayList采用数组实现,对于随机读取效率更高,通常用于查询;LinkedList采用双向链表实现,插入删除不影响其他元素位置,通常用于中间插入删除。
Set接口(无序不重复)
无序集合,元素不可以重复,允许保存null,没有索引。
在接口中定义了一些操作元素的方法,如获取元素数量、添加、删除、替换、截取等。
哈希表hash table
哈希表,也称为散列表,是一种数据结构,能更快地访问数据。
要保存的数据称为原始值,这个原始值通过一个函数得到一个新的数据,这个函数称为哈希函数,这个新数据称为哈希码,哈希码和原始值之间有一个映射关系,这个关系称为哈希映射,可以构建一张映射表,称为哈希表。在哈希表中,可以通过哈希码快速访问对应的原始值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-orKfzY0U-1659864998537)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220801112654395.png)]
哈希码的特点
- 如果两个对象的hashCode不同,这两个对象一定不同
- 如果两个对象的hashCode相同,这两个对象不一定相同
- 如**“通话"和"重地”**这两个字符串的hashCode相同,但是两个对象
- hashCode相同,对象不同,称为哈希冲突
HashSet实现类
- 采用哈希表实现
- 元素不能重复,无序保存,允许保存null
- 本质是一个HashMap对象,调用add()方法,实际调用的也是HashMap中的put()方法,参数作为put()方法的键,new Obejct()作为put()方法的值
HashSet添加数据的原理
if(两个对象hashCode相同&&两个对象equals相同){视为同一对象,不能添加}
每次向集合中添加元素时,先判断该元素的hashCode是否存在,
- 如果不存在,视为不同对象,直接添加
- 如果存在,再判断equals方法的结果
- 如果为false,视为不同对象,直接添加
- 如果为true,视为同一对象,不能添加
可见不能添加的条件是两个对象的hashCode相同并且equals结果为true。
如果每次都只判断equals的话,过程可能会很久,效率不高,
如果每次只判断hashCode的话,有可能会有哈希冲突,
所以先判断hashCode,再判断equals,既能保证效率又能保证添加进去的元素都是不相同的元素。
equals方法和hashCode的关系
-
如果两个对象的equlas方法比较结果为true,在没有重写equals方法的前提下,hashcode相同吗?
- 相同
-
如果两个对象的hashCode不同,在没有重写equals方法的前提下,equals方法比较结果为?
- false
-
如果两个对象的hashCode相同,equals方法比较结果为?
-
可能为true也可能是false
String str1="通话"; String str2="重地"; //以上两个字符串对象的hashCode相同,但equals方法结果为false String str3="abc"; String str4="abc"; //以上两个字符串的hashCode相同,equals方法结果为true
-
构造方法
常用构造方法 | 说明 |
---|---|
HashSet() | 创建一个默认的集合对象,实际是创建了一个大小为16,加载因子为0.75的HashMap对象 |
HashSet(int capacity) | 创建一个指定容量的集合对象,实际是创建了一个指定大小,加载因子为0.75的HashMap对象 |
HashSet(int capacity,float loadFactor) | 创建一个指定容量和指定加载因子的集合对象。 |
常用方法
常用方法 | 说明 |
---|---|
add(Object obj) | 添加元素到集合中 |
size() | 得到元素数量 |
isEmpty() | 判断集合是否为空 |
remove(Object obj) | 根据对象删除元素 |
contains(Object obj) | 判断是否存在某个元素 |
toArray() | 将集合转换为Object数组 |
clear() | 清空集合 |
TreeSet实现类
-
特殊的Set实现类,数据可以有序保存,可以重复,不能添加null元素
-
采用红黑树(自平衡二叉树)实现的集合
- 二叉树表示某个节点最多有两个子节点
- 某个节点右侧的节点值都大于左侧节点值
-
只能添加同一种类型的对象且实现了Comparable接口的对象
- 实现Comparable接口后必须要重写compareTo方法
- 每次调用添加时,参数会自动调用该方法
-
添加的元素可以自动排序
-
compareTo方法的返回值决定了能否添加新元素和新元素的位置
- 如果返回0,视为每次添加的是同一个对象,不能重复添加
- 如果返回正数,将新元素添加到现有元素之后
- 如果返回负数,将新元素添加到现有元素之前
构造方法
常用构造方法 | 说明 |
---|---|
TreeSet() | 实际会创建一个TreeMap对象 |
常用方法
常用方法 | 作用 |
---|---|
add(Object obj) | 添加元素到集合中 |
size() | 得到元素数量 |
isEmpty() | 判断集合是否为空 |
clear() | 清空集合 |
ceiling(Object obj) | 得到比指定元素大的最小元素 |
floor(Object obj) | 得到比指定元素小的最大元素 |
first()/last() | 得到第一个/最后一个元素 |
HashMap实现类(掌握)
- JDK1.8之后,HashMap的数据结构采用"数组+链表+红黑树"实现
- 当没有哈希冲突时,元素保存到数组中
- 如果哈希冲突,在对应的位置上创建链表,元素保存到链表中
- 当链表元素数量大于8,转换为红黑树
- 数据采用**"键值对"的形式保存,键称为key**,值称为value ,键不能重复,允许null,值没有限制,键和值都是引用类型
- 在哈希表中,哈希码就是键,保存的数据就是值,可以通过键得到相应的值。
构造方法
常用构造方法 | 说明 |
---|---|
HashMap() | 创建一个空的集合对象,默认大小为16,加载因子为0.75 |
常用方法
常用方法 | 作用 |
---|---|
put(Object key,Object Value) | 添加一组键值对 |
get(Object key) | 根据键得到值 |
size() | 得到键值对的数量 |
clear() | 清空所有键值对 |
keyset() | 得到所有键的集合 |
values() | 得到所有值的集合 |
containtsKey(Object key) | 判断是否包含某个键 |
containsValue(Object value) | 判断是否包含某个值 |
remove(Object key) | 根据键删除键值对 |
遍历集合的方式
遍历List
方式一:普通for循环
List list= new ArrayList();
list.add("123");
list.add("123");
list.add("123");
list.add("123");
for(int i=0;i<list.size();i++){
Object obj= list.get(i);
}
方式二:增强for循环
List list= new ArrayList();
list.add("123");
list.add("123");
list.add("123");
list.add("123");
for(Object o:list){
Object obj= o;
}
方式三:迭代器
List list= new ArrayList();
//获取迭代器对象
Iterator it= list.iterator();
//hasNext判断是否还有下一个元素
while(it.hasNext()){
//获取读到的元素
Object obj=it.next();
}
遍历Set
方式一:增强for循环
Set list= new HashSet();
list.add("123");
list.add("qwe");
list.add("hello");
list.add(null);
for(Object o:list){
Object obj= o;
}
方式二:迭代器
Set list= new HashSet();
//获取迭代器对象
Iterator it= list.iterator();
//hasNext判断是否还有下一个元素
while(it.hasNext()){
//获取读到的元素
Object obj=it.next();
}
泛型
一种规范,常用于限制集合中元素的类型,省去遍历元素时转型的过程。
//定义集合,默认不限制元素类型
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add(123);
//如果没有限制集合的类型,要通过循环做某个事情时,可能对于某些元素并不适用
for(int i=0;i<list.size();i++){
//String有length属性,Integer没有,运行时就会抛出异常
System.out.println(list.get(i).length);
}
用法
在定义集合时,在接收集合变量的数据类型后,写上<数据类型>。
集合类或接口<数据类型> 集合变量名 = new 集合实现类<>();
List<String> list = new ArrayList<>();
//这样只能在集合中保存String
//list.add(123);//不能通过编译
//如果要限制集合保存int类型的数据,不能写int,要写int的包装类Integer。因为集合中保存的都是引用类型。
//List<int> list2=new ArrayList();//无法通过编译
List<Integer> list2=new ArrayList();
Collections集合工具类
- Collection是集合的根接口,定义了集合的方法
- Collections是集合的工具类,定义了很多静态方法,直接通过类名使用
常用方法
常用方法 | |
---|---|
Collections.shuffle(List list) | 打乱有序集合中的元素顺序 |
Collections.swap(List list,int i,int j) | 交换有序集合中i和j索引上的元素 |
Collections.replaceAll(List list,Object oldVal,Object newVal) | 将有序集合中的旧元素替换为新元素 |
Collections.fill(List list,Object obj) | 将有序集合使用obj填充 |
Collections.rotate(List list,int distance) | 将最后distance个元素放在最前 |
Collections.sort(List list) | 对有序集合中的元素进行排序,元素必须实现了Comparable接口 |
Collections.max(Collection list) | 得到有序集合中的最小元素,元素必须实现了Comparable接口 |
Collections.min(Collection list) | 得到有序集合中的最大元素,元素必须实现了Comparable接口 |
集合与数组之间的转换
-
集合转换为数组
Object[] 数组 = 集合对象.toArray(); List list = new ArrayList(); list.add(123); Object[] obj = list.toArray(); System.out.println((Integer)obj[0]);
-
数组转换为数组集合
int[] ints={1,2,3,9,22,16}; //将数组保存到一个数组集合中,上边定义的那个数组就是集合中的第一个元素 List<int[]> ints1 = Arrays.asList(ints); //遍历转换后的数组 for (int[] ints2 : ints1) { //遍历数组中的元素 for (int i : ints2) { System.out.println(i); } }
File文件
Java中可以将本地硬盘中的文件(文件和目录)以对象的形式表示。
就能通过Java代码对本地文件进行读取或操作。
构造方法
常用构造方法 | 说明 |
---|---|
File(String pathName) | 根据文件的完整路径创建对象 |
File(String parent,String name) | 根据文件所在的父目录路径和自身文件名创建对象 |
File(File parent,String name) | 根据文件所在父目录文件对象和自身文件夹创建对象 |
//创建一个File对象 new File(String pathName);
File file1 = new File("d:/xiaopw84in1111");
//创建一个File对象 new File(String parent,String child);
File file2 = new File("D:\\xiaopw84in1111","Readme-说明.htm");
//创建一个File对象 new File(File parent,String child);
File parent = new File("D:/xiaopw84in1111");
File file3 = new File(parent, "Readme-说明.htm");
常用方法
常用方法 | 说明 | 返回值 |
---|---|---|
exists() | 判断文件是否存在 | boolean |
isFile() | 判断是否为文件 | boolean |
isDirectory() | 判断是否为目录 | boolean |
getName() | 得到文件名 | String |
getPath() | 得到文件所在相对路径 | String |
getAbsolutePate() | 得到文件所在绝对路径 | String |
getParent() | 得到父目录的名称 | String |
getParentFile() | 得到父目录的对象 | File |
lastModified() | 得到文件最后一次修改时间对应的毫秒数 | long |
length() | 得到文件的所占字节大小 | long |
isHidden() | 判断文件是否为隐藏 | boolean |
list() | 得到目录下的第一层子文件名的字符串数组 | String[] |
listFiles() | 得到目录下的第一层子文件的文件数组 | File[] |
delete() | 删除某个文件或空目录 | boolean |
mkdir() | 创建一个空目录 | boolean |
renameTo(File newFile) | 将原文件命名为新文件(新文件可以指定新位置) | boolean |
递归遍历文件夹
package com.hqyj.FileTest;
import java.io.File;
public class Test3 {
public static void main(String[] args) {
File source = new File("d:/test");
showAllFile(source);
}
/*
* 使用递归遍历文件夹
* */
public static void showAllFile(File source) {
System.out.println(source);
//source.delete();//删除
//判断如果能继续展开
if (source.isDirectory()) {
//得到第一层子文件
File[] children = source.listFiles();
//遍历展开后的子文件
for (File child : children) {
//每个子文件又能继续当做新的源文件进行判断
showAllFile(child);
}
}
}
}
IO
I:Input输入
O:Output输出
流
在Java中,流用于表示计算机硬盘与内存之间传输数据的通道。
将内存中的数据存入到硬盘中,称为写write,也称为输出
将硬盘中的数据存入到内存中,称为读read,也称为输入
流的分类
Java中将流定义成了类,以对象的形式保存。流有"四大家族",是所有流的父类。
InputStream字节输入流
FileInputStream文件字节输入流 ObjectInputStream对象字节输入流(反序列化)
OutputStream字节输出流
FileOutputStream文件字节输出流 ObjectOutputStream对象字节输出流(序列化)
Writer字符输出流
FileWriter文件字符输出流 **BufferedWriter缓冲字符输出流(包装流) ** OutputStreamWriter字节输出流转换为字符输出流(转换流)
Reader字符输入流
FileReader文件字符输入流 BuffedredReader缓冲字符输入流(包装流) InputStreamReader字节输入流转换为字符输入流(转换流)
按方向分类
输入流:InputStream、Reader
作用:将硬盘中的数据读取到内存中
输出流:OutputStream、Wrtiter
作用:将内存中的数据写入到硬盘中
按类型分
字节流:InputStream、OutputStream
作用:适用于非文本类型,如图片、文件等的读写
字符流:Reader、Writer
作用:适用于文本类型,尤值txt格式文件的读写
如要将硬盘中某个txt文件中的字符读取到程序中,使用Reader
如要将硬盘中的某个图片读取到程序中,使用InputStream
流的四个父类的特点
- 以上四个类都在java.io包下,都是抽象类,不能直接创建其对象,要使用其子类
- 这四个类都有close()方法,用于关闭流对象释放资源
- 输出流(OutputStream和Write)都有flush()方法,用于将流中的数据到冲刷到硬盘中
- 在使用输出流对象时,一定要调用flush()或close()方法后,才能真正将数据写到硬盘中
- 输入流(InputStream和Reader)都有read()方法读取数据到内存中,输出流都有write()方法写入数据到硬盘中
- 所有的流类中,以Stream结尾的,都是字节流,以Reader或Writer结尾的都是字符流
- 读取硬盘中的数据时,使用输入流;将数据写入到硬盘中时,使用输出流
- 读取或写入文本文件时,使用字符流;读取或写入非文本文件时,使用字节流
FileInputStream文件字节输入流(掌握)
按字节读取本地文件,本地文件必须存在
构造方法
常用构造方法 | 说明 |
---|---|
FileInputStream(String pathName) | 根据文件名创建文件字节输入流对象 |
FileInputStream(File file) | 根据文件对象创建文件字节输入流对象 |
常用方法
常用方法 | 作用 |
---|---|
read() | 读取一个字节。返回读取到的字节本身。 |
read(byte[] b) | 按字节数组读取。返回读取到的字节数量。读取到的内容保存在了字节数组中 |
close() | 关闭流对象 |
FileOutputStream文件字节输出流(掌握)
按字节将数据写入到本地文件中,本地文件可以不存在
构造方法
常用构造方法 | 说明 |
---|---|
FileOutputStream(String pathName) | 根据文件名创建文件字节输出流对象,写入内容时覆盖原内容 |
FileOutputStream(String pathName,boolean append) | 根据文件名创建文件字节输出流对象,写入内容时追加在原内容之后 |
FileOutputStream(File file) | 根据文件对象创建文件字节输出流对象,写入内容时覆盖原内容 |
FileOutputStream(File file,boolean append) | 根据文件对象创建文件字节输出流对象,写入内容时追加在原内容之后 |
常用方法
常用方法 | 作用 |
---|---|
write(int i) | 写入一个字节 |
write(byte[] bytes) | 写入一个字节数组 |
write(byte[] bytes,int off,int len) | 写入字节数组中从off开始的len个字节 |
flush() | 将数据冲刷到本地文件中 |
close() | 关闭流对象 |
使用FileInputStream和FileOutputStream读写时的注意事项
-
在使用read(byte[] bytes)方法时,每次都会读取指定数组大小的字节,将读取到的字节保存到数组中,该方法返回读取到的字节数量。如果最后一次读取的字节数不足字节数组大小时,只会用读取到的内容覆盖最前的几个元素。所以会导致读取到的内容多于实际内容。
-
在使用write(byte[] bytes)方法时,会将字节数组中的所有内容写入到输出流中,在最后一次写入时,可能会写入多余的内容。所以在写入时,最好使用write(byte[] bytes,int off,int len)方法,表示将字节数组中的元素,从off开始,写入len个字节。
使用FileInputStream和FileOutputStream实现单个文件的复制
package com.hqyj.IOTest;
import java.io.*;
public class Test3 {
public static void main(String[] args) throws IOException {
//创建源文件对象
File source = new File("d:/SteamTools-develop.zip");
//创建文件字节输入流对象,用于读取源文件中的内容
FileInputStream fis = new FileInputStream(source);
//创建目标文件对象
File target = new File("C:\\Users\\Administrator\\Desktop", source.getName());
//创建文件字节输出流对象,用于写入内容到该文件中
FileOutputStream fos = new FileOutputStream(target);
//创建一个字节数组,按数组读写,这里表示8M
byte[] bytes = new byte[1024 * 1024 * 8];
//整个数组读取,i表示读取到的字节数量
int i=fis.read(bytes);
while(i!=-1){
//循环读取的同时,按字节数组写入,实际读到了多少字节,就写入多少字节
fos.write(bytes,0,i);
//下次读取
i=fis.read(bytes);
}
fos.close();
fis.close();
if (target.exists()) {
System.out.println("复制成功");
}else{
System.out.println("复制失败");
}
}
}
文件夹的复制
package com.hqyj.homework;
import java.io.*;
public class FileCopy {
public static void main(String[] args) {
File source = new File("d:/xiaopw84in1111");
File target = new File("C:\\Users\\Administrator\\Desktop/copy");
copyDir(source, target);
//source: d:/信息 假设该文件夹下只有一个info.txt文件
//target: 桌面:/copy
//1.调用copyDir方法,判断source是一个文件夹,创建目标文件夹target"桌面:/copy"
//2.遍历source文件夹,发现info.txt文件,用child表示
// 此时的source是"d:/信息/info.txt",即child,target是"桌面:/copy/info.txt"
// 所以要按File(File parent,String child)构造方法创建目标文件对象,即File("桌面/copy","info.txt"),对应代码为File(target,child.getName())
//3.递归调用copyDir方法,参数为copyDir(child,File(target,child.getName()))
}
/*
* 定义复制文件夹的方法
* */
public static void copyDir(File source,File target) {
//判断如果是文件,直接按单文件复制的方式调用
if(source.isFile()){
copyFile(source,target);
}else{//如果是文件夹
//创建目标文件夹
target.mkdir();
//展开原文件夹
for (File child : source.listFiles()) {
//当前新的目标文件是 保存在目标文件夹下的源文件
//如原文件为"d:/source/info.txt" 新目标为"d:/target/info.txt"
//所以按File(File parent,String child)构造方法创建目标文件对象
File newTarget = new File(target, child.getName());
//递归调用时的源文件依然是当前遍历出来的子文件
copyDir(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) | 按文件名创建字符输出流对象,允许追加写入 |
FileWriter(File file) | 按文件对象创建字符输出流对象 |
FileWriter(File file,boolean append) | 按文件对象创建字符输出流对象,允许追加写入 |
常用方法
常用方法 | 作用 |
---|---|
write(String str) | 按字符串写入 |
write(char[] c) | 按字符数组写入 |
write(String str,int off,int len) | 按字符串写入,从off开始写入len个字符 |
write(char[] c,int off,int len) | 按字符数组写入,从off开始写入len个字符 |
flush() | 冲刷流中的数据到硬盘中(必须调用该方法或close方法后,才能真正写入) |
close() | 关闭流对象 |
BufferedReader字符缓冲输入流(掌握)
自带字符数组(缓冲区)的字符输入流。默认字符数组大小为8192,每次最多读取8192个字符
在读取纯文本文件(txt或md等)时,首选该类。
构造方法
常用构造方法 | 说明 |
---|---|
BufferedReader(Reader reader) | 创建一个带有缓冲区(字符数组)的字符输入流对象,默认缓冲区大小为8192。参数注意是一个Reader类型的对象,但Reader是一个抽象类,所以要使用其子类,如FileReader,然后在FileReader中定义要读取的文件信息 |
BufferedReader(Reader reader,int size) | 创建一个指定缓冲区(字符数组)大小的字符输入流对象 |
常用方法
常用方法 | 作用 |
---|---|
ready() | 判断是否还有字符 |
readLine() | 读取整行字符 |
close() | 关闭流对象 |
读取文本练习
public class Test{
public static void main(String[] args){
//创建一个文件对象
File file = new File("D:\\Java\\笔记\\JavaAdv01.md");
//创建一个Reader的子类:FileReader
Reader fr = new FileReader(file);
//创建缓冲字符输入流对象,参数为一个Reader对象,这里使用FileReader,会自动创建一个8192大小的字符数组,作为读取的缓冲区
BufferedReader br = new BufferedReader(fr);
//可以简写为一行
//BufferedReader br = new BufferedReader(new FileReader(new File("d:/信息.txt")));
//使用ready方法判断是否还有字符
while (br.ready()) {
//使用readLine方法读取一行
System.out.println(br.readLine());
}
//这里只需要关闭外层的对象BufferedReader br
br.close();
}
}
BufferedWriter字符缓冲输出流(掌握)
自带字符数组(缓冲区)的字符输出流。
构造方法
常用构造方法 | 说明 |
---|---|
BufferedWriter(Writer writer) | 创建一个带有缓冲区(字符数组)的字符输出流对象,参数为一个Writer对象,但Writer是一个抽象类,所以要使用其子类,如FileWriter对象,在FileWriter中定义要写入的文件 |
BufferedWriter(Writer writer,int size) | 创建一个指定缓冲区(字符数组)大小的字符输出流对象 |
常用方法
常用方法 | 作用 |
---|---|
write(String str) | 写入一个字符串 |
newLine() | 换行 |
flush() | 将输出流中的字符冲刷到文件中 |
close() | 关闭流对象 |
写入文本练习
public class Test{
public static void main(String[] args){
//创建一个自带字符数组的字符输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter(new File("d:/abc.txt"), true));
bw.write("\"第一行\"");
bw.newLine();
bw.write("\"第二行\"");
bw.newLine();
bw.close();
}
}
ObjectOutputStream对象字节输出流(序列化)(掌握)
序列化:将对象转换为文件的过程
被序列化的对象,必须要实现Serializable接口。
这个接口是一个特殊的接口,没有定义方法,只是给这个类打上标记,表示该类可以被序列化。
构造方法
常用构造方法 | 说明 |
---|---|
ObjectOutputStream(OutputStream os) | 创建一个对象字节输出流对象,参数为一个字节输出流对象,由于是抽象类,所以要使用其子类,如FileOutputStream对象,在其中定义要写入的文件。 |
常用方法
常用方法 | 作用 |
---|---|
writeObject(Object obj) | 将一个对象写入到本地文件中 |
close() | 关闭流对象 |
ObjectInputStream对象字节输入流(反序列化)(掌握)
反序列化:将文件转换为对象的过程
构造方法
常用构造方法 | 说明 |
---|---|
ObjectInputStream(InputStream is) | 创建一个对象字节输入流对象,参数为一个字节输入流对象,由于是抽象类,所以要使用其子类,如FileInputStream对象,在其中定义要读取的文件 |
常用方法
常用方法 | 作用 |
---|---|
readObject() | 读取序列化后的文件,返回对象Object |
close() | 关闭流对象 |
序列化与反序列化的案例
Person类,必须要实现Serializable接口才可序列化
package com.hqyj.ObjectStreamTest;
import java.io.Serializable;
//如果要序列化某个类的对象,要将该类实现Serializable接口
//这个接口中没有任何方法,只是一个标记,表示该类的对象可以被序列化
public class Person implements Serializable {
private String name;
private int age;
private String sex;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
//这里省略get/set
}
Main类
package com.hqyj.ObjectStreamTest;
import java.io.*;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建一个对象
Person p = new Person("张敏", 22, "男");
//最终保存的文件
File file = new File("d:/person.p");
//创建一个字节输出流对象,参数为要写入的文件对象,即上一步的file
OutputStream fos = new FileOutputStream(file);
//创建一个对象输出流对象,用于序列化,参数为OutputStream对象,即上一步的fos
ObjectOutputStream oos = new ObjectOutputStream(fos);
//可以简写为
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/person.p")));
//调用writeObject(Object obj)方法,将对象写入到文件中
oos.writeObject(p);
//关闭
oos.close();
//创建一个对象字节输入流对象,用于反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:/person.p")));
//readObject();读取内容,返回对象类型
Object o = ois.readObject();
//要经过转型后才能当做Person对象使用
Person person = (Person) o;
System.out.println(person.getName());
}
}
转换流
实际属于字符流,作用为将一个字节流对象转换为字符流对象
OutputStreamWriter
将字节输出流转换为字符输出流
InputStreamReader
将字节输入流转换为字符输入流
转换流的使用
//如果只提供了一个字节流,又要发送文本时,就可以将字节流转换为字符流后发送文本
//如这里提供一个字节输出流对象
FileOutStream fos = new FileOutStream();
//fos.write("xxx".getBytes());//如果使用字节流写字符串,不方便
//将fos转换为Writer对象
Writer writer = new OutputStreamWriter(fos);
//将字符流对象包装为缓冲字符流对象
BufferedWriter bw = new BufferedWriter(writer);
bw.write("hello");//如果是字符流就可以直接写字符串
bw.close();
网络编程
InetAddress类
表示IP对象的一个类
package com.hqyj.NetTest;
import java.io.IOException;
import java.net.InetAddress;
public class Test {
public static void main(String[] args) throws IOException {
//InetAddress用于表示当前计算机所在网络地址的类(IP地址对象)
InetAddress localHost = InetAddress.getLocalHost();//getLocalHost()获取计算机名和当前网络环境下的IP地址
System.out.println(localHost);
//getByName(域名) www.baidu.com 得到域名对应的ip地址
InetAddress byName = InetAddress.getByName("www.taobao.com");
System.out.println(byName);
//得到IP地址
System.out.println(byName.getHostAddress());
//得到域名部分
System.out.println(byName.getHostName());
}
}
Socket类和ServerSocket类
都属于Socket(套接字)对象,表示网络中的某个端点。
-
ServerSocket指服务器端
-
Socket指普通端点
使用套接字对象实现两个端点(Socket和ServerSocket)之间发送文件
服务器端
package com.hqyj.NetTest;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//InetAddress表示一个IP对象类
//根据当前自己电脑的IP地址创建一个IP对象
InetAddress ip = InetAddress.getByName("192.168.3.46");
//服务器端套接字对象(网络中的某个服务节点) 节点的端口号 连接数 IP对象
ServerSocket server = new ServerSocket(9527, 20, ip);
System.out.println("服务器端已经部署,等待连接");
//等待另一个套接字对象连接,程序处于阻塞状态,直到另一个套接字对象连接才会继续
Socket client = server.accept();//client表示连接成功的客户端对象
//创建一个最终客户端上传上来的目标文件对象和将其保存到本地的输出流对象
File file = new File("upload/test.zip");
FileOutputStream fos = new FileOutputStream(file);
//获取客户端和服务器端的输入流对象
InputStream is = client.getInputStream();
//按字节数组读写
byte[] bytes = new byte[1024 * 1024 * 8];
//使用字节数组获取客户端发送过来的字节
int count = is.read(bytes);
while (count != -1) {
fos.write(bytes, 0, count);
//将读取到的字节保存到服务器本地硬盘中
count = is.read(bytes);
}
is.close();
fos.close();
}
}
客户端
package com.hqyj.NetTest;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//创建一个客户端套接字对象,需要写明要连接的服务端套接字对象的IP地址和端口号
Socket client = new Socket("192.168.3.46", 9527);
//获取客户端与服务端的输出流对象
OutputStream os = client.getOutputStream();
//读取本地文件
File file = new File("d:/信息.zip");
//读取本地文件的输入流对象
FileInputStream fis = new FileInputStream(file);
//按字节数组读写
byte[] bytes = new byte[1024 * 1024 * 8];
int count = fis.read(bytes);
while (count != -1) {
//将读取到的字节数组马上写入到与服务器端的输入流对象中
os.write(bytes, 0, count);
count = fis.read(bytes);
}
fis.close();
os.close();
}
}
进程和线程
进程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对象包装为线程对象同时命名
常用方法
常用方法 | 作用 |
---|---|
setName(String str) | 设置线程名称 |
setPriority(int i) | 设置线程优先级(1~10),数字越大优先级越高,线程越先执行完 |
setDaemon(boolean f) | 是否将该线程设置为守护线程 |
getId() | 获取线程ID |
getName() | 获取线程名,主线程名默认main,自定义线程名默认Thread-N |
getPriority() | 获取线程优先级 |
getState() | 获取线程状态 |
isDaemon() | 判断该线程是否属于守护线程 |
start() | 启动线程 |
run() | 线程启动后执行的方法 |
Thread.currentThread() | 获取当前运行的线程对象 |
Thread.sleep(long m) | 设置当前线程休眠m毫秒 |
Thread.yield() | 线程让步,让其他线程执行 |
实现多线程
方式一:继承Thread类
- 1.让某个类成为Thread类的子类
- 2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中
- 3.创建Thread类的对象后,调用start()方法,启动线程
自定义线程Thread类的子类
package com.hqyj.ThreadTest;
/*
* 自定义线程,让其循环打印1~100
* 1.继承Thread类
* 2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中
* 3.创建Thread类的对象后,调用start()方法,启动线程
* */
public class MyThread extends Thread{
//如果在创建线程对象时需要设置线程名,这里调用父类的构造方法
public MyThread(String name){
super(name);
}
/*
* 希望多线程环境下执行的代码,写在run方法中
* */
@Override
public void run() {
//获取当前线程对象
Thread mt = Thread.currentThread();
for (int i = 0; i < 100; i++) {
//让当前线程休眠,让其他线程有机会在当前线程休眠的时间执行
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(mt.getName()+"--"+i);
}
}
}
main类
package com.hqyj.ThreadTest;
public class MainThread {
public static void main(String[] args) throws InterruptedException {
//先创建自定义线程对象,让该线程启动
Thread mt1 = new MyThread("线程A");
Thread mt2 = new MyThread("线程B");
//不能调用run(),必须要调用start()方法才能启动线程
mt1.start();
mt2.start();
}
}
方式二:实现Runnable接口(建议使用)
由于Java中是单继承,所以如果某个类已经使用了extends关键字去继承了另一个类,这时就不能再使用extends继承Thread类实现多线程,就需要使用实现Runnable接口的方法实现多线程。
- 1.自定义一个类,实现Runnable接口
- 2.重写run方法,将要多线程执行的内容写在该方法中
- 3.创建Thread线程对象,将自定义的Runnable接口实现类作为构造方法的参数
- 4.调用线程对象的start()方法启动线程
自定义Runnable接口的实现类
package com.hqyj.RunnableTest;
/*
* 如果一个类已经使用了extends,就不得不使用implements Runnable实现该接口
* 再通过创建Thread时,将该类进行包装,实现多线程
* */
public class MyRunnableThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
main类
package com.hqyj.RunnableTest;
public class Main {
public static void main(String[] args) {
//依然要创建Thread对象才能使用多线程
// 这里用的是Thread(Runnable target)构造方法
// 或Thread(Runnable target,String name)构造方法
//将Runnable接口的实现类对象包装为一个线程对象
MyRunnableThread myRunnableThread = new MyRunnableThread();
Thread t1 = new Thread(myRunnableThread,"线程A");
//调用start()方法时,如果被分配到CPU时间片,会自动执行run()方法
t1.start();
Thread t2 = new Thread(myRunnableThread,"线程B");
t2.start();
}
}
方式三:使用匿名内部类
如果不想创建一个Runnable接口的实现类,就可以使用匿名内部类充当Runnable接口的实现类
package com.hqyj.UnnameInnerClassThreadTest;
/*
* 使用匿名内部类
* 本质还是使用Thread(Runnable target)
* 并没有创建一个Runnable接口的实现类,而是直接用一个匿名内部类作为参数,包装成一个线程对象
* */
public class Test {
public static void main(String[] args) {
//newThread()小括号中的参数就是一个匿名内部类
//创建线程对象,将匿名内部类包装成Thread对象后直接start()启动
new Thread(new Runnable() {
//多线程做的事情依然写在run方法中
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}, "线程A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}, "线程B").start();
}
}
线程的生命周期
线程的初始化到终止的整个过程,称为线程的生命周期
新生状态
当线程对象被实例化后,就进入了新生状态。new Thread()
就绪状态
当某个线程对象调用了start()方法后,就进入了就绪状态。
在该状态下,线程对象不会做任何事情,只是在等待CPU调用。
运行状态
当某个线程对象得到运行的机会后,则进入运行状态,开始执行run()方法。
不会等待run()方法执行完毕,只要run()方法调用后,该线程就会再进入就绪状态。这时其他线程就有可能穿插其中。
阻塞状态
如果某个线程遇到了sleep()方法或wait()等方法时,就会进入阻塞状态。
sleep()方法会在一段时间后自动让线程重新就绪。
wait()方法只有在被调用notify()或notifyAll()方法唤醒后才能进入就绪状态。
终止状态
当某个线程的run()方法中所有内容都执行完,就会进入终止状态,意味着该线程的使命已经完成。
多线程卖票
package com.hqyj.SellTicket;
/*
* 定义自动售票机,创建多个线程模拟卖10张票
* */
public class TicketMachine implements Runnable {
//假设多线程卖10张票
private int ticket = 10;
/*
* 卖票的方法
* */
public synchronized void sell() {
if (ticket > 0) {
System.out.print(Thread.currentThread().getName() + "售出一张");
ticket--;
System.out.println(Thread.currentThread().getName() + "剩余" + ticket);
} else {
System.out.println(Thread.currentThread().getName() + "已售罄");
}
}
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
public static void main(String[] args) {
//创建一个售票机对象
TicketMachine ticketMachine = new TicketMachine();
//将其使用3个线程类进行包装,让其在多线程环境下卖票
Thread t1 = new Thread(ticketMachine,"窗口A");
Thread t2 = new Thread(ticketMachine,"窗口B");
Thread t3 = new Thread(ticketMachine,"窗口C");
//线程就绪
t1.start();
t2.start();
t3.start();
//如果没有将卖票的方法sell()使用synchronized修饰,
//就有可能在线程A执行的途中,线程B也开始执行,
//从而线程A中原本要打印3句话的,只打印了1句,线程B就抢进来执行
//最终导致后续流程出错
//所以对方法sell()使用synchronized修饰后,
//在某个线程执行sell()方法时,其他线程就会排队等待其执行完毕
}
}
多线程取钱
Account类
package com.hqyj.Withdrawing;
/*
* 定义一个多线程共同访问的一个类:账户类
* */
public class Account {
private double balance;
public Account(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
ATM类
package com.hqyj.Withdrawing;
public class ATM implements Runnable {
private Account account;
public ATM(Account account) {
this.account = account;
}
/*
* 取钱
* */
public void getMoney(int money) {
//同步代码块,在操作account对象时,其他线程不能也同时操作该对象
synchronized (account) {
double v = account.getBalance() - money;
System.out.println("ATM取出" + money);
account.setBalance(v);
System.out.println("ATM显示剩余" + account.getBalance());
}
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
getMoney(5000);
}
}
Bank类
package com.hqyj.Withdrawing;
public class Bank implements Runnable {
private Account account;
public Bank(Account account) {
this.account = account;
}
/*
* 取钱的方法,在操作account对象时,不允许别的线程访问该对象
* */
public void getMoney(int money) {
synchronized (account) {
double v = account.getBalance() - money;
System.out.println("银行取出" + money);
account.setBalance(v);
System.out.println("银行显示剩余" + account.getBalance());
}
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
getMoney(5000);
}
}
main类
package com.hqyj.Withdrawing;
public class Main {
public static void main(String[] args) {
//创建多线程要访问的同一对象
Account account = new Account(10000);
//创建两个线程
Thread t1 = new Thread(new ATM(account));
Thread t2 = new Thread(new Bank(account));
//线程就绪
t1.start();
t2.start();
}
}
-
什么叫死锁?怎么产生?如何解决?
如有两个人吃西餐,必须要有刀和叉才能吃饭,只有一副刀叉。
如果A拿到了刀,B拿到了叉,互相都在等待另一个工具,但都不释放自己的,
这时就会造成死锁的局面,既不结束,也不继续。
模拟死锁出现的原因
定义两个线程类,线程A先获取资源A后,再获取资源B;线程B先获取资源B后,再获取资源A。
如果对A和B对使用了synchronized进行同步,就会在线程A获取资源A时候,线程B无法获取资源A,
相反线程B在获取资源B的时候,线程A无法获取资源B,所以两个线程都不会得到另一个资源。
package com.hqyj.DeadLock;
public class MyThread1 implements Runnable{
//定义两个成员变量:刀、叉
private Object knife;
private Object fork;
public MyThread1(Object knife, Object fork) {
this.knife = knife;
this.fork = fork;
}
//当前线程执行run方法的顺序是:先获取knife对象,等待3s后获取fork对象
//由于对knife使用了synchronized关键字,表示在使用该knife对象期间,其他线程无权使用
//只有等待同步代码块执行结束后,其他线程才有机会获取knife对象
@Override
public void run() {
synchronized (knife){
System.out.println(Thread.currentThread().getName()+"获得了刀,3s后获得叉");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (fork){
System.out.println(Thread.currentThread().getName()+"获得了叉,可以吃饭了");
}
}
}
}
package com.hqyj.DeadLock;
public class MyThread2 implements Runnable{
//定义两个成员变量:刀、叉
private Object knife;
private Object fork;
public MyThread2(Object knife, Object fork) {
this.knife = knife;
this.fork = fork;
}
//当前线程执行run方法的顺序是:先获取fork对象,等待3s后获取knife对象
//由于对fork使用了synchronized关键字,表示在使用该fork对象期间,其他线程无权使用
//只有等待同步代码块执行结束后,其他线程才有机会获取fork对象
@Override
public void run() {
synchronized (fork){
System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (knife){
System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了");
}
}
}
}
package com.hqyj.DeadLock;
public class Main {
public static void main(String[] args) {
Object knife = new Object();
Object fork = new Object();
Thread mt1 = new Thread(new MyThread1(knife, fork), "小明");
Thread mt2 = new Thread(new MyThread2(knife, fork), "刘涵");
mt1.start();
mt2.start();
}
}
死锁的解决方式一:
让线程A和线程B获取资源A和资源B的顺序保持一致。
如让两个线程都先获取fork,再获取knife
@Override
public void run() {
synchronized (fork){
System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (knife){
System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了");
}
}
}
死锁的解决方式二:
让线程A和线程B获取资源A和资源B之前,再获取第三个资源,并对其使用synchronized进行同步,
这样线程A在获取第三个资源后,将所有事情执行完后,线程B才能继续。
如在获取fork和knife之前,先获取paper对象
@Override
public void run() {
//先获取paper,在拥有paper对象期间,其他线程排队等待
synchronized (paper){
System.out.println(Thread.currentThread().getName()+"获得了餐巾,准备下一步");
synchronized (fork){
System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (knife){
System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了");
}
}
}
}