java学习笔记:全部,txt版本
笔者注:
1.不知道怎么上传附件,所以就把txt文本内容全部贴在这里吧。
2.已经把txt版本的笔记上传到CSDN了,我没有设置索要积分才能下载,但是不知道为什么CSDN下载这个资料会需要积分。
一、java的常量
1. 常量的定义:在程序执行过程中,其值不可以发生改变的量。
2. 常量的分类:
(1) 字符串常量 被双引号包裹起来的字符串,如"hello world",""(这是空字符串)
(2)整数常量 如12
(3)小数常量 如12.34
(4)字符常量 被单引号包裹起来的单个字符,如'a'
(5)布尔常量 true,false 打印出来也是true,false
(6)空常量 null
3.常量数据在转换时,按int型转换
二、java变量的数据类型
1. java是一种强类型语言,针对每一个数据都给出了明确的数据类型。
2.java变量数据类型的分类:分为基本数据类型、引用数据类型(类、接口、数组)。String属于类,不属于数据类型。
3. java变量基本数据类型的分类:四类八种
(1)整型:byte 1个字节,short 2,int 4,long 8。默认是int型。
(2)浮点型:float 4,double 8。默认是double型。注意:double,float类型的变量在做算数运算时,要注意有精度丢失的问题,如果做关系运算,就不需要考虑精度丢失的问题。
(3)字符型:char 2,如'a'
(4)布尔型:boolean 1
4. 变量的定义格式:
数据类型 变量名称=初始化值;
5.注意事项:定义long型的数据,末尾加L或l,建议加L;定义float型的数据,末尾加F或f,建议加F。
6.基本数据类型在类中的默认值
byte,short,int long的默认值为0零
float,double的默认值为0.0
char的默认值为'\u0000'优零
boolean的默认值为false
引用类型的默认值为null
三、标识符identifier
1. 定义:就是用来给包、类、变量、方法等起名字的符号
2.组成规则
(1)unicode字符、数字字符、英文大小写字符、汉字(不建议使用汉字)
(2)下划线_
(3)美元符号$
3.注意事项:
(1)不能以数字开头
(2)不能是java的关键字
4.命名规范:
(1)见名知意,最好用英文、汉语拼音
(2)给类、接口命名,每个单词的首字母要大写,如MaxAge
(3)给方法、变量命名,从第二个单词开始,首字母要大写,如maxAge
(4)给常量命名,每个字母都大写,多个单词之间用下划线隔开,如MAX_AGE
(5)给包命名,全部字母小写,多级包之间用"."隔开,一般用公司域名倒过来,如cctv.com的包名可以写成com.cctv
(6)给项目命名:和类或者和方法的名称一致即可。
四、类型转换Type Conversion
1.分类:隐式转换、强制转换
2.隐式转换
(1)byte, short, char ---> int --->long ---> float ---> double
(2)boolean类型不参与这样的运算
(3)注意:byte, short, char在做运算时,无论遇没遇到int,都会首先提升为int,再参与运算。
(4)byte b = 3 + 4;//这里3和4都是常量,而常量会优先做运算,按int型计算编程7,再参与运算。
3.强制转换
(1)格式:目标类型 变量名称 = (目标类型)(被转换的数据);
(2)注意:虽然可以做强制转换,但是不建议,因为强制转换可能有数据的丢失。只有不得不转换,而且你确定不会出现问题的时候,才可以使用强制转换,否则不要强用。
五、写程序时,做数据测试,应该考虑这几种情况:(1)正确数据;(2)边界数据;(3)错误数据。
六、数组
1. 定义:数组是存储同一种数据类型的多个变量的容器。其中每一个变量叫做元素,元素的编号叫做索引。
2. 格式:
(1)数据类型[] 数组名;//推荐用这个
(2)数据类型 数组名[];
3.
int[] arr;//定义了一个int类型的数据,数组名是arr。--推荐用这个
int arr[];//定义了一个int类型的变量,变量名是arr数组。
4.数组的初始化
(1)数组的初始化,就是为数组开辟内存空间,并为数组中的每一个元素赋予初始值。
(2)数组初始化的分类:动态初始化、静态初始化
(3)动态初始化:只给出长度,由系统给出初始化值。
格式:数据类型[] 数组名 = new 数据类型[数组长度];
举例:int[] arr = new int[3];
左边:int:说明数组中的元素类型是int类型
[]:说明这是数组
arr:这是数据的名称
右边:new:为数组分配内存空间
int:说明数组中中的元素是int类型
[]:说明这是数组
3:数组长度,就是数组中的元素个数
赋值:数组名[索引]=数据;
获取元素的格式:数组名[索引]
(4)静态初始化:给出初始值,由系统决定长度。
格式:数据类型[] 数组名 = new 数据类型[]{元素1,元素2,...};
简化格式:数据类型[] 数组名 = {元素1,元素2,...};
5.如果两个数组拿的是同一个堆内存的地址,那么任何一个数组对堆内存数据进行了修改,另一个访问到的也是修改过的。例子:夫妻-娃
int[] arr_1=new int[3];
int[] arr_2=arr_1;//把arr_1数组的地址赋值给arr_2数组,这样两个数组都指向了同一个堆内存空间。
6.数组操作的常见问题
(1)ArrayIndexOutOfBoundsException数组索引越界异常,因为访问了不存在的索引。
(2)NullPointerException空指针异常,因为null是指不再指向堆内存的数据,而程序还要访问它。
空常量null是可以复制给引用数据类型的,表示该引用不再指向堆内存的数据。
(3)代码示例:
int[] arr = {1,2,3};
System.out.println(arr[3]);//java.lang.ArrayIndexOutOfBoundsException
arr = null;
System.out.println(arr[0]);//java.lang.NullPointerException
7.用数据的length属性获取数组元素个数,格式:数组名.length
例子:遍历数组 for(int i = 0; i < arr.length; i++){System.out.println(arr[i]);}
例子:获取数组最值
解题思路:
1.找一个参照物,但是这个参照物只能是该数组中的元素,不能是外部的。
2.拿数组中第一个元素作为参照物。max = arr[0]; min=arr[0];
3.遍历数组,从第二个元素开始,依次与参照物比较,如果元素比max大,那么久留下来做参照物。if(max<arr[i]) max=arr[i]; if(min>arr[i]) min=arr[i];
4.再比较下一个元素。最后输出结果。
例子:不死神兔
有一对兔子,从出生后第三个月起,每个月都生一对兔子,小兔子长到第三个月后,每个月又生一对兔子。假如兔子都不死,问第20个月,兔子对数是多少?
解题思路:
先找规律
规律 对数
第一个月 1
二 1
三 2
四 3
五 5
……
找出的规律为:从第三个月开始,每个月的兔子对数都是前两个月兔子对数相加之和,第一个月和第二个月的兔子对数都是1。
分析:
1.由于数据比较多,所以我们定义数组实现。int[] arr=new int[20];
2.给数据元素赋值。arr[0]=1;arr[1]=1;
3.从第三个月开始,根据规律赋值。for(int i=2;i < length;i++)arr[i]=arr[i-1]+arr[i-2];
8.数组常用的方法
import java.util.Arrays;
Arrays.sort(arr);//可以对数组从小到大排序,只能对一维数组进行排序。
System.out.println(Arrays.toString(arr));//可以把数组元素拼接成一个好看的字符串,输出:[16, 13, 28, 56]
9.二维数组
(1)数组是引用数据类型,所以里面可以再放数组,叫做二维数组。
(2)定义:
int[][] arr;//推荐
int arr[][];
(3)初始化
int[][] arr=new int[3][5];//3代表这个二维数组中有3个一维数组,每个一维数组里有5个元素
int[][] arr={{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15}};
(4)遍历二维数组
for(int i=0;i<arr.length;i++){//遍历的是二维数组里所有的一维数组
for(int j=0;j<arr[i].length;j++){//遍历的是一维数组里的每一个元素
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
10.数组的缺点:数组的长度是固定的。以后工作中用集合。
面向对象
1.OOP的特点:封装encapsulation,继承inheritance,多态polymorphism
2.类的成员:成员变量、成员方法
(1)成员变量:定义在类中、方法外的局部变量。
(2)成员变量与局部变量的相同点和区别:
相同点:成员变量与局部变量的定义是相同的。
区别:
A.在类中的位置不同。成员变量的位置在类中方法外。局部变量在方法内或者方法声明上(形式参数)。
B.在内存中的位置不同。成员变量在堆内存。静态成员变量随着类的加载而加载,随着程序的结束而消失。局部变量在栈内存。
C.生命周期不同。成员变量随着对象的创建而存在,随着对象的消失而消失。局部变量随着方法的调用而存在,随着方法的调用完毕而消失。
D.初始化值不同。成员变量有默认值,可以不用初始化就可以说使用。局部变量没有默认值,必须先定义(即初始化)、赋值,最后使用。
(3)成员方法:与方法的定义相同,区别是成员方法去掉了修饰符的static关键字。
3.类的使用:就是使用该类的成员(成员变量和成员方法)。
要想使用一个类的成员,就必须先拥有该类的对象,即创建对象。
(1)格式:类名 对象名=new 类名();
(2)对象访问成员:A.访问成员变量:对象名.成员变量
B.访问成员方法:对象名.成员方法(...)
封装
1.基本概念
(1)把成员变量都用private修饰,对外提供public修饰的getXxx()、setXxx()方法。
(2)封装的原则:A.将不需要对外提供的东东都隐藏起来。B.把属性隐藏,提供公共方法对其访问。
(3)封装的好处:增加了代码的安全性和复用性。 弊端:牺牲了一些效率。 但是利大于弊,因为变量是不可控的,方法是可控的。
(4)方法中成员变量访问的原则:就近原则
(5)访问变量:局部变量.成员变量
2.this:代表所在类的对象引用,即代表当前的那个对象。谁调用的,this就代表谁。
(1)如果局部变量名和成员变量名称一致,在方法中使用的时候采用的是就近原则,所以用this。
(2)方法被那个对象调用,this就代表谁。
(3)使用场景:就是用于解决成员变量被隐藏的问题。(用在局部变量隐藏成员变量的情况,即局部变量与成员变量重名)
3.构造方法Constructor:用于给对象的数据进行初始化
(1)格式:
A.方法名和类名相同
B.没有返回值类型,连void都不能写
C.没有具体的返回值
D.构造方法可以重载
类名 对象名=new 构造方法名(...)
(2)构造方法的注意事项
A.如果我们没有给出构造方法,系统将给出一个默认的无参构造方法供我们使用。
B.如果我们给出了构造方法,系统将不再提供构造方法供我们使用。
C.构造方法也是可以重载的,一般的时候我们写构造方法写两个:空参构造方法、全参构造方法。
(3)给成员变量赋值的方法
A.setXxx()
B.构造方法
(4)构造方法的使用
给属性初始化:构造方法是在创建对象的时候给属性进行初始化,只能用一次。
用setXxx()方法进行初始化:当创建完对象后,再想修改属性值就需要用到setXxx()方法了。
继承
格式:public class 类名 extends 父类{}
1.构造方法不能继承。
2.开发的原则:高内聚,低耦合
高内聚:一个类或一个方法只完成一个功能,并且不依赖于其它的类或方法。就是自己完成某件事的能力。
低耦合:修改完一个方法后,其它的方法基本不需要动代码,维护起来很方便。类与类的关系尽量减少。
3.java中类的继承特点:(1)java中类只支持单继承,不支持多继承。(2)支持多层继承。
4.就近原则:成员变量名称相同时,在子类方法中访问变量,
(1)在方法的局部范围找,如果有就使用。
(2)在子类的成员范围找,如果有就使用。
(3)在父类的成员范围找,如果有就使用。
(4)如果还找不到,就报错。
5.this和super
(1)this代表本类对象的引用。super代表父类的存储空间,也可以理解为代表父类对象的引用。
(2)用法:
A.访问成员变量:this.成员变量。super.成员变量
B.访问构造方法:this(...) 调用本类的构造方法。super(...) 调用父类的构造方法
C.访问成员方法:this.成员方法。super.成员方法
6.java继承中构造方法的访问特点
(1)子类构造方法执行前,都会先执行父类的无参构造方法。因为子类会继承父类的成员方法,父类必须先初始化好了,子类才能继承。
(2)在子类的构造方法中,默认第一行有一条语句super(),即必须在构造方法的第一行。
如果子类没有写super(),系统会默认加上一条。如果我们写了构造方法,系统就不会给我们提供构造方法了。
问题:如果父类中没有无参构造方法,怎么办呢?
回答:A.在父类中添加一个无参构造方法。(推荐)B.可以通过super去访问父类的带参构造方法。
我们在写构造方法的时候,最好写上无参构造方法和全参构造方法。
7.java继承中成员方法的访问特点:也是就近原则
子类中方法和父类中方法的声明相同时(这叫方法的覆盖override),调用的是子类中的方法。
8.重载overload:在同一个类中出现的方法名相同的情况
覆盖override:子类中出现了和父类中一模一样的方法声明的情况
9.方法覆盖的注意事项
(1)@override注解 标记一个方法,表名该方法是覆盖父类的方法
(2)父类私有的方法不能被覆盖
(3)子类覆盖父类方法时,访问权限不能更低 private<默认修饰符friendly<protected<public
public:本类、别的包的类、本包的类
protected:本包中的类、其他包子类中
friendly:只能在本包中的类
private:只能在本类中
10.分析案例的时候是从子类分析到父类,写代码的时候是从父类写到子类。
多态
1.java多态的前提
(1)有继承关系
(2)有方法覆盖(不是必须的)
(3)有父类引用指向子类对象 Fu f=new Zi();
Dog dog = new Dog();//我说这只狗是狗
Animal ani = new Dog();//我说这只狗是只小动物
2.多态中成员访问的特点
(1)成员变量:编译看左边,运行看左边
(2)成员方法:编译看左边,运行看右边
3.为什么成员变量和成员方法的访问不一样呢?
因为成员方法有覆盖,而成员变量没有覆盖。
4.多态的缺点:父类不能访问子类特有的方法。解决办法:向下转型。
多态的优点:提高了程序的扩展性。
具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作。
fun(Animal a){...}
Cat c = new Cat();
调用:.fun(c);//等价于Animal a=new Cat();
5.多态中的转型问题
(1)A.向上转型:从子到父,父类引用指向子类对象。Animal ani=new Dog();
B.向下转型:从父到子,父类引用转为子类对象。
注:强制转换可能会发生ClassCastException类型转换异常
(2)在做向下转型之前,要先做判断
if(ani instanceof Dog){
Dog dog=(Dog)ani;
dog.lookDoor();
}
instanceof左边的对象应该是右边类的父类或同类对象。
6.静态成员变量:随着类的加载而加载,随着程序的消失而消失。以后会学集合、数组,假如集合、数组被static修饰了,如果你的这个集合或数组比较庞大,占用内存比较大,那么在程序结束之前,它始终会占据着内存空间。如果内存比较小的话,就会出现内存溢出异常。解决的办法:1.加内存;2.不要用static来修饰这种比较占内存的集合。
final和static关键字
1.final
(1)final是一个关键字,表示最终的意思。可以用来修饰类、修饰变量、修饰方法。举例:绝育手术
(2)final修饰类的时候:表明该类是最终类,不能被继承。
(3)final修饰成员变量:表明该变量是常量,不能再次被赋值,只能赋值一次。
(4)final修饰方法:表明该方法是最终方法,不能被覆盖override。
2.static
(1)static是一个关键字,表示静态的意思。可以用来修饰成员变量和成员方法。
(2)static修饰成员的特点:
A.被类的所有对象共享。其实也是判断一个成员是否应该用static修饰的条件。
B.可以通过类名直接访问。类名.static成员。推荐使用。
C.优先于对象存在。优先于所有对象加载。
D.随着类的加载而加载。(它在方法区中的静态区里)
3.使用场景及原因
package com.mystudy.constants;
public class Constants{
/*放各种常量:地址、网址、硬盘上的地址*/
public static final String ADDRESS = "中国北京";//调用:Constants.ADDRESS
//static修饰:是为了用类名进行访问,用到常量的时候。
//final修饰:不让外界进行修改
//public修饰:可以在别的类中尽心访问
}
4.访问权限
(1)非静态的成员方法:
A.能访问静态的成员变量
B.能访问非静态的成员变量
C.能访问静态的成员方法
D.能访问非静态的成员方法
(2)静态的成员方法:
A.能访问静态的成员变量
B.能访问静态的成员方法
(3)注意事项:静态成员方法中不能出现this、super这样的关键字。愿意你:静态是随着类的加载而加载,this、super这样的关键字是随着对象的创建而存在的。(开发的原则:先进内存的东东不能访问后进内存的东东。)
抽象类
1.在java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类。
2.抽象类、抽象方法都必须用abstract修饰。
public abstract void eat();
public abstract class Animal{}
3.抽象类的特点:
(1)抽象类和抽象方法都必须用abstract来修饰。
(2)抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
(3)抽象类不能实例化,不能直接创建对象,但可以通过多态来创建对象。
抽象类如何实例化呢?参照多态的方式,通过子类实例化。
Animal ani = new Cat();
ani.ea();
ani.sleep();
(4)抽象类的子类:要么重写抽象类中的所有抽象方法,要么也是抽象类。
4.抽象类的成员特点:
成员变量:有成员变量,成员变量可以是变量,也可以是常量。
构造方法:有构造方法。抽象类中构造方法的作用是用于子类访问父类数据的初始化。
成员方法:有成员方法,成员方法可以是抽象的,也可以是非抽象的。
抽象方法:限定子类必须完成某些动作,起到一个规范和统一的作用。
非抽象方法:提高代码的复用性。
5.思维的方式:
分析:从具体到抽象
实现:从抽象到具体
使用:使用的是具体的类的对象。
举例:老师、基础班老师、毕业班老师
接口
1.类或抽象类:放一些共性的东西,大家都有的东西,即“你会我也会,你有我也有。”
接口:定义的是额外的扩展性的东西,不是每个人都会的。
2.接口的特点:
(1)定义接口使用的是interface关键字。
(2)类和接口之间是实现关系,用implements关键字。class implements interface
(3)接口不可以创建对象,可以使用多态的形式创建对象。它连构造方法都没有。
接口有没有其他的方式实例化呢?参照多态的形式使用类来实现实例化。
Jumping j = new Cat();
j.jump();
多态的几种形式:
A.具体类多态(几乎不用)
B.抽象类多态
C.接口多态(最常用)
(4)接口的实现类 一个类实现一个接口。
要么重写接口中的所有抽象方法,要么这个类就是抽象类。
3.接口的成员特点:
成员变量:有成员变量,而且只能是常量。有默认的public static final修饰符,即是静态常量。
构造方法:没有构造方法
成员方法:有成员方法,而且都是抽象的。有默认的public abstract修饰符,即是抽象方法。
4.关系区别
类与类:继承关系。只能单继承,可以多层继承。
类与接口:实现关系。可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
接口与接口:继承关系,可以单继承,也可以多继承。
5.抽象类和接口的区别
(1)成员区别:
抽象类:成员变量:可以是变量,也可以是常量。构造方法:有。成员方法:可以是抽象方法,也可以是非抽象方法。
接口:成员变量:只能是常量。成员方法:只能是抽象方法。
(2)关系区别
类与类:继承关系。只能单继承,可以多层继承。
类与接口:实现关系。可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
接口与接口:继承关系,可以单继承,也可以多继承。
(3)设计理念的区别
抽象类:被继承,体现的是is a。抽象类中定义的是继承体系的共性功能。
接口:被实现,体现的是like a。抽象中定义的是该体系的扩展功能。
四种权限修饰符
1.本类 2.同一个包下(子类和无关类) 3.不同包下(子类和无关类)
3.1子类 3.2无关类
private 1.Y
默认(friendly) 1.Y 2.Y
protected 1.Y 2.Y 3.1Y
public 1.Y 2.y 3.1Y 3.2Y
equals方法
1.功能:比较两个对象是否相等。如果没有重写的话,比较的是两个对象的地址值。重写后,可以比较两个对象的属性值。
2.==:
(1)可以比较基本数据类型,比较基本数据类型的时候,比较的是基本数据类型的值是否相同。
(2)可以比较引用数据类型,比较引用数据类型的时候,比较的是引用数据类型的地址是否相同。
3.如果想比较引用对象的内容是否相同,重写public boolean equals(Object obj){...}
4.s1.equals(s2);//this=s1,哪个对象调用的equals,this就是谁。obj=s2,被调用时的参数是哪个对象,obj就是谁。
5.重写equals方法
业务流程:
(1)地址值
(2)对象是否为空null
(3)是否是同一个类的对象
(4)obj向下转型,开始比较各个属性值。
(5)比较基本数据类型
(6)比较引用数据类型:a.对象是否为null。b.值。
具体代码:
public boolean equals(Object obj){
//调用:cat.equals(cat2);
//this: cat
//obj: cat2
if(this == obj) return true;//比较cat和cat2的地址是否相同。如果相同,说明它们是同一个对象,肯定相同。
if(obj == null) return false;//看cat2是否为null。cat肯定不为null。如果cat为null,会报NullPointerException,根本走不到这个方法里。
if(getClass() != obj.getClass()) return false;//看两个对象cat和cat2是不是同一个类的对象。如果不是同一个类的对象,说明不是同一个物种,直接return false,没有比较的必要。
Cat other = (cat)obj;//把obj向下转型,为什么呢?因为下面要比较cat和cat2的属性值。如果不向下转型,还是obj的话,是不能获取cat2的属性值的。
if(age != other.age) return false;//由于age属性是基本数据类型,可以直接用关系运算符比较。
if(name == null){//看cat/this的name属性是否为null。这里为什么要判断name是否为null呢?如果name为null,两边比较name的属性值的时候,会报NullPointerException。
if(other.name != null) return false;//如果this.name为null,但是cat2.name不为null,说明它们不相同,直接返回false。
}else if(!name.equals(other.name)) return false;//比较this.name和cat2.name的值是否相同,如果不同,直接返回false。
return true;
}//the end
String类
1.String:代表字符串类。
字符串是由多个字符组成的一串数组。
字符串的本质就是一个字符数组。
2.构造方法
String(String original): 把字符串数据封装成字符串对象。String s=new String("Hello");
String(char[] value): 把字符数组的数据封装成字符串对象。char[] chs={'H','E','L','L','O'};String s=new String(chs);
String(char[] value, int index, int count): 把字符数组的一部分数据封装成字符串对象。String s=new String(chs, 1, 2);
3.最常用的方式-直接赋值:String s="hello";
4.String类创建对象的特点
(1)通过构造方法创建对象。通过构造方法创建的字符串对象是在堆内存中。
(2)通过直接赋值的方式创建对象。通过直接赋值的方式创建的字符串对象,是在方法区的常量池中。(推荐用这个方式)
字符串的内容存储在方法区的常量池。这样做事为了方便字符串数据的复用。
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = "hello";
String s4 = "hello";
System.out.println(s1 == s2);//false
System.out.println(s1 == s3);//false
System.out.println(s3 == s4);//true
代码
1.遍历字符串,即获取字符串中的每一个字符
String s = "16JKM13";
for(int i = 0; i < s.length(); i++){
char ch = s.charAt(i);//获取字符串s中的某个索引位置的字符串元素
if(ch >= '0' && ch <= '9'){//判断字符是不是数字
System.out.println(ch);//比较的是ASCII码值
}
}
//ch >='A' && ch <= 'Z' 大写字母
//ch >='a' && ch <= 'z' 大写字母
//ch -= ('a' - 'A'); 从小写字母变成大写字母 'a'-'A'=32, 'A'=65, 'a'=97
2.字符串的拼接
int[] arr = {11,22,3,4,5};
String s = "[";
for(int i = 0; i < arr.length; i++){
if(i == arr.length - 1){
s += arr[i];
}else{
s += arr[i] + ", ";
}
}
s += "]";
System.out.println(s);
//字符串拼接s+=... 因为String concat方法重载overload了
3.字符串反转 abc-->cba
String s = "abc";
String s_new = "";//空串
for(int i = s.length - 1; i >= 0; i--){
char ch = s.charAt(i);
s_new += ch;
}
System.out.println(s_new);
StringBuilder
1.StringBuilder:是一个可变的字符串类,内容是可变的,非常适合做字符串拼接操作。
String:内容是固定的,字符串都是常量。
2.构造方法
public StringBuilder();//创建一个空的字符串
public StringBuilder(String str);//把str这个字符串转成了StringBuilder对象
3.一些功能
(1)添加功能:public StringBuilder append(任意类型);//添加数据,并返回对象本身
链式编程:sb.append("hello").append("world");//反着看,这是一个方法,方法由对象调用;正着看,对象调方法,返回的是一个对象。
(2)反转功能:public StringBuilder reverse();
(3)删除功能:public StringBuilder delete(int start, int end);//移除此序列的子字符串中的字符
4.StringBuilder和String的相互转换
StringBuilder-->String: public String toString();//通过toString()就可以把StringBuilder转换为String
String-->StringBuilder: public StringBuilder(String s);//通过构造方法就可以把String转换为StringBuilder
5.练习:字符串反转
思路:String-->StringBuilder-->reverse-->String
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
StringBuilder sb = new StringBuilder(s);
sb.reverse();
String s2 = sb.toString();
StringBuffer和StringBuilder:
StringBuilder:线程不安全,效率高。
StringBuffer:线程安全,效率低一些。
====
字符串的一些常用方法
----
字符串方法需要接收返回结果。
public char charAt(int index);//返回字符串中某个索引位置的字符元素
public boolean endWith(String small);//判断字符串是否已小字符结尾
public boolean equals(Object anObject);//判断两个字符串是否相等
public boolean equalsIgnoreCase(String anotherString);//判断两个字符串是否相等,忽略大小写
public int indexOf(int ch);//获取字符或小字符串在大字符串中的位置,不存在的话返回-1
public int length();//返回字符中元素的个数,字符串的长度
public String replace(CharSequence small, CharSequence replace);//把大字符串中的小字符串替换成replace,替换所有
例如:
String s = "11, 22, 33, 44";
String replace = s.replace(",", "-");
System.out.println(replace);//11-22-33-44
public String replaceFirst(String small, String replace);//把大字符串中的小字符串替换成replace,只替换第一次出现的
例如:
String s = "11,22,33,44";
String replace = s.replaceFirst(",", "-");
System.out.println(replace);//11-22,33,44
public String[] split(String small);//用小字符串切割大字符串
例如:
String s = "11,22,33,44";
String[] split = s.split(",");
for(int i = 0; i < split.length; i++){
System.out.println(split[i]);
}
public boolean startsWith(String prefix);//判断大字符串是否以小字符串开头
public String subString(int beginIndex);//截取字符串,从索引beginIndex开始,截取到最后
例如:
String s = "abcdefg";
String subString = s.subString(3);
System.out.println(subString);//defg
public String subString(int beginIndex, int endIndex);//截取字符串,从beginIndex开始,到endIndex结束,包含beginIndex,不包含endIndex
例如:
String s = "abcedfg";
String subString = s.subString(1, 4);
System.out.println(subString);//bcd
public char[] toCharArray();//把字符串编程字符数组
例如:
String s = "abcdefg";
char[] chs = s.toCharArray();
for(int i = 0; i < chs.length; i++){
System.out.println(chs[i]);
}
public String toLowerCase();//把字符串中的大写字母变成小写字母
例如:
String s = "dfkdjALSJDLJF";
s = s.toLowerCase();
System.out.println(s);
public String toUpperCase();//把字符串中的小写字母变成大写字母
例如:
String s = "dfkdjALSJDLJF";
s = s.toUpperCase();
System.out.println(s);
public static String valueOf(boolean b);//把各种类型的数据转成字符串
例如:
String valueOf = String.valueOf(234);
System.out.println(valueOf);//234
====
Arrays类
----
1.Arrays类提供了对数组操作的各种方法
2.常用方法
public static String toString(int[] arr);//把数组转换成字符串
public static void sort(int[] arr);//对数组进行升序排序
3.Arrays的构造方法是私有的。
私有构造方法是为了不让外界使用创建对象。
成员都用static修饰,可以用类名直接访问。
4.常用工具类的设计思想:(1)构造方法私有。(2)成员都用static修饰。
Math.pow(base, n);//base的n次幂 数学工具类
Collections:关系集合的一些操作方法
====
基本类型的包装类
----
1.为了对基本数据类型进行更多更方便的操作,java为每一种基本数据类型提供了一个对应的引用类型。
基本数据类型的包装类最常用的做法,就是做基本数据类型和字符串的相互转换。
基本类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
String-->char:
String s = "a";
char c = s.charAt(0);
2.Integer类
构造方法:
Integer(int value);
Integer(String s);//s必须是数字字符串组成的,否则抛NumberFormatException
直接调用对象时,调用的是对象的toString()方法。toString()方法不重写,输出的是地址值。
把一个数字字符串转成一个包装类型,字符串里只能包含数字字符。
3.int与String类型的相互转换
Integer类的public int intValue();//Returns the value of this Integer as an int.
Integer类的public static Integer valueOf(String s);//Returns an Integer object holding the value of the specified String.
Integer类的public static Integer valueOf(int i);//Returns an Integer instance representing the specified int value.
Integer类的public static String toString(int i);//Returns a String object representing the specified integer.
Integer类的public static int parseInt(String s);//Parses the string argument as a signed decimal integer.
String类的public static String valueOf(int i);//Returns the string representation of the int argument.
int-->String:
方法一:int number = 100;
String s1 = "" + number;//比较方便,但不专业
方法二:String s2 = String.valueOf(number);
String-->int:
方法一:String-->Integer-->int
String s = "100";
Integer i = new Integer(s);
int x = i.intValue();
方法二:int y = Integer.parseInt(s);
总结:
int-->String: String::public static String valueOf(int i);
String-->int: Integer::public static int parseInt(String s);
4.练习题:把字符串排序"91 27 46 38 50"-->"27 38 46 50 91"
分析:
(1)定义一个字符串对象。
(2)把字符串中的数字数据存储到一个int型的数组中。
A.字符串拆分成字符串数组String::public String[] split(String regex);
B.字符串数组转成整型数组Integer::public static parseInt(String s);
(3)对int数组进行排序Arrays::public static void sort(int[] arr);对指定的int型数组按数字升序进行排序
(4)把排序后的数组中的元素进行拼接,得到一个字符串。
StringBuilder-->String:
StringBuilder.append(String s);
StringBuilder.toString();
代码:
import java.util.Arrays;
String s = "91 27 46 38 50";
String[] sArr = s.split(" ");
int[] numArr = new int[sArr.length];
for(int i = 0; i < numArr.length; i++){
numArr[i] = Integer.parseInt(sArr[i]);
}
Arrays.sort(numArr);
StringBuilder sb = new StringBuilder();
for(int i = 0; i < numArr.length; i++){
if(i ==numArr.length-1) sb.append(numArr[i]);
else{sb.append(numArr[i]).append(" ");}
}
String s2 = sb.toString();
System.out.println(s2);
5.自动装箱和拆箱 JDK5提供的
(1)自动装箱:把基本数据类型转换为对应的包装类类型。Integer::public static Integer valueOf(int i);//Returns an Integer instance representing the specified int value.
(2)自动拆箱:把包装类类型转换为对应的基本数据类型。Integer::public int intValue();//Returns the value of this Integer as an int.
Integer i = 100;//自动装箱,这行代码等价于:Integer i = Integer.valueOf(100);
i +=200;//i = i + 200; 自动拆箱,然后自动装箱。这行代码等价于:i = Integer.valueOf(i.intValue() + 200);
Integer i3 = null;i3 += 300;//NullPointerException
在使用包装类类型的新特性的时候,如果做操作,最好先判断是否是null。
开发中的原则:只要是对象,在使用前就必须进行不为null的判断。
====
Date类和SimpleDateFormat
----
1.Date类:表示特定的瞬间,精确到毫秒
所属的包:import java.util.Date;
构造方法:
Date();//根据当前时间创建的日期对象
Date(long date);//从1970-01-01 00:00:00开始计算的时间,毫秒
long date = 1000(毫秒)*60(分钟)*60(小时),即1小时,因为是东八区,最后给出的是9点。用long类型是因为int类型会出现溢出。
long time = (long)1000*60*60*24*30,即一个月
Date d = new Date(time);//1970-01-31 08:00:00
2.Date对象<-->毫秒值
从Date对象得到一个毫秒值:public long getTime();//获取的是毫秒值,从1970-01-01 00:00:00开始
从一个毫秒值得到一个Date对象:方法一:public void setTime(long time);//设置时间,给的是毫秒值 方法二:构造方法Date(long time);
3.SimpleDateFormat
所属的包:java.text.SimpleDateFormat
功能:格式化和解析日期的具体类
格式化日期-->文本,Date-->String: public final String format(Date date);
解析文本-->日期,String-->Date: public Date parse(String source);//可能会有java.text.ParseException
代码:
//Date-->String
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);//2019年07月10日 15:37:59
可以自己定义pattern,比如:
HH:mm:ss
yyyy-MM-dd HH:mm:ss EEE(EEE表示星期几)
因为Date对象在创立时,已经给每个字符设置了属性
//String-->Date 把一个字符串解析为日期的时候,要注意模式字符串和给定的日期字符串的格式要匹配
String str = "2080-08-08 12:23:45";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(str);//throws ParseException
System.out.println(d);//Thu Aug 08 12:23:45 CST 2080
4.写Date工具类DateUtil
把日期转换为指定格式的字符串
把指定格式的字符串解析为日期
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtil{
private DateUtil(){}
public static String dateToString(Date date, String pattern){
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
String s = sdf.format(date);
return s;
}
public static Date StringToDate(String data, String pattern) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date d = sdf.parse(data);
return d;
}
}
5.Calendar日历类
所在包:java.util.Calendar
Calendar cal = Calendar.getInstance();//创建Calendar的对象,表示的是当前时间
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1;//第一个月是0,第二个月是1
int day = cal.get(Calendar.DAY_OF_MONTH);
int week = cal.get(Calendar.DAY_OF_WEEK) - 1;//星期日是1,星期一是2
cal.set(Calendar.YEAR, 2018);//设置年
int year2 = cal.get(Calendar.YEAR);//获取年
//Calendar的转换功能
Date date = new Date(1000);
cal.setTime(date);//可以把date表示的时间设置给cal, Date-->Calendar
Date time = cal.getTime();//用cal的获取时间的方法可以获取到date对象,Calendar-->Date
====
对象也可以比较大小
----
如果想比较两个对象的大小,可以通过实现Comparable接口,重写compareTo方法。
接口,比较器:java.lang.Comparable
方法:public int compareTo(T o);
调用方法:int i = stu1.compareTo(stu2);//this:stu1; o:stu2
如果大于,用1表示;
如果小于,用-1表示;
如果相等,用0表示。
类声明:
public class Student implements Comparable<Student>{
@Override
public int compareTo(Student o){
if(this.getAge() > o.getAge()){return 1;}//大于
else if(this.getAge() < o.getAge()){return -1;}//小于
else{return 0;}//相等
}
}
====
集合
----
一、集合的体系结构:见截图
二、Collection集合的基础知识
1.所在包:java.util.Collection
2.public interface Collection<E> extends Iterable<E>
3.Collection是单列集合的顶层接口。
它表示一组对象,这些对象也称为Collection的元素
JDK不提供此接口的任何直接实现。
创建Collection集合对象采用的是多态的方式。
4.<E>是一种特殊的数据类型,泛型
使用:在出现E的地方使用引用数据类型替换即可,如Collection<String>
import java.util.ArrayList;
Collection<String> c = new ArrayList<>();//JDK7的新特性
Collection<String> c = new ArrayList<String>();//多态形式,推荐
//boolean add(E e);添加元素
c.add("Hello");
c.add("World");
5.输出集合元素System.out.println(c);
ArrayList重写了toString()方法
6.集合中不能存放基本数据类型,如果想放基本数据,就用它对应的包装类
Collection<Integer> coll2 = new ArrayList<>();
三、Collection的成员方法
1.boolean add(E e);//添加元素
boolean add(int index, E e);//在指定位置添加元素
通过查看源代码,我们知道ArrayList集合的add方法返回值永远都是true。
2.boolean remove(Object o);//根据对象从集合中删除元素
boolean remove(int index);//根据索引从集合中删除元素
3.void clear();//清除集合中的元素
4.boolean contains(Object o);//判断集合中是否存在指定的元素
5.boolean isEmpty();//判断集合是否为空,空true,非空false
6.int size();//集合的长度,也就是集合中的元素个数
四、Collection集合的遍历
1.没有索引,不能用普通for
2.所在的包:java.util.Iterator
3.Iterator<E> iterator();//返回此Collection的元素上进行迭代的迭代器
Iterator<String> it = c.iterator();//返回的是迭代器接口的实现类的对象
通过集合对象调用iterator()方法,得到迭代器对象。
4.Iterator E next();//返回迭代的下一个对象,获取到下一个元素,并一道下一个,等待
NoSuchElementException:没有这样的元素异常
Iterator boolean hasNext();//如果仍有元素可以迭代,则返回true
while(it.hasNext()){
String s = it.netxt();
System.out.println(s);
}
5.代码
import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;
Collection<String> c = new ArrayList<String>();//创建集合对象
c.add("小静");//给集合中添加字符串元素
c.add("大熊");
c.add("康夫");
Iterator<String> it = c.iterator();//通过集合对象获取到迭代器对象
while(it.hasNext()){//使用迭代器对象的方法获取数据
String s = it.next();//在一次循环体中,只能调用一次it.next()
System.out.println(s);
}
6.迭代器是集合遍历的一种方式,依赖于集合而存在。
五、集合的使用步骤
1.创建集合对象
2.创建元素对象
3.把元素添加到集合
4.遍历集合
(1)通过集合对象获取迭代器对象
(2)通过迭代器对象的hasNext()方法判断是否有元素
(3)通过迭代器对象的next()方法获取元素,并指向下一个位置
====
List集合
----
1.所属包名:java.util.List
2.List有索引,可以用普通for循环。(没有索引,就不可以用普通for循环。)
3.List是有序的Collection,也称为序列。
4.List集合的特点:
(1)有序(存储和取出元素的顺序一致)
(2)存储的元素可重复
5.代码
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
List<String> list = new ArrayList<String>();
list.add("hello");
//...
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
6.List集合特有的成员方法(完全是针对索引操作的)
void add(int index, E element);//在指定位置添加元素,后面的元素往后移
E remove(int index);//删除指定位置的元素,返回被删除的元素
E get(int index);//获取指定位置的元素
E set(int index, E element);//修改指定位置的元素,返回被修改的元素
7.List集合的普通for遍历
(1)迭代器
(2)普通for循环 int size()
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
8.List列表迭代器的特有功能
ListIterator<E> listIterator();//返回此列表元素的列表迭代器,允许程序员按任意方向遍历列表。正向遍历方式与Iterator相同。
public interface ListIterator<E> extends Iterator<E>;
ListIterator<String> list = list.listIterator();
特有功能:
E previous();//返回列表中的前一个元素
boolean hasPrevious();//如果以逆向遍历列表,列表迭代器有多个元素,则返回true
注意事项:ListIterator可以实现逆向遍历,但是要求先正向遍历,才能逆向遍历。
9.ConcurrentModificationException并发修改异常的原因及解决方案
产生的原因:迭代器依赖于集合而存在,在判断成功后,集合中添加了新元素,而迭代器并不知道,所以就报错了。即迭代器遍历集合中的元素的时候,不要使用集合对象去修改集合中的元素。
解决方案:
方案一:用列表迭代器时,迭代器遍历的时候,用迭代器修改集合中的元素。元素是跟在刚才迭代的元素后面的。
方案二:用普通for循环时,集合遍历的时候,可以通过集合对象修改集合中的元素。元素是在最后添加的。(推荐此方案)
方案一的代码:
ListIterator<Student> it = list.listIterator();
while(it.hasNext){
Student stu = it.next();
if(stu.equals(stu2)){
it.add(stu4);//用迭代器添加
}
}
方案二的代码:
for(int i = 0; i < list.size(); i++){
Student stu = list.get(i);
if(stu.equals(stu2)){
list.add(stu4);//用集合添加
}
}
10.正向删除时,要调整i,i--
逆向删除时,不用调整i
正向删除的代码:
for(int i = 0; i < list.size(); i++){
if(list.get(i).equals("5")){
list.remove(i);
i--;
}
}
逆向删除的代码:
for(int i = list.size(); i >= 0; i--){
if(list.get(i).equals("5")){
list.remove(i);
}
}
11.增强for:是for循环的一种
格式:
for(元素的数据类型 变量名:数组名或者Collection集合对象名){
使用变量名即可,这个变量名代表的就是数组或者Collection集合中的元素
}
例如:
for(int x:arr){
System.out.println(x);
}
好处:简化了数组和Collection集合的遍历
弊端:目标不能为null
在遍历前,对目标做不为null的判断:
if(list != null){
for(...:list){...}
}
增强for其实就是用来替代迭代器的。
用哪种循环?只想打印集合时,用增强for;如果有增删集合时,用普通for。
====
常见的数据结构
----
1.栈:先进后出。例如子弹夹
2.队列:先进先出。例如:排毒买票
3.数组:存储同一个数据类型的多个元素的容器,有索引,方便我们获取元素。特点:查询快,增删慢。
在数组中添加一个元素的步骤:
(1)定义一个新数组,长度是以前的数组的长度+1
(2)遍历以前的数组,得到每一个元素
(3)数前面的:按照以前的索引存储到新数组中。
数本身:继续存储。
数后面的:添加那个新元素,然后把索引+1存储到新数组中。
从数组中删除一个元素的步骤:
(1)定义一个新数组,长度是以前的数组的长度-1
(2)遍历以前的数组,得到每一个元素。
数前面的:按照以前的索引存储到新数组中。
数本身:不存储。
数后面的:把索引-1存储到新数组中。
4.链表:由一个链子把多个节点连接起来的数据。
节点:由数据和地址组成。
特点:查询慢,增删快。
获取链表中的元素:从头开始
在链表中插入一个元素的步骤(最好自己画一个图):
(1)新元素应该也是一个节点
(2)把数本身的下一个元素地址用tmp存储。
(3)把新元素的地址付给数本身的下一个元素地址处
(4)把tmp的值赋给新元素的下一个元素地址处
从链表中删除一个元素的步骤(最好自己画一个图):
(1)把数本身的下一个元素地址用tmp存储
(2)把tmp的值赋给数本身的前一个元素的下一个元素地址处。
底层数据结构是数组结构,用ArrayList,查询快,增删慢。
底层数据结构是链表结构,用LinkedList,查询慢,增删快。
如果数据查询多,就选ArrayList。
如果数据增删多,就选LinkedList。
不知道时,选ArrayList,因为数据量少。
LinkedList的使用与ArrayList的相似。
====
集合的工具类Collections
----
所属包:java.util.Collections
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.sort(list);//按正向排序
Collections.reverse(list);//反转
Collections.sort(list, Collections.reverseOrder());//倒序
Collection与Collections的区别:
Collection是单列集合的顶层接口。Collections是集合的工具类。
====
Set
----
Set:无序、唯一、元素不可重复,使用方式与Collection一致
在java.util包中,是一个接口,继承了Collection
1.不包含重复元素:HashSet不保证Set的迭代顺序,特别是它不保证该顺序恒久不变
2.什么时候使用?
(1)要对List集合进行去重。
(2)要求生成不重复的元素。
例子:在[1,10]生成10个不重复的数字。
代码:
import java.util.HashSet;
import java.util.Random;
HashSet<Integer> set = new HashSet<Integer>();
Random rd = new Random();
while(set.size() < 10){
int num = rd.nextInt(10) + 1;
//如果生成了相同的元素,这里是添加不了的。
//只要添加不了,size是不会变的。
set.add(num);
}
3.HashSet保证元素唯一性的原理:
通过查看add方法的源代码,我们知道了天价功能的执行过程中,是进行了数据的判断的,这个判断的流程是:
首先比较对象的哈希值是否相同。这个哈希值是根据对象的hashCode()计算出来的。
如果哈希值不同,就直接添加到集合中。
如果哈希值相同,继续执行equals()进行比较。返回的是true,说明元素重复,不添加。返回的是false,说明元素不重复,就添加。
如果我们使用HashSet集合存储对象,你要想保证元素的唯一性,就必须重写(override)hashCode()和equals()方法。注:是元素重写hashCode()和equals()方法。
代码:
import java.util.HashSet;
//创建集合对象
HashSet<String> hs = new HashSet<String>();
//添加元素
hs.add("Hello");//add调用的是Map.put()方法
hs.add("world");
//遍历集合
for(String s : hs){
System.out.println(s);
}
Map::public V put(K key, V value){
if(table == EMPTY_TABLE){
inflateTable(threshold);
}
if(key == null) return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for(Entry<K, V> e = table[i]; e != null; e = e.next){
Object k;
if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
4.因为我们存储的元素所属的类没有重写hashCode()和equals()方法,所以保证不了元素的唯一性,需要重写hashCode()和equals()方法。
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null)? 0 : name.hashCode());
return result;
}
@Override
public String toString(){
return "Student [name=" + name + ", age="+ age +"]";
}
5.遍历:迭代器、增强for,不能用普通for,因为Set没有索引。
====
Map集合
----
Map集合中的实现类的数据结构只针对键有效。
1.public interface Map(K, V);//将键映射到值的对象。一个映射不能包含重复的键,每个键最多只能映射到一个值。
举例:学生的学号和姓名
Map<String, String> map = new HashMap<String, String>();//创建集合对象
map.put("it001", "林青霞");//添加元素, put(K key, V value);
键不能重复。如果键重复,就会用新值替换这个键的老值。
System.out.println(map);//输出结果
2.常用的成员方法
V put(K key, V value);//添加元素,如果键是第一次存储,就直接存储元素,返回null。如果键不是第一次存储,就用值把以前的值替换,返回以前的值。
V remove(Object key);//根据键删除键值对的元素,返回要删除的值
void clear();//移除所有的键值对元素
boolean containsKey(Object key);//判断集合是否包含指定的键
boolean containsValue(Object value);//判断集合是否包含指定的值
boolean isEmpty();//判断集合是否为空
int size();//返回键值对的对数
3.Map的获取方法
V get(Object key);//根据键获取值。没有键就返回null。
Set<K> keySet();//获取所有键的集合
Collection<V> values();//获取所有值的集合
代码:
map.get("张无忌");//返回赵敏
Set<String> set = map.keySet();//获取所有键的集合
for(String key : set){
System.out.println(key);//郭靖,杨过,张无忌
}
Collection<String> values = map.values();//获取所有值的集合
for(String value : values){
System.out.println(value);//黄蓉,小龙女,赵敏
4.Map集合的遍历
因为Map集合是双列的,所以不能用迭代器,不能用增强for。
遍历方式一:键找值
思路:把Map看成是一个夫妻对的集合,A.把所有的丈夫给集中起来;B.遍历丈夫的集合,获取到每一个丈夫;C.根据丈夫去找对应的妻子。
即:A.获取所有键的集合(keySet()); B.遍历键的集合,获取到每一个键(增强for);C.根据键去找值(V get(Object key))。
代码:
//创建集合对象
Map<String, String> map = new HashMap<String, String>();
//添加元素
map.put("郭靖", "黄蓉");
map.put("杨过", "小龙女");
map.put("张无忌", "赵敏");
//获取所有键的集合
Set<String> set = map.keySet();
//遍历键的集合,获取到每一个键
for(String key : set){
//根据键去找值
String value = map.get(key);
System.out.println(key + "--" + value);
}
遍历方式二:键值对对象找键和值
思路:A.获取所有结婚证的集合;B.遍历结婚证的集合,得到每一个结婚证;C.根据结婚证获取丈夫和妻子
即:A.获取所有键值对对象的集合(Set<Map.Entry<K, V>> entrySet(););B.遍历键值对对象的集合,得到每一个键值对对象(增强for);C.根据键值对对象获取键和值(getKey(), getValue())。
代码:
//创建集合对象
Map<String, String> map = new HashMap<String, String>();
//添加元素
map.put("郭靖", "黄蓉");
map.put("杨过", "小龙女");
map.put("张无忌", "赵敏");
//获取所有键值对对象的集合Set<Map.Entry<K, V>> entrySet();
Set<Map.Entry<String, String>> set = map.entrySet();
//遍历键值对对象的集合,得到每一个键值对对象
for(Map.Entry<String, String> me : set){
//根据键值对对象,获取键和值
String key = me.getKey();
String value = me.getValue();
System.out.println(key + "--" + value);
}
练习题1.两种方式遍历
HashMap<String, Student>
键:String 学号
值:Student 学生对象
//创建集合对象
HashMap<String, Student> hm = new HashMap<String, Student>();
//创建元素对象
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 35);
Student s3 = new Student("王祖贤", 33);
//添加元素到集合中
hm.put("it001", s1);
hm.put("it002", s2);
hm.put("it003", s3);
//遍历
//遍历方式一:根据键找值
Set<String> set = hm.keySet();
for(String key : set){
Student value = hm.get(key);
System.out.println(key + "--" + value.getName() + "--" + value.getAge());
}
//遍历方式二:根据键值对对象找键和值
Set<Map.Entry<String, Student>> set2 = hm.entrySet();
for(Map.Entry<String, Student> me : set2){
String key = me.getKey();
Student value = me.getValue();
System.out.println(key + "--" + value.getName() + "--" + value.getAge());
}
练习题2:任选一种方式遍历
HashMap<Student, String>
键:Student 学生对象
值:String 学生住址
要求:如果学生对象的成员变量值相同,就说明是同一个键
代码:
//创建集合对象
HashMap<Student, String> hm = new HashMap<>(Student, String);
//创建元素对象
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 35);
Student s3 = new Student("王祖贤", 33);
//添加元素到集合中
hm.put(s1, "北京");
hm.put(s2, "东京");
hm.put(s3, "纽约");
//遍历,键找值
Set<Student> set = hm.keySet();
for(Student key : set){
String value = hm.get(key);
System.out.println(key.getName() + "---" + key.getAge() + "---" + value);
}
在Student类中重写hashCode()和equals()方法,这样就保证了键的唯一性。
====
集合与集合的嵌套
----
1.ArrayList嵌套HashMap
题目1:需求:ArrayList集合嵌套HashMap集合,并遍历。
定义一个ArrayList集合,它包含三个元素,每一个元素都是HashMap类型的,每一个HashMap集合的键和值都是String类型的。
键:String 丈夫的姓名
值:String 妻子的姓名
数据:
第一个HashMap集合的元素:
孙策 大乔
周瑜 小乔
第二个HashMap集合的元素:
郭靖 黄蓉
杨过 小龙女
第三个HashMap集合的元素:
令狐冲 任盈盈
林平之 岳灵珊
代码:
//创建集合对象
ArrayList<HashMap<String, String>> array = new ArrayList<HashMap<String, String>>();
//创建元素1
HashMap<String, String> hm1 = new HashMap<String, String>();
hm1.put(" 孙策", "大乔");
hm1.put("周瑜", "小乔");
//把元素1添加到array中
array.add(hm1);
...//创建元素2、3,添加到array中
//遍历ArrayList集合
//遍历方法一:键找值
for(HashMap<String, String> hm : array){
Set<String> set = hm.keySet();
for(String key : set){
String value = hm.get(key);
System.out.println(key + "--" + value);
}
System.out.println("----");
}
题目2:对List进行去重
ArrayList<String> list = new ArrayList<String>();
list.add("小红");
...
HashSet<String> set = new HashSet<String>(list);//把ArrayList放到HashSet里,可以给元素去重
for(String s : set){
System.out.println(s);
}
ArrayList<String> list2 = new ArrayList<String>(set);//再把HashSet转回ArrayList
for(String s : list2){
System.out.println(s);
}
题目3:HashMap嵌套ArrayList
需求:HashMap集合嵌套ArrayList集合,并遍历
定义一个HashMap集合,它包含三个元素,每个元素的键是String类型,值是ArrayList类型。
键:String 人物来自哪部电视剧
值:ArrayList 人物的名称,每一个ArrayList集合的数据是String类型的
数据:
第一个ArrayList集合的元素:三国演义
诸葛亮
赵云
第二个ArrayList集合的元素:西游记
唐僧
孙悟空
第三个ArrayList集合的元素:水浒传
武松
鲁智深
代码:
//创建集合对象
HashMap<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();
//创建元素1
ArrayList<String> arr1 = new ArrayList<String>();
arr1.add("诸葛亮");
arr1.add("赵云");
//把元素1添加到hm中
hm.put("三国演义", arr1);
...//创建元素2、3,添加到hm
//遍历集合
//遍历方法一
Set<String> set = hm.keySet();
for(String key : set){
System.out.println(key);
ArrayList<String> value = hm.get(key);
for(String s : value){
System.out.println("\t" + s);
}
}
//遍历方法二:键值对对象找键和值
Set<Map.Entry<String, ArrayList<String>>> entrySet = map.entrySet();
for(Map.Entry<String, ArrayList<String>> entry : entrySet){
System.out.println(entry.getKey());
ArrayList<String> value = entry.getValue();
for(String s : value){
System.out.println("\t" + s);
}
}
====
一些练习题
----
练习题1:统计字符串中每个字符出现的次数
String str = "...";
//HashMap<出现的字符, 字符出现的次数>
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
//遍历字符串
for(int i = 0; i < str.length; i++){
//取出一个字符
char ch = str.charAt(i);
if(map.containsKey(ch)){//如果包含ch,说明map统计过这个字符
Integer count = map.get(ch);//把原来统计的次数拿出来
map.put(ch, count+1);//把次数加1,再放回去
}
else{//如果map没有统计过ch的话
map.put(ch, 1);
}
}
Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();
for(Map.Entry<Character, Integer> entry : entrySet){
System.out.println(entry.getKey() + "(" + entry.getValue() + ")");
}
练习题2:删除集合中长度小于3的数据
练习题3:大小写转换
练习题4:集合反转排序
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.reverse(list);
Collections.sort(list);
Collections.sort(list, Collections.reverseOrder());
练习题5:录入10个随机不重复的随机数
Random rd = new Random();
HashSet<Integer> set = new HashSet<Integer>();
while(set.size() < 10){
int num = rd.nextInt(10);
set.add(num);
}
练习题6:循环录入多个字符串,以end结束,排序。
Scanner sc = new Scanner(System.in);
ArrayList<String> list = new ArrayList<String>();
while(true){
String s = sc.nextLine();
if(s.equals("end")){
break;
}
list.add(s);
}
Collections.sort(list);
System.out.println(list);
练习题7:给ArrayList去重
ArrayList<String> list = new ArrayList<String>();
list.add("小红");
list.add("小丽");
list.add("小红");
HashSet<String> set = new HashSet<String>(list);//用HashSet去重
for(String s : set){
System.out.println(s);
}
ArrayList<String> list2 = new ArrayList<String>(set);
for(String s : list2){
System.out.println(s);
}
练习题8:质数
int count = 0;
for(int i = 2; i < 100; i++){
boolean flag = true;
for(int j = 2; j < i; j++){
if(i % j == 0){
flag = false;
break;
}
}
if(flag){
System.out.println(i + " ");
count++;
if(count % 5 == 0){
System.out.println();
}
}
}
====
异常
----
异常就是程序出现了不正常的情况。
代码在编译或运行的时候出现的不正常的情况。
1.概念
(1)Throwable类是java语言中所有错误(Error)或异常(Exception)的超类。
(2)Error是Throwable的子类,用于指示合理的应用程序不应该试图补货的严重问题,如内存不足,即我们的程序不处理。
(3)Exception类及其子类是Throwable的一种形式,它指出了合理的应用程序想要捕获的条件。也就是说针对程序发生了Exception的情况是我们需要处理的问题。在Exception类里有所有异常情况。
(4)RuntimeException:是哪些可能发生在java虚拟机正常运行期间抛出的异常的超类。
(5)Exception分为运行期异常和编译期异常。
A.运行期的异常:在编译期是不处理的,在程序运行时出现了问题,需要我们回来修改代码。
B.编译期的异常:在编译期就必须处理,否则程序不能通过编译,就更不能正常地执行了。
2.异常的体系
Throwable:是根类,不是接口。
|--Error:我们处理不了的,如硬件、内存不足等。严重问题,不需要程序处理。
|--Exception:我们可以处理的异常。
|--运行时异常:代码在编译期不会报错,我们可以在出现问题后,修改代码,也可以想编译期异常一样,try...catch或throws。
|--编译时异常:代码在编译期会报错,如果不对异常进行处理,程序没有办法执行,即必须先处理再运行。
3.JVM对异常的默认处理方式
java.lang.ArithmeticException:异常的类名,包括包名
by zero:异常的原因
at ... method(....java 17):异常的位置
如果程序出现了问题,我们没有做任何处理,最终JVM会做出默认的处理。处理方案:
A.把异常的类名名称、异常的原因、异常出现的位置等信息在控制台输出。
B.让程序停止执行。
4.异常处理方案try...catch
(1)方案1:try...catch
格式:
try{
可能出现异常的代码;
}catch(异常类名 对象名){
异常的处理代码;
}
执行流程:程序从try开始执行,执行到哪里出现了问题,就会跳转到catch里面执行。执行完毕后,程序还能继续往下执行。
public void printStackTrace():把异常的错误信息输出到控制台。
在实际开发中,遇见异常会给出一个页面进行提示,我们暂时用printStackTrace来处理。
比较常用的格式:
try{
可能出现异常的代码;
}catch(异常类名 对象名){
异常的处理代码;
}
...
}catch(Exception e){//这一段catch代码要加载最后,用来接我们还未发现的异常
System.out.println("各种异常");
e.printStackTrace();
}
(2)方案2:throws
我们通过try...catch可以对异常进行处理了,但是并不是所有的时候,我们都有权限进行异常处理。也就是说,有些时候我们处理不了,但是这个时候异常是存在的,不处理也行,怎么办呢?
这个时候,java就提供了throws的处理方案。
格式:
throws 异常类名
注意这个格式必须跟在方法的括号的后面,在方法定义的后面
...method(...) throws Exception{...}
注意:编译时异常是必须要处理的,两种方案:try...catch或者throws。如果采用了throws这种方案,将来谁调用,还得进行处理。运行时异常可以不用处理,出现问题后,我们需要回来修改代码。
5.什么时候用try...catch,什么时候用throws?
正常情况下,出现异常要给用户一些提示,什么地方出现了问题。但是,不是每个方法里都可以和用户沟通。一般和用户沟通会有一个页面,但不是每个类都能弹出这个页面。
工具类的用throws,main或页面上的用try...catch。因为:工具类它只负责处理一些经常用的功能,它没有页面,不能和用户沟通,它的方法里如果出现异常,只能抛出throws。页面上、main这些可以和用户沟通的页面,可以对异常进行捕获try...catch,然后给用户一个友好的提示。
====
File类
----
所在包:java.io.File
File类是文件和目录路径名的抽象表示形式。也就是说,文件和目录是可以通过File封装成对象的。目录其实就是文件夹。
1.File构造方法
File(String pathname);//通过将给定 路径名 字符串 转换为 抽象路径名,来创建一个新File实例。
File(String parent, String child);//根据parent路径名 字符串和child路径名 字符串 创建一个新File实例。String parent必须是路径名
File(File parent, string child);//根据parent抽象路径名和child路径名 字符串创建一个File实例。
代码举例:把d:\\a\\b.txt转换为File对象
File f1 = new File("d:\\a\\b.txt");
File f2 = new File("d:\\a", "b.txt");
File f3 = new File("d:\\a"); File f4 = new File(f3, "b.txt");
2.File类的创建功能
public boolean createNewFile();//创建文件。如果文件不存在,创建文件并返回true;如果文件存在,创建文件失败,并返回false。
public boolean mkdir();//创建目录。如果目录不存在,创建目录并返回true;如果目录存在,创建目录失败,并返回false。
public boolean mkdirs();//创建多级目录
代码:
//在d盘目录下创建一个文件ee\f.txt
File f1 = new File("d:\\ee");
File f2 = new File("d:\\ee\\f.txt");
f1.mkdir();
f2.createNewFile();//throws IOException
3.File类的删除功能
public boolean delete();//删除文件和目录
File f1 = new File("a.txt");
f1.createNewFile();
注意:如果一个目录中有内容(目录或文件),就不能直接删除,要先删除目录或文件,再删除。即只能删除空目录。
绝对路径:是以盘符开始的路径。
相对路径:不以盘符开始,相对于当前的项目而言,在项目的目录下。
4.File类的判断和获取功能
判断功能:
public boolean isDirectory();//判断是否是目录
public boolean isFile();//判断是否是文件
public boolean exists();//判断是否存在
获取功能:
public String getAbsolutePath();//获取绝对路径
public String getPath();//获取相对路径
public String getName();//获取名称,文件全名+扩展名
5.代码示例:
File file = new File("aa/bb/cc/a.txt");
file.getParent();//返回文件的父路径,以字符串的形式返回
File parentFile = file.getParentFile();//返回文件的父路径,以文件的形式返回
parentFile.mkdirs();
file.createNewFile();
File dir = new File("aa/bb/cc");
File[] listFiles = dir.listFiles();//获取目录下所有的文件,以文件的形式返回
for(File file : listFiles){
System.out.println(file.getName());
}
String[] list = dir.list();//获取目录下所有的文件,以字符串的形式返回
for(String s : list){
System.out.println(s);
}
====
IO流
----
一、基本概念
IO流是用来处理设备Jan的数据传输问题。
数据流向:站在java程序的角度来看数据流向,从硬盘、文件读数据到java程序里,是输入流;从java程序里写数据到硬盘、文件,是输出流。
数据类型:分为字节流、字符流(字符流数据是通过notepad.txt打开后可以读懂的数据)
IO流常见的应用:文件复制、文件上传、文件下载
IO流分类:
A.按照数据流向分:输入流、输出流
B.按照操作类型分:字节流、字符流
二、字节流写数据
1.都是抽象基类
字节流:
InputStream 字节输入流
OutputStream 字节输出流(此抽象类是表示输出字节流的所有类的超类)
字符流:
Reader 字符输入流
Writer 字符输出流
字节流写数据outputStream,它的子类FileOutputStream文件输出流是用于将数据写入File
2.构造方法
FileOutputStream(String name);//创建一个向具有指定名称的文件中写入数据的输出文件流
//创建字节输出流对象,throws FileNotFoundException
FileOutputStream fos = new FileOutputStream("a.txt");//如果文件不存在,它会帮我们创建一个文件。如果文件存在,它会把文件内容清空。
创建字节输出流对象做了这样的三件事情:
A.调用系统功能创建了a.txt文件
B.调用写数据的方法
C.释放资源
fos.write(65);//wite(int b) 写到文件里的是65,是文本阅读器把65转成了A
fos.write(66);
fos.close();//关闭此文件输出流,并释放与此留有关的所有系统资源
3.字节流写数据的步骤:
A.创建字节输出流对象
B.调用写数据的方法
C.释放资源
4.构造方法
FileOutputStream(String name); throws FileNotFoundException
FileOutputStream(File file);
代码示例:
FileOutputStream fos = new FileOutputStream("b.txt");
FileOutputStream fos = new FileOutputStream(new File("b.txt"));
fos.write(65);
5.写字节 throws IOException
public void write(int b);//一次写一个字节
public void write(byte[] b, int off, int len);//一次写一个字节数组的一部分,off表示指定位置开始,len表示写几个字节
public void write(byte[] b);//一次写一个字节数组
代码示例:
byte[] bys = {65,66,67,68,69};
fos.write(bys);//ABCDE
//String类中的方法 String::public byte[] getBytes();把一个字符串转换为字符数组
fos.write("ABCDE".getBytes());
fos.write("ABCDE".getBytes(), 0, 3);//ABC
6.字节流写数据的常见问题
A.如何实现数据的换行?
不同的操作系统针对换行的符号识别是不一样的。
Windows: \r\n
Linux: \n
Mac: \r
加入换行符的代码示例:
FileOutputStream fos = new FileOutputStream("c.txt");
for(int i = 0; i < 10; i++){
fos.write("hello".getBytes());
//加入换行符号
fos.write("\r\n".getBytes());
}
fos.close();
B.如何实现数据的追加写入?
用构造方法带第二个参数是true的情况即可
FileOutputStream(String name, boolean append);
如果第二个参数为true,则将字节写入文件末尾处,而不是写入文件开始处。
7.字节流写数据加入异常处理
方法一:分开做异常处理(太繁琐)
方法二:放在一起做异常处理。这种方式代码虽然简洁了,但是释放资源的动作可能未执行到
方法三:try-catch-finally
代码:
FileOutputStream fos = null;
try{
fos = new FileOutputStream("d:\\d.txt");//如果操作系统没有d:这个盘符,就不会成功创建对象,fos就为null。
fos.write("hello".getBytes());
}catch(IOException e){
e.printStackTrace();
}finally{
if(fos != null){
try{
//释放资源
fos.close();//null调用方法,就会出异常
}catch(IOException e){
e.printStackTrace();
}
}
}
三、字节流读数据InputStream
FileInputStream 从文件系统中的某个文件中获得输入字节
1.构造方法
FileInputStream(String name);
代码示例:
FileInputStream fis = new FileInputStream("a.txt");
int by;
while((by = fis.read()) != -1){//读到文件的结尾,会返回-1
System.out.println((char)by);//一次读取一个字节
}
fis.close();//释放资源
2.字节流读数据的步骤:
A.创建字节输入流对象
B.调用读数据的方法
C.释放资源
3.字节流读数据
方式一:public int read();//一次读取一个字节,如果已经 到达文件末尾,则返回-1
方式二:public int read(byte[] b);//一次读取一个字节数组。从此输入流中将最多b.length个字节的数据读入一个byte数组中,返回值是读入缓冲区的字节总数,也就是实际的读取个数,如果已经到达文件末尾,则返回-1。
代码示例:
//String::public String(byte[] bytes);
//String::public String(byte[] bytes, int offset, int length);
byte[] bys = new byte[1024];//1024或1024的整数倍。因为1024byte=1M
int len;
while((len = fis.read(bys)) != -1){
System.out.print(new String(bys, 0, len));//不用println(),因为文件里有换行符
}
fis.close();
4.IO字节流的练习题
练习题1:
题目描述:文件复制,其实就是从一个文件中读数据(数据源),然后把数据写到另一个文件中(目的地)。
数据源:d:\\窗里窗外.txt -->读数据-->InputStream-->FileInputStream
目的地:林青霞.txt -->写数据-->OutputStream-->FileOutputStream
代码:
//封装数据源
FileInputStream fis = new FileInputStream("d:\\窗里窗外.txt");
//封装目的地
FileOutputStream fos = new FileOutputStream("林青霞.txt");
//读写数据
//方式1:一次读取一个字节
int by;
while((by = fis.read()) != -1){
fos.write(by);
}
//方式2:一次读取一个字节数组
byte[] bys = new byte[1024];
int len;
while((len = fis.read(bys)) != -1){
fos.write(bys, 0, len);
}
//释放资源
fos.close();
fis.close();
练习题2:
题目描述:复制图片文件
数据源:d:\\mn.jpg -->读数据-->FileInputStream
目的地:mn.jpg -->写数据-->FileOutputStream
代码:
//封装数据源
FileInputStream fis = new FileInputStream("d:\\mn.jpg");
//封装目的地
FileOutputStream fos = new FileOutputStream("mn.jpg");
//读写数据
byte[] bys = new byte[1024];
int len;
while((len = fis.read(bys)) != -1){
fos.write(bys, o, len);
}
//释放资源
fos.close();
fis.close();
四、字节缓冲流
字节流一次读写一个数组的速度比一次读写一个字节的速度快很多,这是加了数组这样的缓冲区效果。所以java提供了字节缓冲区流。
1.
BufferedOutputStream: 字节缓冲输出流
BufferedInputStream: 字节缓冲输入流
2.构造方法
BufferedOutputStream(OutputStream out);
使用这种构造方法,它提供了一个默认的缓冲区大小,所以一般我们使用默认的缓冲区就可以了。
为什么构造方法传递的是一个OutputStream,而不是具体的文件或路径呢?因为字节缓冲区流仅仅提供缓冲区,而真正的底层的读写数据还得需要基本的流对象进行操作。
代码示例:
FileOutputStream fos = new FileOutputStream("a.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
上述两行代码等价于:BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("a.txt"));
bos.close();//释放资源
3.写数据
bos.write(65);//写一个字节
bos.write("abcdefg".getBytes());//写一个字节数组
bos.write("abcdefg".getBytes(), 0, 4);//写一个字节数组的一部分
4.构造方法
BufferedInputStream(InputStream in);
代码示例:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
读取方式1:一次读取一个字节
int by;
while((by = bis.read()) != -1){
System.out.print((char)by);
}
读取方式2:一次读取一个字节数组
int len;
byte[] bys = new byte[1024];
while((len = bis.read(bys)) != -1){
System.out.print(new String(bys, 0, len));
}
//释放资源
bis.close();
5.字节流复制文件的效率
System类的public static long currentTimeMillis();//返回以毫秒为单位的当前时间
long start = System.currentTimeMillis();//记录开始时间
...//待测试效率的代码,如:methodA()
long end = System.currentTimeMillis();//记录结束时间
共耗时 = end - start;//毫秒
设计四个方案:
方案A:基本字节流,一次读写一个字节
方案B:基本字节流,一次读写一个字节数组
方案C:缓冲字节流,一次读写一个字节(要求记住)
方案D:缓冲字节流,一次读写一个字节数组(要求记住)
代码:
//方案A:基本字节流,一次读写一个字节
private static void methodA() throws IOException{
//封装数据源
FileInputStream fis = new FileInputStream("d:\\复制图片.avi");
//封装目的地
FileOutputStream fos = new FileOutputStream("copy.avi");
int by;
while((by = fis.read()) != -1){
fos.write((char)by);
}
fos.close();
fis.close();
}
//方案B:基本字节流,一次读写一个字节数组
private static void methodB() throws IOException{
//封装数据源
FileInputStream fis = new FileInputStream("d:\\复制图片.avi");
//封装目的地
FileOutputStream fos = new FileOutputStream("copy.avi");
byte[] bys = new byte[1024];
int len;
while((len = fis.read(bys)) != -1){
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
//方案C:缓冲字节流,一次读写一个字节(要求记住)
private static void methodC() throws IOException{
//封装数据源
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d:\\复制图片.avi"));
//封装目的地
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.avi"));
int by;
while((by = bis.read()) != -1){
bos.write((char)by);
}
bos.close();
bis.close();
}
//方案D:缓冲字节流,一次读写一个字节数组(要求记住)
private static void methodD() throws IOException{
//封装数据源
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d:\\复制图片.avi"));
//封装目的地
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.avi"));
byte[] bys = new byte[1024];
int len;
while((len = bis.read(bys)) != -1){
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
====
五、转换流
转换流=字节流+编码表
1.转换流出现的原因:因为字节流操作字符文件的时候,可能会出现乱码问题。
2.字节流读数据可能出现的问题:字节流一次读取一个字节的方式读取带有汉字的文件是有问题的。因为你读取到一个字节后,就转为字符在控制台输出了,而汉字是由2个字节组成的,所以这里会出问题。
3.文件复制的时候,字节流读取一个字节,写入一个字节,这个没有出现问题,是因为最终底层会根据字节做拼接,得到汉字。
4.汉字存储的规则:左边的字节数据肯定是负数,右边的字节数据可能是负数,也可能是正数,大部分情况下是负数。
代码示例:
String s1 = "hello";
String s2 = "你好";
byte[] bys1 = s1.getBytes();//[104,101,108,111]
byte[] bys2 = s2.getBytes();//[-60,-29,-70,-61]
System.out.println(Arrays.toString(bys1));
System.out.println(Arrays.toString(bys2));
创建一个输出流后,如果文件不存在,它会创建一个文件。如果文件存在,它会把文件清空。因为BufferedInputStream和BufferedOutputStream不能共用同一个文件。
六、编码表概述和常见的码表
1.编码表:由字符和对应的数据组成的一张表。
2.常见的编码表
ASCII:一个字节的7位表示数据,美国标准码表
ISO-8859-1:欧洲码表,一个字节的8位表示数据,兼容ASCII
GB2312:中文码表,兼容ASCII
GBK:中文码表的升级版,更多中文字符,兼容ASCII
UTF-8:可变长度的字符编码,用1-3个字节表示数据,又叫万国码表,兼容ASCII。用在网页上可以统一页面中的中文简体、繁体和其他语言的显示。
3.乱码问题:针对同一个数据,采用的编码和解码的编码不一致造成的。
为了不出现乱码的问题,要保证编码和解码采用统一的编码表。
七、字符串中的编码、解码问题
1.编码:
public byte[] getBytes(String charsetName) throws UnSupportedEncodingException;//使用指定的字符集将此String编码为byte序列,并将结果存储到一个新的byte数组中。默认编码为GBK。
String::public byte[] getBytes();//使用平台默认的编码表来编码
2.解码
String::public String(byte[] bytes, String charsetName);//通过使用指定的charset解码指定的byte数组,构造一个新的String
String::public String(byte[] bytes);//使用平台默认的编码表来解码
3.代码示例:
String s = "苍老师";
byte[] bys = s.getBytes("UTF-8");//编码
System.out.println(Arrays.toString(bys));
String s2 = new String(bys, "UTF-8");//解码
System.out.println(s2);
八、转换流的编码、解码
1.转换流其实就是一个字符流
转换流=字节流+编码表
2.字符输出流OutputStreamWriter:
public OutputStreamWriter(OutputStream out);//根据默认编码把字节流转换为字符流
public OutputStreamWriter(OutputStream out, String charsetName);//根据指定编码,把字节流数据转换为字符流
代码示例:
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"), "UTF-8");
osw.write("你好");//调用写数据的方法
osw.close();//释放资源
3.OutputStreamWriter写数据的方法
public void write(int c);//写一个字符
public void write(char[] cbuf);//写一个字符数组
public void write(char[] cbuf, int offSet, int len);//写一个字符数组的一部分
public void write(String str);//写一个字符串
public void write(String str, int offSet, int len);//写一个字符串的一部分
写完数据后,没有发现数据,为什么呢?因为需要手动刷缓冲。
字符流写数据需要手动刷新缓冲。void flush()刷新该流的缓冲。close()方法在关闭流之前,先做了刷新缓冲的动作。
1字符=2字节
文件中的数据存储的基本单位是字节。
代码示例:
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "UTF-8");
osw.write("我爱苍老师".toCharArray());//把字符串转成字符数组 String::char[] toCharArray() 将此字符串转换为一个新的字符数组
osw.write("我爱苍老师".toCharArray(), 0, 2);
osw.write("我爱苍老师");
osw.write("我爱苍老师", 0, 2);
4.字符输入流InputStreamReader
public InputStreamReader(InputStream in);//用默认的编码读数据
public InputStreamReader(InputStream in, String charsetName);//用指定的编码读数据
代码示例:
InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"), "UTF-8");
//读数据,一次读取一个字符数据
int ch;
while((ch = isr.read()) != -1){
System.out.print((char)ch);
}
isr.close();//释放资源
5.InputStreamReader读数据的方法
public int read();//一次读取一个字符
public int read(char[] cbuf);//一次读取一个字符数组
代码示例:
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "UTF-8");
//方式1:一次读取一个字符
int by;
while((by = isr.read()) != -1){
System.out.print((char)by);
}
//方式2:一次读取一个字符数组
char[] chs = new char[1024];
int len;
while((len = isr.read(chs)) != -1){
System.out.print(new String(chs, 0, len));//String::public String(byte[] bytes, int offset, int length);
}
//释放资源
isr.close();
6.练习题:把当前项目目录下的StringDemo.java内容复制到当前项目目录下的copy.java中。
数据源:StringDemo.java -->读数据-->字符流-->InputStreamReader
目的地:copy.java -->写数据-->字符流-->OutputStreamWriter
//封装数据源
InputStreamReader isr = new InputStreamReader(new FileInputStream("StringDemo.java"));
//封装目的地
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("copy.java"));
//读写数据
//方式1:一次读写一个字符
int ch;
while((ch = isr.read()) != -1){
osw.write(ch);
}
//方式2:一次读写一个字符数组
char[] chs = new char[1024];
int len;
while((len = isr.read(chs)) != -1){
osw.write(chs, 0, len);
}
//释放资源
osw.close();
isr.close();
====
九、转换流的子类FileWriter, FileReader
转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以为了简化书写,转换流提供了对应的子类。
1.FileWriter, FileReader
FileWriter extends OutputStreamWriter 用来写入字符文件的便捷类
FileReader extends InputStreamReader 用来读取字符文件的便捷类
OutputStreamWriter = FileOutputStream + 编码表
FileWriter = FileOutputStream + 编码表
InputStreamReader = FileInputStream + 编码表
FileReader = FileInputStream + 编码表
2.构造方法
FileWriter(String fileName);
FileReader(String fileName);
代码示例:
//封装数据源
FileReader fr = new FileReader("StringDemo.java");
//封装目的地
FileWriter fw = new FileWriter("copy.java");
//读写数据
//方法1
int ch;
while((ch = fr.read()) != -1){
fw.write(ch);
}
//方法2
char[] chs = new char[1024];
int len;
while((len = fr.read(chs)) != -1){
fw.write(chs, 0, len);
}
//释放资源
fw.close();
fr.close();
十、字符缓冲区流BufferedWriter,BufferedReader
1.BufferedWriter:
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
构造方法:
BufferedWriter(Writer out);//Creates a buffered character-output stream that uses a default-sized output buffer.
BufferedWriter(Writer out, int sz);//Creates a new buffered character-output stream that uses an output buffer of the given size.
写数据的方法:
void newLine();//Writes a line separator.
void write(char[] cbuf, int off, int len);//Writes a portion of an array of characters.
void write(int c);//Writes a single character.
void write(String s, int off, int len);//Writes a portion of a String.
2.BufferedReader:
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
构造方法:
BufferedReader(Reader in);//Creates a buffering character-input stream that uses a default-sized input buffer.
BufferedReader(Reader in, int sz);//Creates a buffering character-input stream that uses an input buffer of the specified size.
读数据的方法:
int read();//Reads a single character.
int read(char[] cbuf, int off, int len);//Reads characters into a portion of an array.
String readLine();//Reads a line of text.
3.代码示例:
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
//调用写数据的方法
bw.write("Hello");
//释放资源
bw.close();
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("1.java"));
//方式1:一次读取一个字符
int ch;
while((ch = br.read()) != -1){
System.out.print((char)ch);
}
//方式2:一次读取一个字符数组
char[] chs = new char[1024];
int len;
while((len = br.read(chs)) != -1){
System.out.print(new String(chs, 0, len));
}
//释放资源
br.close();
4.练习题
题目描述:字符缓冲区流复制文件,把a.txt内容复制到b.txt中。
数据源:a.txt -->读数据-->字符流-->InputStreamReader-->FileReader-->BufferedReader
目的地:b.txt -->写数据-->字符流-->OutputStreamWriter-->FileWriter-->BufferedWriter
代码:
//封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
//封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
//读写数据
//一次读写一个字符数组
char[] chs = new char[1024];
int len;
while((len = br.read(chs)) != -1){
bw.write(chs, 0, len);
}
//释放资源
bw.close();
br.close();
5.特殊功能--换行功能
BufferedWriter::void newLine();//写入一个行分隔符,分隔符由系统属性决定。
BufferedReader::String readLine();//包含该行内容的字符串,不包含任何终止符。如果已到达流末尾,则返回null。即不读换行符。所以要自己换行System.out.println();
代码示例:
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
//写数据
for(int i = 0; i < 3; i++){
bw.write("Hello");
//bw.write("\r\n");
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("bw.txt"));
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
//释放资源
br.close();
6.练习题
题目描述:用字符缓冲区流的换行功能复制文件
数据源:a.java -->BufferedReader
目的地:b.java -->BufferedWriter
代码:
//封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
//封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
String line;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
br.close();
十一、IO流总结(重点)
1.字节流
FileInputStream fis = new FileInputStream("a.txt");
FileOutputStream fos = new FileOutputStream("b.txt");
2.字节缓冲流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));
3.转换流
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"), "UTF-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"), "UTF-8");
4.字符流
FileReader fr = new FileReader("1.java");
FileWriter fw = new FileWriter("2.java");
5.字符缓冲流
BufferedReader br = new BufferedReader(new FileReader("1.java"));
BufferedWriter bw = new BufferedWriter(new FileWriter("2.java"));
十二、IO流的一些练习题
练习题1:字符流复制文本文件
一共有5种方式:
A.基本字符流一次读写一个字符串
B.基本字符流一次读写一个字符数组
C.缓冲字符流一次读写一个字符
D.缓冲字符流一次读写一个字符数组
E.缓冲字符串一次读写一个字符串
代码:
//A.基本字符流一次读写一个字符串
private static void methodA() throws IOException{
FileReader fr = new FileReader("d:\\林青霞.txt");
FileWriter fw = new FileWriter("窗里窗外.txt");
int ch;
while((ch = fr.read()) != -1){
fw.write((char)ch);
}
fw.close();
fr.close();
}
//B.基本字符流一次读写一个字符数组
private static void methodB() throws IOException{
FileReader fr = new FileReader("d:\\林青霞.txt");
FileWriter fw = new FileWriter("窗里窗外.txt");
char[] chs = new char[1024];
int len;
while((len = fr.read(chs)) != -1){
fw.write(chs, 0, len);
}
fw.close();
fr.close();
}
//C.缓冲字符流一次读写一个字符
private static void methodC() throws IOException{
BufferedReader br = new BufferedReader(new FileReader("d:\\林青霞.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("窗里窗外.txt"));
int ch;
while((ch = br.read()) != -1){
bw.write((char)ch);
}
bw.close();
br.close();
}
//D.缓冲字符流一次读写一个字符数组
private static void methodD() throws IOException{
BufferedReader br = new BufferedReader(new FileReader("d:\\林青霞.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("窗里窗外.txt"));
char[] chs = new char[1024];
int len;
while((len = br.read(chs)) != -1){
bw.write(chs, 0, len);
}
bw.close();
br.close();
}
//E.缓冲字符串一次读写一个字符串
private static void methodE() throws IOException{
BufferedReader br = new BufferedReader(new FileReader("d:\\林青霞.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("窗里窗外.txt"));
String line;
while((line = br.readLine()) != null){
bw.write(line);
bw.writeLine();
bw.flush();
}
bw.close();
br.close();
}
====
练习题2:把ArrayList集合中的字符串数据存储到文本文件,每一个字符串元素作为文件中的一行数据
分析:
A.创建集合对象
B.往集合中添加字符串对象
C.创建字符缓冲输出流对象
D.遍历集合,得到每一个字符串元素,把字符串元素作为数据写到文本文件
E.释放资源
代码:
//创建集合对象
ArrayList<String> array = new ArrayList<String>();
//往集合中添加字符串元素
array.add("hello");
array.add("world");
array.add("Java");
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("array.txt"));
//遍历集合,得到每一个字符串元素,把字符串元素作为数据写入到文本文件
for(String s : array){
bw.write(s);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
练习题3:从文本文件中读取数据到ArrayList集合中,并遍历集合,每一行数据作为一个字符串元素。
分析:
A.创建字符缓冲输入流对象
B.创建集合对象
C.读取数据,每一次读取一行,并把该数据作为元素存储到集合中
D.释放资源
E.遍历集合
代码:
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("array.txt"));
//创建集合对象
ArrayList<String> array = new ArrayList<String>();
//读取数据,每一次读取一行,并把该数据作为元素存储到集合中
String line;
while((line = br.readLine()) != null){
array.add(line);
}
//释放资源
br.close();
//遍历集合
for(String s : array){
System.out.println(s);
}
练习题4:把ArrayList集合中的学生数据存储到文本文件,每一个学生数据作为文件中的一行数据
分析:
A.创建集合对象
B.创建学生对象
C.把学生对象添加到集合中
D.创建字符缓冲输出流对象
E.遍历集合,得到每一个学生对象,然后把该对象的数据拼接成一个指定格式的字符串,写到文本文件
F.释放资源
代码:
//创建集合对象
ArrayList<Student> array = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("it001", "林青霞", 30, "北京");
Student s2 = new Student("it002", "张曼玉", 35, "东京");
Student s3 = new Student("it003", "王祖贤", 33, "纽约");
//把学生对象添加到集合中
array.add(s1);
array.add(s2);
array.add(s3);
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt"));
//遍历集合,得到每一个学生对象
for(Student s : array){
StringBuilder sb = new StringBuilder();
sb.append(s.getSid()).append(", ").append(s.getName()).append(", ").append(s.getAge()).append(", ").append(s.getCity());//it001, 林青霞, 30, 北京
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
练习题5:从文本文件中读取学生数据到ArrayList集合中,并遍历集合,每一行数据作为一个学生元素。这里要使用String类中的方法split()。
分析:
A.创建字符缓冲输入流对象
B.创建集合对象
C.读取数据,每一次读取一行数据,把该行数据想办法封装成学生对象,并把学生对象存储到集合中
D.释放资源
E.遍历集合
代码:
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("students.txt"));
ArrayList<Student> array = new ArrayList<Student>();
//读取数据,每一次读取一行数据
String line;
while((line = br.readLine()) != null){it001, 林青霞, 30, 北京
String[] strArray = line.split(", ");
Student s = new Student();
s.setSid(strArray[0]);
s.setName(strArray[1]);
s.setAge(Integer.parseInt(strArray[2]));
s.setCity(strArray[3]);
array.add(s);
}
//释放资源
br.close();
//遍历集合
for(Student s : array){
System.out.println(s.getSid() + ", " + s.getName() + ", " + s.getAge() + ", " + s.getCity());
}
====