笔记总结01"/>
Java自学笔记总结01
Java自学笔记01
- 一、抽象类
- 1.1 抽象类概述
- 1.2 抽象类的特点
- 1.3 抽象类的成员特点
- 二、接口
- 2.1 接口概述
- 2.2 接口的特点
- 2.3 接口的成员特点
- 2.4 类和接口的关系
- 2.5 抽象类和接口的区别
- 三、形参和返回值
- 3.1 类名作为形参和返回值
- 3.2 抽象类名作为形参和返回值
- 3.3 接口名作为形参和返回值
- 四、内部类
- 4.1 内部类概述
- 4.2 成员内部类(类中定义类,为了隐藏)
- 4.3 局部内部类(方法中定义类)
- 4.4 匿名内部类(特殊的局部内部类)
- 五、常用`API`
- 5.1.1 Math 类概述
- 5.1.2 Math 类的常用方法
- 5.2 System类概述
- 5.3 Object类的概述
- 5.4 Date类
- 5.4.1 Date类概述和构造方法
- 5.4.2 Date类的常用方法
- 5.5 SimpleDateFormat 类概述
- 5.5.2 SimpleDateFormat的构造方法
- 5.5.3 `SimpleDateFormat`格式化和解析日期
- 5.6 Calendar类概述
- 5.6.2 Calendar的常用方法
- 六、基本类型包装
- 6.1 基本类型包装类概述
- 6.2 Integer类的概述和使用
- 6.3 int和String的相互转换
- 6.4 自动装箱和拆箱
- 七、异常
- 7.1 异常概述
- 7.2 JVM的默认处理方案
- 7.3 异常处理
- 7.4 异常处理之`try...catch...`
- 7.5 Throwable的成员方法
- 7.6 编译时异常和运行时异常的区别
- 7.7 异常处理之`throws` (抛出异常)
- 7.8 自定义异常
- 7.9 throws和throw的区别
- 八、集合
- 8.1 集合概述
- 8.2 集合类体系结构
- 8.3 Collection
- 8.3.1 Collection集合概述和使用
- 8.3.2 Collection集合常用方法
- 8.3.3 Collection集合的遍历
- 8.3.4 集合的使用步骤
- 8.4 List
- 8.4.1 List集合概述和特点
- 8.4.2 List集合特有方法
- 8.4.3 并发修改异常
- 8.4.4 Listlterator
- 8.4.5 增强for循环
- 8.4.6 数据结构
- 8.4.7 List集合子类特点
- 8.4.8 LinkedList集合的特有功能
- 8.5 Set
- 8.5.1 Set集合概述和特点
- 8.5.2 哈希值
- 8.5.3 HashSet集合概述和特点
- 8.5.4 HashSet集合保证元素唯━性源码分析
- 8.5.5 常见数据结构之哈希表
- 8.5.6 LinkedHashSet集合概述和特点
- 8.5.7 TreeSet集合概述和特点TreeSet集合特点
- 8.5.8 自然排序Comparable的使用
- 8.5.9 比较器排序Comparator的使用
- 8.6、泛型
- 8.6.1 泛型概述
- 8.6.2 泛型类
- 8.6.3 泛型方法
- 实现类似方法重载功能的多种方式
- 8.6.4 泛型接口
- 8.6.5 类型通配符
- 8.6.6 可变参数
- 8.6.7 可变参数的使用
- 8.7 Map
- 8.7.1 Map集合概述和使用
- 8.7.2 Map集合的基本功能
- 8.7.3 Map集合的获取功能
- 8.7.4 Map集合的遍历(方式1)
- 8.7.5 Map集合的遍历(方式2)
- 案例
- 8.8 Collections
- 8.8.1 Collections概述和使用
- 案例:模拟斗地主升级版
- 九、File
- 9.1 File类概述和构造方法
- 9.2 File类创建功能
- 9.3 File类删除功能
- 9.4 File类判断和获取功能
- 9.5 递归
- 案例
- 十、字节流
- 10.1 IO流概述和分类
- 10.2 字节流写数据
- 10.3 字节流写数据的3种方式
- 10.4 字节流写数据的两个小问题
- 10.5 字节流写数据加异常处理
- 10.6 字节流读数据(一次读一个字节数据)
- 案例
- 10.6 字节流读数据(一次读一个字节数组数据)
- 10.7 字节缓冲流
- 案例
- 十一、字符流
- 11.1 为什么会出现字符流
- 11.2 编码表
- 11.3 字符串中的编码解码问题
- 11.5 字符流写数据的5种方式
- 11.6 字符流读数据的2种方式
- 案例
- 11.7 字符缓冲流
- 案例
- 11.8 字符缓冲流特有功能
- 案例
- 11.9 IO流小结
- 案例
- 11.10 复制文件的异常处理
- 十二、特殊操作流
- 12.1 标准输入输出流
- 12.2 打印流
- 案例
- 12.3 对象序列化流
- 12.4 Properties
- 案例
自学java的笔记整理
推荐自学视频资源是B站的黑马程序员
一、抽象类
1.1 抽象类概述
在Java
中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类
1.2 抽象类的特点
- 抽象类和抽象方法必须使用
abstract
关键字修饰
public abstract class
类名 {}
public abstract void eat();
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 抽象类不能实例化
抽象类如何实例化参照多态的方式,通过子类对象实例化,这叫抽象类多态
- 抽象类的子类
重写抽象类中的所有抽象方法
定义为抽象类
1.3 抽象类的成员特点
- 成员变量
变量
常量
-
构造方法
有构造方法,但是不能实例化
构造方法用于子类访问父类数据的初始化
-
成员方法
可以有抽象方法:限定子类必须完成某些动作 (不重写抽象方法会报错)
也可以有非抽象方法:提高代码复用性
二、接口
2.1 接口概述
接口就是一种公共的规范标准,只要符合规范标准。大家都可以通用Java中的接口更多的体现在对行为的抽象
2.2 接口的特点
-
接口用关键字
interface
修饰
public interface 接口名 {}
-
类实现接口用
implements
表示
public class 类名 implements 接口名 {}
-
接口不能实例化
接口实例化: 参照多态的方式,通过实现类对象实例化,这叫接口多态。
多态的形式:具体类多态,抽象类多态,接口多态。
多态的前提:有继承或者实现关系;有方法重写;有父(类/接口)引用指向(子/实现)类对象
-
接口的实现类
重写接口中的所有抽象方法
定义为抽象类
2.3 接口的成员特点
-
成员变量
只能是常量
默认修饰符:
public static final
-
构造方法
接口没有构造方法,因为接口主要是对行为进行抽象的,是没有具体存在
—个类如果没有父类,默认继承自Object类
- 成员方法
只能是抽象方法
默认修饰符: public abstract
2.4 类和接口的关系
-
类和类的关系
继承关系,只能单继承,但是可以多层继承
-
类和接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
-
接口和接口的关系
继承关系,可以单继承,也可以多继承
2.5 抽象类和接口的区别
-
成员区别
抽象类 变量, 常量;有构造方法;有抽象方法,也有非抽象方法
接口 常量 ; 抽象方法
-
关系区别
类与类 继承,单继承
类与接口 实现,可以单实现,也可以多实现
接口与接口 继承,单继承,多继承
-
设计理念区别
抽象类 对类抽象,包括属性、行为
接口 对行为抽象,主要是行为
三、形参和返回值
3.1 类名作为形参和返回值
- 方法的形参是类名,其实需要的是该类的对象·
- 方法的返回值是类名,其实返回的是该类的对象
3.2 抽象类名作为形参和返回值
-
方法的形参是抽象类名,其实需要的是该抽象类的子类对象
-
方法的返回值是抽象类名,其实返回的是该抽象类的子类对象
3.3 接口名作为形参和返回值
-
方法的形参是接口名,其实需要的是该接口的实现类对象
-
方法的返回值是接口名,其实返回的是该接口的实现类对象
四、内部类
4.1 内部类概述
内部类: 就是在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
内部类的定义格式
- 格式
public class类名{修饰符 class 类名{}
}
- 范例:
publie class outer {public class Inner {}
}
-
内部类的访问特点
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象
4.2 成员内部类(类中定义类,为了隐藏)
按照内部类在类中定义的位置不同,可以分为如下两种形式
-
在类的成员位置:成员内部类
-
在类的局部位置:局部内部类
外界使用成员内部类创建对象
-
格式:
外部类名.内部类名对象名=外部类对象.内部类对象
-
范例:
Outer.Inner oi = new Outer0.new Inner();
示例
publie class outer {private int num = 10; // 一般使用private权限,由外部类直接创建对象使用,避免被外界访问private class Inner {public void show(){System.out.println(num);}}public void method(){Inner i = new Inner();i.show();}}
4.3 局部内部类(方法中定义类)
局部内部类是在方法
中定义的类,所以外界是无法直接使用,需要在方法内部创建对象并使用
-
可以直接访问外部类的成员
-
也可以访问方法内的局部变量
示例
publie class outer {private int num = 10; public void method(){int num2 = 20;class Inner {public void show(){System.out.println(num);System.out.println(num2);}} Inner i = new Inner();i.show();}}
4.4 匿名内部类(特殊的局部内部类)
前提: 存在一个类或者接口,这里的类可以是具体类也可以是抽象类
本质: 是一个继承了该类或者实现了该接口的子类匿名对象
- 格式
new 类名或接口名(){重写方法
};
- 范例
new Inter(){public void show(){}
};
示例
接口定义
public interface Inter {// 接口可以省略 public abstractvoid show();
}
Outer.java
public class Outer {public void mether(){// 直接定义并实例化一个对象new Inter() {@Overridepublic void show(){System.ot.println("匿名内部类");}};// 使用该对象的show()方法new Inter() {@Overridepublic void show(){System.ot.println("匿名内部类");}}.show();// 使用 i 记录Inner对象的位置Inner i = new Inter() {@Overridepublic void show(){System.ot.println("匿名内部类");}};// 使用改对象的show()方法i.show();}}
测试类
public class OuterDemo {public staic void main(String[] args){Outer o = new Outer();o.methed();}
}
五、常用API
若类没有构造方法,则查看类中成员是否都是静态的,如果是,通过类名就可以直接调用
5.1.1 Math 类概述
Math
包含执行基本数字运算的方法
5.1.2 Math 类的常用方法
方法名 | 说明 |
---|---|
public static int abs(int a) | 返回参数绝对值 |
public static double ceil(double a) | 返回大于或等于参数的最小double 值,等于一个整数 |
public static double floor(double a) | 返回小于或等于参数的最大double 值,等于一个整数 |
public static int round(float a) | 按照四舍五入返回最接近参数的int |
public static int max(int a, int b) | 返回两个int 中的较大值 |
public static int min(int a, int b) | 返回两个int 中的较小值 |
public static double pow(double a, double b) | 返回a 的b 次幂 |
public static double random() | 返回值为double 的正值 [0.0 , 1.0) |
5.2 System类概述
System
包含几个有用的类字段和方法。它不能被实例化
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的java 虚拟机,非零表示异常终止 |
public static long currentTimeMills() | 返回当前时间(以毫秒为单位) |
5.3 Object类的概述
object
是类层次结构的根,每个类都可以将Object
作为超类。所有类都直接或者间接的继承自该类
构造方法: public Object()
回想面向对象中,为什么说子类的构造方法默认访问的是父类的无参构造方法?因为它们的顶级父类只有无参构造方法
toString
将输出对象本身(它已经是一个字符串)
equals
用于比较对象中所包含的内容是否相同
一般建议重写 toString
和 equals
方法 ,(按需求完成输出和比较功能)
5.4 Date类
5.4.1 Date类概述和构造方法
Date
代表了一个特定的时间,精确到毫秒
方法名 | 说明 |
---|---|
public Date() | 分配一个Date 对象,并初始化,以便它代表它被分配的时间,精确到毫秒 |
public Date(long date) | 分配一个Date对象,并将其初始化为表示从标准基时 间起指定的毫秒数 |
5.4.2 Date类的常用方法
方法名 | 说明 |
---|---|
public long getTime() | 获取的是日期对象从1970年1月1日00:00:00到现在的毫秒 |
public void setTime(long time) | 设置时间,给的是亳秒值 |
实例
public static void main(String[] args){// 创建日期对象Date d= new Date();System.out.println(d.getTime());long time = System.currentTimeMillis();d.setTime(time);System.out.println(d);
}
5.5 SimpleDateFormat 类概述
SimpleDateFormat
是一个具体的类,用于以区域设置敏感的方式格式化和解析日期
我们重点学习日期格式化和解析
日期和时间格式由日期和时间模式字符串指定,在日期和时间模式字符串中,从A
到Z
以及从a
到z
引号的字母被解释为表示日期或时间字符串的组件的模式字母
常用的模式字母及对应关系如下:
-
y
年 -
M
月 -
d
日 -
H
时 -
m
分 -
s
秒
5.5.2 SimpleDateFormat的构造方法
方法名 | 说明 |
---|---|
public SimpleDateFormat() | 构造一个SimpleDateFormat ,使用默认模式和日期格式 |
public SimpleDateFormat(String pattern) | 构造一个SimpleDateFormat 使用给定的模式和默认的日期格式 |
5.5.3 SimpleDateFormat
格式化和解析日期
- 格式化(从
Date
到String
)
public final String format(Date date):
将日期格式化成日期/时间字符串
- 解析(从
String
到Date
)
public Date parse(String source)
: 从给定字符串的开始解析文本以生成日期
实例
public static void main(String[] args) throws ParseException {// 格式化:从Date 到 StringDate d = new Date();// 按模式创建对象SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");String s = sdf.format(d);System.out.println(s);// 从 String 到 DateString ss = "2048-0809 11:11:11";// ParseException, 需要模式与上述 String 相同SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date dd = sdf2.parse(ss);System.out.println(dd);
}
5.6 Calendar类概述
Calendar
为某一时刻和一组日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法
Calendar
提供了一个类方法getlnstance用于获取Calendar对象,其日历字段已使用当前日期和时间
初始化:
Calendar rightNow = Calendar.getlnstance();
5.6.2 Calendar的常用方法
方法名 | 说明 |
---|---|
public int get(int field) | 返回给定日历字段的值 |
public abstract void add(int field, int amount) | 根据日历的规则,将指定的时间量添加或减去给定的日历字段 |
public final void set(int year,int month,int date) | 设置当前日历的年月日 |
实例
public static void main(String[] args) {// 获取对象Calendar c = Calendar.getInstance(); // 该函数利用多态实现// 若要改变 日历对象 c 则需要使用函数 add 如:// 正数则加年份,负数则减去年份c.add(Calendar.YEAR, -1);// 天数c.add(Calendar.DATE, 5)// 设置日历, 年月日c.set(2022, 10, 10);int year = c.get(Calendar.YEAR);// 从零开始计数int month = c.get(Calendar.MONTH) + 1;int date = c.get(Calendar.DATE);System.out.println(year + "年" + month + "月" + date + "日");}
六、基本类型包装
6.1 基本类型包装类概述
将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据常用的操作之一:用于基本数据类型与字符串之间的转换
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
6.2 Integer类的概述和使用
lnteger
: 包装一个对象中的原始类型int
的值
方法名 | 说明 |
---|---|
public Integer(int value) | 根据int 值创建Integer 对象(已过时) |
public Integer(String s) | 根据String 值创建Integer 对象(已过时) |
public static Integer valueOf(int i) | 返回表示指定的 int 值的 Integer 实例 |
public static Integer valueOf(String s) | 返回一个保存指定值的Integer 对象String |
6.3 int和String的相互转换
基本类型包装类的最常见操作就是:用于基本类型和字符串之间的相互转换
int
转换为String
public static String valueOf(int i)
:
返回int
参数的字符串表示形式。该方法是String
类中的方
String
转换为int
public static int parseInt(String s)
:
将字符串解析为int
类型。该方法是Integer
类中的方法
String
和 int
相互转换
// int --> String
int number = 100;
// 方式1
String s1 = ""+ number;
System.out.println(s1);
// 方式2
String s2 = String.valueOf(number);
System.out.println(s2);// String --> int
String s = "100";
// 方式1
//String --- Integer --- int
Integer i = Integer.valueOf(s);
int x = i.intValue();
System.out.println(x);
// 方式2
int y = Integer.parseInt(s);
System.out.println(y);
6.4 自动装箱和拆箱
装箱: 把基本数据类型转换为对应的包装类类型
拆箱: 把包装类类型转换为对应的基本数据类型
Integer i =100; //自动装箱
i += 200; // i= i+200; i+200 是自动拆箱; i = i+200; 是自动装箱;
七、异常
7.1 异常概述
异常:就是程序出现了不正常的情况
异常体系
Throwable
分为 Error
和 Exception
Exception
分为 RuntimeExceion
和 非RuntimeExceion
Error
: 严重问题,不需要处理
Exception
: 称为异常类,它表示程序本身可以处理的问题
-
RuntimeException
: 在编译期是不检查的,出现问题后,需要我们回来修改代码 -
非RuntimeException
: 编译期就必须处理的,否则程序不能通过编译,就更不能正常运行了
7.2 JVM的默认处理方案
如果程序出现了问题,我们没有做任何处理,最终JM会做默认的处理
-
把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
-
程序停止执行
7.3 异常处理
如果程序出现了问题,我们需要自己来处理,有两种方案:
try ... catch ...
throws
7.4 异常处理之try...catch...
- 格式:
try {可能出现异常的代码;
}catch (异常类名 变量名){异常的处理代码;
}
执行流程:
程序从try里面的代码开始执行
出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java
运行时系统
当Java
运行时系统接收到异常对象时,会到catch
中去找匹配的异常类,找到后进行异常的处理执行完
毕之后,程序还可以继续往下执行
7.5 Throwable的成员方法
方法名 | 说明 |
---|---|
public String getMessage() | 返回此throwable的详细消息字符串 |
public String toString() | 返回此可抛出的简短描述 |
public void printStackTrace() | 把异常的错误信息输出在控制台 |
7.6 编译时异常和运行时异常的区别
Java
中的异常被分为两大类:编译时异常和运行时异常,也被称为受检异常和非受检异常
所有的RuntimeException
类及其子类被称为运行时异常,其他的异常都是编译时异常
-
编译时异常: 必须显示处理,否则程序就会发生错误,无法通过编译
-
运行时异常: 无需显示处理,也可以和编译时异常一样处理
7.7 异常处理之throws
(抛出异常)
虽然我们通过try...catch...
可以对异常进行处理,但是并不是所有的情兄我们都有权限进行异常的处理
针对出现的异常是我们处理不了的这种情况,Java
提供了throws
的处理方案
格式:
throws 异常类名;
注意: 这个格式是跟在方法的括号后面的
throws
抛出异常但不做处理(程序不会往下执行),仍然需要try...catch...
捕获异常并对异常进行处理
- 编译时异常必须要进行处理,两种处理方案:
try..catch
或者throws
,如果采用throws
这种方案,将来谁调用谁处理 - 运行时异常可以不处理,出现问题后,需要我们回来修改代码
7.8 自定义异常
格式:
public class异常类名extends Exception {无参构造带参构造
}
范例:
public class scoreException extends Exception {public scoreException () {}public scoreException (string message) {super (message) ;}
}
使用实例
public void checkScore(int score) throws ScoreException{if(score<0 || score>100) {// 没有给出异常原因, 此处掏出异常throw没有s// throw new ScoreException();// 给出异常原因 throw newScoreException("你给的分数有误");} else {System.out.println("分数正常");}}
7.9 throws和throw的区别
throws | throw |
---|---|
用在方法声明后面,跟的是异常类名 | 用在方法体内,跟的是异常对象名 |
表示抛出异常,由该方法的调用者来处理 | 表示抛出异常,由方法体内的语句处理 |
表示出现异常的一种可能性,并不一定会发生这些异常 | 执行 throw 一定抛出了某种异常 |
八、集合
8.1 集合概述
集合类的特点:提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变
8.2 集合类体系结构
集合分为 Colletion
(单列) 和 Map
(双列)
Colletion
(单列) 分为List
(可重复) 和 Set
(不可重复)
List
可分为 ArrayList
、LinkedList
等
Set
可 分为 HashSet
、TreeSet
等
Map
(双列) 可分为 HashMap
等
8.3 Collection
8.3.1 Collection集合概述和使用
Collection
集合概述
-
是单例集合的顶层接口,它表示一组对象,这些对象也称为
Collection
的元素 -
JDK
不提供此接口的任何直接实现,它提供更具体的子接口(如Set
和List
)实现
创建Collection
集合的对象
-
多态的方式
-
具体的实现类
ArrayList
示例
public static void main(){// 创建Collection集合的对象Collection<String> c = new Arraylist<String>();// 添加元素c.add("hello");c.add("world");c.add("java");// 输出集合对象System.out.println(c);// [hello, world, java]
}
8.3.2 Collection集合常用方法
alt+7
打开一个窗口可以查看类的所有信息
方法名 | 说明 |
---|---|
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定的元素 |
void clear() | 清空集合中的元素 |
boolean contains(Object o) | 判断集合中是否存在指定的元素 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中元素的个数 |
8.3.3 Collection集合的遍历
lterator
: 迭代器 集合的专用遍历方式
-
lterator<E> iterator()
: 返回此集合中元素的迭代器,通过集合的iterator()
方法得到 -
迭代器是通过集合的
iterator()
方法得到的,所以我们说它是依赖于集合而存在的
lterator
中的常用方法
-
E next()
: 返回迭代中的下一个元素 -
boolean hasNext()
: 如果迭代具有更多元素,则返回true
实例
public static void main(String[] args){// 创建集合对象Collection<String> c = new ArrayList<String>();// 添加元素c.add("hello");c.add("world");c.add("java");// 返回此集合中元素的迭代器Iterator<String> it = c.iterator();// 循环遍历while(it.hasNext()){// 取出值String s = it.next();System.out.println(s);}
}
8.3.4 集合的使用步骤
步骤1: 创建集合对象
步骤2: 添加元素
步骤2.1:创建元素
步骤2.2:添加元素到集合
合并:添加元素到集合
步骤3: 遍历集合
步骤3.1∶ 通过集合对象获取迭代器对象
步骤3.2: 通过迭代器对象的hasNext()
方法判断是否还有元素
步骤3.3: 通过迭代器对象的next ()
方法获取下一个元素
实例
public static void main(String[] args){Collection<String> c = new ArrayList<String>(); // 步骤1: 创建集合对象// 步骤2: 添加元素String s = "hello"; // 步骤2.1:创建元素 c.add(s); // 步骤2.2:添加元素到集合c.add("world"); // 合并:添加元素到集合// 步骤3: 遍历集合Iterator<String> it = c.iterator(); // 步骤3.1∶ 通过集合对象获取迭代器对象while(it.hasNext()){ // 步骤3.2: 通过迭代器对象的`hasNext()`方法判断是否还有元素String s = it.next(); // 步骤3.3: 通过迭代器对象的`next ()`方法获取下一个元素System.out.println(s);}
}
8.4 List
8.4.1 List集合概述和特点
List
集合概述
-
有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
-
与
Set
集合不同,列表通常允许重复的元素
List
集合特点
-
有序: 存储和取出的元素顺序一致
-
可重复: 存储的元素可以重复
8.4.2 List集合特有方法
方法名 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
8.4.3 并发修改异常
并发修改异常 ConcurrentModificationException
产生原因: 迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致
解决方案: 使用for
循环而不是迭代器进行遍历(可以使用索引),然后用集合对象做对应的操作即可
实例
public static void main(String[] args){// 创建集合对象List<String> list = new ArrayList<String>();// 添加元素list.add("hello");list.add("world");list.add("java");// 遍历集合,得到每一个元素,查看是否存在"world"这个元素,存在则添加"javaee"// 以下代码存在并发修改异常Iterator<String> it = list.iterator();while(it.hasNext()){String s = it.next();if(s.equals("world")){list.add("javaee");}}// 解决方法for(int i=0; i<list.size(); i++){String s = list.get(i);if(s.equals("world")){list.add("javaee"); }}}
8.4.4 Listlterator
Listlterator
: 列表迭代器
-
通过
List
集合的listlterator()
方法得到,所以说它是List集合特有的迭代器 -
用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置
Listlterator
中的常用方法
方法名 | 说明 |
---|---|
E next() | 返回迭代中的下一个元素 |
boolean hasNext() | 如果迭代具有更多元素,则返回true |
E previous() | 返回列表中的上一个元素 |
boolean hasPrevious() | 如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true |
void add(E e) | 将指定的元素插入列表 |
8.4.5 增强for循环
增强for
: 简化数组和Collection
集合的遍历
-
实现
lterable
接口的类允许其对象成为增强型for
语句的目标 -
它是
JDK5
之后出现的,其内部原理是一个lterator
迭代器
增强for
的格式
- 格式
for(元素数据类型变量名∶数组或者Collection集合){//在此处使用变量即可,该变量就是元素
}
- 范例
int[] arr = {1,2,3,4,5};
for(ift i : arr) {System.out.printIn(i);
}
8.4.6 数据结构
栈 先进后出
队列 先进先出
数组
数组是一种查询快,增删慢的模型
查询数据通过索引定位,查询任意数据耗时相同,查询效率高
删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
添加数据时,添加位置后的每个数据后移,再添加元素,添加效率极低
链表 链表是一种增删快的模型(对比数组)
8.4.7 List集合子类特点
List
集合常用子类: ArrayList
,LinkedList
ArrayList
: 底层数据结构是数组,查询快,增删慢
LinkedList
: 底层数据结构是链表,查询慢,增删快
8.4.8 LinkedList集合的特有功能
方法名 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
publicE removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
8.5 Set
8.5.1 Set集合概述和特点
Set
集合特点
-
不包含重复元素的集合
-
没有带索引的方法,所以不能使用普通
for
循环遍历
8.5.2 哈希值
哈希值: 是JDK
根据对象的地址或者字符串或者数字算出来的int
类型的数值
Object
类中有一个方法可以获取对象的哈希值
public int hashCode()
: 返回对象的哈希码值
对象的哈希值特点
同一个对象多次调用hashCode()
方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。而重写hashCode()
方法,可以实现让不同对象的哈希值相同
8.5.3 HashSet集合概述和特点
HashSet
集合特点
-
底层数据结构是哈希表
-
对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
-
没有带索引的方法,所以不能使用普通for循环遍历
-
由于是Set集合,所以是不包含重复元素的集合
8.5.4 HashSet集合保证元素唯━性源码分析
HashSet
集合添加一个元素的过程:
HashSet
集合存储元素:
要保证元素唯一性,需要重写 hashCode()
和 equals()
8.5.5 常见数据结构之哈希表
哈希表
JDK8
之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
JDK8
以后,在长度比较长的时候,底层实现了优化
示例
// 创建HashSet集合对象
HashSet<类名> ht = new HashSet<类名>();
// 添加进入Hash表
ht.add(该类对象实例);
8.5.6 LinkedHashSet集合概述和特点
LinkedHashSet
集合特点
-
哈希表和链表实现的
Set
接口,具有可预测的迭代次序 -
由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
-
由哈希表保证元素唯一,也就是说没有重复的元素
8.5.7 TreeSet集合概述和特点TreeSet集合特点
元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方法取决于构造方法
TreeSet()
: 根据其元素的自然排序进行排序
TreeSet(Comparator comparator)
: 根据指定的比较器进行排序
-
没有带索引的方法,所以不能使用普通
for
循环遍历(可以使用迭代器或增强for
进行遍历) -
由于是
Set
集合,所以不包含重复元素的集合
8.5.8 自然排序Comparable的使用
-
存储学生对象并遍历,创建
TreeSet
集合使用无参构造方法 -
要求: 按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
结论
-
用
TreeSet
集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的 -
自然排序,就是让元素所属的类实现
Comparable
接口,重写compareTo(T o)
方法 -
重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
this
在前 升序
this
在后 降序
实例:
Student
类
public class Student implements Comparable<Student> {private String name;private int age;public Student() {// TODO 自动生成的构造函数存根}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic int compareTo(Student s) {
// return 0; 相同
// return 1; 升序
// return -1; 降序// int num = this.age -s.age; // 升序
// int num = s.age - this.age; // 降序
// return num;int num = this.age -s.age;int num2 = num==0?this.namepareTo(s.name):num;return num2;}
}
测试类 需要导包 import java.util.TreeSet;
public static void main(String[] args) {// 创建集合对象TreeSet<Student> ts = new TreeSet<Student>();// 创建学生对象Student s1 = new Student("xishi", 29);Student s2 = new Student("wangzhaojun", 28);Student s3 = new Student("diaochan", 23);Student s4 = new Student("yangyuhuan", 27);Student s5 = new Student("linqingxia", 23);Student s6 = new Student("linqingxia", 23);// 把学生添加到ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);ts.add(s6);// 遍历集合for (Student s : ts) {System.out.println(s.getName() + "," + s.getAge());;}
}
8.5.9 比较器排序Comparator的使用
-
存储学生对象并遍历,创建
TreeSet
集合使用带参构造方法 -
要求: 按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
结论
-
用
TreeSet
集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的 -
比较器排序,就是让集合构造方法接收
Comparator
的实现类对象,重写compare(T o1,T o2)
方法 -
重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
Student
类
public class Student implements Comparable<Student> {private String name;private int age;public Student() {// TODO 自动生成的构造函数存根}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
测试类 需要导包 import java.util.TreeSet;
public static void main(String[] args) {// 创建集合对象, 此处使用匿名内部类的方式创建比较器对象TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {int num = s1.getAge() - s2.getAge();int num2 = num==0 ? s1.getName()pareTo(s2.getName()):num;return num2;}});// 创建学生对象Student s1 = new Student("xishi", 29);Student s2 = new Student("wangzhaojun", 28);Student s3 = new Student("diaochan", 23);Student s4 = new Student("yangyuhuan", 27);Student s5 = new Student("linqingxia", 23);Student s6 = new Student("linqingxia", 23);// 把学生添加到ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);ts.add(s6);// 遍历集合for (Student s : ts) {System.out.println(s.getName() + "," + s.getAge());;}
}
产生随机数
// 创建随机数对象 Random r = new Random(); // 生成一个1~20之间的随机数 int number = r.nextInt(20) + 1;
8.6、泛型
8.6.1 泛型概述
泛型: 是JDK5
中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型
本质是参数化类型,也就是说所操作的数据类型被指定为一个参数
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原
来的具体的类型参数化,然后在使用/调用时传入具体的类型
这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
泛型定义格式:
-
<类型>
: 指定一种类型的格式。这里的类型可以看成是形参 -
<类型1,类型2.…>
: 指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参 -
将来且体调用时候给定的型可以看成是实参,并且实参的类型只能是引用数据类型
泛型的好处:
-
把运行时期的问题提前到了编译期间
-
避免了强制类型转换
8.6.2 泛型类
泛型类的定义格式:
格式: 修饰符 class 类名<类型> {}
范例: public class Generic<T> {}
此处T
可以随便写为任意标识,常见的如T
、E
、K
、V
等形式的参数常用于表示泛型
实例
public class Generic<T> {private T t;public T getT(){return t;}public void setT(T t){this.t = t;}
}
测试类
public static void main(String[] args){Generic<String> s1 = new Generic<String>();s1.setT("aaa");System.out.println(s1.getT());Generic<Integer> s1 = new Generic<Integer>();s1.setT(18);System.out.println(s1.getT());
}
8.6.3 泛型方法
泛型方法的定义格式:
格式: 修饰符<类型> 返回值类型 方法名(类型 变量名) {}
范例: public <T> void show(T t){}
实现类似方法重载功能的多种方式
方法重载
public class Generic {public void show(String s) {System.out.println(s);}public void show(Integer s) {System.out.println(s);}public void show(Boolean s) {System.out.println(s);}
}
测试类
public class GenericDemo {public static void main(String[] args) {Generic g = new Generic();g.show("hello!");g.show(18);g.show(true);}
}
泛型类改进
public class Generic<T>{public void show(T t) {System.out.println(t);}
}
测试类
public static void main(String[] args) { Generic<String> s1 = new Generic<String>();s1.show("hello");Generic<Integer> s2 = new Generic<Integer>();s2.show(18);Generic<Boolean> s3 = new Generic<Boolean>();s3.show(true);}
}
泛型方法改进
public class Generic {public <T> void show(T t) {System.out.println(t);}
}
测试类
public class GenericDemo {public static void main(String[] args) {Generic g = new Generic();g.show("hello!");g.show(18);g.show(true);}
}
8.6.4 泛型接口
泛型接口的定义格式:
格式: 修饰符 interface 接口名<类型> {}
范例: public interface Generic<T> {}
实例
泛型接口
public interface Generic<T> {void show(T t);
}
类
public class GenericImpl<T> implements Generic<T>{@Overridepublic void show(T t) {System.out.println(t);}
}
测试类
public class GenericDemo {public static void main(String[] args) {Generic<String> g1 = new GenericImpl<String>();g1.show("hello");Generic<Integer> g2 = new GenericImpl<Integer>();g2.show(18);}
}
8.6.5 类型通配符
为了表示各种泛型List
的父类,可以使用类型通配符
-
类型通配符:
<?>
-
List<?>
: 表示元素类型未知的List
,它的元素可以匹配任何的类型 -
这种带通配符的
List
仅表示它是各种泛型List
的父类,并不能把元素添加到其中
如果说我们不希望List<?>
是任何泛型List
的父类,只希望它代表某一类泛型List
的父类,可以使用类型通配符的上限
-
类型通配符上限:
<? extends类型>
-
List<? extends Number>
:它表示的类型是Number
或者其子类型
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限类型
-
通配符下限:
<?super类型>
-
List<? super Number>
: 它表示的类型是Number
或者其父类型
实例
public class Generic {public static void main(String[] args) {// 类型通配符: <?>List<?> list1 = new ArrayList<Object>();List<?> list2 = new ArrayList<Number>();List<?> list3 = new ArrayList<Integer>();// 通配符上限: <? extends 类型>
// List<? extends Number> list4 = new ArrayList<Object>();List<? extends Number> list5 = new ArrayList<Number>();List<? extends Number> list6 = new ArrayList<Integer>();// 通配符下限: <? super 类型>List<? super Number> list7 = new ArrayList<Object>();List<? super Number> list8 = new ArrayList<Number>();
// List<? super Number> list9 = new ArrayList<Integer>(); }
}
8.6.6 可变参数
可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
格式 : 修饰符 返回值类型 方法名(数据类型...变量名){}
范例: public static int sum(int...a){}
可变参数注意事项
-
这里的变量其实是一个数组
-
如果一个方法有多个参数,包含可变参数,可变参数要放在最后
实例
public class Demo {public static void main(String[] args) {System.out.println(sum(10, 20));System.out.println(sum(10, 20, 30, 40));}public static int sum(int...a) {int sum = 0;for(int i : a) {sum += i;}return sum;}
}
8.6.7 可变参数的使用
Arrays
工具类中有一个静态方法:
public static <T> List<T> asList(T... a)
: 返回由指定数组支持的固定大小的列表
List
接口中有一个静态方法:
public static <E> List<E> of(E... elements)
: 返回包含任意数量元素的不可变列表
Set
接口中有一个静态方法:
public static <E> Set<E> of(E... elements)
: 返回一个包含任意数量元素的不可变集合
实例
public class ArgsDemo {public static void main(String[] args) {List<String> list = Arrays.asList("hello", "world", "java");
// list.add("javaee"); // UnsupportedOperationException
// list.remove("world"); // UnsupportedOperationExceptionlist.set(1, "javaee");System.out.println(list);List<String> list1 = List.of("hello", "world", "java");
// list1.add("javaee"); // UnsupportedOperationException
// list1.remove("world"); // UnsupportedOperationException
// list1.set(1, "javaee"); // UnsupportedOperationExceptionSystem.out.println(list1);// Set<String> set = Set.of("hello", "world", "java", "hello"); // IllegalArgumentExceptionSet<String> set = Set.of("hello", "world", "java");
// set.add("javaee"); // UnsupportedOperationException
// set.remove("java"); // UnsupportedOperationExceptionSystem.out.println(set);}
}
8.7 Map
8.7.1 Map集合概述和使用
Map
集合概述
lnterface Map<K,V>
K
: 键的类型 ; V
: 值的类型
-
将键映射到值的对象
-
不能包含重复的键
-
每个键可以映射到最多一个值
举例:
学生的学号和姓名
itheima001
林青霞
itheima002
张曼玉
itheima003
王祖贤
使用实例
public class Mapdemo {public static void main(String[] args) {// 创建集合对象Map<String,String> map = new HashMap<String,String>();// 将指定的值与该映射中的指定键相关联map.put("001", "aa");map.put("002", "ab");map.put("003", "ac");map.put("004", "ad");// 输出集合对象System.err.println(map);}
}
8.7.2 Map集合的基本功能
方法名 | 说明 |
---|---|
v put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
8.7.3 Map集合的获取功能
方法名 | 说明 |
---|---|
V get(Object key) | 根据键获取值 |
Set<K> keySet() | 获取所有键的集合 |
Collection<V> values() | 获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
8.7.4 Map集合的遍历(方式1)
我们刚才存储的元素都是成对出现的,所以我们把Map
看成是一个夫妻对的集合遍历思路
把所有的丈夫给集中起来
遍历丈夫的集合,获取到每一个丈夫根据丈夫去找对应的妻子
转换为Map
集合中的操作:
获取所有键的集合。用keySet()方法实现
遍历键的集合,获取到每一个键。用增强for实现根据键去找值。用get(Object key)
方法实现
8.7.5 Map集合的遍历(方式2)
我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
遍历思路
-
获取所有结婚证的集合
-
遍历结婚证的集合,得到每一个结婚证
-
根据结婚证获取丈夫和妻子
转换为Map
集合中的操作:
- 获取所有键值对对象的集合
Set<Map.Entry<K,V> > entrySet()
: 获取所有键值对对象的集合
- 遍历键值对对象的集合,得到每一个键值对对象
用增强for实现,得到每一个Map.Entry
- 根据键值对对象获取键和值
用getKey()
得到键
用getValue()
得到值
遍历实例
public class MapDemo01 {public static void main(String[] args) {// 创建集合对象Map<String,String> map = new HashMap<String,String>();// 添加元素map.put("张无忌", "赵敏");map.put("郭靖", "黄蓉");map.put("杨过", "小龙女");// 方法一// 获取所有键的集合 用keySet() 方法实现Set<String> keySet = map.keySet();// 遍历所有键的集合,用增强for获取每一个键for (String key : keySet) {// 根据键找值用get(Object key) 方法实现String value = map.get(key);System.out.println(key + "," + value);}// 方法二// 获取所有键值对对象的集合,得到每一个键值对对象Set<Map.Entry<String, String>> entrySet = map.entrySet();// 遍历键值对对象的集合,得到每一个键值对对象for(Map.Entry<String, String> me : entrySet) {// 根据键值对对象获取键和值String key = me.getKey();String value = me.getValue();System.out.println(key + "," + value);}}
}
案例
案例: ArrayList
集合存储HashMap
元素并遍历
需求: 创建一个ArrayList
集合,存储三个元素,每一个元素都是HashMap
,每一个HashMap
的键和值都是String
,并遍历
思路:
-
创建
ArrayList
集合 -
创建
HashMap
集合,并添加键值对元素 -
把
HashMap
作为元素添加到ArrayList
集合 -
遍历
ArrayList
集合
public static void main(String[]args){//创建ArrayList集合ArrayList<HashMap<String,String>> array = new ArrayList<HashMap<String,String>>();//创建HashMap集合,并添加键值对元素HashMap<String,String> hm1 = new HashMap<String, String>();hm1.put("孙策", "大乔");hm1.put("周瑜", "小乔");//把HashMap作为元素添加到ArrayList集合array .add(hm1);HashMap<String,String> hm2 = new HashMap<String, String>();hm2.put("郭靖", "黄蓉");hm2.put("杨过", "小龙女");//把HashMap作为元素添加到ArrayList集合array .add(hm2) ;HashMap<String,String> hm3 = new HashMap<String, String>();hm3.put("令孤冲", "任盈盈");hm3.put("林平之", "岳灵珊");//把HashMap作为元素添加到ArrayList集合array .add( hm3) ;//遍历ArrayList集合for (HashMap<String, String> hm : array ) {Set<String> keySet = hm.keySet( );for (String key : keySet) {String value = hm.get( key ) ;System.out.println( key + "," + value) ;}}
}
案例: HashMap
集合存储ArayList
元素并遍历
需求: 创建一个HashMap
集合,存储三个键值对元素,每一个键值对元素的键是String
,值是ArrayList
,每一个ArrayList
的元素是String
,并遍历
思路:
-
创建
HashMap
集合 -
创建
ArrayList
集合,并添加元素 -
把
ArrayList
作为元素添加到HashMap
集合 -
遍历
HashMap
集合
public static void main(String[] args) {// 创建HashMap集合HashMap<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();// 创建ArrayList 集合,并添加元素ArrayList<String> sgyy = new ArrayList<String>();sgyy.add("诸葛亮");sgyy.add("周瑜");// 把ArrayList作为元素添加到HashMap集合hm.put("三国演义",sgyy);ArrayList<String> xyj = new ArrayList<String>();xyj.add("孙悟空");xyj.add("唐僧");// 把ArrayList作为元素添加到HashMap集合hm.put("西游记",xyj);ArrayList<String> shz = new ArrayList<String>();shz.add("武松");shz.add("鲁智深");// 把ArrayList作为元素添加到HashMap集合hm.put("水浒传",shz);// 遍历HashMap集合Set<String> keySet = hm.keySet();for(String key : keySet) {System.out.println(key);ArrayList<String> value = hm.get(key);for(String s : value) {System.out.println("\t" + s);}}
}
案例: 统计字符串中每个字符出现的次数
需求: 键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
举例: 键盘录入 aababcabcdabcde
在控制台输出: a(5)b(4)c(3)d(2)e(1)
思路:
键盘录入一个字符串
创建HashMap
集合,键是Character
,值是Integer
遍历字符串,得到每一个字符
拿得到的每一个字符作为键到HashMap
集合中去找对应的值,看其返回值
-
如果返回值是
null
: 说明该字符在HashMap
集合中不存在,就把该字符作为键,1
作为值存储 -
如果返回值不是
null
: 说明该字符在HashMap
集合中存在,把该值加1,然后重新存储该字符和对应的值
public static void main(String[] args) {// 键盘录入一个字符串Scanner sc = new Scanner(System.in);System.out.println("请输入一个字符串:");String line = sc.nextLine();// 创建HashMap集合,键是Character,值是IntegerHashMap<Character, Integer> hm = new HashMap<Character, Integer>();// 遍历字符串,得到每一个字符for (int i = 0; i < line.length(); i++) {char key = line.charAt(i);// 拿的到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值Integer value = hm.get(key);if (value==null) {hm.put(key, 1);} else {hm.put(key, value);}}// 遍历HashMap集合, 得到键和值,按照要求进行拼接StringBuilder sb = new StringBuilder();Set<Character> keySet = hm.keySet();for(Character key : keySet) {Integer value = hm.get(key);sb.append(key).append("(").append(value).append(")");}String result = sb.toString();// 输出结果System.out.println(result);
}
8.8 Collections
8.8.1 Collections概述和使用
Collections
类的概述
是针对集合操作的工具类
Collections
类的常用方法
public static <T extends Comparable<? superT>> void sort(List<T> list)
: 将指定的列表按升序排序
可以给定一个比较器进行排序
sort(List<T> list, Comparator<? super T> c)
public static void reverse(List<? > list)
: 反转指定列表中元素的顺序
public static void shuffle(List<?> list)
: 使用默认的随机源随机排列指定的列表
案例:模拟斗地主升级版
需求: 通过程序实现斗地主过程中的洗牌,发牌和看牌。
要求: 对牌进行排序
思路:
创建HashMap
,键是编号,值是牌创建ArrayList
,存储编号
创建花色数组和点数数组
从0
开始往HashMap
里面存储编号,并存储对应的牌。同时往ArrayList
里面存储编号洗牌(洗的是编号),用Collections
的shuffle()
方法实现
发牌 (发的也是编号,为了保证编号是排序的,创建TreeSet
集合接收)
定义方法看牌(遍历TreeSet
集合,获取编号,到HashMap
集合找对应的牌)
调用看牌方法
public static void main(String[] args) {// 创建HashMap,键是编号,值是牌HashMap<Integer, String> hm = new HashMap<Integer, String>();// 创建ArrayList,存储编号ArrayList<Integer> array = new ArrayList<Integer>();// 创建花色数组和点数数组String[] colors = {"♦", "♣", "🖤", "♠"};String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};// 从0开始网HashMao里面存储编号,并存储对应的牌。同时网ArrayList里存储编号int index = 0;for(String number : numbers) {for(String color : colors) {hm.put(index, color + number);array.add(index);index++;}}hm.put(index, "小王");array.add(index);index++;hm.put(index, "大王");array.add(index);// 洗牌(洗的是编号),用Collections的shuffle()方法实现Collections.shuffle(array);// 发牌(发的也是编号,为了保证编号是排序的,创建TreeSet集合接收)TreeSet<Integer> lqxSet = new TreeSet();TreeSet<Integer> lySet = new TreeSet();TreeSet<Integer> fqySet = new TreeSet();TreeSet<Integer> dpSet = new TreeSet();for (int i=0; i < array.size(); i++) {int x = array.get(i);if(i >= array.size() - 3) {dpSet.add(x);} else if (i % 3 == 0) {lqxSet.add(i);} else if (i % 3 == 1) {lySet.add(x);} else if (i % 3 == 2){fqySet.add(x);}}// 调用看牌方法lookPoker("林青霞", lqxSet, hm);lookPoker("刘岩", lySet, hm);lookPoker("风清扬", fqySet, hm);lookPoker("底牌", dpSet, hm);
}// 定义方法看牌(遍历TreeSet集合, 获取编号,到HashMao集合找对应的牌)
public static void lookPoker(String name, TreeSet<Integer> ts, HashMap<Integer, String> hm) {System.out.println(name + "的牌是: ");for (Integer key : ts) {String poker = hm.get(key);System.out.print(poker + " ");}System.out.println();
}
九、File
9.1 File类概述和构造方法
File
: 它是文件和目录路径名的抽象表示
文件和目录是可以通过File
封装成对象的
对于File
而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的
方法名 | 说明 |
---|---|
File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的File 实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的File 实例 |
File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的File 实例 |
9.2 File类创建功能
方法名 | 说明 |
---|---|
public boolean createNewFile() | 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件 |
public boolean mkdir() | 创建由此抽象路径名命名的目录 |
public boolean mkdirs() | 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录 |
实例
public static void main(String[] args) {// 创建一个文件File f1 = new File("E:\\java\\java.txt");System.out.println(f1);System.out.println(f1.createNewFile());// 创建一个目录File f2 = new File("E:\\java", "java.txt");System.out.println(f2);System.out.println(f2.mkdir());// 创建多个目录File f3 = new File("E:\\java");File f4 = new File( f3, "java.txt");System.out.println(f4);System.out.println
}
9.3 File类删除功能
方法名 | 说明 |
---|---|
public boolean delete() | 删除由此抽象路径名表示的文件或目录 |
绝对路径和相对路径的区别
-
绝对路径: 完整的路径名,不需要任何其他信息就可以定位它所表示的文件。例如:
E:\litcast\ljava.txt
-
相对路径:必须使用取自其他路径名的信息进行解释。例如:
myFilelljava.txt
删除目录时的注意事项:
如果一个目录中有内容(目录,文件),不能直接删除。应该先删除目录中的内容,最后才能删除目录
9.4 File类判断和获取功能
方法名 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File 是否为目录 |
public boolean isFile() | 测试此抽象路径名表示的File 是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File 是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName) | 返回由此抽象路径名表示的文件或目录的名称 |
public String[] list() | 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组 |
public File[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的File 对象数组 |
9.5 递归
递归概述: 以编程的角度来看,递归指的是方法定义中调用方法本身的现象
递归解决问题的思路:
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算
递归解决问题要找到两个内容:
-
递归出口: 否则会出现内存溢出
-
递归规则: 与原问题相似的规模较小的问题
实例
public static in jc(int n) {if(n == 1){return 1;} else {return n * jc(n-1);}
}
案例
案例: 遍历目录
需求: 给定一个路径(E\\itcast)
,请通过递归完成遍历该目录下的所有内容,并把所有文件的绝对路径输出在控制台
思路:
根据给定的路径创建一个File
对象
定义一个方法,用于获取给定目录下的所有内容,参数为第1步创建的File
对象
获取给定的File
目录下所有的文件或者目录的File
数组
遍历该File
数组,得到每一个File
对象
判断该File对象是否是目录
-
是:递归调用
-
不是:获取绝对路径输出在控制台调用方法
public static void main(String[] args) {// 根据给定的路径创建一个File对象File srcFile = new File("E:\\code\\java");// 调用方法getAllFilePath(srcFile);
}// 定义一个方法,用于获取给定目录下的所有内容,参数为第一步创建的File对象
public static void getAllFilePath(File srcFile) {// 获取给定的File目录下所有的文件或者目录的File数组File[] fileArray = srcFile.listFiles();// 遍历该File数组,得到每一个File对象if(fileArray != null) {for(File file : fileArray) {// 判断该File对象是否是目录if(file.isDirectory()) {// 是则递归调用getAllFilePath(file);} else {// 不是则获取绝对路径输出在控制台System.out.println(file.getAbsolutePath());}}}
}
十、字节流
10.1 IO流概述和分类
IO
流概述:
-
IO
: 输入/输出(Input/Output
) -
流 : 是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
-
IO流就是用来处理设备间数据传输问题的
常见的应用: 文件复制;文件上传;文件下载
IO
流分类:
- 按照数据的流向
输入流:读数据
输出流:写数据
- 按照数据类型来分
字节流
字节输入流;字节输出流
字符流
字符输入流;字符输出流
一般来说,我们说IO流的分类是按照数据类型来分的那么这两种流都在什么情况下使用呢?
- 如果数据通过
Window
自带的记事本软件打开,我们还可以读懂里面的内容,就使用字符流,否则使用字节流。如果你不知道该使用哪种类型的流,就使用字节流
10.2 字节流写数据
字节流抽象基类
-
lnputStream
: 这个抽象类是表示字节输入流的所有类的超类 -
OutputStream
: 这个抽象类是表示字节输出流的所有类的超类 -
子类名特点:子类名称都是以其父类名作为子类名的后缀
FileOutputStream
: 文件输出流用于将数据写入File
FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件
使用字节输出流写数据的步骤:
-
创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
-
调用字节输出流对象的写数据方法
-
释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
10.3 字节流写数据的3种方式
方法名 | 说明 |
---|---|
void write(int b) | 将指定的字节写入此文件输出流 一次写一个字节数据 |
void write(byte[] b) | 将b.length 字节从指定的字节数组写入此文件输出流一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 将len 字节从指定的字节数组开始,从偏移量off 开始写入此文件输出流一次写一个字节数组的部分数据 |
实例
public static void main(String[] args) throws IOException {// 创建文件输出流一指定的名称写入文件 FileOutputStream fos = new FileOutputStream("E:\\code\\java\\fos.txt");// 与以下写法相同
// FileOutputStream fos = new FileOutputStream(new File("E:\\\\code\\\\java\\\\fos.txt"));// a, b, c, d, e// 将指定的字节写入此文件输出流fos.write(97);fos.write(98);fos.write(99);fos.write(100);fos.write(101);// byte[] bys = {97, 98, 99, 100, 101};// 将指定数组写入文件输出流byte[] bys = "abcde".getBytes();fos.write(bys);// 将len字节从指定字节数组开始,从偏移量off开始写入此文件输入流fos.write(bys, 0, bys.length);
}
10.4 字节流写数据的两个小问题
字节流写数据如何实现换行呢?
写完数据后,加换行符
-
windows
:\r\n
-
linux
:\n
-
mac
:\r
字节流写数据如何实现追加写入呢?
publicFileOutputStream(String name, boolean append)
创建文件输出流以指定的名称写入文件。如果第二个参数为true
,则字节将写入文件的末尾而不是开头
10.5 字节流写数据加异常处理
finally
: 在异常处理时提供finally
块来执行所有清除操作。
- 比如说
IO流
中的释放资源
特点 : 被finally
控制的语句一定会执行,除非JVM
退出
格式:
try{可能出现异常的代码;
} catch (异常类名 变量名) {异常的处理代码;
} finally {执行所有清除操作;
}
实例
public static void main(String[] args) {// 加入finally来实现释放资源FileOutputStream fos = null;try {fos = new FileOutputStream("Z:\\a.txt");fos.write("hello".getBytes());} catch (IOException e) {e.printStackTrace();} finally {if( fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}
}
10.6 字节流读数据(一次读一个字节数据)
需求 : 把文件fos.txt
中的内容读取出来在控制台输出
FilelnputStream
: 从文件系统中的文件获取输入字节
FilelnputStream(String name)
: 通过打开与实际文件的连接来创建一个FileInputstream
,该文件由文件系统中的路径名name
命名
使用字节输入流读数据的步骤:
-
创建字节输入流对象
-
调用字节输入流对象的读数据方法
-
释放资源
实例
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("E:\\code\\java\\fos.txt");int by;// 如果到达文件末尾:-1while((by = fis.read()) != -1) {System.out.print((char)by);}fis.close();
}
案例
案例: 复制文本文件
需求 : 把 E:\code\java\java.txt
复制到模块目录下的 clcw.txt
分析:
复制文本文件,其实就把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
数据源:
E:\code\java\java.txt
—读数据— InputStream
— FilelnputStream
目的地:
MyByteStream\clcw.txt
—写数据—OutputStream
— FileOutputStream
思路:
-
根据数据源创建字节输入流对象
-
根据目的地创建字节输出流对象
-
读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
-
释放资源
实例
public static void main(String[] args) throws IOException {// 根据数据源创建字节输入流对象FileInputStream fis = new FileInputStream("E:\\code\\java\\java.txt");// 根据目的地创建字节输出流对象FileOutputStream fos = new FileOutputStream("E:\\clcw.txt");// 读写数据int by;while((by = fis.read()) != -1) {fos.write(by);}// 释放资源fos.close();fis.close();
}
10.6 字节流读数据(一次读一个字节数组数据)
需求 : 把文件fos.txt
中的内容读取出来在控制台输出
-
使用字节输入流读数据的步骤:
-
创建字节输入流对象
-
调用字节输入流对象的读数据方法
-
释放资源
实例
public static void main(String[] args) throws IOException {// 创建字节输入流对象FileInputStream fis = new FileInputStream("E:\\code\\java\\java.txt");// 调用字节流对象的读数据方法// 1024及其整数倍byte[] bys = new byte[1024];int len;// read 返回成功读取字符个数 ,读取到末尾返回-1while((len=fis.read(bys)) != -1) {// 将bys 字节数组的一部分转化为字符串从0开始len个System.out.println(new String(bys, 0, len));}fis.close();
}
\r\n
也会被解析成字符并记录到个数中
10.7 字节缓冲流
字节缓冲流:
BufferOutputStream
: 该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用BufferedlnputStream
: 创建BufferedInputstream
将创建一个内部缓冲区数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
构造方法:
-
字节缓冲输出流 :
BufferedOutputStream(OutputStream out)
-
字节缓冲输入流 :
BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
实例
public static void main(String[] args) throws IOException {// 字节缓冲输出流BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\code\\java\\java.txt"));// 写数据bos.write("hello".getBytes());bos.write("world".getBytes());// 释放资源bos.close();// 字节缓存输入流BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\code\\java\\java.txt"));// 一次读取一个字节数据
// int by;
// while ((by=bis.read())!=-1) {
// System.out.println((char)by);
// }// 一次读取一个字节数组数据byte[] bys = new byte[1024];int len;while ((len=bis.read(bys))!=-1) {System.out.println(new String(bys, 0 ,len));}// 释放资源bis.close();
}
案例
复制视频查看不同流的速度快慢
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;/*1:基本字节流一次读写一个字节 387168毫秒2:基本字节流一次读写一个字节数组 502毫秒3:字节缓冲流一次读写一个字节 1268毫秒4:字节缓冲流一次读写一个字节数组 143毫秒*/public class CopyAviDemo {public static void main(String[] args) throws IOException {// 记录开始时间long startTime = System.currentTimeMillis();// 复制视频method1();// 记录结束时间long endTime = System.currentTimeMillis();System.out.println("共耗时:" + (endTime - startTime) + "毫秒");}// 1:基本字节流一次读写一个字节public static void method1() throws IOException {FileInputStream fis = new FileInputStream("E:\\code\\java\\t.avi");FileOutputStream fos = new FileOutputStream("E:\\code\\java\\1.avi");int by;while((by=fis.read())!=-1) {fos.write(by);}fos.close();fis.close();}// 2:基本字节流一次读写一个字节数组public static void method2() throws IOException {FileInputStream fis = new FileInputStream("E:\\code\\java\\t.avi");FileOutputStream fos = new FileOutputStream("E:\\\\code\\\\java\\\\2.avi");byte[] bys = new byte[1024];int len;while((len=fis.read(bys))!=-1) {fos.write(bys, 0 ,len);}fos.close();fis.close();}// 3:字节缓冲流一次读写一个字节public static void method3() throws IOException {BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\code\\java\\t.avi"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\code\\java\\3.avi"));int by;while((by=bis.read())!=-1) {bos.write(by);}bos.close();bis.close();}// 4:字节缓冲流一次读写一个字节数组public static void method4() throws IOException {BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\code\\java\\t.avi"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\code\\java\\4.avi"));byte[] bys = new byte[1024];int len;while((len=bis.read(bys))!=-1) {bos.write(bys, 0 ,len);}bos.close();bis.close();}
}
十一、字符流
11.1 为什么会出现字符流
由于字节流操作中文不是特别的方便,所以Java就提供字符流
- 字符流=字节流+编码表
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
●汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
11.2 编码表
基础知识:
-
计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
-
按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。这里强调一下:按照A编码存储,必须按照A编码解析,这样才能显示正确的文本符号。否则就会导致乱码现象
字符编码:就是一套自然语言的字符与二进制数之间的对应规则(A, 65)
字符集:
-
是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
-
计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCIl字符集、GBXXX字符集、Unicode字符集等
ASCIl
字符集:
ASCII
(American Standard Code for Information Interchange
,美国信息交换标准代码):是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)- 基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等、
GBXXX
字符集:
GB2312
:简体中文码表。一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名等都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了- GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案
- 共收录了
- 21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
- GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及百韩汉字等
Unicode
字符集:
- 为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,
UTF-8
、UTF-16
和UTF32
。最为常用的UTF-8
编码 UTF-8
编码:可以用来表示Unicode
标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用- 中,优先采用的编码。互联网工程工作小组(
IETF
)要求所有互联网协议都必须支持UTF-8
编码。它使用一至四个字节为每个字符编码
编码规则:
128个US-ASCII
字符,只需一个字节编码拉丁文等字符,需要二个字节编码
大部分常用字(含中文),使用三个字节编码
其他极少使用的Unicode
辅助字符,使用四字节编码
小结:采用何种规则编码,就要采用对应规则解码,否则就会出现乱码
11.3 字符串中的编码解码问题
编码:
-
byte[] getBytes()
: 使用平台的默认字符集将该String
编码为一系列字节,将结果存储到新的字节数组中 -
byte[] getBytes(String charsetName)
: 使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中
解码:
String(byte[] bytes)
: 通过使用平台的默认字符集解码指定的字节数组来构造新的String
String(byte[] bytes, String charsetName)
: 通过指定的字符集解码指定的字节数组来构造新的String
11.5 字符流写数据的5种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
11.6 字符流读数据的2种方式
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
案例
案例 : 复制Java
文件
需求 : 把E:\\code\\java\\t.java
复制到 E:\\code\\java\\Copy.java
思路:
-
根据数据源创建字符输入流对象
-
根据目的地创建字符输出流对象
-
读写数据,复制文件
-
释放资源
public static void main(String[] args) throws IOException {// 根据数据源创建字符输入流对象InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\code\\java\\test.java"));// 根据目的地创建字符输出流对象OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\code\\java\\copy.java"));// 读写数据,复制文件// 一次读写一个字符数据
// int ch;
// while ((ch=isr.read())!=-1) {
// osw.write(ch);
// }char[] chs = new char[1024];int len;while((len=isr.read(chs))!=-1) {osw.write(chs, 0 ,len);}// 释放资源osw.close();isr.close();
}
案例 : 复制Java文件(改进版)
需求 : 把E:\\code\\java\\t.java
复制到 E:\\code\\java\\Copy.java
分析:
-
转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化书写,转换流提供了对应的子类
-
FileReader
: 用于读取字符文件的便捷类
FileReader(String fileName)
FileWriter
: 用于写入字符文件的便捷类
FileWriter(String fileName)
- 数据源和目的地的分析
数据源: E:\\code\\java\\t.java
—读数据–Reader
—InputStreamReader
—FileReader
目的地: E:\\code\\java\\Copy.java
—写数据—Writer
— OutputStreamWriter
—FileWriter
思路:
-
根据数据源创建字符输入流对象
-
根据目的地创建字符输出流对象
-
读写数据,复制文件
-
释放资源
public static void main(String[] args) throws IOException {// 根据数据源创建字符输入流对象FileReader fr = new FileReader("E:\\code\\java\\t.java");// 根据目的地创建字符输出流对象FileWriter fw = new FileWriter("E:\\code\\java\\copy.java");// 读写数据,复制文件
// int ch;
// while ((ch=fr.read())!=-1) {
// fw.write(ch);
// }char[] chs = new char[1024];int len;while ((len=fr.read(chs))!=-1) {fw.write(chs, 0 , len);}fr.close();fw.close();}
11.7 字符缓冲流
字符缓冲流:
BufferedWriter
: 将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
BufferedReader
: 从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途
构造方法:
-
BufferedWriter(Writer out)
-
BufferedReader(Reader in)
案例
案例 : 复制Java
文件(字符缓冲流改进版)
需求 : 把E:\\code\\java\\t.java
复制到 E:\\code\\java\\Copy.java
思路:
-
根据数据源创建字符缓冲输入流对象
-
根据目的地创建字符缓冲输出流对象
-
读写数据,复制文件
-
释放资源
实例
public static void main(String[] args) throws IOException {// 根据数据源创建字符缓冲输入流对象BufferedReader br = new BufferedReader(new FileReader("E:\\code\\java\\t.java"));// 根据目的地创建字符缓冲输出流对象BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\code\\java\\copy.java"));// 读写数据,复制文件// 一次读写一个字符数据
// int ch;
// while ((ch=br.read())!=-1) {
// bw.write(ch);
// }// 一次读写一个字符数组数据char[] chs = new char[1024];int len;while ((len=br.read(chs))!=-1) {bw.write(chs, 0 , len);}// 释放资源bw.close();br.close();
}
11.8 字符缓冲流特有功能
BufferedWriter
:
void newLine()
: 写一行行分隔符,行分隔符字符串由系统属性定义
BufferedReader
:
publicString readLine()
: 读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
案例
案例 : 复制Java
文件(字符缓冲流特有功能改进版)
需求 : 把E:\\code\\java\\t.java
复制到 E:\\code\\java\\Copy.java
思路:
-
根据数据源创建字符缓冲输入流对象
-
根据目的地创建字符缓冲输出流对象
-
读写数据,复制文件使用字符缓冲流特有功能实现
-
释放资源
实例
public static void main(String[] args) throws IOException {// 根据数据源创建字符缓冲输入流对象BufferedReader br = new BufferedReader(new FileReader("E:\\code\\java\\t.java"));// 根据目的地创建字符缓冲输出流对象BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\code\\java\\copy01.java"));// 读写数据,复制文件// 使用字符缓冲流特有功能实现String line;while ((line=br.readLine())!=null) {bw.write(line);bw.newLine();bw.flush();}// 释放资源bw.close();br.close();
}
11.9 IO流小结
字节流
-
字节输入流 (
lnputStream
)读数据
int read()
: 一次读取一个字节
int read(byte[] bys)
: 一次读取一个字节数组FilelnputStream
BufferedInputStream
-
字节输出流 (
OutputStream
)写数据
void write(int by)
: 一次写一个字节
void write(byte[] bys, int index, int len)
: 一次写一个字节数组的一部分FileOutputStream
BufferedOutputStream
小结 : 字节流可以复制任意文件数据,有4种方式一般采用字节缓冲流一次读写一个字节数组的方式
字符流
-
字符输入流 (
Reader
)读数据
int read()
: 一次读取一个字符
int read(char[] chs)
: 一次读取一个字符数组-
lnputStreamReader
FileReader
-
BufferedReader
String readLine()
: 一次读取一个字符串
-
-
字符输出流 (
Writer
)写数据
void write(int ch)
: 一次写一个字符
void write(char[] chs, int index, int len)
: 一次写一个字符数组的一部分-
OutputStreamWriter
FileWriter
-
BufferedWriter
void newLine()
: 写一个换行符(根据系统)
void write(String line)
: 一次写一个字符串
-
小结 : 字符流只能复制文本数据,有5种方式,一般采用字符缓冲流的特有功能
案例
案例 : 复制多级文件夹
需求 : 把 E:\\code\\java\\tt
”复制到E:\\code\\java\\test
目录下
思路:
创建数据源File
对象,路径是 E:\\code\\java\\tt
创建目的地File对象,路径是 E:\\code\\java\\test
写方法实现文件夹的复制,参数为数据源File
对象和目的地File
对象判断数据源File
是否是目录
是::
A :在目的地下创建和数据源File
名称一样的目录
B : 获取数据源File下所有文件或者目录的File
数组
C : 遍历该File
数组,得到每一个File
对象
D : 把该File
作为数据源File
对象,递归调用复制文件夹的方法
不是::
说明是文件,直接复制,用字节流
实例
public static void main(String[] args) throws IOException {// 创建数据源File对象File srcFile = new File("E:\\Code\\Java\\learnbyself\\tt");// 创建目的地File对象File descFile = new File("E:\\Code\\Java\\learnbyself\\test");// 实现文件加复制copyFolder(srcFile, descFile);}
private static void copyFolder(File srcFile, File descFile) throws IOException {// 判断是否为目录if(srcFile.isDirectory()) {// 在目的地创建相同目录// 获取目录名String filename = srcFile.getName();// 创建新的文件对象File newFile = new File(descFile, filename);// 判断目录是否存在if(!newFile.exists()) {newFile.mkdir();}// 获取数据源所有文件和目录File[] fileArray = srcFile.listFiles();// 循环复制for (File file : fileArray) {// 递归复制copyFolder(file, newFile);}} else {File newFile = new File(descFile, srcFile.getName());copyFile(srcFile, newFile);}}
private static void copyFile(File srcFile, File descFile) throws IOException {BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(descFile));System.out.println(bis);byte[] bys = new byte[1024];int len;while((len=bis.read()) != -1) {bos.write(bys, 0, len);}bis.close();bos.close();
}
11.10 复制文件的异常处理
try...catch...finally
的做法:
try:{可能出现异常的代码;
} catch(异常类名 变量名) {异常的处理代码;
} finally {执行所有清除操作;
}
JDK7
改进方案:
try(定义对象) {可能出现异常的代码;
} catch {异常的处理代码;
}// 自动释放资源
JDK9
改进方案:
定义输入流对象;
定义输出流对象;
try(输入流对象:输出流对象) {可能出现异常的代码;
} catch(异常类名 变量名) {异常的处理代码;
}// 自动释放资源
实例
public static void main(String[] args) throws IOException {
// method1();
// method2();
// method3();method4();
}// 抛出异常
private static void method1() throws IOException {FileReader fr = new FileReader("E:\\code\\java\\learnbyself\\tt\\java.txt");FileWriter fw = new FileWriter("E:\\code\\java\\learnbyself\\test\\copy01.txt");char[] chs = new char[1024];int len;while((len = fr.read())!=-1) {fw.write(chs, 0, len);}fw.close();fr.close();
}// try...catch...finally
private static void method2() {FileReader fr = null;FileWriter fw = null;try {fr = new FileReader("E:\\code\\java\\learnbyself\\tt\\java.txt");fw = new FileWriter("E:\\code\\java\\learnbyself\\test\\copy02.txt");char[] chs = new char[1024];int len;while((len = fr.read())!=-1) {fw.write(chs, 0, len);}} catch(IOException e) {e.printStackTrace();} finally {if (fw!=null) {try {fw.close();} catch(IOException e) {e.printStackTrace();}}if(fr!=null) {try {fr.close();} catch(IOException e) {e.printStackTrace();}}}
}// JDK7的改进方案
private static void method3() {try(FileReader fr = new FileReader("E:\\code\\java\\learnbyself\\tt\\java.txt");FileWriter fw = new FileWriter("E:\\code\\java\\learnbyself\\test\\copy03.txt");){char[] chs = new char[1024];int len;while((len = fr.read())!=-1) {fw.write(chs, 0, len);}} catch(IOException e) {e.printStackTrace();}
}// JDk9的改进方案
private static void method4() throws IOException {FileReader fr = new FileReader("E:\\code\\java\\learnbyself\\tt\\java.txt");FileWriter fw = new FileWriter("E:\\code\\java\\learnbyself\\test\\copy04.txt");try(fr ; fw){char[] chs = new char[1024];int len;while((len = fr.read())!=-1) {fw.write(chs, 0, len);}} catch(IOException e) {e.printStackTrace();}
}
十二、特殊操作流
12.1 标准输入输出流
System
类中有两个静态的成员变量:
public static final InputStream in
: 标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
publicstatic final PrintStream out
: 标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
自己实现键盘录入数据:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in);
写起来太麻烦,Java
就提供了一个类实现键盘录入
Scanner sc = new Scanner(System.in);
输出语句的本质:是一个标准的输出流
PrintStream ps = System.out;
PrintStream
类有的方法,System.out
都可以使用
实例
public static void main(String[] args) throws IOException {// 标准输入流
// InputStream is = System.in;// int by;
// while ((by=is.read())!=-1) {
// System.out.print((char)by);
// }// 使用转换流把字节流转换为字符流
// InputStreamReader isr = new InputStreamReader(is);// 使用字符流可以一次读取一行数据, 但是一次读取一行数据的方法是字符缓冲流的特有方法
// BufferedReader br = new BufferedReader(isr);BufferedReader br = new BufferedReader(new InputStreamReader(System.in));System.out.println("请输入一个字符串:");String line = br.readLine();System.out.println("你输入的字符串是:" + line);System.out.println("请输入一个整数");int i = Integer.parseInt(br.readLine());System.out.println("你输入的整数是:" + i);
}
public static void main(String[] args) {// 标准输出流PrintStream ps = System.out;// 能够方便打印各种数据值
// ps.print("hello");
// ps.print(100);// ps.println("hello");
// ps.println(100);// System.out的本质是一个字节输出流System.out.println("hello");System.out.println(100);// 此处无print的空参数方法System.out.println();
// System.out.print();
}
12.2 打印流
打印流分类:
-
字节打印流 :
PrintStream
-
字符打印流 :
PrintWriter
打印流的特点:
- 只负责输出数据,不负责
- 读取数据有自己的特有方法
字节打印流
-
PrintStream(String fileName)
: 使用指定的文件名创建新的打印流 -
使用继承父类的方法写数据,查看的时候会转码;使用自己的特有方法写数据,查看的数据原样输出
实例
public static void main(String[] args) throws IOException {// 使用指定文件名创建新的打印流PrintStream ps = new PrintStream("E:\\code\\java\\java01.txt");// 写数据// 字节输出流有的方法
// ps.write(97);// 使用特有方法写数据ps.print(97);ps.println();ps.print(98);ps.println(97);ps.println(98);
}
字符打印流PrintWriter
的构造方法:
方法名 | 说明 |
---|---|
PrintWriter(String fileName) | 使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新 |
PrintWriter(Writer out, boolean autoFlush) | 创建一个新的PrintWriter out : 字符输出流autoFlush : 一个布尔值,如果为真,则println , printf ,或format 方法将刷新输出缓冲区 |
实例
public static void main(String[] args) throws IOException {
// PrintWriter pw = new PrintWriter("E:\\code\\java\\java02.txt");// pw.write("hello");
// pw.write("\r\n");
// pw.flush();
// pw.write("world");
// pw.write("\r\n");
// pw.flush();// pw.println("hello");
// pw.println("world");
// pw.flush();PrintWriter pw = new PrintWriter(new FileWriter("E:\\code\\java\\java02.txt", true));
// PrintWriter pw = new PrintWriter(new FileWriter("E:\\code\\java\\java02.txt", false));// pw.write("he2222llo\r\nw2orld\r\n");
// pw.flush();pw.println("hello");pw.println("world");pw.close();
}
案例
案例 : 复制Java
文件(打印流改进版)
需求 : 把模块目录下的PrintStreamDemo.java复制到模块目录下的Copy.java
思路:
-
根据数据源创建字符输入流对象
-
根据目的地创建字符输出流对象
-
读写数据,复制文件
-
释放资源
实例
public static void main(String[] args) throws IOException {// 根据数据源创建字符输入流对象 BufferedReader br = new BufferedReader(new FileReader("E:\\Code\\Java\\fos.txt"));// 根据目的地创建字符输出流对象PrintWriter pw = new PrintWriter(new FileWriter("E:\\Code\\Java\\copy03.txt"));// 读写数据,复制文件String line;while ((line=br.readLine())!=null) {pw.println(line);}// 释放资源pw.close();br.close();
}
12.3 对象序列化流
对象序列化 : 就是将对象保存到磁盘中,或者在网络中传输对象
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
要实现序列化和反序列化就要使用对象序列化流和对象反序列化流:
对象序列化流 : ObjectOutputStream
对象反序列化流 : ObjectlnputStream
对象序列化流 : ObjectOutputStream
- 将
Java
对象的原始数据类型和图形写入OutputStream
。可以使用ObjectInputStream
读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
构造方法:
ObjectOutputStream(OutputStream out)
: 创建一个写入指定的OutputStream
的ObjectOutputStream
序列化对象的方法:
void writeObject(Object obj)
: 将指定的对象写入ObjectOutputStream
注意:
-
一个对象要想被序列化,该对象所属的类必须必须实现
Serializable
接口 -
Serializable
是一个标记接口,实现该接口,不需要重写任何方法
实例
Student
类
public class Student implements Serializable{private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
测试类
public static void main(String[] args) throws IOException {// 创建一个写入指定OutputStream的文件ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\code\\java\\oos.txt"));// 创建对象Student s = new Student("林青霞", 30);// 将指定对象写入文件oos.writeObject(s);// 释放资源oos.close();
}
对象反序列化流 : ObjectInputStream
ObjectInputStream
反序列化先前使用ObjectOutputStream
编写的原始数据和对象
构造方法:
ObjectInputStream(InputStream in)
: 创建从指定的InputStream
读取的ObjectInputStream
反序列化对象的方法:
Object readObject()
: 从ObjectInputStream
读取一个对象
实例
public static void main(String[] args) throws IOException, ClassNotFoundException {// 创建从指定的InputStream读取的ObjectInputStreamObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\code\\java\\oos.txt"));// 读取对象Object obj = ois.readObject();Student s = (Student) obj;System.out.println(s.getName() + "," + s.getAge());// 释放资源ois.close();
}
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会出问题,抛出lnvalidClassException
异常
解决方法
给对象所属的类加一个serialVersionUID
private static final long serialVersionUID = 42L;
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
给该成员变量加transient
关键字修饰,该关键字标记的成员变量不参与序列化过程
12.4 Properties
Properties
概述:
-
是一个
Map
体系的集合类 -
Properties
可以保存到流中或从流中加载
练习: Properties
作为Map
集合的使用
public static void main(String[] args) {// 创建集合对象
// Properties<String,String> prop = new Properties<String,String>(); // 错误Properties prop = new Properties();// 存储元素prop.put("001", "林青霞");prop.put("002", "张曼玉");prop.put("003", "王祖贤");// 遍历集合Set<Object> keySet = prop.keySet();for (Object key : keySet) {Object value = prop.get(key);System.out.println(key + "," + value);}
}
Properties
作为集合的特有方法:
方法名 | 说明 |
---|---|
Object setProperty(String key, String value) | 设置集合的键和值,都是String 类型,底层调用Hashtable 方法put |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
Set<String> stringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
Properties
和IO
流结合的方法:
方法名 | 说明 |
---|---|
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此Properties 表中,以适合于使用load(InputStream) 方法的格式写入输出字节流 |
void store(Writer writer,String comments) | 将此属性列表(键和元素对)写入此Properties 表中,以适合使用load(Reader) 方法的格式写入输出字符流 |
实例
public static void main(String[] args) throws IOException {// 把集合中的数据保存到文件中myStore();// 把文件中的数据加载到集合myLoad();
}private static void myLoad() throws IOException {Properties prop = new Properties();FileReader fr = new FileReader("E:\\code\\java\\pf.txt");prop.load(fr);fr.close();System.out.println(prop);
}private static void myStore() throws IOException {Properties prop = new Properties();prop.put("001", "林青霞");prop.put("002", "张曼玉");prop.put("003", "王祖贤");FileWriter fw = new FileWriter("E:\\code\\java\\pf.txt");prop.store(fw, null);fw.close();
}
案例
案例 : 游戏次数
需求 : 请写程序实现猜数字小游戏只能试玩3次,如果还想玩,提示:游戏试玩已结束,想玩请充值
思路:
写一个游戏类,里面有一个猜数字的小游戏
写一个测试类,测试类中有main()方法,main()方法中按照下面步骤完成:
A : 从文件中读取数据到Properties
集合,用load()
方法实现
文件已经存在 : game.txt
里面有一个数据值 : count=0
B : 通过Properties
集合获取到玩游戏的次数
C : 判断次数是否到3次了
如果到了,给出提示:游戏试玩已结束,想玩请充值
如果不到3次:
玩游戏
次数+1,重新写回文件,用Properties
的store()
方法实现
实例
游戏类
public class GuessNumber {private GuessNumber() {}public static void start() {// 生成随机数Random r = new Random();int number = r.nextInt(100) + 1;while(true) {Scanner sc = new Scanner(System.in);System.out.println("请输入你要猜的数字:");int guessNumber = sc.nextInt();if (guessNumber > number) {System.out.println("你猜的数字" + guessNumber + "大了");} else if(guessNumber < number) {System.out.println("你猜的数字" + guessNumber + "小了");} else {System.out.println("恭喜你猜中了");break;}}}}
测试类
public static void main(String[] args) throws IOException {// 从文件中读取数据到Properties集合,用load()方法实现Properties prop = new Properties();FileReader fr = new FileReader("E:\\code\\java\\guessNumber.txt");prop.load(fr);fr.close();// 通过properties集合获取到玩游戏的次数String count = prop.getProperty("count");int number = Integer.parseInt(count);System.out.println(number);// 判断次数是否到三次了if (number >=3) {System.out.println("游戏试玩已结束");} else {// 玩游戏 GuessNumber.start();// 次数+1,重新写回文件number++;prop.setProperty("count", String.valueOf(number));FileWriter fw = new FileWriter("E:\\code\\java\\guessNumber.txt");prop.store(fw, null);fw.close();}
}
更多推荐
Java自学笔记总结01
发布评论