1. java命名规范: ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/560f2deb04d1963bf3f13c71d58122d4.png)
2. java的数据类型:![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/515c8cdd213e674efb59995f6f63eca3.png)
2.1 整形类型:
2.2 浮点类型:
2.3 字符类型:
3.成员变量和局部变量的区别:
4.基本数据类型之间的运算规则:
5.进制的表示
6.运算符
6.1 运算符的规则
==&& 和 & 的区别:==如果左边为 False 的话使用 && 就不会运算右边了,但是使用 & 还会继续运算。
==|| 和 | 的区别:==如果左边为 True 的话使用 || 就不会运算右边了,但是使用 | 还会继续运算。
6.2 运算符的优先级
7.Java中的JDK,JRE的关系
7.1 概念
7.2 关系
7.3 什么是API
8 This关键字
* this关键字的使用:
8.1.this可以用来修饰、调用:属性、方法、构造器
8. 2.this修饰属性和方法:
-
this理解为:当前对象 或 当前正在创建的对象
-
2.1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
-
2.2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
8.3.this调用构造器
- ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
- ② 构造器中不能通过"this(形参列表)"方式调用自己
- ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
- ④ 规定:"this(形参列表)"必须声明在当前构造器的首行
- ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
9 package和import关键字
9.1 package关键字的使用
- 1.为了更好的实现项目中类的管理,提供包的概念
- 2.使用package声明类或接口所属的包,声明在源文件的首行
- 3.包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
- 4.每"."一次,就代表一层文件目录。
- 补充:同一个包下,不能命名同名的接口、类。不同的包下,可以命名同名的接口、类。
9.2 import关键字的使用
- import:导入
- 1.在源文件中显式的使用import结构导入指定包下的类、接口
- 2.声明在包的声明和类的声明之间
- 3.如果需要导入多个结构,则并列写出即可
- 4.可以使用"xxx.*"的方式,表示可以导入xxx包下的所有结构
- 5.如果使用的类或接口是java.lang包下定义的,则可以省略import结构
- 6.如果使用的类或接口是本包下定义的,则可以省略import结构
- 7.如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
- 8.使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
- 9.import static:导入指定类或接口中的静态结构:属性或方法。
10 方法重写
- 方法的重写(override / overwrite)
- 1.重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
- 2.应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
- 3.重写的规定:
- 方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ //方法体 }
- 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
- ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
- ② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
- 特殊情况:子类不能重写父类中声明为private权限的方法
- ③ 返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
- ④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
- 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
** 面试题:区分方法的重载与重写**
- 二者的概念:
- 重载和重写的具体规则
- 重载:不表现为多态性 重写:表现为多态性
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
11 suqer关键字
- super关键字的使用
- 1.super理解为:父类的
- 2.super可以用来调用:属性、方法、构造器
- 3.super的使用:调用属性和方法
- 3.1 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用
- 父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 3.2 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的
- 使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 3.3 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
- 使用"super.方法"的方式,表明调用的是父类中被重写的方法。
- 4.super调用构造器
- 4.1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- 4.2 "super(形参列表)"的使用,必须声明在子类构造器的首行!
- 4.3 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
- 4.4 在构造器的首行,没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 4.5 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
12 多态性
- 面向对象特征之三:多态性
- 1.理解多态性:可以理解为一个事物的多种形态。
- 2.何为多态性:
- 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
- 3.多态的使用:虚拟方法调用
- 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
- 总结:编译,看左边;运行,看右边。
- 4.多态性的使用前提: ① 类的继承关系 ② 方法的重写
- 5.对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
13 子类对象实例化的全过程
- 子类对象实例化的全过程
- 1.从结果上来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 2.从过程上来看:
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
- 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
14 继承性
- 面向对象的特征之二:继承性 why?
- 一、继承性的好处:
- ① 减少了代码的冗余,提高了代码的复用性
- ② 便于功能的扩展
- ③ 为之后多态性的使用,提供了前提
- 二、继承性的格式:
- class A extends B{}
- A:子类、派生类、subclass
- B:父类、超类、基类、superclass
- 2.1
- 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
- 特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
- 2.2
- 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
- 子类和父类的关系,不同于子集和集合的关系。
- extends:延展、扩展
- 三、Java中关于继承性的规定:
- 1.一个类可以被多个子类继承。
- 2.Java中类的单继承性:一个类只能有一个父类
- 3.子父类是相对的概念。
- 4.子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
- 四、
- 1.如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
- 2.所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 3.意味着,所有的java类具有java.lang.Object类声明的功能。
15 构造器
-
类的结构之三:构造器(或构造方法、constructor)的使用
-
construct:建设、建造。 construction:CCB constructor:建设者
-
一、构造器的作用:
-
1.创建对象
-
2.初始化对象的信息
-
二、说明:
-
1.如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
-
2.定义构造器的格式:权限修饰符 类名(形参列表){}
-
3.一个类中定义的多个构造器,彼此构成重载
-
4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
-
5.一个类中,至少会有一个构造器。
总结:属性赋值的先后顺序
-
① 默认初始化
-
② 显式初始化
-
③ 构造器中初始化
-
④ 通过"对象.方法" 或 "对象.属性"的方式,赋值
-
以上操作的先后顺序:① - ② - ③ - ④
16 封装与隐藏
- 面向对象的特征一:封装与隐藏 3W:what? why? how?
- 一、问题的引入:
- 当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到 属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs()) 同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private). -->此时,针对于属性就体现了封装性。
- 二、封装性的体现:
- 我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
- 拓展:封装性的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式 …
- 三、封装性的体现,需要权限修饰符来配合。
- 1.Java规定的4种权限(从小到大排列):private、缺省、protected 、public
- 2.4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
- 3.具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
- 修饰类的话,只能使用:缺省、public
- 总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
17 传递机制和方法重载
-
方法的形参的传递机制:值传递
-
1.形参:方法定义时,声明的小括号内的参数
-
实参:方法调用时,实际传递给形参的数据
-
2.值传递机制:
-
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
-
如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
-
关于变量的赋值:
-
如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
-
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
-
可变个数形参的方法
-
1.jdk 5.0新增的内容
-
2.具体使用:
-
2.1 可变个数形参的格式:数据类型 … 变量名
-
2.2 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个,。。。
-
2.3 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
-
2.4 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
-
2.5 可变个数形参在方法的形参中,必须声明在末尾
-
2.6 可变个数形参在方法的形参中,最多只能声明一个可变形参。
-
方法的重载(overload) loading…
-
1.定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
-
“两同一不同”:同一个类、相同方法名 参数列表不同:参数个数不同,参数类型不同
-
- 举例:
-
Arrays类中重载的sort() / binarySearch()
-
3.判断是否是重载:
-
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
-
4.在通过对象调用方法时,如何确定某一个指定的方法:
-
方法名 —> 参数列表
18 方法和对象
- 一、理解“万事万物皆对象”
- 1.在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构
- Scanner,String等
- 文件:File
- 网络资源:URL
- 2.涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。
- 二、内存解析的说明
- 1.引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
- 三、匿名对象的使用
- 1.理解:我们创建的对象,没有显式的赋给一个变量名。即为匿名对象
- 2.特征:匿名对象只能调用一次。
- 3.使用:如下
19 Object类中toString()的使用:
- Object类中toString()的使用:
- 1.当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
- 2.Object类中toString()的定义:
- public String toString() {
return getClass().getName() + “@” + Integer.toHexString(hashCode());
} - 3.像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息
- 4.自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
20 Java中的JUnit单元测试
- Java中的JUnit单元测试
- 步骤:
- 1.选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
- 2.创建Java类,进行单元测试。
- 此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
- 3.此类中声明单元测试方法。
- 此时的单元测试方法:方法的权限是public,没有返回值,没有形参
- 4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 6.写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
- 说明:
- 1.如果执行结果没有任何异常:绿条
- 2.如果执行结果出现异常:红条
21 static关键字的使用
- static关键字的使用
- 1.static:静态的
- 2.static可以用来修饰:属性、方法、代码块、内部类
- 3.使用static修饰属性:静态变量(或类变量)
- 3.1 属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
- 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
- 3.2 static修饰属性的其他说明:
- ① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
- ② 静态变量的加载要早于对象的创建。
- ③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
- ④ 类变量 实例变量
- 类 yes no
- 对象 yes yes
- 3.3 静态属性举例:System.out; Math.PI;
- 4.使用static修饰方法:静态方法
- ① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
- ② 静态方法 非静态方法
- 类 yes no
- 对象 yes yes
- ③ 静态方法中,只能调用静态的方法或属性
- 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
- 5.static注意点:
- 5.1 在静态的方法内,不能使用this关键字、super关键字
- 5.2 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
- 6.开发中,如何确定一个属性是否要声明为static的?
- 6.1 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 6.2类中的常量也常常声明为static
- 7.开发中,如何确定一个方法是否要声明为static的?
- 7.1操作静态属性的方法,通常设置为static的
- 7.2工具类中的方法,习惯上声明为static的。 比如:Math、Arrays,Collections
22 单例设计模式
- 单例设计模式:
- 1.所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
- 2.如何实现?
- 饿汉式 vs 懒汉式
- 3.区分饿汉式 和 懒汉式
- 饿汉式:
- 坏处:对象加载时间过长。
- 好处:饿汉式是线程安全的
- 懒汉式:好处:延迟对象的创建。
- 目前的写法坏处:线程不安全。—>到多线程内容时,再修改
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
//懒汉式
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
23 代码块
-
类的成员之四:代码块(或初始化块)
-
1.代码块的作用:用来初始化类、对象
-
2.代码块如果有修饰的话,只能使用static.
-
3.分类:静态代码块 vs 非静态代码块
-
4.静态代码块
-
内部可以有输出语句
-
随着类的加载而执行,而且只执行一次
-
作用:初始化类的信息
-
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
-
静态代码块的执行要优先于非静态代码块的执行
-
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
-
5.非静态代码块
-
内部可以有输出语句
-
随着对象的创建而执行
-
每创建一个对象,就执行一次非静态代码块
-
作用:可以在创建对象时,对对象的属性等进行初始化
-
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
-
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
-
//总结:由父及子,静态先行
-
对属性可以赋值的位置:
-
①默认初始化
-
②显式初始化/⑤在代码块中赋值
-
③构造器中初始化
-
④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
-
执行的先后顺序:① - ② / ⑤ - ③ - ④
24 final关键字
- final:最终的
- 1.final可以用来修饰的结构:类、方法、变量
- 2.inal 用来修饰一个类:此类不能被其他类所继承。
- 比如:String类、System类、StringBuffer类
- 3.final 用来修饰方法:表明此方法不可以被重写
- 比如:Object类中getClass();
- 4.final 用来修饰变量:此时的"变量"就称为是一个常量
- 4.1 final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
- 4.2 final修饰局部变量:
- 尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
- static final 用来修饰属性:全局常量
25 抽象类
- abstract关键字的使用
- 1.abstract:抽象的
- 2.abstract可以用来修饰的结构:类、方法
- 3.abstract修饰类:抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
- 4.abstract修饰方法:抽象方法
- 抽象方法只有方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
- 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
- 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
- abstract使用上的注意点:
- 1.abstract不能用来修饰:属性、构造器等结构
- 2.abstract不能用来修饰私有方法、静态方法、final的方法、final的类
26 接口
- 接口的使用
- 1.接口使用interface来定义
- 2.Java中,接口和类是并列的两个结构
- 3.如何定义接口:定义接口中的成员
- 3.1 JDK7及以前:只能定义全局常量和抽象方法
- 全局常量:public static final的.但是书写时,可以省略不写
- 抽象方法:public abstract的
- 3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
* //知识点1:接口中定义的静态方法,只能通过接口来调用。
CompareA.method1();
//知识点2:通过实现类的对象,可以调用接口中的默认方法。
//如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
s.method2();
//知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
//那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
//知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
//那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
//这就需要我们必须在实现类中重写此方法
//知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
public interface CompareA {
//静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
default void method3(){
System.out.println("CompareA:上海");
}
}
-
4.接口中不能定义构造器的!意味着接口不可以实例化
-
5.Java开发中,接口通过让类去实现(implements)的方式来使用.
-
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
-
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
-
6.Java类可以实现多个接口 —>弥补了Java单继承性的局限性
-
格式:class AA extends BB implements CC,DD,EE
-
7.接口与接口之间可以继承,而且可以多继承
-
8.接口的具体使用,体现多态性
-
9.接口,实际上可以看做是一种规范
-
面试题:抽象类与接口有哪些异同?
-
接口的使用
-
1.接口使用上也满足多态性
-
2.接口,实际上就是定义了一种规范
-
3.开发中,体会面向接口编程!
27 模板方法
28 内部类
- 类的内部成员之五:内部类
- 1.Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
- 2.内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
- 3.成员内部类:
- 一方面,作为外部类的成员:
- 调用外部类的结构
- 可以被static修饰
- 可以被4种不同的权限修饰
- 另一方面,作为一个类:
- 类内可以定义属性、方法、构造器等
- 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
- 可以被abstract修饰
- 4.关注如下的3个问题
- 4.1 如何实例化成员内部类的对象
- 4.2 如何在成员内部类中区分调用外部类的结构
- 4.3 开发中局部内部类的使用 见《InnerClassTest1.java》
注意:
- 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,
- 要求此局部变量声明为final的。
- jdk 7及之前版本:要求此局部变量显式的声明为final的
- jdk 8及之后的版本:可以省略final的声明
29 异常处理
29.1 常用异常
- 一、异常体系结构
* java.lang.Throwable
* |-----java.lang.Error:一般不编写针对性的代码进行处理。
* |-----java.lang.Exception:可以进行异常的处理
* |------编译时异常(checked)
* |-----IOException
* |-----FileNotFoundException
* |-----ClassNotFoundException
* |------运行时异常(unchecked,RuntimeException)
* |-----NullPointerException
* |-----ArrayIndexOutOfBoundsException
* |-----ClassCastException
* |-----NumberFormatException
* |-----InputMismatchException
* |-----ArithmeticException
*
* 面试题:常见的异常都有哪些?举例说明
29.2 异常处理
* 一、异常的处理:抓抛模型
* 过程一:"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。
* 并将此对象抛出。
* 一旦抛出对象以后,其后的代码就不再执行。
* 关于异常对象的产生:① 系统自动生成的异常对象
* ② 手动的生成一个异常对象,并抛出(throw)
* 过程二:"抓":可以理解为异常的处理方式:① try-catch-finally ② throws
*
* 二、try-catch-finally的使用
*
* try{
* //可能出现异常的代码
*
* }catch(异常类型1 变量名1){
* //处理异常的方式1
* }catch(异常类型2 变量名2){
* //处理异常的方式2
* }catch(异常类型3 变量名3){
* //处理异常的方式3
* }
* ....
* finally{
* //一定会执行的代码
* }
*
* 说明:
* 1. finally是可选的。
* 2. 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象
* 的类型,去catch中进行匹配
* 3. 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的
* try-catch结构(在没有写finally的情况)。继续执行其后的代码
* 4. catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
* catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
* 5. 常用的异常对象处理的方式: ① String getMessage() ② printStackTrace()
* 6. 在try结构中声明的变量,再出了try结构以后,就不能再被调用
* 7. try-catch-finally结构可以嵌套
*
* 体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。
* 相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
*
* 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。
* 针对于编译时异常,我们说一定要考虑异常的处理。
* try-catch-finally中finally的使用:
* 1.finally是可选的
*
* 2.finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有
* return语句等情况。
*
* 3.像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的
* 释放。此时的资源释放,就需要声明在finally中。
* 异常处理的方式二:throws + 异常类型
* 1. "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
* 一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常
* 类型时,就会被抛出。异常代码后续的代码,就不再执行!
*
* 2. 体会:try-catch-finally:真正的将异常给处理掉了。
* throws的方式只是将异常抛给了方法的调用者。 并没有真正将异常处理掉。
*
* 3. 开发中如何选择使用try-catch-finally 还是使用throws?
* 3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果
* 子类重写的方法中有异常,必须使用try-catch-finally方式处理。
* 3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws
* 的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
* 方法重写的规则之一:
* 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
- 如何自定义异常类?
- 1.继承于现有的异常结构:RuntimeException 、Exception
- 2.提供全局常量:serialVersionUID
- 3.提供重载的构造器
30 线程Threat
30.1 创建线程:
* 多线程的创建,方式一:继承于Thread类
* 1.创建一个继承于Thread类的子类
* 2.重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3.创建Thread类的子类的对象
* 4.通过此对象调用start()
* 例子:遍历100以内的所有的偶数
*
* 创建多线程的方式二:实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
* 比较创建线程的两种方式。
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没有类的单继承性的局限性
* 2. 实现的方式更适合来处理多个线程有共享数据的情况。
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
* 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
* 1. call()可以有返回值的。
* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的
*
* 创建步骤:
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
* 创建线程的方式四:使用线程池
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
**面试题:创建多线程有几种方式?四种!**
创建步骤:
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
30.2 线程的常用方法
测试Thread中的常用方法:
* 1. start():启动当前线程;调用当前线程的run()
* 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3. currentThread():静态方法,返回执行当前代码的线程
* 4. getName():获取当前线程的名字
* 5. setName():设置当前线程的名字
* 6. yield():释放当前cpu的执行权
* 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
* 8. stop():已过时。当执行此方法时,强制结束当前线程。
* 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前 线程是阻塞状态。
* 10. isAlive():判断当前线程是否存活
线程的优先级:
* 1
* MAX_PRIORITY:10
* MIN _PRIORITY:1
* NORM_PRIORITY:5 -->默认优先级
* 2.如何获取和设置当前线程的优先级:
* getPriority():获取线程的优先级
* setPriority(int p):设置线程的优先级
- 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下
- 被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
30.3 同步
-
例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
-
1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
-
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
-
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他 线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
-
4.在Java中,我们通过同步机制,来解决线程的安全问题。
-
方式一:同步代码块
-
synchronized(同步监视器){
-
//需要被同步的代码,包含共享数据的操作,只能刚刚好不可以多
-
}
-
说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
-
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
-
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
-
要求:多个线程必须要共用同一把锁。
-
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
-
方式二:同步方法。
-
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
-
5.同步的方式,解决了线程的安全问题。—好处
-
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性
-
关于同步方法的总结:
-
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
-
2.非静态的同步方法,同步监视器是:this
-
静态的同步方法,同步监视器是:当前类本身
30.4 死锁
- 演示线程的死锁问题
- 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
- 都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 2.说明:
- 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 2)我们使用同步时,要避免出现死锁。
30.5 线程安全问题
- 解决线程安全问题的方式三:Lock锁 — JDK5.0新增
- 1.面试题:synchronized 与 Lock的异同?
- 相同:二者都可以解决线程安全问题
- 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器,Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
- 2.优先使用顺序:
- Lock —> 同步代码块(已经进入了方法体,分配了相应资源) —> 同步方法(在方法体之外)
- 面试题:如何解决线程安全问题?有几种方式
- 2种,Lock,synchrionized方式
30.6 线程通信
-
线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
-
涉及到的三个方法:
-
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
-
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
-
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
-
说明:
-
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
-
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
-
否则,会出现IllegalMonitorStateException异常
-
3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
-
面试题:sleep() 和 wait()的异同?
-
1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
-
2.不同点:
-
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
-
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
-
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
31 常用的类
31.1 String类
31.1.1 String类的概述
String:字符串,使用一对""引起来表示。
1.String声明为final的,不可被继承
2.String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
3.String内部定义了final char[] value用于存储字符串数据
4.String:代表不可变的字符序列。简称:不可变性。
体现:
1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
3. 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
5.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
6.字符串常量池中是不会存储相同内容的字符串的。
结论:
1.常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
2.只要其中有一个是变量,结果就在堆中。
3.如果拼接的结果调用intern()方法,返回值就在常量池中
String的实例化方式:
方式一:通过字面量定义的方式
方式二:通过new + 构造器的方式
面试题:String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区
中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在
堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false
30.1.1 String类常用方法:
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
匹配:
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
切片:
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
31.1.3 String类和其他数据类型的转化
String 与基本数据类型、包装类之间的转换:
String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)
String 与 char[]之间的转换:
String --> char[]:调用String的toCharArray()
char[] --> String:调用String的构造器
String 与 byte[]之间的转换:
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器
编码:字符串 -->字节 (看得懂 —>看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 —> 看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
System.out.println(Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
System.out.println(Arrays.toString(gbks));
System.out.println("******************");
String str2 = new String(bytes);//使用默认的字符集,进行解码。
System.out.println(str2);
String str3 = new String(gbks);
System.out.println(str3);//出现乱码。原因:编码集和解码集不一致!
String str4 = new String(gbks, "gbk");
System.out.println(str4);//没有出现乱码。原因:编码集和解码集一致!
}
30.2 StringBuffer类和StringBuilder类
30.2.1 String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列;底层使用char[]存储
StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
源码分析:
String str = new String();//char[] value = new char[0];
String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
System.out.println(sb1.length());//
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
//问题1. System.out.println(sb2.length());//3
//问题2. 扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
指导意义:开发中建议大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
30.2.2 StringBuffer的常用方法:(StringBuilder也有)
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
总结:
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
*遍历:for() + charAt() / toString()
对比String、StringBuffer、StringBuilder三者的效率:
从高到低排列:StringBuilder > StringBuffer > String
30.2 date日期类
JDK 8之前日期和时间的API测试:
java.util.Date类
|---java.sql.Date类
1.两个构造器的使用
>构造器一:Date():创建一个对应当前时间的Date对象
>构造器二:创建指定毫秒数的Date对象
2.两个方法的使用
>toString():显示当前的年、月、日、时、分、秒
>getTime():获取当前Date对象对应的毫秒数。(时间戳)
3. java.sql.Date对应着数据库中的日期类型的变量
>如何实例化
>如何将java.util.Date对象转换为java.sql.Date对象
*/
@Test
public void test2(){
//构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
System.out.println(date1.toString());//Sat Feb 16 16:35:31 GMT+08:00 2019
System.out.println(date1.getTime());//1550306204104
//构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(155030620410L);
System.out.println(date2.toString());
//创建java.sql.Date对象
java.sql.Date date3 = new java.sql.Date(35235325345L);
System.out.println(date3);//1971-02-13
//如何将java.util.Date对象转换为java.sql.Date对象
//情况一:
// Date date4 = new java.sql.Date(2343243242323L);
// java.sql.Date date5 = (java.sql.Date) date4;
//情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
}
//1.System类中的currentTimeMillis()
@Test
public void test1(){
long time = System.currentTimeMillis();
//返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
//称为时间戳
System.out.println(time);
}
* jdk 8之前的日期时间的API测试
* 1. System类中currentTimeMillis();
* 2. java.util.Date和子类java.sql.Date
* 3. SimpleDateFormat
* 4. Calendar
*
* @author shkstart
* @create 2019 上午 11:35
*/
public class DateTimeTest {
/*
SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
1.两个操作:
1.1 格式化:日期 --->字符串
1.2 解析:格式化的逆过程,字符串 ---> 日期
2.SimpleDateFormat的实例化
*/
@Test
public void testSimpleDateFormat() throws ParseException {
//实例化SimpleDateFormat:使用默认的构造器
SimpleDateFormat sdf = new SimpleDateFormat();
//格式化:日期 --->字符串
Date date = new Date();
System.out.println(date);
String format = sdf.format(date);
System.out.println(format);
//解析:格式化的逆过程,字符串 ---> 日期
String str = "19-12-18 上午11:43";
Date date1 = sdf.parse(str);
System.out.println(date1);
//*************按照指定的方式格式化和解析:调用带参的构造器*****************
// SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化
String format1 = sdf1.format(date);
System.out.println(format1);//2019-02-18 11:48:27
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,抛异常
Date date2 = sdf1.parse("2020-02-18 11:48:27");
System.out.println(date2);
}
/*
练习一:字符串"2020-09-08"转换为java.sql.Date
练习二:"三天打渔两天晒网" 1990-01-01 xxxx-xx-xx 打渔?晒网?
举例:2020-09-08 ? 总天数 总天数 % 5 == 1,2,3 : 打渔 总天数 % 5 == 4,0 : 晒网
总天数的计算?
方式一:( date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24) + 1
方式二:1990-01-01 --> 2019-12-31 + 2020-01-01 -->2020-09-08
*/
@Test
public void testExer() throws ParseException {
String birth = "2020-09-08";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf1.parse(birth);
// System.out.println(date);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
}
/*
Calendar日历类(抽象类)的使用
*/
@Test
public void testCalendar(){
//1.实例化
//方式一:创建其子类(GregorianCalendar)的对象
//方式二:调用其静态方法getInstance()
Calendar calendar = Calendar.getInstance();
// System.out.println(calendar.getClass());
//2.常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
//set()
//calendar可变性
calendar.set(Calendar.DAY_OF_MONTH,22);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
//getTime():日历类---> Date
Date date = calendar.getTime();
System.out.println(date);
//setTime():Date ---> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
}
}
JDK8后的日期AP:
JDK8后的日期API
LocalDate、LocalTime、LocalDateTime 的使用
说明:
1.LocalDateTime相较于LocalDate、LocalTime,使用频率要高
2.类似于Calendar
*/
@Test
public void test1(){
//now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
//of():设置指定的年、月、日、时、分、秒。没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
System.out.println(localDateTime1);
//getXxx():获取相关的属性
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getDayOfWeek());
System.out.println(localDateTime.getMonth());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getMinute());
//体现不可变性
//withXxx():设置相关的属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate);
System.out.println(localDate1);
LocalDateTime localDateTime2 = localDateTime.withHour(4);
System.out.println(localDateTime);
System.out.println(localDateTime2);
//不可变性
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
System.out.println(localDateTime);
System.out.println(localDateTime3);
LocalDateTime localDateTime4 = localDateTime.minusDays(6);
System.out.println(localDateTime);
System.out.println(localDateTime4);
}
/*
Instant的使用
类似于 java.util.Date类
*/
@Test
public void test2(){
//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);//2019-02-18T07:29:41.719Z
//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00
//toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 ---> Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);
//ofEpochMilli():通过给定的毫秒数,获取Instant实例 -->Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1550475314878L);
System.out.println(instant1);
}
/*
DateTimeFormatter:格式化或解析日期、时间
类似于SimpleDateFormat
*/
@Test
public void test3(){
// 方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
System.out.println(localDateTime);
System.out.println(str1);//2019-02-18T15:42:18.797
//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);
// 方式二:
// 本地化相关的格式。如:ofLocalizedDateTime()
// FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(localDateTime);
System.out.println(str2);//2019年2月18日 下午03时47分16秒
// 本地化相关的格式。如:ofLocalizedDate()
// FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
//格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18
// 重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
}
}
30.3 Comparable和Comparator接口
- 一、说明:Java中的对象,正常情况下,只能进行比较:== 或 != 。不能使用 > 或 < 的
- 但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
- 如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
- 二、Comparable接口与Comparator的使用的对比:
- Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
- Comparator接口属于临时性的比较。
30.3.1 Comparable接口的使用举例: 自然排序
1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
3. 重写compareTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
4. 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。
在compareTo(obj)方法中指明如何排序
30.3.2 Comparator接口的使用:定制排序
1.背景:
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,
或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,
那么可以考虑使用 Comparator 的对象来排序
2.重写compare(Object o1,Object o2)方法,比较o1和o2的大小:
如果方法返回正整数,则表示o1大于o2;
如果返回0,表示相等;
返回负整数,表示o1小于o2。
30.4 System类
30.5 Math类
30.6 BigInteger 和 BigDecimal
BigInteger 类:
BigDecimal 类:
31 枚举类
* 一、枚举类的使用
* 1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
* 2.当需要定义一组常量时,强烈建议使用枚举类
* 3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
*
* 二、如何定义枚举类
* 方式一:jdk5.0之前,自定义枚举类
* 方式二:jdk5.0,可以使用enum关键字定义枚举类
*
* 三、Enum类中的常用方法:
* values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
* valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
* toString():返回当前枚举类对象常量的名称
*
* Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());
// System.out.println(Season1.class.getSuperclass());
System.out.println("****************");
//values():返回所有的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
values[i].show();
}
*//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没有objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
**定义枚举类:**
enum Season1{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地")};
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
}
* 四、使用enum关键字定义的枚举类实现接口的情况
* 情况一:实现接口,在enum类中实现抽象方法
* 情况二:让枚举类的对象分别实现接口中的抽象方法
* *
* 使用enum关键字定义枚举类
* 说明:定义的枚举类默认继承于java.lang.Enum类
31 注解
* 注解的使用
* 1. 理解Annotation:
* ① jdk 5.0 新增的功能
* ② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读
* 取, 并执行相应的处理。通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下,
* 在源文件中嵌入一些补充信息。
* ③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在
* JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代
* 替JavaEE旧版中所遗留的繁冗 代码和XML配置等。
*
* 2. Annocation的使用示例
* 示例一:生成文档相关的注解
* 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
* 示例三:跟踪代码依赖性,实现替代配置文件功能
*
* 3. 如何自定义注解:参照@SuppressWarnings定义
* ① 注解声明为:@interface
* ② 内部定义成员,通常使用value表示
* ③ 可以指定成员的默认值,使用default定义
* ④ 如果自定义注解没有成员,表明是一个标识作用。
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通过都会指明两个元注解:Retention、Target
* 4. jdk 提供的4种元注解
元注解:对现有的注解进行解释说明的注解
Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME,只有声明为RUNTIME生命周期的注解,才能通过反射获取。
Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
*******出现的频率较低*******
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具有继承性。
* 5.通过反射获取注解信息 ---到反射内容时系统讲解
* 6. jdk 8 中注解的新特性:可重复注解、类型注解
* 6.1 可重复注解:
① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。
* 6.2 类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
MyAnnotation[] value();
}
32 集合
32.1 Collection接口
32.1.1 list接口
* 1. List接口框架
*
* |----Collection接口:单列集合,用来存储一个一个的对象
* |----List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原有的数组
* |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
* |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
* |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
*
* 结论:向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
*
* 2. ArrayList的源码分析:
* 2.1 jdk 7情况下
* ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
* list.add(123);//elementData[0] = new Integer(123);
* ...
* list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
* 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
* 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
*
* 2.2 jdk 8中ArrayList的变化:
* ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
* list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
* ...
* 后续的添加和扩容操作与jdk 7 无异。
* 2.3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象 的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
*
* 3. LinkedList的源码分析:
* LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
* list.add(123);//将123封装到Node中,创建了Node对象。
* 其中,Node定义为:体现了LinkedList的双向链表的说法
* private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
*
* 4. Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
* 在扩容方面,默认扩容为原来的数组长度的2倍。
*
* 面试题:ArrayList、LinkedList、Vector三者的异同?
* 同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据
* 不同:见上
*
* 5. List接口中的常用方法
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
总结:常用方法
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:① Iterator迭代器方式
② 增强for循环
③ 普通的循环
32.1.2 Iterator接口
* 集合元素的遍历操作,使用迭代器Iterator接口
* 1.内部的方法:hasNext() 和 next()
* 2.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,
* 默认游标都在集合的第一个元素之前。
* 3.内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
* //集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Iterator iterator = coll.iterator();
//方式二:不推荐
// for(int i = 0;i < coll.size();i++){
// System.out.println(iterator.next());
// }
//方式三:推荐
hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
32.1.3 set接口
* 1. Set接口的框架:
* |----Collection接口:单列集合,用来存储一个一个的对象
* |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
* |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
* |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
* 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
* |----TreeSet:可以按照添加对象的指定属性,进行排序。
*
* 1. Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
*
* 2. 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
* 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
* 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
* 一、Set:存储无序的、不可重复的数据
以HashSet为例说明:
1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。
二、添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。 --->情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。--->情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。--->情况2
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构。
/LinkedHashSet的使用
//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个
//数据和后一个数据。
//优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().
@Override
public int hashCode() { //return name.hashCode() + age;
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
//按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据类型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Mary",33));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
32.2 Map接口
32.2.1 hashMap
* 一、Map的实现类的结构:
* |----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
* |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
* |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
* 原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
* 对于频繁的遍历操作,此类执行效率高于HashMap。
* |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
* 底层使用红黑树
* |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
* |----Properties:常用来处理配置文件。key和value都是String类型
*
* HashMap的底层:数组+链表 (jdk7及之前)
* 数组+链表+红黑树 (jdk 8)
* 面试题:
* 1. HashMap的底层实现原理?
* 2. HashMap 和 Hashtable的异同?
* 3. CurrentHashMap 与 Hashtable的异同?(暂时不讲)
*
* 二、Map结构的理解:
* Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
* Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
* 一个键值对:key-value构成了一个Entry对象。
* Map中的entry:无序的、不可重复的,使用Set存储所有的entry
*
* 三、HashMap的底层实现原理?以jdk7为例说明:
* HashMap map = new HashMap():
* 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
* ...可能已经执行过多次put...
* map.put(key1,value1):
* 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
* 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
* 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
* 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
* 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
* 如果equals()返回false:此时key1-value1添加成功。----情况3
* 如果equals()返回true:使用value1替换value2。
*
* 补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
*
* 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
*
* jdk8 相较于jdk7在底层实现方面的不同:
* 1. new HashMap():底层没有创建一个长度为16的数组
* 2. jdk 8底层的数组是:Node[],而非Entry[]
* 3. 首次调用put()方法时,底层创建长度为16的数组
* 4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
* 4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
*
* DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
* DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
* threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
* TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
* MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
*
* 四、LinkedHashMap的底层实现原理(了解)
* 源码中:
* static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;//能够记录添加的元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
*
//向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
//因为要按照key进行排序:自然排序 、定制排序
//按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
* 五、Map中定义的方法:
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
*总结:常用方法:
* 添加:put(Object key,Object value)
* 删除:remove(Object key)
* 修改:put(Object key,Object value)
* 查询:get(Object key)
* 长度:size()
* 遍历:keySet() / values() / entrySet()
*
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
public void test5(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,1234);
map.put("BB",56);
//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println();
//遍历所有的value集:values()
Collection values = map.values();
for(Object obj : values){
System.out.println(obj);
}
System.out.println();
//遍历所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
//entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
System.out.println();
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator();
while(iterator2.hasNext()){
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + "=====" + value);
}
}
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
32.3 Collections工具类
- Collections:操作Collection、Map的工具类
- 面试题:Collection 和 Collections的区别?
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
33 泛型
* 泛型的使用
* 1.jdk 5.0新增的特性
*
* 2.在集合中使用泛型:
* 总结:
* ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
* ② 在实例化集合类时,可以指明具体的泛型类型
* ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
* 比如:add(E e) --->实例化以后:add(Integer e)
* ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
* ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
*
* 3.如何自定义泛型结构:泛型类、泛型接口;泛型方法。
//如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
//要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
//建议:实例化时指明类的泛型
//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<Integer>();
//泛型不同的引用不能相互赋值。
// list1 = list2;
/泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没有关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
//泛型方法在调用时,指明泛型参数的类型。
List<Integer> list = order.copyFromArrayToList(arr);
//静态方法中不能使用类的泛型。
// public static void show(T orderT){
// System.out.println(orderT);
// }
public Order(){
//编译不通过
// T[] arr = new T[10];
//编译通过
T[] arr = (T[]) new Object[10];
}
public class SubOrder extends Order<Integer> {//SubOrder:不是泛型类
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
//异常类不能声明为泛型类
//public class MyException<T> extends Exception{
//}
1. 泛型在继承方面的体现
虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A<G> 是 B<G> 的父类
public void test1(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
//编译不通过
// Date date = new Date();
// str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时的list1和list2的类型不具有子父类关系
//编译不通过
// list1 = list2;
/*
反证法:
假设list1 = list2;
list1.add(123);导致混入非String的数据。出错。
*/
show(list1);
show1(list2);
}
2. 通配符的使用
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
public void test3(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//编译通过
// print(list1);
// print(list2);
//
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
//添加(写入):对于List<?>就不能向其内部添加数据。
//除了添加null之外。
// list.add("DD");
// list.add('?');
list.add(null);
//获取(读取):允许读取数据,读取的数据类型为Object。
Object o = list.get(0);
System.out.println(o);
}
3.有限制条件的通配符的使用。
? extends A:
G<? extends A> 可以作为G<A>和G<B>的父类,其中B是A的子类
? super A:
G<? super A> 可以作为G<A>和G<B>的父类,其中B是A的父类
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
//读取数据:
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
编译不通过
// Person obj = list2.get(0);
//写入数据:
//编译不通过
// list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student());
}
34 IO流
34.1 File类
* File类的使用
*
* 1. File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
* 2. File类声明在java.io包下
* 3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
* 并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
* 4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".
*
* 1.如何创建File类的实例
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)
//构造器1
File file1 = new File("hello.txt");//相对于当前module
File file2 = new File("D:\\workspace_idea1\\JavaSenior\\day08\\he.txt");
System.out.println(file1);
System.out.println(file2);
//构造器2:
File file3 = new File("D:\\workspace_idea1","JavaSenior");
System.out.println(file3);
//构造器3:
File file4 = new File(file3,"hi.txt");
System.out.println(file4);
2.
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
3.路径分隔符
windows:\\
unix:/
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
/*
public boolean renameTo(File dest):把文件重命名为指定的文件路径
比如:file1.renameTo(file2)为例:
要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
*/
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
/*
创建硬盘中对应的文件或文件目录
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
删除磁盘中的文件或文件目录
public boolean delete():删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。
//要想删除成功,io4文件目录下不能有子目录或文件
*/
练习:
判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
@Test
public void test1(){
File srcFile = new File("d:\\code");
String[] fileNames = srcFile.list();
for(String fileName : fileNames){
if(fileName.endsWith(".jpg")){
System.out.println(fileName);
}
}
}
@Test
public void test2(){
File srcFile = new File("d:\\code");
File[] listFiles = srcFile.listFiles();
for(File file : listFiles){
if(file.getName().endsWith(".jpg")){
System.out.println(file.getAbsolutePath());
}
}
}
遍历指定目录所有文件名称,包括子文件目录中的文件。
拓展1:并计算指定目录占用空间的大小
拓展2:删除指定文件目录及其下的所有文件
public class ListFilesTest {
public static void main(String[] args) {
// 递归:文件目录
/** 打印出指定目录所有文件名称,包括子文件目录中的文件 */
// 1.创建目录对象
File dir = new File("E:\\teach\\01_javaSE\\_尚硅谷Java编程语言\\3_软件");
// 2.打印目录的子文件
printSubFile(dir);
}
public static void printSubFile(File dir) {
// 打印目录的子文件
File[] subfiles = dir.listFiles();
for (File f : subfiles) {
if (f.isDirectory()) {// 文件目录
printSubFile(f);
} else {// 文件
System.out.println(f.getAbsolutePath());
}
}
}
// 方式二:循环实现
// 列出file目录的下级内容,仅列出一级的话
// 使用File类的String[] list()比较简单
public void listSubFiles(File file) {
if (file.isDirectory()) {
String[] all = file.list();
for (String s : all) {
System.out.println(s);
}
} else {
System.out.println(file + "是文件!");
}
}
// 列出file目录的下级,如果它的下级还是目录,接着列出下级的下级,依次类推
// 建议使用File类的File[] listFiles()
public void listAllSubFiles(File file) {
if (file.isFile()) {
System.out.println(file);
} else {
File[] all = file.listFiles();
// 如果all[i]是文件,直接打印
// 如果all[i]是目录,接着再获取它的下一级
for (File f : all) {
listAllSubFiles(f);// 递归调用:自己调用自己就叫递归
}
}
}
// 拓展1:求指定目录所在空间的大小
// 求任意一个目录的总大小
public long getDirectorySize(File file) {
// file是文件,那么直接返回file.length()
// file是目录,把它的下一级的所有大小加起来就是它的总大小
long size = 0;
if (file.isFile()) {
size += file.length();
} else {
File[] all = file.listFiles();// 获取file的下一级
// 累加all[i]的大小
for (File f : all) {
size += getDirectorySize(f);// f的大小;
}
}
return size;
}
// 拓展2:删除指定的目录
public void deleteDirectory(File file) {
// 如果file是文件,直接delete
// 如果file是目录,先把它的下一级干掉,然后删除自己
if (file.isDirectory()) {
File[] all = file.listFiles();
// 循环删除的是file的下一级
for (File f : all) {// f代表file的每一个下级
deleteDirectory(f);
}
}
// 删除自己
file.delete();
}
}
34.2 流的分类和流的体系结构
-
一、流的分类:
-
1.操作数据单位:字节流、字符流
-
2.数据的流向:输入流、输出流
-
3.流的角色:节点流、处理流
-
二、流的体系结构
* 抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
* InputStream FileInputStream (read(byte[] buffer)) BufferedInputStream (read(byte[] buffer))
* OutputStream FileOutputStream (write(byte[] buffer,0,len) BufferedOutputStream (write(byte[] buffer,0,len) / flush()
* Reader FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) / readLine())
* Writer FileWriter (write(char[] cbuf,0,len) BufferedWriter (write(char[] cbuf,0,len) / flush()
将day09下的hello.txt文件内容读入程序中,并输出到控制台
说明点:
1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
3. 读入的文件一定要存在,否则就会报FileNotFoundException。
@Test
public void testFileReader(){
FileReader fr = null;
try {
//1.实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");//相较于当前Module
//2.提供具体的流
fr = new FileReader(file);
//3.数据的读入
//read():返回读入的一个字符。如果达到文件末尾,返回-1
//方式一:
// int data = fr.read();
// while(data != -1){
// System.out.print((char)data);
// data = fr.read();
// }
//方式二:语法上针对于方式一的修改
int data;
while((data = fr.read()) != -1){
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭操作
// try {
// if(fr != null)
// fr.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
//或
if(fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//对read()操作升级:使用read的重载方法
@Test
public void testFileReader1() {
FileReader fr = null;
try {
//1.File类的实例化
File file = new File("hello.txt");
//2.FileReader流的实例化
fr = new FileReader(file);
//3.读入的操作
//read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
//方式一:
//错误的写法
// for(int i = 0;i < cbuf.length;i++){
// System.out.print(cbuf[i]);
// }
//正确的写法
// for(int i = 0;i < len;i++){
// System.out.print(cbuf[i]);
// }
//方式二:
//错误的写法,对应着方式一的错误的写法
// String str = new String(cbuf);
// System.out.print(str);
//正确的写法
String str = new String(cbuf,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fr != null){
//4.资源的关闭
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
从内存中写出数据到硬盘的文件里。
说明:
- 输出操作,对应的File可以不存在的。并不会报异常
- File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:
FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:
FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
@Test
public void testFileWriter() {
FileWriter fw = null;
try {
//1.提供File类的对象,指明写出到的文件
File file = new File("hello1.txt");
//2.提供FileWriter的对象,用于数据的写出
fw = new FileWriter(file,false);
//3.写出的操作
fw.write("I have a dream!\n");
fw.write("you need to have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流资源的关闭
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testFileReaderFileWriter() {
FileReader fr = null;
FileWriter fw = null;
try {
//1.创建File类的对象,指明读入和写出的文件
File srcFile = new File("hello.txt");
File destFile = new File("hello2.txt");
//不能使用字符流来处理图片等字节数据
// File srcFile = new File("爱情与友情.jpg");
// File destFile = new File("爱情与友情1.jpg");
//2.创建输入流和输出流的对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.数据的读入和写出操作
char[] cbuf = new char[5];
int len;//记录每次读入到cbuf数组中的字符的个数
while((len = fr.read(cbuf)) != -1){
//每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭流资源
//方式一:
// try {
// if(fw != null)
// fw.close();
// } catch (IOException e) {
// e.printStackTrace();
// }finally{
// try {
// if(fr != null)
// fr.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
//方式二:
try {
if(fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
34.2.1 处理流之一:缓冲流的使用
- 1.缓冲流:
- BufferedInputStream
- BufferedOutputStream
- BufferedReader
- BufferedWriter
- 2.作用:提供流的读取、写入的速度
- 提高读写速度的原因:内部提供了一个缓冲区
- 3.处理流,就是“套接”在已有的流的基础上。
public class BufferedTest {
/*
实现非文本文件的复制
*/
@Test
public void BufferedStreamTest() throws FileNotFoundException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情3.jpg");
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
// bos.flush();//刷新缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}
//实现文件复制的方法
public void copyFileWithBuffered(String srcPath,String destPath){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}
@Test
public void testCopyFileWithBuffered(){
long start = System.currentTimeMillis();
String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
String destPath = "C:\\Users\\Administrator\\Desktop\\03-视频.avi";
copyFileWithBuffered(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("复制操作花费的时间为:" + (end - start));//618 - 176
}
/*
使用BufferedReader和BufferedWriter实现文本文件的复制
*/
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
//创建文件和相应的流
br = new BufferedReader(new FileReader(new File("dbcp.txt")));
bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
//读写操作
//方式一:使用char[]数组
// char[] cbuf = new char[1024];
// int len;
// while((len = br.read(cbuf)) != -1){
// bw.write(cbuf,0,len);
// // bw.flush();
// }
//方式二:使用String
String data;
while((data = br.readLine()) != null){
//方法一:
// bw.write(data + "\n");//data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(bw != null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 测试FileInputStream和FileOutputStream的使用
- 结论:
- 1.对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
- 2.对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,…),使用字节流处理
public class FileInputOutputStreamTest {
//使用字节流FileInputStream处理文本文件,可能出现乱码。
@Test
public void testFileInputStream() {
FileInputStream fis = null;
try {
//1. 造文件
File file = new File("hello.txt");
//2.造流
fis = new FileInputStream(file);
//3.读数据
byte[] buffer = new byte[5];
int len;//记录每次读取的字节的个数
while((len = fis.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
//4.关闭资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
实现对图片的复制操作
*/
@Test
public void testFileInputOutputStream() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情2.jpg");
//
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
//
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//指定路径下文件的复制
public void copyFile(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
//
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testCopyFile(){
long start = System.currentTimeMillis();
String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
String destPath = "C:\\Users\\Administrator\\Desktop\\02-视频.avi";
// String srcPath = "hello.txt";
// String destPath = "hello3.txt";
copyFile(srcPath,destPath);
long end = System.currentTimeMillis();
System.out.println("复制操作花费的时间为:" + (end - start));//618
}
}
34.2.2 处理流之二:转换流的使用
- 1.转换流:属于字符流
- InputStreamReader:将一个字节的输入流转换为字符的输入流
- OutputStreamWriter:将一个字符的输出流转换为字节的输出流
- 2.作用:提供字节流与字符流之间的转换
- 3.解码:字节、字节数组 —>字符数组、字符串
- 编码:字符数组、字符串 —> 字节、字节数组
- 4.字符集
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表,用一个字节的8位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码。
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
public class InputStreamReaderTest {
/*
此时处理异常的话,仍然应该使用try-catch-finally
InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
*/
@Test
public void test1() throws IOException {
FileInputStream fis = new FileInputStream("dbcp.txt");
// InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
//参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
String str = new String(cbuf,0,len);
System.out.print(str);
}
isr.close();
}
/*
此时处理异常的话,仍然应该使用try-catch-finally
综合使用InputStreamReader和OutputStreamWriter
*/
@Test
public void test2() throws Exception {
//1.造文件、造流
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
//2.读写过程
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
//3.关闭资源
isr.close();
osw.close();
}
}
34.2.3 其他流的使用
- 1.标准的输入、输出流
- 2.打印流
- 3.数据流
public class OtherStreamTest {
/*
1.标准的输入、输出流
1.1
System.in:标准的输入流,默认从键盘输入
System.out:标准的输出流,默认从控制台输出
1.2
System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。
1.3练习:
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,
直至当输入“e”或者“exit”时,退出程序。
方法一:使用Scanner实现,调用next()返回一个字符串
方法二:使用System.in实现。System.in ---> 转换流 ---> BufferedReader的readLine()
*/
public static void main(String[] args) {
BufferedReader br = null;
try {
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);
while (true) {
System.out.println("请输入字符串:");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
String upperCase = data.toUpperCase();
System.out.println(upperCase);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
2. 打印流:PrintStream 和PrintWriter
2.1 提供了一系列重载的print() 和 println()
2.2 练习:
*/
@Test
public void test2() {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt"));
// 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
ps = new PrintStream(fos, true);
if (ps != null) {// 把标准输出流(控制台输出)改成文件
System.setOut(ps);
}
for (int i = 0; i <= 255; i++) { // 输出ASCII字符
System.out.print((char) i);
if (i % 50 == 0) { // 每50个数据一行
System.out.println(); // 换行
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
/*
3. 数据流
3.1 DataInputStream 和 DataOutputStream
3.2 作用:用于读取或写出基本数据类型的变量或字符串
练习:将内存中的字符串、基本数据类型的变量写出到文件中。
注意:处理异常的话,仍然应该使用try-catch-finally.
*/
@Test
public void test3() throws IOException {
//1.
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
//2.
dos.writeUTF("刘建辰");
dos.flush();//刷新操作,将内存中的数据写入文件
dos.writeInt(23);
dos.flush();
dos.writeBoolean(true);
dos.flush();
//3.
dos.close();
}
/*
将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
*/
@Test
public void test4() throws IOException {
//1.
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
//2.
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("isMale = " + isMale);
//3.
dis.close();
}
}
34.3 Files工具类的使用:操作文件或目录的工具类
public class FilesTest {
@Test
public void test1() throws IOException{
Path path1 = Paths.get("d:\\nio", "hello.txt");
Path path2 = Paths.get("atguigu.txt");
// Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
//要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
// Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
// Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
//要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
Path path3 = Paths.get("d:\\nio\\nio1");
// Files.createDirectory(path3);
// Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
//要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
Path path4 = Paths.get("d:\\nio\\hi.txt");
// Files.createFile(path4);
// void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
// Files.delete(path4);
// void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
Files.deleteIfExists(path3);
// Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
//要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
// Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
// long size(Path path) : 返回 path 指定文件的大小
long size = Files.size(path2);
System.out.println(size);
}
@Test
public void test2() throws IOException{
Path path1 = Paths.get("d:\\nio", "hello.txt");
Path path2 = Paths.get("atguigu.txt");
// boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));
// boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
//不要求此path对应的物理文件存在。
System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));
// boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
// boolean isHidden(Path path) : 判断是否是隐藏文件
//要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
// System.out.println(Files.isHidden(path1));
// boolean isReadable(Path path) : 判断文件是否可读
System.out.println(Files.isReadable(path1));
// boolean isWritable(Path path) : 判断文件是否可写
System.out.println(Files.isWritable(path1));
// boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
}
/**
* StandardOpenOption.READ:表示对应的Channel是可读的。
* StandardOpenOption.WRITE:表示对应的Channel是可写的。
* StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
* StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
*
* @author shkstart 邮箱:shkstart@126.com
* @throws IOException
*/
@Test
public void test3() throws IOException{
Path path1 = Paths.get("d:\\nio", "hello.txt");
// InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);
// OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);
// SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
// DirectoryStream<Path> newDirectoryStream(Path path) : 打开 path 指定的目录
Path path2 = Paths.get("e:\\teach");
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
Iterator<Path> iterator = directoryStream.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
34.4 对象流的使用
- 1.ObjectInputStream 和 ObjectOutputStream
- 2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
- 3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
- 4.序列化机制:
- 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种 二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
public class ObjectInputOutputStreamTest {
/*
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
*/
@Test
public void testObjectOutputStream(){
ObjectOutputStream oos = null;
try {
//1.
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
//2.
oos.writeObject(new String("我爱北京天安门"));
oos.flush();//刷新操作
oos.writeObject(new Person("王铭",23));
oos.flush();
oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
反序列化:将磁盘文件中的对象还原为内存中的一个java对象
使用ObjectInputStream来实现
*/
@Test
public void testObjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = ois.readObject();
String str = (String) obj;
Person p = (Person) ois.readObject();
Person p1 = (Person) ois.readObject();
System.out.println(str);
System.out.println(p);
System.out.println(p1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Person类:
- Person需要满足如下的要求,方可序列化
- 1.需要实现接口:Serializable
- 2.当前类提供一个全局常量:serialVersionUID。
//public static final long serialVersionUID = 475463534532L; - 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性 也必须是可序列化的。(默认情况下,基本数据类型可序列化)。
- 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
34.5 Path
-
- jdk 7.0 时,引入了 Path、Paths、Files三个类。
- 2.此三个类声明在:java.nio.file包下。
- 3.Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
-
- 4.如何实例化Path:使用Paths.
- static Path get(String first, String … more) : 用于将多个字符串串连成路径
- static Path get(URI uri): 返回指定uri对应的Path路径
public class PathTest {
//如何使用Paths实例化Path
@Test
public void test1() {
Path path1 = Paths.get("d:\\nio\\hello.txt");//new File(String filepath)
Path path2 = Paths.get("d:\\", "nio\\hello.txt");//new File(String parent,String filename);
System.out.println(path1);
System.out.println(path2);
Path path3 = Paths.get("d:\\", "nio");
System.out.println(path3);
}
//Path中的常用方法
@Test
public void test2() {
Path path1 = Paths.get("d:\\", "nio\\nio1\\nio2\\hello.txt");
Path path2 = Paths.get("hello.txt");
// String toString() : 返回调用 Path 对象的字符串表示形式
System.out.println(path1);
// boolean startsWith(String path) : 判断是否以 path 路径开始
System.out.println(path1.startsWith("d:\\nio"));
// boolean endsWith(String path) : 判断是否以 path 路径结束
System.out.println(path1.endsWith("hello.txt"));
// boolean isAbsolute() : 判断是否是绝对路径
System.out.println(path1.isAbsolute() + "~");
System.out.println(path2.isAbsolute() + "~");
// Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
System.out.println(path1.getParent());
System.out.println(path2.getParent());
// Path getRoot() :返回调用 Path 对象的根路径
System.out.println(path1.getRoot());
System.out.println(path2.getRoot());
// Path getFileName() : 返回与调用 Path 对象关联的文件名
System.out.println(path1.getFileName() + "~");
System.out.println(path2.getFileName() + "~");
// int getNameCount() : 返回Path 根目录后面元素的数量
// Path getName(int idx) : 返回指定索引位置 idx 的路径名称
for (int i = 0; i < path1.getNameCount(); i++) {
System.out.println(path1.getName(i) + "*****");
}
// Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
System.out.println(path1.toAbsolutePath());
System.out.println(path2.toAbsolutePath());
// Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
Path path3 = Paths.get("d:\\", "nio");
Path path4 = Paths.get("nioo\\hi.txt");
path3 = path3.resolve(path4);
System.out.println(path3);
// File toFile(): 将Path转化为File类的对象
File file = path1.toFile();//Path--->File的转换
Path newPath = file.toPath();//File--->Path的转换
}
}
34.6 RandomAccessFile的使用
- 1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
- 2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
- 3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。
- 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
- 4.可以通过相关的操作,实现RandomAccessFile“插入”数据的效果
public class RandomAccessFileTest {
@Test
public void test1() {
RandomAccessFile raf1 = null;
RandomAccessFile raf2 = null;
try {
//1.
raf1 = new RandomAccessFile(new File("爱情与友情.jpg"),"r");
raf2 = new RandomAccessFile(new File("爱情与友情1.jpg"),"rw");
//2.
byte[] buffer = new byte[1024];
int len;
while((len = raf1.read(buffer)) != -1){
raf2.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.
if(raf1 != null){
try {
raf1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(raf2 != null){
try {
raf2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test2() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");
raf1.seek(3);//将指针调到角标为3的位置
raf1.write("xyz".getBytes());//
raf1.close();
}
/*
使用RandomAccessFile实现数据的插入效果
*/
@Test
public void test3() throws IOException {
RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");
raf1.seek(3);//将指针调到角标为3的位置
//保存指针3后面的所有数据到StringBuilder中
StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
byte[] buffer = new byte[20];
int len;
while((len = raf1.read(buffer)) != -1){
builder.append(new String(buffer,0,len)) ;
}
//调回指针,写入“xyz”
raf1.seek(3);
raf1.write("xyz".getBytes());
//将StringBuilder中的数据写入到文件中
raf1.write(builder.toString().getBytes());
raf1.close();
//思考:将StringBuilder替换为ByteArrayOutputStream
}
}
34.7 练习
- 练习3:获取文本上字符出现的次数,把数据写入文件
- 思路:
- 1.遍历文本每一个字符
- 2.字符出现的次数存在Map中
- Map<Character,Integer> map = new HashMap<Character,Integer>();
- map.put(‘a’,18);
- map.put(‘你’,2);
- 3.把map中的数据写入文件
public class WordCount {
/*
说明:如果使用单元测试,文件相对路径为当前module
如果使用main()测试,文件相对路径为当前工程
*/
@Test
public void testWordCount() {
FileReader fr = null;
BufferedWriter bw = null;
try {
//1.创建Map集合
Map<Character, Integer> map = new HashMap<Character, Integer>();
//2.遍历每一个字符,每一个字符出现的次数放到map中
fr = new FileReader("dbcp.txt");
int c = 0;
while ((c = fr.read()) != -1) {
//int 还原 char
char ch = (char) c;
// 判断char是否在map中第一次出现
if (map.get(ch) == null) {
map.put(ch, 1);
} else {
map.put(ch, map.get(ch) + 1);
}
}
//3.把map中数据存在文件count.txt
//3.1 创建Writer
bw = new BufferedWriter(new FileWriter("wordcount.txt"));
//3.2 遍历map,再写入数据
Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();
for (Map.Entry<Character, Integer> entry : entrySet) {
switch (entry.getKey()) {
case ' ':
bw.write("空格=" + entry.getValue());
break;
case '\t'://\t表示tab 键字符
bw.write("tab键=" + entry.getValue());
break;
case '\r'://
bw.write("回车=" + entry.getValue());
break;
case '\n'://
bw.write("换行=" + entry.getValue());
break;
default:
bw.write(entry.getKey() + "=" + entry.getValue());
break;
}
bw.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关流
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class PicTest {
//图片的加密
@Test
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("爱情与友情.jpg");
fos = new FileOutputStream("爱情与友情secret.jpg");
byte[] buffer = new byte[20];
int len;
while ((len = fis.read(buffer)) != -1) {
//字节数组进行修改
//错误的
// for(byte b : buffer){
// b = (byte) (b ^ 5);
// }
//正确的
for (int i = 0; i < len; i++) {
buffer[i] = (byte) (buffer[i] ^ 5);
}
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//图片的解密
@Test
public void test2() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("爱情与友情secret.jpg");
fos = new FileOutputStream("爱情与友情4.jpg");
byte[] buffer = new byte[20];
int len;
while ((len = fis.read(buffer)) != -1) {
//字节数组进行修改
//错误的
// for(byte b : buffer){
// b = (byte) (b ^ 5);
// }
//正确的
for (int i = 0; i < len; i++) {
buffer[i] = (byte) (buffer[i] ^ 5);
}
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class MyInput {
// Read a string from the keyboard
public static String readString() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// Declare and initialize the string
String string = "";
// Get the string from the keyboard
try {
string = br.readLine();
} catch (IOException ex) {
System.out.println(ex);
}
// Return the string obtained from the keyboard
return string;
}
// Read an int value from the keyboard
public static int readInt() {
return Integer.parseInt(readString());
}
// Read a double value from the keyboard
public static double readDouble() {
return Double.parseDouble(readString());
}
// Read a byte value from the keyboard
public static double readByte() {
return Byte.parseByte(readString());
}
// Read a short value from the keyboard
public static double readShort() {
return Short.parseShort(readString());
}
// Read a long value from the keyboard
public static double readLong() {
return Long.parseLong(readString());
}
// Read a float value from the keyboard
public static double readFloat() {
return Float.parseFloat(readString());
}
}
35 网络编程
- 一、网络编程中有两个主要的问题:
- 1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 2.找到主机后如何可靠高效地进行数据传输
- 二、网络编程中的两个要素:
- 1.对应问题一:IP和端口号
- 2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
- 三、通信要素一:IP和端口号
- 1.IP:唯一的标识 Internet 上的计算机(通信实体)
- 2.在Java中使用InetAddress类代表IP
- 3.IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
- 4.域名: www.baidu.com www.mi.com www.sina.com www.jd.com www.vip.com
- 5.本地回路地址:127.0.0.1 对应着:localhost
- 6.如何实例化InetAddress:两个方法:getByName(String host) 、 getLocalHost()。 两个常用方法:getHostName() / getHostAddress()
- 7.端口号:正在计算机上运行的进程。
- 要求:不同的进程有不同的端口号
- 范围:被规定为一个 16 位的整数 0~65535。
- 8.端口号与IP地址的组合得出一个网络套接字:Socket
public class InetAddressTest {
public static void main(String[] args) {
try {
//File file = new File("hello.txt");
InetAddress inet1 = InetAddress.getByName("192.168.10.14");
System.out.println(inet1);
InetAddress inet2 = InetAddress.getByName("www.atguigu.com");
System.out.println(inet2);
InetAddress inet3 = InetAddress.getByName("127.0.0.1");
System.out.println(inet3);
//获取本地ip
InetAddress inet4 = InetAddress.getLocalHost();
System.out.println(inet4);
//getHostName()
System.out.println(inet2.getHostName());
//getHostAddress()
System.out.println(inet2.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
35.1 TCP
- 实现TCP的网络编程
- 例子1:客户端发送信息给服务端,服务端将数据显示在控制台上
public class TCPTest1 {
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("192.168.14.100");
socket = new Socket(inet,8899);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.写出数据的操作
os.write("你好,我是客户端mm".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
//2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//不建议这样写,可能会有乱码
// byte[] buffer = new byte[1024];
// int len;
// while((len = is.read(buffer)) != -1){
// String str = new String(buffer,0,len);
// System.out.print(str);
// }
//4.读取输入流中的数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){
baos.write(buffer,0,len);
}
System.out.println(baos.toString());
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(baos != null){
//5.关闭资源
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ss != null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 实现TCP的网络编程
- 例题2:客户端发送文件给服务端,服务端将文件保存在本地。
public class TCPTest2 {
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream();
//3.
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//5.
fis.close();
os.close();
socket.close();
}
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty1.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
//6.
fos.close();
is.close();
socket.close();
ss.close();
}
}
- 实现TCP的网络编程
- 例题3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。
- 并关闭相应的连接。
public class TCPTest3 {
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream();
//3.
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//5.接收来自于服务器端的数据,并显示到控制台上
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bufferr = new byte[20];
int len1;
while((len1 = is.read(buffer)) != -1){
baos.write(buffer,0,len1);
}
System.out.println(baos.toString());
//6.
fis.close();
os.close();
socket.close();
baos.close();
}
/*
这里涉及到的异常,应该使用try-catch-finally处理
*/
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("图片传输完成");
//6.服务器端给予客户端反馈
OutputStream os = socket.getOutputStream();
os.write("你好,美女,照片我已收到,非常漂亮!".getBytes());
//7.
fos.close();
is.close();
socket.close();
ss.close();
os.close();
}
}
35.2 UDP
- UDPd协议的网络编程
public class UDPTest {
//发送端
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close();
}
//接收端
@Test
public void receiver() throws IOException {
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
}
35.3 URL
- URL网络编程
- 1.URL:统一资源定位符,对应着互联网的某一资源地址
- 2.格式:
- http://localhost:8080/examples/beauty.jpg?username=Tom
- 协议 主机名 端口号 资源地址 参数列表
public class URLTest {
public static void main(String[] args) {
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom");
// public String getProtocol( ) 获取该URL的协议名
System.out.println(url.getProtocol());
// public String getHost( ) 获取该URL的主机名
System.out.println(url.getHost());
// public String getPort( ) 获取该URL的端口号
System.out.println(url.getPort());
// public String getPath( ) 获取该URL的文件路径
System.out.println(url.getPath());
// public String getFile( ) 获取该URL的文件名
System.out.println(url.getFile());
// public String getQuery( ) 获取该URL的查询名
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
public class URLTest1 {
public static void main(String[] args) {
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream("day10\\beauty3.jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
System.out.println("下载完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(urlConnection != null){
urlConnection.disconnect();
}
}
}
}