CMD文件操作
- md 创建文件
- rd 删除文件夹
- del 删除文件
- dir 列出当前目录下的文件及文件夹
- cd 进入指定目录
- cd … 退回到上一级目录
- cd\ 退回到根目录
- exit 退出dos命令行
Java的特点
面向对象
两个要素:类、对象
三个特征:封装、继承、多态
健壮性
- 去除了C语言中的指针
- 自动的垃圾回收机制 仍然会出现内存溢出,内存泄露
跨平台性
write once, run anywhere:一次编译,到处运行
功劳归功于JVM
注释
单行注释和多行注释的作用:
- 对所写的程序进行解释说明,增强可读性,方便自己,方便别人
- 调试所写代码
特点:单行注释和多行注释,注释了的内容不参与编译
- 多行注释不可嵌套使用
文档注释(JAVA特有)
格式:
/**
@author 指定java程序的作者
@version 指定源文件的版本
*/
-
注释内容可以被JDK提供的工具javadoc所解析,生成一套以网页文件形式体现的该程序的说明文档
-
javadoc解析的使用方法:
javadoc -d mydoc -author -version HelloWorld,java
java总结
-
编写-编译-运行
-
在一个java源文件中可以声明多个class。但是,最多只能有一个类声明为public的。
-
而且要求声明为public的类的类名必须和源文件名相同
-
程序的入口是main()方法。格式是固定的。
-
编译的过程:编译以后,会生成一个或多个字节码文件。字节码文件的文件名与java源文件中的类名相同
JDK JRE JVM 之间的关系
JDK = JRE + JAVA的开发工具(javac.exe, java.exe, javadoc.exe)
JRE = JVM + Java的核心类库
配置path环境变量是为了在任意位置都可以使用Java,javac等命令
应用程序 = 算法 + 数据结构
标识符的使用
标识符的命名规则:
- 由26个英文字母大小写, 0-9, _或$组成
- 数字不可以开头
- 不可以使用关键字和保留字,但能包含关键字和保留字
- java中严格区分大小写,长度无限制
- 标识符不能包含空格
JAVA中的名称命名规范:
包名:多单词组成时所有字母都小写:xxxyyyzzz
类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写xxxYyyZzz
常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
变量
-
整型: byte(1字节=8bit) short(2字节) int(4字节) long(8字节)
- byte范围:-128 ~ 127
- 声明long型变量,必须以l或L结尾
-
浮点数
- float表示数值的范围比long还大
- 定义float类型变量时,变量要以f或F结尾
-
自动类型提升
byte short int long float double
当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果自动提升为容量大的数据类型。
当byte、char、short三种类型的变量做运算时,结果为int型
-
强制类型转换
强制类型转化:自动类型提升运算的逆运算。
整型常量,默认类型为int型
浮点常量,默认类型为double型
-
String类型变量的使用
String属于引用数据类型
String可以和8种基本数据类型变量做运算,且只能是连接运算:+
二进制
反码 = 源码除符号位取反
补码 = 反码 + 1
计算机底层都以补码的方式来存储数据!
运算符
取余运算
结果的符号与被模数的符号相同
自增运算
short s1 = 10;
s++
自增1不会改变本身的变量数据类型
例如:
i*=0.1;
结果:i=0;
比较运算符
instanceof 检查是否是类的对象 “Hello” instanceof String true
逻辑运算符
1.逻辑运算符操作的都是boolean类型的变量
2.区别&与&&:
**当符号左边是false时,&继续执行符号右边的运算。&&不再执行符号右边的运算。 **
开发中推荐使用短路运算符
位运算符
-
位运算符操作的都是整型的数据
-
<< 在一定范围内,每向左移1位,相当于 *2
>>在一定范围内,每向右移1位,相当于 /2
-
>>> 无符号位移,无论正负最高位补0
-
取反运算是包括符 号位
流程控制
条件判断
switch表达式只能是如下6六种数据类型之一:byte、short、char、int、枚举类型(JDK5.0新增)、String(JDK7.0新增)
但是例如boolean类型是非法的
if语句嵌套不要超过三个
循环
continue后面不能声明执行语句,不然会编译错误
break和continue跳出指定的循环
实例
label:for(int i=1; i<=4; i++){
for(int j=1; j<=10; j++){
if(j%4 == 0){
break label;//结束指定标识的一层循环结构
}
}
}
无限循环:while(true) for(;😉
衡量一个功能代码的优劣:
1.正确性
2.可读性
3.健壮性
4.高效率与低存储:时间复杂度、空间复杂度(衡量算法的好坏)
数组
数组属于引用数据类型的变量
一维数组的使用
一维数组的声明和初始化
-
静态初始化:数组的初始化和数组元素的赋值操作同时进行
int[] ids;//声明
ids = new int[]{1001,1002,1003,1004};
-
动态初始化:数组的初始化和数组元素的赋值操作分开进行
String[] names = new String[5];
-
类型推断
int[] arr4 = {1, 2, 3, 4, 5};
如何调用数组的指定位置的元素
通过角标的方式调用
数组的角标(或索引)从0开始的,到数组的长度-1结束
name[0] = “XXX”;
name[1] = “YYY”
如何获取数组的长度
属性:length
System.out.println(names.length);
如何遍历数组
for(int i=0; i<names.length; i++){
System.out.println(names[i]);
}
数组元素的默认初始化值
数组元素是整型:0
数组元素是浮点型:0.0
数组元素是char型:\0或\u0000
数组元素是boolean型:false
数组元素是引用数组类型:null
数组的内存解析
栈(stack):局部变量
堆(heap):new 出来的结构:对象和数组
方法区 常量池
静态域
二维数组
声明与初始化
int[][] arr1 = new int[][]{{1,2,3}, {4,5}, {6,7,8}};
String[][] arr2 = new String[3][2];
String[][] arr3 = new String[3][];
arr3[1] = new String[4];
int[] arr4[] = new int[][]{{1,2,3}, {4,5}, {6,7,8}};
int[] arr4[] = {{1,2,3}, {4,5}, {6,7,8}};
数组元素的默认初始化
规定:二维数组分为外层数组的元素,内层数组的元素
int[][] arr = new int[4][3];
外层元素: arr[0], arr[1]等
内层元素:arr[0][0], arr[1][2]等
-
针对初始化方式一:比如: int[][] arr = new int[4][3];
外层元素的初始化值为:地址值
内层元素的初始化值:与一维数组初始化情况相同
-
针对初始化方式二:比如: int[][] arr = new int[4][];
外层元素的初始化值为:null
内层元素的初始化值:不能调用,否则报错
eclipse中的快捷键
- 不全代码的声明:alt + /
- 快速修复:ctrl + 1
- 使用单行注释: ctrl + /
- 使用多行注释: ctrl + shift + /
- 取消多行注释: ctrl + shift + \
- 复制指定行的代码: ctrl + alt +down 或 ctrl + alt + up
- 删除指定行的代码: ctrl + d
- 上下移动代码: alt + up 或 alt + down
- 切换到下一行代码的空位: shift + enter
- 切换到上一行代码的空位: ctrl + shift + enter
- 如何查看源码: ctrl + 选中指定的结构 或 ctrl + shift + t
- 退回到前一个编辑的页面: alt + left
数据结构
-
数据与数据之间的逻辑关系:集合、一对一、一对多、多对多
-
数据的存储结构:
线性表:顺序表(比如:数组)、链表、栈、队列
树形结构:二叉树
图形结构:
算法
排序算法:
搜索算法:
快排时间复杂度:O(nlogn)
冒牌时间复杂度:O(n^2)
堆排序、归并排序
十大排序算法
选择排序
直接选择排序、堆排序
交换排序
冒泡排序、快速排序
插入排序
直接插入排序、折半插入排序、Shell排序
归并排序
桶式排序
基数排序
JAVA 面向对象
- Java类及类的成员:属性、方法、构造器:代码块、内部类
- 面向对象的三大特征:封装性、继承性、多态性、(抽象性)
- 其他关键字:this、super、static、final、abstract、interface、package、import
面向对象的两个要素
类:对一类事物的描述,是抽象的,概念上的定义
对象:是实际存在的该类事物的每个个体,因而也称为实例(instance)
面向对象程序设计的重点是类的设计
设计类,就是设计类的成员
设计类,其实就是设计类的成员
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
创建类的对象 = 类的实例化 = 实例化类
类和对象的使用(面向对象思想落地的实现)
-
创建类,设计类的成员
-
创建类的对象
-
通过“对象.属性”或“对象.方法”调用对象的结构
如果创建一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值
类中属性的使用
属性(成员变量) VS 局部变量
-
相同点
- 定义变量的格式:数据类型 变量名 = 变量值
- 先声明,后使用
- 变量都有其对应的作用域
-
不同点
-
在类中声明的位置不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
-
关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符。
常用权限修饰符:private、public、缺省、protected ---->封装性
局部变量:不可以使用权限修饰符。
-
默认初始化值的情况:
属性:类的属性,根据其类型,都有默认初始化值。
局部变量:没有默认初始化值。
意味着,我们在调用局部变量之前,一定要显式赋值。
特别地,形参在调用时,我们赋值即可。
-
在内存中加载的位置
属性:加载到堆空间中(非static)
局部变量:加载到栈空间
-
类中方法的声明和使用
方法:描述类应该具有的功能。
-
方法的声明:权限修饰符 返回值类型 方法名(形参列表){
方法体
}
注意:static、final、abstract 来修饰的方法,后面再讲
-
说明:
-
关于权限修饰符
Java规定的4种权限修饰符:private、public、缺省、protected
-
返回值类型:有返回值 VS 没有返回值
-
如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量。
如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return。但是,如果使用的话,只能“return;”表示结束此方法的意思。
-
我们定义方法该不该有返回值?
- 题目要求
- 凭经验,具体问题具体分析
-
-
方法名:属于标识符,遵循标识符的规则和规范,“见名知意”
-
形参列表:方法可以声明0个、1个,或多个形参。
-
格式:数据类型1 形参1, 数据类型2 形参2,…
-
我们定义方法时,该不该定义形参
-
题目要求
凭经验,具体问题具体分析
-
-
方法体:方法功能的体现。
-
-
return关键字的使用:
-
使用范围:使用在方法体中
-
作用:结束方法
针对于有返回值类型的方法,使用“return 数据”方法返回所要的数据
-
注意点:return关键字后面不可以声明执行语句。
-
-
方法的使用中,可以调用当前类的属性或方法
特殊的:方法A中又调用了方法A:递归方法。
方法中,不可以定义方法。
程序的运行
编译完程序以后,生成一个或多个字节码文件
我们使用JVM中的类的加载器和解释器对生成的字节码文件进行解释运行。意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析。
JVM规范
虚拟机栈,即为平时提到的栈结构。我们将局部变量存储在栈结构中
堆,我们将new出来的结构(比如:数组、对象)加载在堆空间中。补充:对象的属性(非static)加载在堆空间中。
方法区:类的加载信息、常量池、静态域
方法的重载
-
定义:在一个类中,允许存在一个以上的同名方法,只要它们的参数个数类型不同即可。
“两同一不同”:同一个类,相同方法名
参数列表不同,参数个数不同,参数类型不同
-
判断是否是重载
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
-
在通过对象调用方法时,如何确定某一个指定的方法:
方法名 —>参数列表
可变个数形参的方法
- jdk 5.0 新增内容
- 具体使用
- 可变个数形参的格式:数据类型 … 变量名
- 当调用可变个数形参的方法时,传入的参数个数可以是:0个、1个、2个。。。。。。
- 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
- 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
- 可变个数形参在方法的形参中,必须声明在末尾
- 可变个数形参在方法的形参中,最多只能声明一个可变形参。
关于变量的赋值
如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。
方法的形参的传递机制:值传递
-
形参:方法定义时,声明的小括号内的参数
实参:方法调用时,实际传递给形参的值
-
值传递机制:
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
面向对象的特征之一:封装与隐藏
问题的引入:
当我们创建一个类的对象以后,我们可以通过“对象.属性”的方式,对对象的属性进行赋值。这里,赋值操作受到数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值,加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setlegs())。同时我们需要避免用户再使用“对象.属性”的方式对属性进行赋值。则需要将属性声明为私有的(private)。
此时,针对属性就体现了封装性
封装性的体现
我们将类的属性私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
拓展:封装性的体现:
- 如上
- 不对外暴露的私有的方法
- 单例模式
封装性的体现,需要权限修饰符来配合。
-
Java规定的4种权限(从小到大排列):private、缺省、protected、public
-
4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
-
具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
类的结构之三:构造器(或构造方法、constructor)的使用
- 构造器的作用:
- 创建对象
- 初始化对象的信息
- 说明:
- 如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
- 定义构造器的形式:权限修饰符 类名(形参列表){}
- 一个类中定义的多个构造器,彼此构成重载
- 一旦我们显式的定义了类的构造器之后,系统就不在提供默认的空参构造器
- 一个类中,至少会有一个构造器
JavaBean是一种Java语言写成的可重用组件
所谓javaBean,是指符合如下标准的Java类:
类是公共的
有一个无参的公共的构造器
有属性,且有对应的get、set方法
this关键字的使用
-
可以用来修饰、调用:属性、方法、构造器
-
this修饰属性和方法:
this理解为:当前对象 或 当前正在创建的对象
- 在类的方法中,我们可以使用“this.属性”或“this.方法”的方式,调用当前对象属性或方法。但是通常情况下,我们都选择省略“this.”。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用“this.变量”的方式,表明此变量是属性,而非形参。
-
this调用构造器
- 我们在类的构造器中,可以显式的使用“this(形参列表)”方式,调用本类的其他构造器
- 构造器中不能通过“this(形参列表)”方式调用自己
- 如果一个类中有n个构造器,则最多有n-1构造器中使用了“this(形参列表)”
- 规定:“this(形参列表)”必须声明在当前构造器的首行
- 构造器内部,最多只能声明一个“this(形参列表)”,用来调用其他的构造器
package关键字的使用
-
为了更好的实现项目中类的管理,提供包的概念
-
使用package声明类或接口所属的包,声明在源文件的首行
-
包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
-
每“.”一次,就代表一层文件目录。
补充:同一个包下,不能命名同名的接口、类
不同的包下,可以命名同名的接口、类
import关键字的使用
- 在源文件中显式的使用import结构导入指定包下的类、接口
- 声明在包的声明和类的声明之间
- 如果需要导入多个结构,则并列写出即可
- 可以使用“xxx.*”的方式,表示可以导入xxx包下的所有结构
- 如果使用的类或接口是java.lang包下定义的,则可以省略import结构
- 如果使用的类或接口是本包下定义的,则可以省略import结构
- 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
- 使用“xxx.*”方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
- import static:导入指定类或接口中的静态结构。
面向对象的特征之二:继承性
-
继承性的好处:
- 减少了代码的冗余,提高了代码的复用性
- 便于功能的扩展
- 为之后多态性的使用,提供了前提
-
继承性的格式:class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
-
体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。
只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
-
子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
子类和父类的关系,不同于子集和集合的关系。
-
-
Java中关于继承性的规定:
- 一个类可以被多个子类继承。
- Java中类的单继承性:一个类只能有一个父类
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类以后,就获取了直接父类已经所有间接父类中声明的属性和方法
-
- 如果我们没有显式的声明一个类的父类的话,则此类继承与java.lang.Object类
- 所有java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 意味着,所有的java类具有java.lang.Object类声明的功能。
如何调试程序
-
System.out.println().
-
Eclipse - Debug调试
方法的重写(override / overwrite)
-
重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
-
应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名参数的方法时,实际执行的是子类重写父类的方法。
-
重写的规定:
方法的声明:权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
//方法体
}
约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法
-
子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
-
子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
特殊情况:子类不能重写父类中声明为private权限的方法
-
返回值类型:
-
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
-
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
-
父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
-
-
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
-
子类和父类中的同名同参数的方法要买都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
super关键字的使用
-
super理解为:父类的
-
super可以用来调用:属性、方法、构造器
-
super的使用:
- 我们可以在子类的方法或构造器中。通过使用“super.属性”或“super.方法”的方式,显示调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super.”
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显示的使用“super.属性”的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用“super.方法”的方式,表明调用的是父类中被重写的方法。
-
super调用构造器
- 我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器
- “super(形参列表)”的使用,必须声明在子类构造器的首行!
- 我们在类的构造器中,针对于“this(形参列表)”或“super(形参列表)”只能二选一,不能同时出现
- 在构造器的首行,没有显式的声明“this(形参列表)”或“super(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表)“,调用父类中的构造器
子类对象实例化的全过程
-
从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
-
从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用起父类的构造器,进而调用父类的构造器。。。直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
面向对象特征之三:多态性
-
理解多态性:可以理解为一个事物的多种形态。
-
何为多态性:
对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
-
多态的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
总结:编译,看左边;运行,看右边
-
多态性的使用前提:
- 类的继承关系
- 方法的重写
-
对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
PS:
不能调用子类所特有的方法、属性:编译时,p2是Person类型。
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
如何才能调用子类特有的属性和方法?向下转型,使用强制类型转换符
instanceof关键字的使用
a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
如果a instanceof A返回true,则a instanceof B也返回true.
其中,类B是类A的父类
java.lang.Object类
-
Object类是所有Java类的根父类
-
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
-
Object类中的功能(属性和方法)就具有通用性。
-
Object类只声明了一个空参的构造器
属性:无
方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
面试题: == 和 equals()区别
-
回顾 == 的使用:
==:运算符
-
可以使用在基本数据类型变量和引用数据类型变量中
-
如果比较的是基本数据变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用数据变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
-
-
equals()方法的使用:
-
是一个方法,而非运算符
-
只能适用于引用数据类型
-
Object类中equals()的定义:
public boolean equals(Object obj){
return (this == obj);
}
说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
-
像String、Date、File、包装类都重写了Object类中的equals()方法,重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的“实体内容“是否相同。
-
通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的“实体内容”是否相同。那么我们就需要对Object类中的equals()进行重写
重写的原则:比较两个对象的实体内容是否相同
-
Object类中toString()的使用:
-
当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
-
Object类中toString()的定义:
public String toString(){ return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
像String、Date、File、包装类都重写了Object类中的toString()方法。
使得在调用对象的toString()时,返回“实体内容”信息
-
自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”
Java中的JUnit单元测试
步骤:
-
选中当前工程 - 右键选择:build path - add libraries - JUnit4 - 下一步
-
创建Java类,进行单元测试
此时的Java类要求:
- 此类是public的
- 此类提供公共的无参的构造器
-
此类中声明单元测试方法。
此时的单元测试方法:方法的权限是public,没有返回值,没有形参
-
此单元测试方法上需要声明注解@Test,并在单元测试类中导入:import org.junit.Test;
-
声明好单元测试方法以后,就可以在方法体内测试相关的代码。
-
写完代码以后,左键双击单元测试方法名:右键:run as - JUnit Test
说明:
- 如果执行结果没有任何异常:绿条
- 如果执行结果出现异常:红条
包装类的使用
-
java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
-
掌握的:基本数据类型、包装类、String三者之间的相互转换:
自动装箱、自动拆箱
Integer i = 10;
基本数据类型、包装类—>String: valueOf(Xxx xx)
String---->基本数据类型、包装类:parseXxx(String s)
**注意:**转换时,可能会报NuberFormatException
-
JDK 5.0 新特性:自动装箱与自动拆箱
面试题:
Integer m = 1; Integer n = 1; System.out.println(m == n);//true
Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了从-128127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在-128127范围内时,可以直接使用数组中的元素,不用再去new了。目的,提高效率
Static关键字的使用
-
static:静态的
-
static可以用来修饰:属性、方法、代码块、内部类
-
使用static修饰属性:静态变量(或类变量)
-
属性,按是否使用static修饰,有分别:静态属性 vs 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
-
static修饰属性的其他说明:
-
静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用
-
静态变量的加载要早于对象的创建。
-
由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
-
类变量 实例变量
类 yes no
对象 yes yes
-
静态属性举例:System.out; Math.Pi;
-
-
-
使用static修饰方法:静态方法
-
随着类的加载而加载,可以通过“类.静态方法”的方式进行调用
-
静态方法 非静态方法
类 yes no
对象 yes yes
-
静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
-
static注意点:
- 在静态的方法内,不能使用this关键字、super关键字
- 关于静态属性和静态方法的使用,都从生命周期的角度去理解。
-
开发中,如何确定一个属性是否要声明为static的?
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
类中的常量也常常声明为static
开发中,如何确定一个方法是否要声明为static的?
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的。比如;Math、Arrays、Collections
-
单例设计模式
-
所谓的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
-
如何实现?
饿汉式 VS 懒汉式
饿汉式:
class Bank{ private Bank(){ } private static Bank instance = new Bank(); public static Bank getInstance(){ return instance; } }
懒汉式:
class Order{ private Order(){ } private static Order instance = null; public static Order getInstance(){ if(instance == null){ instance = new Order(); } return instance; } }
-
区分饿汉式和懒汉式
饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的
懒汉式:
好处:延迟对象的创建。
目前的写法坏处:线程不安全。—>到多线程内容时。再修改
main()方法的使用说明
- main()方法作为程序的入口
- main()方法也是一个普通的静态方法
- main()方法可以作为我们和控制台交互的方式。(之前:使用Scanner)
类的成员之四:代码块(或初始化块)
-
代码块的作用:用来初始化类、对象
-
代码块如果有修饰的话,只能使用static.
-
分类:静态代码块 vs 非静态代码块
-
静态代码块
-
内部可以有输出语句
-
随着类的加载而执行,而且只执行一次
-
作用:初始化类的信息
-
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
-
静态代码块的执行要优先于非静态代码块的执行
-
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
-
-
非静态代码块
-
内部可以有输出语句
-
随着对象的创建而执行
-
每创建一个对象,就执行一次非静态代码块
-
作用:可以在创建对象时,对对象的属性等进行初始化
-
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
-
非静态代码块内可以调用静态的属性、静态的方法、或非静态的属性、非静态的方法
-
对属性可以赋值的位置:
- 默认初始化
- 显式初始化
- 构造器中初始化
- 有了对象以后,可以通过“对象.属性”或“对象.方法”的方式,进行赋值
- 在代码块中赋值
final:最终的
-
final可以用来修饰的结构:类、方法、变量
-
final用来修饰一个类:此类不能被其他类所继承
比如:String类、System类、StringBuffer类
-
final 用来修饰方法:表明此方法不可以被重写
比如:Object类中getClass();
-
final 用来修饰变量:此时的“变量”就称为是一个常量
-
final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
-
final修饰局部变量:
尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。 一旦赋值以后,就只能在方法体内使用此形参,不能进行重新赋值。
-
static final 用来修饰属性:全局常量
abstract关键字的使用
-
abstract:抽象的
-
abstract可以用来修饰的结构:类、方法
-
abstract修饰类:抽象类
-
此类不能实例化
-
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
-
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
-
-
abstract修饰方法:抽象方法
-
抽象方法只有方法的声明,没有方法体
-
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
-
若子类重写了父类中的所有的抽象方法中,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
-
abstract使用上的注意点:
- abstract不能用来修饰:属性、构造器等结构
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类
抽象类的匿名子类
Person p = new Person(){
//类的方法
}
接口的使用
-
接口使用interface来定义
-
Java中,接口和类是并列的两个结构
-
如何定义接口,定义接口中的成员
- JDK7及以前:只能定义全局常量和抽象方法
- 全局常量:public static final的,但是书写时,可以省略不写
- 抽象方法:public abstract的
- JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
- JDK7及以前:只能定义全局常量和抽象方法
-
接口中定义的静态方法,只能通过接口来调用
-
通过实现类的对象,可以调用接口中的默认方法
-
如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
-
如果子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。 -->类优先原则
-
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。–>接口冲突。这就需要我们必须在实现类中重写此方法
-
如何在子类(或者实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){ method3();//调用自己定义的重写的方法 super.method3();//调用的是父类中声明的 //调用接口中的默认方法 CompareA.super.method3(); CompareB.super.method3(); }
-
-
接口中不能定义构造器的!意味着接口不可以实例化
-
Java开发中,接口通过让类去实现(implements)的方式来使用
如果实现类覆盖了接口中所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
-
Java类可以实现多个接口 —>弥补了Java单继承性的局限性
格式:class AA extends BB implements CC,DD,EE
-
接口和接口之间可以继承,而且可以多继承
-
接口的具体使用,体现多态性
-
接口,实际上可以看做是一种规范
接口的使用
- 接口使用上也满足多态性
- 接口,实际上就是定义了一种规范
- 开发中,体会面向接口编程!
接口的应用:代理模式
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse(){
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse(){
check();
work.browse();
}
}
类的内部成员之五:内部类
-
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
-
内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
-
成员内部类:
一方面:作为外部类的成员:
调用外部类的结构
可以被static修饰
可以被4种不同的权限修饰
另一方面:作为一个类:
类内可以定义属性、方法、构造器等
可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
可以被abstract修饰
-
关注如下的3个问题
- 如何实例化成员内部类的对象
- 如何在成员内部类中区分调用外部类的结构
- 开发中局部内部类的使用
5.如何创建静态成员内部类和非静态成员内部类的对象?
Person static Dog BirdPerson.Dog dog = new Person.Dog();Person p = new Person();Person.Bird bird = p.new Bird();
PS:
在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,要求此局部变量声明为final的。
jdk7 及之前版本:要求此局部变量显式的声明为final的
jdk8 及之后的版本:可以省略final的声明
public void method(){ //局部变量 int num = 10; class AA{ public void show(){ System.out.println(num); } }}
异常体系结构
java.lang.Theowable
- java.lang.Error:一般不编写针对性的代码进行处理。
- java.lang.Exception:可以进行异常的处理
- 编译时异常(checked)
- IOException
- FileNotFonudException
- ClassNotFoundException
- IOException
- 运行时异常(unchecked,RuntimeException)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException
- InputMismatchException
- ArithmaticException
- 编译时异常(checked)
面试题:
常见的异常有哪些?举例说明
Error:
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不便携针对性的代码进行处理
一、异常的处理:抓抛模型
过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会异常代码处生成一个对应异常类的对象。并将此对象抛出。
一旦抛出对象以后,其后的代码就不再执行。
关于异常对象的产生:
-
系统自动生成的异常对象
-
手动的生成一个异常对象,并抛出(throw)
过程二:“抓”:可以理解为异常的处理方式:
- try-catch-finally
- throws
二、try-catch-finally的使用
try{ //可能出现异常的代码}catch(异常类型1 变量名1){ //处理异常的方式1}catch(异常类型2 变量名2){ //处理异常的方式2}catch(异常类型3 变量名3){ //处理异常的方式3}...finally{ //一定会执行的代码}
说明:
-
finally是可选的。
-
使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
-
一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况),继续执行其后的代码
-
catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
-
常用的异常对象处理的方式:
- String getMessage()
- printStackTrace()
-
在try结构中声明的变量,在出了try结构以后,就不能在被调用
-
try-catch-finally结构可以嵌套
**体会1:**使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
**体会2:**开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了 。
针对于编译时异常,我们说一定要考虑异常的处理。
try-catch-finally中finally的使用:
-
finally是可选的
-
finally中声明的是一定会被执行的代码。即使catch中有出现异常了,try中有return语句,catch中有retrun语句等情况。
-
像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。
异常处理的方式二:throws + 异常类型
-
“throws + 异常类型”写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象。此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
-
体会:try-catch-finally:真正的将异常处理掉了。
throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。
-
开发中如何选择使用try-catch-finally 还是使用throws?
- 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
- 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
方法重写的规则之一:
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
如何自定义异常类
- 继承于现有的异常结构:RuntimeException、Exception
- 提供全局常量:提供serialVersionUID
- 提供重载的构造器
多线程
程序、进程、线程的理解
-
程序
概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
-
进程
概念:程序的一次执行过程,或是正在运行的一个程序
说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
-
线程
概念:进程可进一步细化为线程,是一个程序内部的一条执行路径
说明:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
并行与并发的理解
-
单核CPU与多核CPU的理解
-
单核CPU,其实是一种假的多线程,因为在一个时间单元中,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU久好比收费人员。如果某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费。)但是因为CPU时间单元特备短,因此感觉不出来。
-
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
-
一个Java应用程序java.exe,其实至少3个线程:mian()主线程、gc()垃圾回收线程、异常处理线程。当然如果发生异常,会影响主线程。
-
-
并行与并发的理解
- 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
多线程的创建,方式一:继承于Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start()
**问题一:**我们不能通过直接调用run()的方式启动线程。
**问题二:**不可以还让已经start()的线程再去执行。会报IllegalThreadStateException。我们需重写创建一个线程的对象。
测试Thread中的常用方法:
- start():启动当前线程;调用当前线程的run()
- run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread():静态方法,返回当前代码执行的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
- stop():已过时。当执行此方法时,强制结束当前线程。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
- isAlive():判断当前线程是否存活
线程的优先级:
-
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 -->默认优先级
-
如何获取和设置当前线程的优先级:
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是至少从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
**线程通讯:**wait()/notify()/notifyAll():此三个方法定义在Object类中的。
创建多线程的方式二:实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
比较创建线程的两种方式:
开发中:优先选择:实现Runnable接口的方式
原因:
- 实现的方式没有类的单继承性的局限性
- 实现的方式更适合来处理多个线程有共享数据的情况。
**联系:**public class Thread implements Runnable
**相同点:**两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
补充:线程的分类
一种是守护线程,一种是用户线程。
线程的生命周期
新建
就绪
阻塞
运行
死亡
说明:
- 生命周期关注两个概念:状态、相应的方法
- 关注:
解决线程的安全问题
**方式一:**同步代码块
synchronized(同步监视器){ //需要被同步的代码}
说明:
-
操作共享数据的代码,即为需要被同步的代码。 --> 不能包含代码多了,也不能包含代码少了
-
共享数据:多个线程共同操作的变量
-
同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
**方法二:**同步方法。
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
private synchronized void show(){} //同步监视器:this
关于同步方法的总结:
-
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
-
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
好处与坏处
**好处:**同步的方式,解决了线程的安全问题。
**坏处:**操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
使用同步方法处理继承Thread类的方式中的线程安全问题
private static synchronized void show(){} //同步监视器:windows.class
使用同步机制将单例模式中的懒汉式改写为线程安全的
class Bank{ private Bank(){} private static Bank instance = null; public static Bank getInstance(){ //方式一:效率稍差 synchronized(Bank.class){ if(instance == null){ instance = new Bank(); } return instance; } }}
class Bank{ private Bank(){} private static Bank instance = null; public static Bank getInstance(){ //方式二:效率更高 if(instance == null){ synchronized(Bank.class){ if(instance == null){ instance = new Bank(); } } } return instance; }}
线程的死锁问题
-
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
-
说明:
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 我们使用同步时,要避免出现死锁。
解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
解决线程安全问题的方式三:Lock锁 — JDK5.0新增
-
实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
-
调用锁定方法
try{ lock.lock(); //同步代码}finally{ //3.调用解锁方法:unlock() lock.unlock();}
**面试题:**synchronized与Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
-
优先使用顺序:
Lock -> 同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)
**面试题:**如何解决线程安全问题?有几种方式
线程的通信
-
三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
-
wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中。
-
wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
否则,会出现IllegalMonitorStateException异常
-
wait()、notify()、notifyAll()三个方法是定义在java.lang.Object类中。
**面试题:**sleep()和wait()的异同?
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
- 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明的wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
会释放锁的操作
- 当前线程的同步方法、同步代码块执行结束。
- 当前线程的同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
- 当前线程的同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不会释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
JDK5.0新增线程创建方式
实现Callable接口
-
创建一个实现Callable的实现类
class NumThread implements Callable{ //2.实现call方法,将此线程需要执行的操作声明在callz()中 @Override public Object call() throws Exception{ //方法体 }}
-
创建Callable接口实现类的对象
NumThread numThread = new NumThread();
-
将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
-
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
-
获取Callable中call方法的返回值
get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
- call()可以有返回值的。
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
使用线程池
**背景:**经常创建和销毁、使用量特别打的资源,比如并发情况下的线程,对性能影响很大。
**思路:**提前创建好多个线程,放入线程池中,使用时直接获取,使用完返回池中。可以避免频繁创建销毁、实现重复利用。类似生活中公共交通工具
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理:
- corePoolSize:核心池的大小
- maximumPoolSize:最大的线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
-
提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(nThread:10);
-
执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnableservice.submit(Callable callable);//适合使用于Callable
-
关闭连接池
service.shutdown();
**面试题:**创建线程有几种方式?四种!
Java常用类
字符串 相关的类:String
-
String类:代表字符串。Java程序中的所有字符串字面值(如“abc”)都作为此类的实例实现。
-
String是一个final类,代表不可变的字符序列。
-
字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
-
String对象的字符内容是存储在一个字符数组value[]中的。
String的使用
-
String声明为final的,不可被继承
-
String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
-
String内部定义了final char[] value用于存储字符串数据
-
String:代表不可变的字符序列。简称:不可变性。
体现:
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String的replace()方法修改字符或字符串时,不能使用原有的value进行赋值。
-
通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
-
字符串常量池中是不会存储相同内容的字符串的。
String的实例化方式:
方式一:通过字面量定义的方式
此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中
方式二:通过new + 构造器的方式
此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
面试题:
String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
答:两个,一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”
PS:
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
- 只要其中有一个是变量结果就在堆中
- 如果拼接的结果调用intern()方法,返回值就在常量池中
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
涉及到String类与其他结构之间的转换
String 与基本数据类型、包装类之间的转换。
String -->基本数据类型、包装类:调用包装类的静态方法:paeseXxx(str)
基本数据类型、包装类 -->String:调用String重载的valueOf(xxx)
String与char[]之间的转换
String --> char[]:调用String的toCharArray()
char[] – > String:调用String的构造器
String与byte[]之间的转换
String --> byte[]:调用 String的getBytes()
byte[] --> String:调用String的构造器
编码:字符串 -->字节(看得懂 —>看不懂的二进制数据)
解码:编码的逆过程,字节–>字符串(看不懂的二进制数据 —> 看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
面试题:
String s1 = "javaEEhadoop";String s2 = "javaEE";String s3 = s2 + "hadoop";System.out.println(s1 == s3);//falsefinal String s4 = "javaEE";//s4:常量String s5 = s4 + "hadoop";System.out.println(s1 == s5);//true
JVM中字符串常量池存放位置说明:
jdk 1.6(jdk 6.0, java 6.0):字符串常量池存储在方法区(永久区)
jdk 1.7:字符串常量池存储在堆空间
jdk 1.8:字符串常量池存储在方法区(元空间)
关于StringBuffer和StringBuilder的使用
-
String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列:底层使用char[]存储
StringBuffer:可变的字符序列:线程安全的,效率低;底层使用char[]存储
StringBuilder:可变的字符序列:JDK5.0新增的,线程不安全的,效率高;底层使用char[]存储
源码分析:
String str = new String();//new char[0];String str1 = new String("abc");//new char[]{'a','b','c'};StringBuffer sb1 = new StringBuffer();//new char[16];底层创建了一个长度是16的数组。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)
StringBuffer的常用方法:
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)
public int length()
public char charAt(int n)
public void setCharAt(int n, char ch)
对比String、StringBuffer、StringBuilder三者的效率:
从高到低排列:StringBuilder > StringBuffer > String
String与StringBuffer、StringBuilder之间的转换
String --> StringBuffer、StringBuilder:调用StringBuffer、StringBuilder构造器
StringBuffer、StringBuilder -->String:1. 调用String构造器;2. StringBuffer、StringBuilder的toString()
JDK8 之前日期和时间的API测试
-
System类中的currentTimeMillis()
long time = System.currentTimeMillis();
返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。——称为时间戳
-
java.util.Date类
|—java.sql.Date类
-
两个构造器的使用
- 构造器一:Date():创建一个对应当前时间的Date对象
Date date1 = new Date();
-
构造器二:创建指定毫秒数的Date对象
Date date2 = new Date(156156518516161L)
-
两个方法的使用
- toString():显示当前的年、月、日、时、分、秒
- getTime():获取当前Date对象对应的毫秒数。(时间戳)
-
java.sql.Date对应这数据库中的日期类型的变量
-
如何实例化
java.sql.Date date3 = new java.sql.Date(326263562566632L);
-
如何将java.util.Date对象转换为java.sql.Date对象
情况一:
Date date4 = new java.sql.Date(25651656351L);
java.sql.Date date5 = (java.sql.Date) date4;
情况二:
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
-
-
-
SimpleDateFormat的使用:SimpleDateFormat对日期Date类的格式化和解析
-
两个操作:
-
格式化 :日期—>字符串
Date date = new Date();String format = sdf.format(date);
-
解析:格式化的逆过程,字符串—>日期
要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),否则,抛异常
String str = "21-3-23 上午11:43";Date date1 = sdf.parse(str);
-
-
SimpleDateFormat的实例化
SimpleDateFormat sdf = new SimpleDateFormat();
-
安装指定的方式格式化和解析:调用带参的构造器
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");String format1 = sdf1.format(date);
-
-
Calendar日历类(抽象类)的使用
-
实例化
-
方式一:创建其子类(GregorianCalendar)的对象
-
方式二:调用起静态方法getInstance()
Calendar calendar = Calendar.getInstance();
-
-
常用方法
-
get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
-
set()
calendar.set(Calendar.DAY_OF_MONTH,22);
-
add()
calendar.add(Calendar.DAY_OF_MONTH,3);
-
getTime():日历类—>Date
Date date = calendar.getTime();
-
setTime():Date —>日历类
Date date1 = new Date();calendar.setTime(date1);
-
-
JDK8中新日期时间API
- java.time - 包含值对象的基础包
- java.time.chrono - 提供对不同的日历系统的访问
- java.time.format - 格式化和解析时间和日期
- java.time.temporal - 包括底层框架和扩展特性
- java.time.zone - 包含时区支持的类
LocalDate、LocalTime、LocalDateTime的使用
说明:LocalDateTime相较于LocalDate、LocalTime,使用频率更高
类似于Calendar
-
now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();LocalTime localTime = LocalTime.now();LocalDateTime localDateTime = LocalDateTime.now();
-
of():设置指定的年、月、日、时、分、秒。没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
-
getXxx():获取相关的属性
localDateTime.getDayOfMonth();localDateTime.getDayOfWeek();localDateTime.getMonth();localDateTime.getMonthValue();localDateTime.getMinute();
-
体现不可变性 withXxx();设置相关的属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
-
不可变性
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);LocalDateTime localDateTime4 = localDateTime.minusDays(6);
Instant的使用
类似于java.util.Date类
//now():获取本初子午线对应的标准时间Instant instant = Instant.now();//添加时间的偏移量OffsetDateTime offsetDateTime =instant.atOffset(ZoneOffset.ofHours(8));//获取自1970年1月1日0时0分0秒(UTC)对应的毫秒数 ---> Date类的getTime()long milli = instant.toEpochMilli();//ofEpochMilli():通过给定的毫秒数,获取Instant实例 -->Date(Long millis)Instant instant1 = Instant.ofEpochMilli(1550475314878L);
DateTimeFormatter:格式化或解析日期、时间
类似SimpleDateFormat
-
方式一:预定义的标准格式。如: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);//解析:字符串-->日期TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
-
方式二:本地化相关的格式。如:ofLocalizedDateTime()
FormatStyle.LONG、FormatStyle.MEDIUM、FormatStyle.SHORT:适用于LocalDateTime
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);//格式化 String str2 = formatter1.format(localDateTime);
本地化相关的格式。如:ofLocalizedDate()
FormatStyle.FULL、FormatStyle.LONG、FormatStyle.MEDIUM、FormatStyle.SHORT:适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);String str = formatter2.format(LocalDate.now());
-
重点:方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss E”)
DateTimeFormat formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");//格式化formatter3.format(LocalDateTime.now());//解析formatter3.parse("2019-02-18 03:52:09");
Java比较器
-
说明;Java中的对象,正常情况下,只能进行比较:== 或 !=,不能使用 > 或 <的,但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
如何实现 ?使用两个接口中的任何一个:Comparable或Comparator
-
Comparable接口的使用 :自然排序
-
像String、包装类等实现了Comparable接口,重写了compareTo()方法,给出了比较两个对象大小的方式。
-
像String、包装类重写compareTo()方法以后,进行了从小到大的排列
-
重写comparaTo(obj)的规则:
如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数,
如果当前对象this等于形参对象obj,则返回零。
-
对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
public class Goods implements Comparable{ private double price; //指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从低到高排序 @Override public int compareTo(Object o){ if(o instanceof Goods){ Goods goods = (Goods)o; //方式一: if(this.price > goods.price){ return 1; }else if(this.price < goods.price){ return-1; }else{ return this.name.compareTo(goods.name); } //方式二: //return Double.compare(this.price, goods.price); } throw new RuntimeException("传入的数据类型不一致!"); }}
-
-
Comparator接口的使用:定制排序
-
背景:
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序
-
重写compare(Object o1, Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1小于o2。
public void test3(){ String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"}; Arrays.sort(arr,new Comparator(){ //按照字符串从大到小的顺序排列 @Override public int compare(Object o1,Object o2){ if(o1 instanceof String && o2 instanceof String){ String s1 = (String)o1; String s2 = (String)o2; return -s1.compareTo(s2); } throw new RuntimeException("输入的数据类型不一致"); } }); System.out.println(Arrays.toString(arr));}
-
-
Comparable接口与Comparator的使用的对比:
Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小
Comparator接口属于临时性的比较。
其他常用类的使用
-
System
-
Math
-
BigInteger 和 BigDecimal
BigInteger可以表示不可变的任意精度的整数
BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
枚举类&注解
枚举类的使用:入门
-
枚举类的使用
-
枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
-
当需要定义一组常量时,强烈建议使用枚举类
-
如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
-
-
如何定义枚举类
方式一:jdk5.0之前,自定义枚举类
方式二:jdk5.0,可以使用enum关键字定义枚举类
//自定义枚举类class Season{ //1.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //2.私有化类的构造器,并给对象属性赋值 private Season(String seasonName, String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //3.提供当前枚举类的多个对象:public static final的 public static Season SPRING = new Season("春天", "春暖花开"); public static Season SUMMER = new Season("夏天", "夏日炎炎"); public static Season AUTUMN = new Season("秋天", "秋高气爽"); public static Season WINTER = new Season("冬天", "冰天雪地"); //4.其他诉求1:获取枚举类对象的属性 public String getSeasonName(){ return seasonName; } public String getSeasonDesc(){ return seasonDesc; } //4.其他诉求2:提供toString() @Override public String toString(){ return "Season{"+ "seasonName='" + seasonName + '\'' + ", seasonDesc='" + seasonDesc + '\'' + '}'; } }
使用enum关键字定义枚举类
说明:定义的枚举类默认继承于java.lang.Enum类
//使用enum关键字枚举类enum Season{ //1.提供当前枚举类的对象,多个对象之间用“,”隔开,末尾对象“;”结束 SPRING("春天", "春暖花开"), SUMMER("夏天", "夏日炎炎"), AUTUMN("秋天", "秋高气爽"), WINTER("冬天", "冰天雪地"); //2.声明Season对象的属性:private final修饰 private final String seasonName; private final String seasonDesc; //3.私有化类的构造器,并给对象属性赋值 private Season(String seasonName, String seasonDesc){ this.seasonName = seasonName; this.seasonDesc = seasonDesc; } //4.其他诉求1:获取枚举类对象的属性 public String getSeasonName(){ return seasonName; } public String getSeasonDesc(){ return seasonDesc; } }
-
Enum类中的常用方法:
- values()方法:返回枚举类的对象数组。该方法可以很方便地遍历所有的枚举值。
- valueOf(String str):可以把一个字符串为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:illegalArgunmentException。
- toString():返回当前枚举类对象常量的名称
-
使用enum关键字定义的枚举类实现接口的情况
情况一:实现接口,在enum类中实现抽象方法
interface Info{ void show();}enum Season implements Info{ ...... @Override public void show(){ System.out.println("这是一个季节"); }}
情况二:让枚举类的对象分别实现接口中的抽象方法
interface Info{ void show();}enum Season implements Info{ //1.提供当前枚举类的对象,多个对象之间用“,”隔开,末尾对象“;”结束 SPRING("春天", "春暖花开"){ @Override public void show(){ System.out.println("春天在哪里?") } }, SUMMER("夏天", "夏日炎炎"){ @Override public void show(){ System.out.println("宁静的夏天") } }, AUTUMN("秋天", "秋高气爽"){ @Override public void show(){ System.out.println("秋天不回来") } }, WINTER("冬天", "冰天雪地"){ @Override public void show(){ System.out.println("大约在冬季") } }; ......}
注解(Annotation)
注解的使用
-
理解Annotation:
- jdk 5.0 新增的功能
- Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
- 在JavaSEzhong ,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版本中所遗留的繁冗代码和XML配置等。
-
Annocation的使用示例
- 示例一:生成文档相关的注解
- 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
- @Override:限定重写父类方法,该注解只能用于方法
- @Deprecated:用于表示所修饰的元素(类、方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
- @SuppressWarnings:抑制编译器警告
- 实例三:跟踪代码依耐性,实现替代配置文件功能
-
如何自定义注解:参照@SuppressWarings定义
- 注解声明为:@interface
- 内部定义成员,通常使用value表示
- 可以指定成员的默认值,使用default定义
- 如果自定义注解没有成员,表明是一个标识作用。
如果注解有成员,在使用注解时,需要指明成员的值。
自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
自定义注解通常都会指明两个元注解:Retention、Target
public @interface MyAnnotation{ String value() default "hello";}@MyAnnotation(value="hi")class Person(){ ......}
-
jdk 提供的4中元注解
元注解:对现有的注解进行解释说明的注解
Retention:指定所修饰的Annotation的生命周期:SOURCE\CLASS(默认行为)\RUNTIME
只有声明为RUNTIME生命周期的注解,才能通过反射获取。
Target:用于指定被修饰的Annotation能用于修饰那些程序元素
出现的频率较低:
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的Annotation将具有继承性。
-
通过反射获取注解信息
-
jdk 8 中注解的新特性:可重复注解、类型注解
-
可重复注解:
- 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
- MyAnnotaion的Target和Retention等元注解与MyAnnotations相同。
-
类型注解:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
-
Java 集合
集合框架的概述
-
集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,jpg.avi\数据库中)
-
数组
- 数组在存储多个数据方面的特点:
- 一旦初始化以后,其长度就确定了。
- 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。比如:String[] arr;int[] arr1;Object[] arr2;
- 数组在存储多个数据方面的缺点:
-
一旦初始化以后,其长度就不可修改。
-
数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
-
获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
-
数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
-
- 数组在存储多个数据方面的特点:
集合框架
-
Collection接口:单列集合,用来存储一个一个的对象
-
List接口:存储有序的、可重复的数据。 -->“动态”数组
- ArrayList、LinkedList、Vector
-
Set接口:存储无序的,不可重复的数据。 -->高中讲的“集合”
- HashSet、LinkedHashSet、TreeSet
-
-
Map接口:双列集合,用来存储一对(key - value)一对的数据 -->高中函数:y=f(x)
- HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
Collection接口中的方法的使用
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().
- add(Object e):将元素e添加到集合coll中
- size():获取添加的元素的个数
- addAll(Collection collection):将collection集合中的元素添加到当前的集合中
- clear():清空集合元素
- isEmpty():判断当前集合是否为空
- contains(Object obj):判断当前集合中是否包含obj,我们在判断时会调用obj对象所在类的equals()。
- containsAll(Collection coll):判断形参coll1中的所有元素是否都存在于当前集合。
- remove(Object obj):从当前集合中移除obj元素。
- removeAll(Collection coll):从当前集合中移除coll中所有元素
- retainAll(Collection coll):交集:获取当前集合和coll集合的交集,并返回给当前集合
- equals(Object obj):要想返回true,需要判断当前集合和形参集合的元素都相同。
- hashCode():返回当前对象的哈希值
- 集合---------> 数组:toArray()
- 数组---------->集合:Arrays.asList(数组)
- iterator():返回Iterator接口的实例,用于遍历集合元素。放在IteratorTest.java中测试
集合元素的遍历操作,使用迭代器Iterator接口
- 内部的方法:hasNext()和next()
- 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
- 内部定义了remove(),可以在遍历的时候,删除集合中的元素。此方法不同于集合直接调用remove()
Iterator iterator = coll.iterator();
方式一:
System.out.println(iterator.next());
方式二:不推荐
for(int i=0; i<coll.size();i++){ System.out,println(iterator.next());}
方式三:推荐
while(iterator.hasNext()){ //next():1.指针下移 2.将下移以后集合位置上的元素返回 System.out.println(iterator.next());}
错误方式:
while((iterator.next()) != null){ System.out.println(iterator.next());}
while(coll.iterator().hasNext()){ System.out.println(coll.iterator().next());}
Iterator中的remove():
//删除集合中的“Tom”Iterator iterator = coll.iterator();while(iterator.hasNext()){ Object obj = iterator.next(); if("Tom".equals(obj)){ iterator.remove(); }}
jdk5.0 新增了foreach循环,用于遍历集合和数组
for(集合元素的类型 局部变量 : 集合对象)
内部仍然调用了迭代器
List接口:存储有序的、可重复的数据。 —>“动态”数组,替换原有的数组
- ArrayList:作为List接口的主要实现类;线程不安全,效率高;底层使用Object[] elementData存储
- LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
- Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
ArrayList的源码分析: jdk 7 的情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementDatalist.add(123);//elementData[0] = new Integer(123);...list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
jdk 8 中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组list.add(123);第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]...
后续的添加和扩容操作与jdk 7 无异
**小结:**jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList的源码分析:
LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为nulllist.add(123);//将123封装到Node中,创建了Node对象。其中,Node定义为: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; }}
Vector的源码分析:
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组
在扩容方面,默认扩容为原来的数组长度的2倍。
面试题:ArrayList、LinkedList、Vector三者的异同
同:三个类都实现了List接口,存储数据特点相同:存储有序的、可重复的数据
不同:见上
List接口中的常用方法
void add(int inde, Object eles):在index位置开始将eles中的所有元素添加进来
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置。如果不存在,返回-1
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循环
- 普通的循环
面试题:
public class ListExer{ /* 区分List中remove(int index)和remove(Object obj) */ @Test public void testListRemoive(){ List list = new ArrayList(); list.add(1); list.add(2); list.add(3); updateList(list); System.out.println(list);//1,2 } private void updateList(List list){ list.remove(2); //list.remove(new Integer(2)); }}
Collection子接口之二:Set接口
-
set接口的框架:
-
Collection接口:单列集合,用来存储一个一个的对象
- Set接口:存储无序的、不可重复的数据
- HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
- LinkedHashSet:作为HashSet的子类:遍历其内部数据时,可以按照添加的顺序遍历
- TreeSet:可以按照添加对象的指定属性,进行排序。
- Set接口:存储无序的、不可重复的数据
-
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
-
要求:向Set中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具备有相等的散列码
**重写两个方法的小技巧:**对象中用作equals()方法比较的Filed,都应该用来计算hashCode
-
-
Set:储存无序的、不可重复的数据
以HashSet为例说明:
-
无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
-
不可重复性:保证添加的元素按照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添加成功。--------------->情况3
对于添加成功的情况2和情况3而言:元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素
jdk 8 :原来的元素在数组中,指向元素a
LinkedHashSet的使用
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
TreeSet
- 向TreeSet中添加的数据,要求是相同类的对象。
- 两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
- 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals().
- 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals().
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()方法:
如果equals()返回false:此时key1-value1添加成功。 ----------情况3
如果eqauls()返回true:使用value1替换相同key的value值。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8 相较于jdk7在底层实现方面的不同:
-
new HashMap():底层没有创建一个长度为16的数组
-
jdk 8底层的数组是:Node[],而非Entry[]
-
首次调用put()方法时,底层创建长度为16的数组
-
jdk7底层结构只有:数组+链表。jdk8底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储。
DEFAULT_INITITAL_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);
}
}
-
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-valeu对构成的Set集合
遍历:
遍历所有的key集:keySet()
Set set = map.keySet();Iterator iterator = set,iterator();while(iterator.hasNext()){ System.out.println(iterator.next());}
遍历所有的value:values()
Collection values = map.values();for(Object obj : values){ System.out.println(obj);}
遍历所有的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());}
方式二:
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);}
总结:常用方法:
添加:put(Object key, Object value);
删除:remove(Objcet key);
修改:put(Object key, Object value);
查询:get(Objcet key);
长度:size();
遍历:keySet() / values() / entrySet()
-
TreeMap
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要安装key进行排序:自然排序、定制排序
Properties
properties:常用来处理配置文件。key和value都是String类型
try{ Properties pros = new Properties(); FileInputStream fis = new FileInputStream("jdbc.properties"); pros.load(fis);//加载对应的文件 String name = pros.getProperty("name"); String password = pros.getProperty("password");}catch(IOException e){ e.printStackTrace();}finally{ if(fis != null){ try{ fis.close(); }catch(IOException e){ e.printStackTrace(); } }}
Collections工具类
Collections:操作Collection、Map的工具类
面试题:Collection 和 Collections的区别?
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 指定的顺序,返回给定集合中的最大元素
Objcet min(Collection)
Objcet min(Collection, Comparator)
int frequency(Collection, Object):返回指定集合中指定元素的出现次数
void copy(List dest, List str):将src中的内容复制到dest中
List list = new ArrayList();list.add(123);list.add(43);list.add(765);list.add-97);list.add(0);//报异常:IndexOutOfBoundsException("Source dose not fit in dest")// List dest = new ArrayList();// Collections.copy(dest, list);List dest = Arrays.asList(new Object[list.size()]);Collections.copy(dest, list);
boolean replaceAll(List list, Objcet oldVal, Object newVal):使用新值替换List对
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
//返回的list即为线程安全的ListList list = Collections.synchronizedList(list);
泛型
泛型的使用
-
jdk 5.0新增的特性
-
集合中使用泛型:
总结:
-
集合接口或集合类在jdk5.0时都修改为带泛型的结构。
-
在实例化集合类时,可以指明具体的泛型类型
-
指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) -------> 实例化以后:add(Integer e)
-
注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。
-
如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
-
在集合中使用泛型之前的情况
@Testpublic void test1(){ //需求:存放学生的成绩 list.add(78); list.add(76); list.add(89); list.add(88); //问题一:类型不安全 list.add("Tom"); for(Object score : list){ //问题而:强转时,可能出现ClassCastException int stuScore = (Integer) score; System.out,println(stuScore); }}
在集合中使用泛型的情况:
@Testpublic void test2(){ new ArrayList<Integer> list = new ArrayList<Integer>(); list.add(78); list.add(87); list.add(99); list.add(65); //编译时,就会进行类型检查,保证数据的安全 //list.add("Tom"); for(Integer score : list){ //避免了强转操作 int stuScore = score; System.out.println(stuScore); }}
//方式二:Iterator<Integer> iterator = list.iterator();while(iterator.hasNext()){ int stuScore = iterator.next(); System.out.println(stuScore);}
在集合中使用泛型的情况:以HashMap为例
@Testpublic void test3(){ Map<String, Integer> map = new HashMap<String, Integer>(); map.put("Tom", 87); map.put("Jerry", 87); map.put("jack", 67); //map.put(123,"ABC"); //泛型的嵌套 Set<Entry<String,Integer>> entry = map.entrySet(); Iterator<Map.Entry<String, Integer>> iterator = entry.iterator(); while(iterator.hasNext()){ Map.Entry<String, Integer> e = iterator.next(); String key = e.getKey(); Integer value = e.getValue(); System.out.println(key + "--------" + value); }}
-
如何自定义泛型结构:泛型类、泛型接口、泛型方法
-
关于自定义泛型类、泛型接口:
@Testpublic void test1(){ //如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型 //要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。 Order order = new Order(); Order.setOrderT(123); Order.setOrderT("ABC"); //建议:实例化时指明类的泛型 Order<String> order1 = new Order<String>("orderAA", 1001, "order:AA"); order1.setOrderT("AA:hello");}
由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
泛型不同的引用不能相互赋值
ArrayList<String> list1 = null;ArrayList<Integer> list2 = null;list1 = list2; //错误
静态方法中不能使用类的泛型
public static void show(T orderT){ //错误 System.out.println(orderT);}
异常类不能声明为泛型类
public class MyException<T> extends Exception{ //错误 }
public void show(){ try{ }catch(T t){ //错误 }}
类的内部结构就可以使用类的泛型
T orderT;public Order(){ //编译不通过 T[] arr = new T[10];}
T orderT;public Order(){ //编译通过 T[] arr = (T[]) new Object[10];}
-
泛型方法
如下三个方法都不是泛型方法
public T getOrderT(){ return orderT;}public void setOrder(T orderT){ this.orderT = orderT;}@Overridepublic String toString(){ return "Order{" + "orderName=" + orderName + "\" + ",orderId=" + orderId + "}"}
泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
换句话说,泛型方法所属的类是不是泛型类都没有关系。
泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public static <E> List<E> copyFromArrayToList(E[] arr){ ArrayList<E> list = new ArrayList<>(); for(E e : arr){ list.add(e); } return list;}
-
-
泛型在继承方面的体现
虽然类A是类B的父类,但是G和G二者不具备子父类关系,二者是并列关系。
List<Object> list1 = null;List<String> list2 = null;//此时的list1和list2的类型不具有子父类关系list1 = list2; //错误
-
通配符的使用
通配符:?
类A是类B的父类,G和G是没有关系的,二者共同的父类是:G<?>
对于List<?>就不能向其内部添加数据。除了添加null之外。允许读取数据,读取的数据类型为Object。
@Testpublic void test3(){ List<Object> list1 = null; List<String> list2 = null; List<?> list = null; list = list1; list = list2;}
-
有限制条件的通配符的使用
? extends A:G<? extends A>可以作为G和G的父类的,其中B是A的子类
? super A:G<? super A>可以作为G和G的父类的,其中B是A的父类
@Testpublic void test(){ List<? extends Person> list1 = null; List<? super Person> list2 = null; List<Student> list3 = null; List<Person> list4 = null; List<Object> list5 = null; list1 = list3; list1 = list4; list2 = list4; list2 = list5; //读取数据 list1 = list3; Person p = list1.get(0); //编译不通过:Student s = list1.get(0);}
IO流
File类的使用
- File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
- File类声明在java.io包下
- File类中涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
- 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的“终点”。
-
如何创建File类的实例
File(String filePath)
File(String parentPath, String childPath)
File(File parentFile, String childPath)
-
相对路径:相较于某个路径下,指明的路径
File file = new File("hello.txt");//相对于当前module
绝对路径:包含盘符在内的文件或文件目录的路径
File file = new File("D:\\workspace_ideal1\\JavaSenior\\day08\\he.txt");
说明:
IDEA中:
如果大家开发使用JUnit中的单元测试方法测试,相对路径即为当前Module下。
如果大家使用main()测试,相对路径即为当前的Project下。
Eclipse中:
不管使用单元测试方法还是使用main()测试,相对路径都是当前的Project下。
-
路径分隔符
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中的删除不走回收站。
IO流原理及流的分类
-
流的分类:
- 操作数据单位:字节流、字符流
- 数据的流向:输入流、输出流
- 流的角色:节点流、处理流
-
流的体系结构
抽象基类:
InputStream OutputStream Reader Writer
节点流(或文件流):
FileInputStream FileOutputStream FileReader FileWriter
缓冲流(处理流的一种):
BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
FileReader fr = null;try{ //1.实例化File类的对象,指明要操作的文件 File file = new File("hello.txt"); //2.提供具体的流 fr = new FileReader(file); //3.数据的读入 //read():返回读入的一个字符。如果达到文件末尾,返回-1 int data; while((data = fr.read()) != ){ System.out.println((char)data); }}catch(IOException e){ e.printStackTrace();}finally{ //4.流的关闭操作 try{ if(fr!=null){ fr.close(); } }catch(IOException e){ e.printStackTrace(); }}
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]); } }}catch(IOException e){ e.printStackTrace();}finally{ //4.资源关闭 try{ if(fr!=null){ fr.close(); } }}
从内存中写出数据到硬盘的文件里
说明:
-
输出操作,对应的File可以不存在的。并不会报异常
-
File对应的硬盘中的文如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file, false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:FileWriter(file, true):不会对原有文件覆盖,而是在原有文件基础上追加内容
//1.提供File类的对象,指明写出到的文件File file = new File("hello1.txt");//2.提供FileWriter的对象,用于数据的写出FileWriter fw = new FileWriter(file, false);//3.写出的操作fw.write("i have a dream!\n");fw.write("you need to have a dream!");//4.资源流的关闭fw.close();
不能使用字符流来处理图片等字节数据
测试FileInputStream和FileOutputStream的使用
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){ fis.close(); }}
结论:
- 对于文本文件(.txt, .java, .c, .cpp),使用字符流处理
- 对于非文本文件(.jpg, .mp3, .mp4, .avi, .doc, .ppt, ···),使用字节流处理
- 使用字节流FileInputStream处理文本文件,可能出现乱码
-
-
处理流之一:缓冲流的使用
-
缓冲流
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
-
作用:提高流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
实现非文本文件的复制
@Testpublic void BufferedStreamTest(){ //1.造文件 File srcFile = new File("文件名1"); File destFile = new File("文件名2"); //2.造流 //2.1造节点流 FileInputStream fis = new FileInputStream(srcFile); FileOutputStream fos = new FileOutputStream(destFile); //2.2造缓冲流 BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fow); //3.复制的细节:读取、写入 byte[] buffer = new byte[10]; int len; while((len=bis.read(buffer)) != -1){ bos.write(buffer, 0, len); } //4.资源关闭 //要求:先关闭外层的流,再关闭内层的流 bos.close(); bis.close(); //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略。 fos.close(); fis.close();}
- 处理流,就是“套接”在已有的流的基础上。
-
-
处理流之二:转换流的使用
-
转换流:
InputStreamReader:将一个字节的输入流转换为字符的输入流
OutputStreamWriter:将一个字符的输出流转换为字节的输出流
-
作用:提供字节流与字符流之间的转换
-
解码:字节、字节数组 -------->字符数组、字符串
-
编码:字符数组、字符串 -----------> 字节、字节数组
-
字符集
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表用一个字节的8位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
-
-
其他流的使用
-
标准的输入、输出流
-
System.in:标准的输入流,默认从键盘输入
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(); } }}
System.out:标准的输出流,默认从控制台输出
-
System类的setIn(InputStream is) / setOut(PrintStream ps)方式重写指定输入和输出的流。
-
-
打印流:PrintStream 和 PrintWriter
- 提供了一系列重载的print()和println()
-
数据流
- DataInputStream 和 DataOutputStream
- 作用:用于读取或写出基本数据类型的变量或字符串
- 注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
-
对象流的使用
- ObjectInputStream 和 ObjectOutputStream
-
作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
-
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
@Testpublic void testObjectOutputStream(){ ObjectOutputStream oos = null; try{ oos = new ObjectOutputStream(new FileOutputStream("object.dat")); oos.writeObject(new String("我爱北京天安门")); oos.flush();//刷新操作 }catch(IOException e){ e.printStackTrace(); }finally{ if(oos != null){ try{ oos.close(); }catch(IOException e){ e.printStackTrace(); } } } }
-
反序列化:将磁盘文件中的对象还原为内存中的一个java对象
使用ObjectInputStream来实现
@Testpublic void testObjectInputStream(){ ObjectInputStream ois = null; try{ ois = new ObjectInputStream(new FileInputStream("object.dat")); Object obj = ois.readObject(); String str = (String)obj; }catch(IOException e){ e.printStackTrace(); }finally{ if(ois != null){ try{ ois.close(); }catch(IOException e){ e.printStackTrace(); } } }}
-
-
要想一个java对象是可序列化的,需要满足相应的要求。
- 需要实现接口:Serializable
- 当前类提供一个全局常量:serialVersionUID
- 除了当前类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
-
序列化机制:
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其他程序获取了这种二进制流,就可以恢复成原来的Java对象。
-
RandomAccessFile的使用
- RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口。
- RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
- RandomAccessFile作为输出流时,写出到 的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
使用RandomAccessFile实现数据的插入效果
@Testpublic void test() throws IOException{ RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw"); raf1.seek(3);//将指针调到角标为3的位置 //保存指针3后面的所有数据到StringBuliuder中 StringBuilder builder = new StringBuilder(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().getByte()); raf1.close();}
-
jdk 7.0 时,引入 了Path、Paths、Files三个类。
-
此三个类声明在:java.nio.file包下。
-
Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
-
如何实例化Path:使用Paths。
static Path get(String first, String …more):用于将多个字符串串连成路径
static Path get(URI uri):返回指定uri对应的Path路径
-
-
网络编程
-
网络编程中有两个主要的问题
- 如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 找到主机后如何可靠高效地进行数据传输
-
网络编程中的两个要素:
- 对应问题一:IP和端口号
- 对应问题二:提供网络通讯协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
-
通讯要素一:IP和端口号
-
IP:唯一的表示Internet上的计算机(通讯实体)
-
在Java中使用InetAddress类代表IP
InetAddress inet1 = InetAddress.getByName("192.168.10.14")
-
IP分类:IPv4 和 IPv6;万维网 和 局域网
-
域名
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
-
本地回路地址:127.0.0.1 对应着:localhost
InetAddress inet3 = InetAddress.getByName("127.0.0.1");InetAddress inet4 = InetAddress.getLocalHost();
-
如何实例化InetAddress:两个方法:getByName(String host)、getLocalHost()
两个常用方法:getHostName() / getHostAddress()
-
端口号:正在计算机上运行的进程
要求:不同的进程有不同的端口号
范围:被规定为一个16位的整数 0~65535
-
端口号与IP地址的组合得出一个网络套接字:Socket
//客户端@Testpublic void client() throws IOException{ Socekt socket = null; OutputStream os = null; try{ InetAddress inet = InetAddress.getByName("127.0.0.1"); socket = new Socket(inet, 8899); os = socket.getOutputStream(); os.write("你好,我是客户端mm".getBytes()); }catch(IOException e){ e.printStackTrace(); }finally{ if(is != null){ try{ os.close(); }catch(IOException e){ e.printStackTrace(); } } if(socket != null){ try{ socket.close(); }catch(IOException e){ e.printStackTrace(); } } } }
//服务端@Testpublic void server(){ ServerSocket ss = new ServerSocket(8899); Socket socket = ss.accept(); InputStream is = socket.getInputStream(); ByteArrayOutputStream 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()); //关闭资源 baos.close(); is.close(); socket.close(); ss.close(); //注意try-catch-finally格式}
-