目录
(声明:参考资料为韩顺平循序渐进学Java零基础)
一、Java简单概述
1、Java重要特点
1)Java语言是面向对象的;
2)Java语言是健壮的;
3)Java语言是跨平台性的。
2、Java核心机制-Java虚拟机
1)JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域;
2)对于不同的平台,有不同的虚拟机;
3)Java虚拟机机制屏蔽了底层运行平台的差别,实现一次编译,到处运行。
3、JDK,JRE
1)JDK(Java Development Kit):Java开发工具包,包含了JRE和java的开发工具;
2)JRE(Java Runtime Environment):Java运行环境,包含了JVM和java的核心类库。
4、Java执行流程
1).java文件(源文件)通过javac编译形成.class文件(字节码文件),.class文件通过java运行。
5、Java开发注意事项
1)Java源文件以.java为扩展名;
2)Java语言严格区分大小写,并以";"结束;
3)一个源文件最多只能有一个public类,其它类个数不限;
4)如果源文件包含一个public类,则文件名必须按该类名命名。
6、Java常用转义字符
1)\t:一个制表位,实现对齐的功能;
\n:换行符;
\\:一个\;
\":一个";
\':一个';
\r:一个回车。
7、注释
1)单行注释 //;
2)多行注释 /* */(多行注释里不允许有多行注释嵌套);
3)文档注释 /** */。
8、路径
1)相对路径:从当前目录定位,形成的一个路径;
2)绝对路径:从顶级目录如d盘定位,形成的路径。
二、变量
1、变量使用
1)先声明变量,再赋值;
2)不同变量类型不同占用存储空间大小不同;
3)变量在同一个作用域内不能重名。
2、数据类型
Java数据类型分为基本数据类型和引用数据类型;
1)基本数据类型:数值型,byte[1],short[2],int[4],long[8],float[4],doubke[8];字符型,char[2];布尔型,boolean[1];
2)引用数据类型:类(class)、接口(interface)、数组([]);
3)使用细节:java整型常量默认为int型,声明long型常量须后加"l"或"L",java的浮点型常量默认为double型,声明常量float型常量,须后加"f"或"F";
4)字符类型可以表示单个字符,字符类型是char,多个字符用字符串String表示,字符常量是用' '括起来的单个字符;
5)Java中允许使用转义字符'\'来将其后的字符转变为特殊字符型常量;
char c1 = '\n'; //'\n'表示换行符
6)在java中,char的本质是一个整数,输出时是unicode码对应的字符,给char赋一个整数,输出时会按照对应的unicode字符输出,且char类型可以进行运算,相当于一个整数;
7)boolean类型只允许取值true和false,占1个字节,相当于逻辑运算。
3、字符编码表
1)ASCII编码表:一个字节表示,一共128个字符,缺点不能表示所有字符;
2)Unicode编码表:一种编码将世界上所有符号都纳入,每个符号都有独自的编码,使用两个字节来表示字符,字母和汉字统一占两个字节,浪费空间;
3)utf-8编码表:大小可变的编码,字母使用1个字节,汉字使用2个字节;
4)gdk编码表:可以表示汉字范围广,字母使用1个字节,汉字2个字节。
4、数据类型转换
1)自动数据类型转换:精度小的类型自动转换为精度大的数据类型,多种类型的数据混合运算时,系统会自动将所有数据转换成容量大的数据类型,然后计算;
2)把精度大的数据类型赋值给精度小的数据类型时会报错,反之进行自动类型转换,byte、short和char不会自动转换,但可以计算,计算时首先转换为int类型;
3)强制类型转换:将容量大的数据类型转换为容量小的数据类型,使用时加上强制转换符(),但可能造成精度降低或溢出;
int i = (int) 1.9;
4)强制符号只针对最近的操作数有效,可以使用小括号提升优先级,char类型可以保存int类型的常量值,但不能保存int类型的变量值,需要强转;
5)基本数据类型转String类型:将基本类型的值+ " "即可,
int n1 = 100;
String str1 = n1 + "";
String类型转基本数据类型:通过基本类型的包装类调用parseXX方法即可。
Interger.parseInt("123");
Double.parseDouble("123.1");
三、运算符
1、算术运算符
1)算术运算符是对数值类型的变量进行运算;
2)+ :正号、加、字符串相加,- :负号、减,* :乘,/ :除,% :取余,
++ :自增(前),先自增再运算,
int a = 2;
int b = ++a;
//结果:a = 3, b = 3;
++ :自增(后),先运算再自增,
int a = 2;
int b = a++;
//结果:a = 3, b = 2;
-- :自减(前),先自减再运算,
-- :自减(后),先运算再自减;
3)细节:除号"/",整数之间做除法时只保留整数,对一个数取模时,可以等价为 a % b = a - a / b * b,当自增独立使用时,++i和i++等价。
int a = 5;
int b = 2;
int c = a % b;
//结果:c = 5 - 5/2*2 = 1,注意 a/b 为int类型,舍弃小数保留整数
2、关系运算符
1)关系运算符的结果都是boolean型,即取值为true或false;
2)关系运算符:== :是否相等,!= :是否不相等,< :是否小于,> :是否大于,
<= :是否小于等于,>= :是否大于等于,instanceof :检查是否是类的对象。
3、逻辑运算符
1)用于连接多个关系表达式,结果为一个boolean值;
2)逻辑与:&,逻辑或:|,逻辑异或:^,短路与:&&,短路或:||,取反:!;
3)逻辑运算规则:
a&b:当a和b同时为true时,结果为true,否则为false,
a&&b:当a和b同时为true时,结果为true,否则为false,
a|b:当a和b有一个为true时,结果为true,否则为false,
a||b:当a和b有一个为true时,结果为true,否则为false,
!a:当a为true时,结果为false,当a为false时,结果为true,
a^b:当a和b不同时,结果为true,否则为false,
区别:使用&&短路与时,如果第一个条件为false,则第二个条件不会判断,结果为false,效率高;使用&逻辑与时,不论第一个条件是否为false,第二个条件都要判断,效率低,同样短路或||和逻辑或|原理相同。
int age = 20;
if(age > 20 && age < 30){
System.out.println("OK");
}
//当使用短路与&&时,if语句进行判断时,第一个条件age > 20 不成立,则不会继续判断age < 30
//而如果使用逻辑与&时,if语句会继续判断age < 30;
4、赋值运算符
1)基本赋值运算符:=,复合赋值运算符如 +=,-=,*=,/=等;
2)特点:运算顺序从右往左;赋值运算符左边只能是变量,右边可以是变量、常量值、表达式等;复合运算符会进行类型转换。
5、三元运算符
1)条件表达式 ? 表达式1 :表达式2;
运算规则:如果条件表达式为true,运算后结果是表达式1,如果条件表达式为false,运算结果是表达式2;
2)表达式1和表达式2要为可以赋给接收变量的类型(可以自动转换或强制转换)。
int a = 3, b = 8;
int c = a > b ? (int)1.1 : (int)3.4;// 强制将浮点型数转换为整型数,可以
int d = a > b ? 1.1 : 3.4; // 不可以将浮点型数赋值给整型
double e = a > b ? 1 : 3; // 可以将整型数赋值给浮点型数,自动转换,精度提高
6、运算符优先级
1)运算符有不同的优先级,只有单目运算符、赋值运算符是从右往左运算。
7、标识符命名规则和规范
1)Java对各种变量、方法和类等命名时使用的字符序列称为标识符;
2)命名规则:由26个英文字母大小写,0-9,_或$组成;数字不可以开头;不可以使用关键字和保留字;可以包含关键字和保留字;Java中严格区分大小写,长度无限制;标识符不能包含空格;
3)包名:多单词组成时所有字母小写;
类名、接口名:多单词组成时,所有单词的首字母大写如XxxxYyyy[大驼峰];
变量名、方法名:多单词组成时,第一个首字母小写,第二个单词开始每个单词首字母大写xxxYyyy[小驼峰];
常量名:所有字母都大写,多单词时用下划线连接。
8、关键字、保留字
1)关键字:被Java赋予特殊含义,有专门用途的字符串;特点:关键字中所有字母都小写;
2)保留字:现在Java版本未使用,但以后版本可能作为关键字使用。
9、键盘输入语句
1)导入该类的包,java.util.*;创建该类对象;调用功能。
import java.util.Scanner;//导入包
Scanner myScanner = new Scanner(System.in);//创建Scanner对象
System.out.println("请输入名字:");//程序执行到这会等待用户输入
String name = myScanner.next();//接收用户输入的字符串
10、进制
1)二进制:0,1,满2进1,以0b或0B开头;
十进制:0-9,满10进1;
八进制:0-7,满8进1,以数字0开头;
十六进制:0-9及A(10)- F(15),满16进1,以0x或0X开头表示,且A-F不区分大小写。
2)进制转换:二进制、八进制和十六机制转换成十进制都是将每个位上的数提出来乘以对应的2、8、16的(位数-1)次方,然后求和;
十进制转换成二进制:将该数不断除2,直到商为0,然后将每步得到的余数倒过来就是对应的二进制,十进制转换成八进制、十六进制类似;
二进制转换成八进制、十六进制:从低位开始,将二进制数每三位一组、每四位一组转换成对应的八进制、十六进制数,八进制、十六进制转换成二进制则过程相反;
11、原码、反码、补码
1)二进制最高位是符号位:0表示正数,1表示负数;
正数的原码、反码、补码都一样;
负数的反码 = 它的原码符号位不变,其它位取反(0->1,1->0);
负数的补码 = 它的反码 + 1;负数的反码 = 负数的补码 - 1;
0的反码、补码都是0;
java没有无符号数即java中数都是有符号的;
在计算机运算的时候,是以补码的方式来运算的;
看运算结果的时候看它的原码。
12、位运算符
1)按位与&:两位全为1,结果为1,否则为0;
按位或|:两位有一个为1,结果为1,否则为0;
按位异或^:两位一个为0,一个为1,结果为1,否则为0;
按位取反~:0->1,1->0;
算术右移>>:低位溢出符号位不变,用符号位补溢出的高位;
int a = 1>>2;// 1即0000 0001算术右移2位,即低位右移2位,用符号位0补溢出的高位
//结果是0000 0000 本质是1 / 2 / 2 = 0
算术左移<<:符号位不变,低位补0;
int c = 1 << 2;// 1即0000 0001算术左移2位,符号位不变低位用0补充,本质是1 * 2 * 2 = 4
//结果 0000 0100
>>>:逻辑右移也叫无符号右移,运算规则是:低位溢出,高位补0。
四、程序控制结构
1、顺序控制
程序从上到下逐行执行,中间没有判断和跳转。
2、分支控制
1)if - else
单分支 if
if(条件表达式){
执行代码块;
}// 如果{}中只有一条语句则可以不用写{}
双分支
if(条件表达式){
执行代码块1;
}else{
执行代码块2;
}
多分支
if(条件表达式){
执行代码块1;
}else if(条件表达式) {
执行代码块2;
}
......
else{执行代码块n;
}
2)嵌套分支:一个分支结构中嵌套了另一个完整的分支结构(最好不超过3层)
3)switch分支结构
switch(表达式){
case 常量1:语句块1;break;
case 常量2:语句块2;break;
.....
case 常量n:语句块n;break;
default:default语句块;break;
}
注意事项:
switch关键字表示switch分支;
表达式对应一个值,且数据类型与case后的常量类型一致,或者可以自动转换成可以相互比较的类型;
表达式返回值必须是:byte、short、int、char、enum(枚举)、String;
case中的值必须是常量不能是变量;
当表达式的值等于常量1就执行语句块1,有break则结束switch语句,否则继续匹配case常量2,诸如此类,最后一个常量都没有匹配则执行default语句块。
3、循环控制
1)for 循环控制
for(循环变量初始化;循环条件;循环变量迭代){
循环代码块;
}
注意事项:循环条件中初始化和变量迭代可以写到其它地方,但是两边分号不能省略;
循环初始值可以有多条初始化语句,但要求类型一样,中间用逗号隔开;
2)while 循环控制
循环变量初始化;
while(循环条件){
循环体;
循环变量迭代;
}// 先判断再执行
3)do while 循环控制
循环变量初始化;
do{
循环体;
循环变量迭代;
}while(循环条件);
// 先执行,再判断
4)多重循环控制
将一个循环放在另一个循环体内,形成循环嵌套,当内层循环的循环条件位false时,才会跳出内层循环,结束外层的本次循环开始下一次循环。
5)跳转控制语句break:用于终止某个语句块的执行;
break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止那一层语句块;
label1:
for(int j = 0;j < 4; j++){
label2:
for(int i = 0; i < 5; i++){
if(i==2){
break label1;
}
System.out.println(i);
}
}
6)跳转控制语句 continue:用于结束本次循环,继续执行下一次循环;
continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是那一层循环。
7)跳转控制语句 return :跳出所在的方法。
五、数组、排序
1、数组
1)数组的定义: 数据类型 数组名[] = new 数据类型[大小]
声明数组:数据类型 数组名[];创建数组:数组名 = new 数据类型[大小];
数据类型 数组名[] = {元素值,元素值....}
int a[] = new int[5];//创建数组方式一
int [] b;
b = new int[10];//创建数组方式二
int c[] = {2,3,4,6};//创建数组方式三
2)数组的引用:数组名[下标/索引/index];数组的下标从0开始;数组属引用类型,数组型数据是对象(object);数组在默认情况下是引用传递,赋的值是地址;
3)二维数组
语法:类型[] [] 数组名 = new 类型[大小][大小]
类型 数组名[][];
数组名 = new 类型[大小][大小];
类型[][] 数组名 = new 类型[大小][];
类型 数组名[][] = {{值1,值2....},{值1,值2....},{值1,值2....},{值1,值2....}}。
int a[][] = new int[2][3] // 动态初始化 使用方式1
int b[][];
b = new[2][3]; // 动态初始化 使用方式2
int[][] c = new int[3][]; // 动态初始化 列数不确定 使用方式3
int d[][] = {{值1,值2...},{值1,值2...},{值1,值2...}} // 静态初始化 使用方式4
4)注意事项
二维数组实际上由多个一维数组组成,各个一维数组的长度可以相同,也可以不同。
int map [][] = {{1,2},{3,4,5}}
2、排序
1)冒泡排序法基本思想:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部;
// 分析冒泡排序 数组[24,69,80,57,13]
int sum [] = new int[5];
sum = {24,69,80,57,13};
int temp = 0;
for(int i = 0;i < sum.length - 1; i++)
{
for(int j = 0; j < sum.length - i - 1; j++){
if(sum[j] > sum[j+1]) {
temp = sum[j];
sum[j] = sum[j+1];
sum[j+1] = temp;
}
}
}
六、面向对象编程(初级部分)
1、类与对象
1)类是抽象的,概念的,代表一类事物;
对象是具体的,实际的,代表一个具体事物;
类是对象的模板,对象是类的一个个体。
2)属性/成员变量/字段
属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象,数组);
属性的定义语法同变量,如访问修饰符 属性类型 属性名;
属性的定义类型可以是基本数据类型也可以是引用类型;
属性不赋值有默认值,int 0,short 0,byte 0,long 0,float 0.0,double 0.0,char \u0000,boolean false,String null。
3)创建对象
Cat cat;
cat = new Cat();//先声明后创建
Cat cat = new Cat();//直接创建
4)访问属性
基本语法:对象名.属性名。
5)类与对象的内存分配机制
栈:一般存放基本数据类型(局部变量);
堆:存放对象;
方法区:常量池(常量),类加载信息;
6)成员方法
提高代码的复用性;
可以将实现细节封装起来,供用户调用;
访问修饰符 返回数据类型 方法名(形参列表){
方法体;
return 返回值;
}
返回数据类型:表示成员方法输出,void表示没有返回值;
方法体:实现某一功能代码块,但是方法里不能再定义方法;
return语句不是必须的;
一个方法最多有一个返回值,如果有多个返回结果返回数组;
返回类型可以是任意类型,包含基本类型或引用类型;
形参列表
参数类型可以为任意类型,包含基本类型或引用类型,比如pringArr(int [][] map);
一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开;
形参和实参类型、个数、顺序要对应;
调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数;
方法调用
同一个类中的方法调用,直接调用即可;
跨类中的方法A类调用B类方法,需要通过对象名调用。
7)传参机制
基本数据类型传递的是值,形参的改变不影响实参;
引用类型传递的是地址,可以通过形参影响实参;
8)方法递归:方法自己调用自己;
递归重要规则
执行一个方法时,就会创建一个新的受保护的独立空间(栈空间);
方法的局部变量是独立的,不会相互影响;
如果方法中使用的是引用类型变量,就会共享该引用类型的数据;
递归必须向退出递归的条件逼近,否则就是无限递归;
当一个方法执行完毕或遇到return,就会返回遵守谁调用,就将结果返回给谁,同样当方法执行完毕或者返回时,该方法也就执行完毕;
9)方法重载
java中允许同一个类中有多个同名方法的存在,但要求形参列表不一致;
注意事项
方法名必须相同,形参列表必须不同;
10)可变参数:Java允许将同一个类中多个同名同功能但参数个数不同的方法封装成一个方法;
访问修饰符 返回类型 方法名(数据类型... 形参名){}//注意...不是省略就是...
使用可变参数时可以当做数组来使用;
注意事项
可变参数的实参可以为0个或任意多个;
可变参数的实参可以是数组;
可变参数的本质就是数组;
可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数再最后且一个形参列表只能出现一个可变参数。
11)作用域
全局变量:作用域为整个类体,可以不赋值直接使用有默认值;
局部变量:作用域为定义它的代码块中,必须赋值后才能使用,没有默认值;
注意事项
属性和局部变量可以重名,访问时遵循就近原则;
在同一个作用域中,两个局部变量不能重名;
属性生命周期较长,伴随对象的创建而创建,伴随对象的销毁而销毁;
局部变量生命周期较短,伴随着它的代码块的执行而创建,伴随代码块的结束而销毁;
全局变量/属性可以加修饰符,局部变量不可以加修饰符;
12)构造器/构造方法
[修饰符] 方法名(形参列表){方法体;}
构造器的修饰符可以默认,也可以是public protected private;
构造器无返回值;
方法名和类名必须一致,参数列表和成员方法一样的规则;
构造器的调用由系统完成,创建对象时系统会自动调用该类的构造器完成对象的初始化;
注意事项
一个类可以定义多个不同构造器;
构造器是完成对象的初始化,不是创建对象;
如果没有定义构造器系统会自动生成一个无参构造器;
一旦定义了构造器,默认的构造器就被覆盖,就不能使用默认的无参构造器,除非显式定义;
13)this关键字
this关键字可以用来访问本类的属性、方法、构造器;
this用来区分当前类的属性和局部变量;
访问成员方法的语法:this.方法名(参数列表);
访问构造器语法:this.(参数列表),注意只能在构造器中使用(即只能在构造器中访问另一个构造器且必须放在第一条语句);
this不能在类定义的外部使用,只能在类定义的方法中使用;
七、面向对象编程(中级部分)
1、包
1)包的作用:区分相同名字的类;当类很多时可以很好的管理类;控制访问范围。
2)包的本质就是创建不同的文件夹/目录来保存类文件。
3)命名规则
只能包含数字、下划线、小圆点,不能用数字开头,不能是关键字或保留字;
一般是com.公司名.项目名.业务模块名;
4)包的基本语法
package com.hspedu;
package 关键字表示打包;
com.hspedu 表示包名;
5)注意事项
package作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package;
2、访问修饰符
1)公开级别:public 修饰,对外公开;
受保护级别:用protected修饰,对子类和同一个包中的类公开;
默认级别:没有修饰符号,向同一个包的类公开;
私有级别:用private修饰,只有类本身可以访问,不对外公开。
2)注意事项
修饰符可以用来修饰类中的属性、成员方法及类;
只有默认的和public才能修饰类,并遵循上述的访问权限的特点;
成员方法的访问规则和属性完全一样;
3、封装、继承、多态
1)封装:就是把抽象出的数据【属性】和对数据的操作【方法】封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作【方法】,才能对数据进行操作;
实现步骤
①、将属性进行私有化private;
②、提供公共的set方法用于对属性判断并赋值;
③、提供公共的get方法用于获取属性的值;
2)继承:当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends来声明继承父类;
基本语法
class 子类 extends 父类P{};
细节
子类继承了父类的所有属性和方法但是私有属性和方法需要通过父类提供公共的方法去访问;
子类必须调用父类的构造器完成父类的初始化;
当创建子类对象时,不管使用子类的那个构造器,默认情况下总会调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的那个构造器完成对父类的初始化工作;
如果希望指定去调用父类的某个构造器,则显式调用一下:super(参数列表);
super()和this()都只能放在构造器第一行,因此这俩个方法不能共存在一个构造器;
java所有类都是Object的子类,Object是所有类的基类;
父类构造器的调用不限于父类,一直到Object类;
子类最多只能继承一个父类,即java中是单继承机制,但是可以间接继承;
3)多态:方法或对象具有多种形态;
对象的多态
一个对象的编译类型和运行类型可以不一样;
编译类型在定义对象时就确定,运行类型是可以变化的;
编译类型看定义时 = 号的左边,运行类型看 = 号的右边;
注意事项
多态的前提:两个对象存在继承关系;
多态的向上转型:
本质:父类的引用指向了子类的对象;
语法: 父类类型 引用名 = new 子类类型();
特点:编译类型看左边,运行类型看右边;
可以调用父类中的所有成员(需遵守访问权限);
不能调用子类中特有成员,最终运行效果看子类的具体实现;
多态向下转型:
语法:子类类型 引用名 = new 子类类型();
只能强转父类的引用,不能强转父类的对象;
要求父类的引用必须指向的是当前目标类型的对象;
当向下转型后,可以调用子类类型中所有的成员;
instanceOf:比较操作符,用于判断对象的运行类型是否为XX类型或XX类型的子类型;
多态数组:数组的定义类型为父类类型,实际元素为子类类型;
多态参数:方法定义的形参类型为父类类型,实参类型允许为子类类型;
4)super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器;
基本语法
访问父类的属性,但不能访问父类的private属性,super.属性名;
访问父类的方法,不能访问父类的private方法,super.方法名(参数列表);
访问父类的构造器,super(参数列表);
super的访问不限于直接父类,可以访问更上一级的成员;
如果有多个基类中都有同名的成员,使用super访问遵循就近原则;
super和this的比较
5)java的动态绑定机制
当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定;
当调用对象属性时,没有动态绑定机制,哪里声明哪里使用。
4、方法重写/覆盖
1)方法覆盖:子类有一个方法和父类的某个方法的名称、返回类型、参数一样,就说子类的这个方法覆盖了父类的方法。
2)注意事项
子类的方法的形参列表、方法名称要和父类方法的形参列表、方法名称完全一样;
子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类;
子类方法不能缩小父类方法的访问权限。
3)方法重写和重载比较
5、Object类详解
1、equals方法
1)== 和 equals的对比
==:既可以判断基本类型又可以判断引用类型;
==判断基本类型,判断的是值是否相等,判断引用类型,判断的是地址是否相等,即判断是不是同一个对象;
equals:是Object类中的方法,只能判断引用类型,默认判断的是地址是否相等,子类中往往重写该方法。
2)hashCode方法
返回该对象的哈希码值;
提高具有哈希结构的容器的效率;
两个引用,如果指向同一个对象,则哈希值肯定是一样的,如果指向不同对象,哈希值是不一样的;
哈希值主要是根据地址号来的,不能完全将哈希值等价于地址。
3)toString方法
默认返回:全类名 + @ +哈希值的十六进制;
子类往往重写该方法,用于返回对象的属性信息。
4)finalize方法
当对象被回收时,系统自动调用该对象的finalize方法,子类可以重写该方法,做一些释放资源的操作。