一.File类型
如果我们想在程序中操作或者描述一个文件夹或文件,可以使用File类型
File类型在java.io包下
File可以新建,删除,移动,修改,重命名文件夹,也可以对文件或者文件夹的属性进行访问;但是不能对文件的内容进行访问(读和写);
常用的构造器:
File(String pathname)
File(String parent , String child)
File(File parent , String child)
需要注意的是,构造器只负责接收程序员传入的文件路径,并不会判断这个文件路径是否真实存在
目录的分隔符可以使用 File.separator来获取;
File file = new File("D:"+File.separator+"dir1");
boolean result = file.exists();
if(result){
System.out.println("所描述的文件或者文件夹存在");
}else{
System.out.println("所描述的文件或者文件夹不存在");
}
我们首先可以描述一个文件,然后使用file.exists();方法来判断该文件是否存在,上述代码使用的是第一种构造器,直接传入路径
/**
* 调用构造器File(String parent String child)
*/
File file1 = new File("D:/chengxu","ch2.R");
System.out.println(file1.exists());
/**
* 调用构造器File(File parent String child)
*/
File parent = new File("D:/chengxu");
File file2 = new File(parent,"ch2.R");
System.out.println(file2.exists());
根据上述代码,我们可以使用另外两种构造器;第一种是传入父路径和子路径;第二种是传入File类型的父文件和字路径;
查看文件属性的相关方法
File file = new File("D:/code/ch2.R");
System.out.println("文件名称:"+file.getName());
System.out.println("绝对路径:"+file.getAbsolutePath());
System.out.println("该文件的父路径:"+file.getParent());
System.out.println("路径:"+file.getPath());
System.out.println("是否是一个文件:"+file.isFile());
System.out.println("是否是一个文件夹:"+file.isDirectory());
System.out.println("该文件是否是一个隐藏文件:"+file.isHidden());
System.out.println("该文件是一个可读文件:"+file.canRead());
System.out.println("该文件是一个可写文件:"+file.canWrite());//可写一定是可读文件
System.out.println("该文件是一个可执行的文件:"+file.canExecute());
//可执行一定是可读文件
System.out.println("最后一次修改时间:"+file.lastModified());
System.out.println("文件的大小:"+file.length());
上述代码中我们可以查看文件的名称,并查看文件的绝对路径,文件的父路径,文件的路径
可以判断该文件是否是一个文件,或判断该文件是否是一个文件夹,可以判断该文件是否隐藏,可读,可写或可执行;
最后还可以得到该文件最后一次修改的时间以及文件的大小;
文件检索,查询相关方法
我们可以使用两种方式;一个是返回字符串类型的数组,一个是返回File类型的数组;
File file = new File("D:/code");
String[] lists = file.list();
for (String list : lists) {
System.out.println(list);
}
首先描述一个文件,并检索该文件,使用String类型的数组来接收,并对该数组进行遍历,打印结果;
File[] files = file.listFiles();
for (File file1 : files) {
System.out.println(file1.getName()+" "+file1.getAbsolutePath());
}
之后我们使用File类型的数组来检索该文件,并对其进行遍历,因为File类型的对象可以调用File类型的方法,因此我们可以得到文件名和绝对路径;
过滤文件
在对文件进行操作的时候,我们经常会需要过滤文件,过滤器对象可以实现这个功能,这是一个内部类需要重写里面的accept方法,编写过滤逻辑,该方法有两个形参,dir指需要操作的文件夹;name指文件夹中的每一个文件或文件夹;
FilenameFilter filter = new FilenameFilter() {
//重写accept方法:编写过滤逻辑,name指的就是文件夹里的每一个文件或文件夹
//dir就是要操作的那个文件列表,即文件夹
public boolean accept(File dir, String name) {
return !name.endsWith(".R");
}
};
这样一个过滤器对象就创建好了;之后我们就可以对文件进行遍历;传入过滤器对象
String[] list = file.list(filter);
for (String list1 : list) {
System.out.println(list1);
}
File[] files1 = file.listFiles(filter);
System.out.println(Arrays.toString(files1));
然后打印出符合要求的文件即可
File的新建,删除
File的新建可以使用mkdir()方法,主要用于新建目录;
File dir = new File("D:/dir1");
if (!dir.exists()) {
//创建文件命令 文件名.mkdir
dir.mkdir();
}
如果要新建文件,可以使用createNewFile( );
File file1 = new File("D:/dir1/file1.txt");
if (!file1.exists()) {
file1.createNewFile();
}
这样文件夹和文件就创建好了
文件夹的多层级创建:
使用mkdir( )命令只能创建一个文件夹,不能多层级创建文件夹,如果想多层级创建,则需要使用mkdirs( )命令
File file = new File("D:/dir11/dir22/dir33");
if(!file.exists()){
file.mkdirs();//mkdir方法只能创建出一个空文件夹
}
文件的重命名:
使用reName( )方法来对文件重命名,方法的形参是文件的文件的路径,在路径中修改文件名
File file1 = new File("D:/dir11/dir22/dir33");
if(file1.exists()){
file1.renameTo(new File("D:/dir11/dir22/dir3333"));
}
这样就可以把file1的dir33修改为dir3333;
文件的移动:
我们可以使用reName()方法来实现文件的移动操作;
/**
* 使用renameTo(File file)方法来达到移动文件的作用
*/
File file2 = new File("D:/dir11/dir22/dir3333/file1.txt");
File file3 = new File("D:/dir11/dir22/file1.txt");
if(file2.exists()){
file2.renameTo(file3);
}
使用原文件去调用reName,传入新文件的地址即可,没有修改文件的名字,只是修改了文件的路径,就可以达到移动的目的;
文件的删除操作:
delete( )方法可以实现文件的删除操作;需要注意的是,delete只能删除空文件夹或者文件,不能多层级删除,如果想删除非空文件夹,需要先把里面所有的子文件夹和文件全部删除,才能删除该文件夹;
File file = new File("D:/dir11/dir22/file1.txt");
if (file.exists()) {
boolean f = file.delete();
System.out.println(f);
}
我们可以通过上述的代码删除在dir22目录中的file1.txt文件;
delete方法可以删除某一个文件;只需要传入文件的路径对象即可;
如果此时我们想删除dir11,属于删除非空文件夹,使用delete方法无法删除
/**
* 测试: 删除D:/dir11
*/
File file1 = new File("D:/dir11");
if (file1.exists()) {
boolean f1 = file1.delete();
System.out.println(f1);
}
返回结果是false,说明删除操作失败了;那么此时我们就需要使用删除的递归操作,多层次删除
public static void deleteAll(File file) {
if(!file.exists()){
throw new RuntimeException("要删除的文件或文件夹不存在");
}
File[] files = file.listFiles();
for (File f : files) {
if(f.isDirectory()){
deleteAll(f);
}
f.delete();
}
file.delete();
}
在方法体中,首先判断需要删除的文件或文件夹是否存在,可以抛一个运行时异常来提醒调用者,之后我们对文件夹遍历,如果子文件还是文件夹,就重新进入循环,直到不是文件夹为止,然后就可以调用delete方法进行删除,需要注意,最外层的文件夹也需要使用删除操作进行删除;
这样就可以把非空文件夹删除了;
二.IO流--字节流
InputStream是字节输入流的顶级父类,是抽象类。定义了基本的读取方法。OutputStream是字节输出流的顶级父类,也是抽象类,定义了基本的写出方法。
FileInputStream--文件输入流
FileInputStream是InputStream抽象类的子类型,用于连接文件和程序,常用的构造器
FileInputStream(String pathname)
FileInputStream ( File file)
FileInputStream fis = null;
fis = new FileInputStream("D:/file1.txt");
//读取一个字节
int ch = fis.read();
System.out.println((char)ch);
//读取后面的10个字节
for (int i = 0; i < 10; i++) {
ch = fis.read();//每读取一次,都会与磁盘交互一次,次数越多,性能越差
System.out.println((char)ch);
}
//提前创建一个10个长度的byte数组
byte[] bs = new byte[10];
//将数据读到数组中了
int length = -1;
while ((length = fis.read(bs)) != -1) {//大大减少了与磁盘的交互次数
String str = new String(bs,0,length);
System.out.println(str);
}
我们首先创建一个文件输入流的对象fis;赋值为null,并调用构造器,创建新的对象,
读取一个字节可以使用方法read( ); 需要注意,我们使用的接收变量是int类型,所以在打印的时候需要强转;
如果我们想读取十个字节的话,可以使用for循环来进行读取,但是每次调用read( )方法都会与磁盘进行一次交互,如果读取很多数据的话,会导致性能变差,所
以我们可以提前创建一个数组,将数据先读取到数组中,之后每当数组满了就一次性读取到程序中,这样可以大大减少与磁盘的交互次数,从而提高性能;
根据上述代码,我们创建了一个长度为10的数组,并建立一个循环,让字节读入数组,当数组的长度不是-1时,就可以让数组转换为字符串形式输出。
因为在输入流中可能会报错,所以我们需要使用try-catch结构来运行程序
try {
fis = new FileInputStream("D:/file1.txt");
//读取一个字节
int ch = fis.read();
System.out.println((char)ch);
//读取后面的10个字节
for (int i = 0; i < 10; i++) {
ch = fis.read();//每读取一次,都会与磁盘交互一次,次数越多,性能越差
System.out.println((char)ch);
}
//提前创建一个10个长度的byte数组
byte[] bs = new byte[10];
//将数据读到数组中了
int length = -1;
while ((length = fis.read(bs)) != -1) {//大大减少了与磁盘的交互次数
String str = new String(bs,0,length);
System.out.println(str);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
在catch结构中我们可以抛出多种异常,有文件找不到异常,还有IO异常;我们在使用IO流时,在使用完必须要对其进行关闭;在关闭时也有可能出现异常,所以我们需要对fis.close( )也添加一个try-catch结构
FileOutputStream--文件输出流
FileOutputStream是OutputStream的子类型;是一个低级流,用于连接程序和文件常用的构造器:
FileOutputStream(File file)
FileOutputStream(String pathname)
此外,还可以为构造器对象的形参添加追加效果,在形参中加上 boolean append即可;追加操作可以在每次运行程序时,都在程序的末尾重新运行代码,不会覆盖掉原来的内容
需要注意的是,上述的构造器会将文件中原有的内容覆盖掉;
输出流不会帮助我们创建不存在的文件夹,但是可以帮助我们创建一个文件;
FileOutputStream fos =null;
try{
// fos = new FileOutputStream("D:/file1.txt");
//使用追加模式的构造器
fos = new FileOutputStream("D:/file1.txt",true);
//写一个A
fos.write(65);
//将B到Z写到文件中
// for (int i = 66; i < 91; i++) {
// fos.write(i);
// }
for (int i = 'B'; i <='Z' ; i++) {
fos.write(i);//注意:每写一次,都会与磁盘进行交互一次。次数越多,性能越差
}
//将hello world写入到文件中
byte[] bytes = "hello world".getBytes("UTF-8");
// fos.write(bytes);
fos.write(bytes,0,7);
首先我们需要创建文件输出流对象;并赋值为null;之后在try-catch模块中,调用构造器来创建一个新的对象,并添加追加操作,并在文件中写一个A;
需要使用fos.write( )方法;传入的参数是int类型,但是会自动转换成字符型数据;所以我们传入65,编译器会自动转换成为' A ';
之后我们可以使用for循环;把B-Z全部传入文件,每次在调用write方法时,都会与磁盘交互一次,性能会变差;为了解决这种问题,我们可以利用字节数组来调用write方法;
首先创建一个byte[ ]类型的数组bytes,直接赋值一个字符串;并调用getBytes方法,传入需要转换的编码集;然后利用输出流对象fos.wtrte( );形参有三个,一个是字符数组名,第二个是开始索引,第三个是输出的长度;
在写完try模块的内容后,我们依然要补充catch模块和finall模块;
因为构造器加上了追加操作,运行了两次程序,文件中就会出现两次A-Z hello w;说明添加追加操作之后,文件原本的内容不会被覆盖;
BufferedInputStream--字节缓冲输入流
BufferedInputStream是一个高级流,内部维护了一块缓冲区,默认8KB,在读取文件的数据时,会尽可能的读取缓冲区大小的字节;在使用read( )方法来从缓冲区获取数据时;如果全部读取完,就会继续从磁盘上读取数据储存到缓冲区;
常用的构造器:
BufferedInputStream(InputStream is)
BufferedInputStream(InputStream is , int size)
形参需要传入一个低级流;FileInputStream
BufferedInputStream bis = null;
// FileInputStream fis = null;
try{
bis = new BufferedInputStream(
new FileInputStream("D:/file1.txt"));
//读取一个字节
int ch = bis.read();
System.out.println((char)ch);
//一次性读取多个字节
byte[] bytes = new byte[10];
int len = -1;
while((len=bis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
我们依旧是先创建一个字节缓冲输入流对象;并赋值为null;之后在try模块中调用构造器,需要注意的时,由于高级流的构造器的形参是低级流,所以我们还需要调用低级流的构造器,传入需要读取的文件如果需要读取一个字节的话,就可以直接调用read()方法,并在打印输出语句时强转为char类型;,如果一次性读取多个字节,可以创建一个byte数组,并定义一个int类型的变量len,之后添加一个while循环;当数组中有新添加的元素时,就需要把数组的元素打印出来;
还需要再添加catch模块和finally模块,用来抛出异常和关闭流,再关闭流时,先关闭高级流,再关闭低级流
}catch(IOException e){
e.printStackTrace();
}finally {
try {
bis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
BufferedOutputStream--字节缓冲输出流
BufferedOutputStream内部维护了一个字节数组作为缓冲区:默认值是8KB;
当我们在写数据时,不是直接写在磁盘上,而是先写到缓冲区内,当缓冲区满了之后再一次性写在磁盘上,从而减少了与磁盘的交互次数,提高了性能;
当我们在最后一次写在缓冲区时,可能内容没有写满,此时就需要调用flush( )方法来强制写出BufferedOutputStream构造器:
BufferedOutputStream(OutputStream os)
BufferedOutputStream(OutputStream os , int size )
我们可以自己定义缓存区的大小;通过size来设置;
BufferedOutputStream bos = null;
try{
bos = new BufferedOutputStream(
new FileOutputStream("D:/file2.txt", true));
//将A写入磁盘
bos.write('A');
//将"helloworld"写入缓冲区
byte[] bytes = "helloworld".getBytes("UTF-8");
bos.write(bytes);
首先定义一个字节缓冲输出流对象;并赋值为null;在try模块中调用构造器创建新的对象在构造器中调用文件输出流的构造器,传入文件的地址,并添加追加操作;
写入磁盘使用write方法即可,写入缓冲区时,需要先创建一个数组,并用字符串赋值,之后调用getBytes方法传入编码集;
之后不要忘记添加catch模块和finally模块;
DataOutputStream--数据输出流
DataOutputStream是OutputStream的子类型;扩展了一些功能,可以直接写出基本数据类型的方法;该流的构造器:
DataOutputStream(OutputStream os)
try( DataOutputStream dos =
new DataOutputStream(
new FileOutputStream("D:/file3.txt"))){
dos.writeInt(10);//4个字节
dos.writeDouble(3.14);//8个字节
dos.writeBoolean(true);//1个字节
dos.writeUTF("你是最棒的");//15个字节 注意:每写一次utf的字符串,都会使用两个字节来记录字符串的长度
dos.writeUTF("你是最棒的");//15个字节
dos.flush();
}catch (IOException e){
e.printStackTrace();
}
该流在try模块中可以直接使用小括号的形式来创建流的对象,这样操作会在流不使用时自动关闭流,不需要再写finally模块专门关闭流了;在创建数据流的对象时,传入的形参是文件输出流;可以直接调用文件输出流的构造器,传入一个文件的地址;
在输出内容时,可以调用writeInt方法,可以直接输出int类型的数字,也可以使用浮点数,布尔类型的值,需要注意的是:在我们调用writeUTF方法时,传入的字符串每个字符占3个字节,而且每调用一次该方法,就会使用额外的两个字节来记录字符串的长度,每个int类型的值的数字占两个字节,浮点数也是两个字节,布尔类型的值占一个字节;之后我们可以调用flush方法将缓冲区的内容强制输出
DataInputStream--数据输入流
DataInputStream是InputStream的子类型;扩展了一些功能,可以直接读取基本数据类型和String类型的数据;该流的构造器:
DataInputStream( InputStream is)
其使用方法与DataOutputStream类似,只是把write方法转变成read方法;并且read方法是需要返回值的;记得添加,然后我们就可以把变量打印出来了;
try (DataInputStream dos =
new DataInputStream(
new FileInputStream("D:/file3.txt")
)
){
int i = dos.readInt();
double v = dos.readDouble();
boolean b = dos.readBoolean();
String s = dos.readUTF();
System.out.println(i);
System.out.println(v);
System.out.println(b);
System.out.println(s);
}catch (IOException e) {
e.printStackTrace();
}
我们可以直接在try模块的花括号前添加小括号;将创建流对象的过程写在小括号里调用数据输入流的构造器,形参传入一个文件输出流的构造器,形参传入需要写入的文件地址,尤其需要注意在写小括号的时候,不要落下;否则报错
在调用方法的时候需要添加返回值;
ObjectOutputStream--对象输出流
对象序列化:我们需要将内存中的对象转换为字节数组;这个过程称为序列化。序列化的目的:存储或传输
反序列化:字节数组转换为对象;这个过程称为反序列化,反序列化的目的:恢复对象的状态,以便在程序中使用。
序列化的构造器: ObjectOutputStream(OutputStream os)
在我们自定义的类中;如果想要使对象序列化,就必须要实现 Serializable接口,该接口内部什么也没有,只是一个规范
serailVersionUID: 序列化的版本号:
在进行序列化时,系统会默认给该类一个版本号。当反序列化时,系统会检查该类的版本号与对象的版本号是否一致,如果不一样,反序列化将会失败,报出异常:版本号不兼容
注意:系统提供的版本号在不同时间可能不同
为了解决上述的问题,我们需要自己定义一个版本号,这样序列化和反序列化用的就都是同一个版本号,不会出现不兼容的现象
class Student implements Comparable<Student> , Serializable {
public static final long serialVersionUID = 2100000000;
private String name;
private int age;
private char gender;
public Student(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Student() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && gender == student.gender && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, gender);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
我们创建了一个Student类,实现了comparable接口和Serializable接口;之后创建了静态常量
serailVersionUID: 序列化的版本号:2100000000,这样版本号就固定了,不会出现序列化和反序列化过程不兼容的问题。创建了三个成员变量:name , age , gender 然后分别提供了全参构造器和无参构造器;并提供了getter和setter方法,又重写了equals和hashCode方法,toString方法,并重写了compareTO方法
public static void main(String[] args) {
//创建一个学生
Student s1 = new Student("小明",20,'男');
Student s2 = new Student("小明",20,'男');
//使用对象输出流将学生写出到文件中
ObjectOutputStream oos = null;
try{
oos = new ObjectOutputStream(
//流的构造器的相对路径相对的是Project(项目)文件夹 ./表示的是项目文件夹
new BufferedOutputStream(new FileOutputStream("./student.s",true),4096));
//写出对象
oos.writeObject(s1);
oos.writeObject(s2);
}catch(IOException e){
e.printStackTrace();
}finally {
try {
oos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
在上述方法中,我们创建了两个学生对象;并使用对象输出流将学生输出到文件中,首先使用对象输出流的构造器为其赋值,在构造器中传入字节缓冲流的构造器,在构造器中传入文件输出流的构造器,在构造器中传入文件的相对路径;并添加追加操作,在字节缓冲流的构造器中添加字节缓冲区的大小;之后我们可以调用writeObject方法来写出对象,需要添加catch模块和finally模块来关闭流。
可以发现,创建出来的文件是在project下的,所以说相对路径指的就是当前项目的路径;
ObjectInputStream--对象输入流
反序列化的构造器: ObjectInputStream(InputStream is)
知识扩展:
在序列化时,程序每执行一次都会创建一个两个字节长度的流头,如果文件输出流是追加模式,则会出现多个流头,所以当文件中如果有多个流头时,可能会将除了第一个流头之外,其余的流头会被当成文件内容而解析,会报异常:非法类型的代码
ObjectInputStream ois = null;
try{
ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream("./student.s"),4096));
Object obj = ois.readObject();
System.out.println(obj);
Object o = ois.readObject();
System.out.println(o);
}catch (IOException | ClassNotFoundException e){
e.printStackTrace();
}finally {
try {
ois.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
在上述代码中,我们先创建了一个对象输入流对象ois;然后调用对象输入流构造器进行赋值,传入字节缓冲流构造器,并提供字节缓冲区的大小为4096个字节,传入文件输入流构造器,传入文件的地址,之后就可以调用readObject方法了;来打印输出结果;
我们还可以做一个这个实例演示:
class Teacher implements Serializable , Comparable<Teacher>{
public static final int serialVersionUID = 1;
private String name;
private int age;
private String address;
private transient String remark;
public Teacher(String name, int age, String address, String remark) {
this.name = name;
this.age = age;
this.address = address;
this.remark = remark;
}
public Teacher() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Teacher teacher = (Teacher) o;
return age == teacher.age && Objects.equals(name, teacher.name) && Objects.equals(address, teacher.address) && Objects.equals(remark, teacher.remark);
}
@Override
public int hashCode() {
return Objects.hash(name, age, address, remark);
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
", remark='" + remark + '\'' +
'}';
}
@Override
public int compareTo(Teacher o) {
return 0;
}
}
在上述代码中,我们依旧是创建了一个Teacher类,添加成员变量name , age , address , remark , 需要注意的是,我们可以在成员变量的类型之前添加一个关键字transient来修饰该成员变量;
在我们序列化时,该成员变量的信息不需要保存或传递,也就是不重要的意思;那么此时我们就可以使用transient关键字来进行修饰;
我们正常添加无参构造器和全参构造器;并添加getter和setter方法,重写equals方法,hashCode方法,toString方法,以及在实现comparable接口时需要重写的compareTo方法。
public static void serialObject(Object obj){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(
new ObjectOutputStream(
new FileOutputStream("object.txt")));
oos.writeObject(obj);
}catch (IOException e){
e.printStackTrace();
}finally{
try {
oos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
在上述的代码中,我们把序列化对象的过程封装在了方法中,首先创建了对象输出流的对象oos并赋值为null;在try模块中对oos变量调用构造器来进行赋值,传入的形参是字节缓冲输出流的构造器,传入的形参是文件输出流的构造器,传入的形参是文件地址
之后我们可以使用对象调用writeObject方法来写出对象。
/**
* 反序列化对象
*/
public static Teacher deSerialObject() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(
new ObjectInputStream(
new FileInputStream("object.txt")));
Object o = ois.readObject();
if(o instanceof Teacher){
return (Teacher)o;
}
}catch (IOException | ClassNotFoundException e){
e.printStackTrace();
}finally{
try {
ois.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
我们也可以把反序列化的过程封装在一个方法里;首先在方法体中创建一个对象输入流的对象ois,并赋值为null,在try模块里为ois调用构造器进行赋值,传入的参数是字节缓冲输入流的构造器;传入的形参是文件输入流的构造器;传入文件的地址,之后可以调用readObject方法进行读取内容,该方法是有返回值的,需要返回Teacher类型的变量或表达式,我们需要判断读取到的数据是不是Teacher类型的对象,并对Object类型的对象进行强转之后才能返回;
//创建一个Teacher对象
Teacher teacher = new Teacher("张老师",25,"China","..");
//序列化Teacher对象
serialObject(teacher);
//反序列化Teacher对象
Teacher teacher1 = deSerialObject();
System.out.println(teacher1);
在上述代码中,我们在main方法里实例化了一个Teacher类型的对象teacher,并调用构造器对其进行赋值;之后我们可以调用序列化和反序列化方法对其进行操作,最后打印出接受的结果;
此时我们就会发现remark中的信息并没有被传递过来,因为我们在成员变量前面添加了transient关键字。
三.IO流--字符流
Writer--字符输出流
Writer是字符输出流的抽象父类,书写的单位是以一个char为单位;底层依旧是一个字节流;
基本的字符流,我们称之为转换流,因为涉及到字符与字节之间的转换时所使用的编码集,在编写代码时默认使用的编码集是UTF-8,每个字母都是一个字节,汉字是三个字节;因为Writer是抽象类,我们都使用其子类OutputWriter来创建对象;构造器:
OutputWriter(OutputStream os )
OutputWriter(OutputStream os , String charsetName): 传入指定的字符集
OutputStreamWriter osw = null;
try {
// osw = new OutputStreamWriter(new FileOutputStream("./test07/char.txt"));
osw = new OutputStreamWriter(
//常用的字符集 UTF-8 GBK GB2312 UNICODE
// ISO-8859-1:HTTP通信时使用
new FileOutputStream("./test07/char.txt"),"GBK");
//写一个字符出去
osw.write('中');
//写一个字符数组出去
char[] ch = {'国','欢','迎','你'};
osw.write(ch);
//将ch中的后三个在写一次
osw.write(ch,ch.length-3,3);
//将一个字符串写出去
osw.write("奥运健儿加油");
//如果有缓存区,强制重刷一下(写出)
osw.flush();
我们还是在模块外创建一个字符输出流对象 osw ,并赋值为null;
之后在try模块中,调用构造器对其进行赋值,传入的形参是文件输出流的构造器,传入的形参是文件的地址,此外,还可以添加编码集,必须添加在字符输出流构造器中;编码集用字符串类型;
在我们写出字符时,使用对象来调用write方法即可,传入的形参是字符类型;该方法的重载形式可以传入一个字符数组,还可以传入字符数组的一部分,确定开始传输的索引,和传输的长度即可;
Reader--字符输入流
Reader是字符输入流的抽象父类,我们一般使用其子类InputReader来实例化对象;
常用的构造器:
InputReader(InputStresm is )
InputReader(InputStream is , String charsetName)
InputStreamReader isr = null;
try {
isr = new InputStreamReader(
new FileInputStream("./test07/char.txt"),"GBK");
//取出一个元素
char i = (char)isr.read();
System.out.println(i);
//取出10个元素
//继续读取10个字符
char[] chs = new char[10];
int len = 0;
while ((len = isr.read(chs)) != -1) {
System.out.println("chs:"+ new String(chs,0,len));
System.out.println("len: "+len);
}
在上述代码中,我们先创建了一个字符输入流对象isr,并赋值为null;
在try模块中我们调用构造器对其进行赋值,传入的参数是文件输入流的构造器,传入的参数是文件的路径,并且可以在字符输入流的构造器中传入和输出流相同的字符编码集;
之后可以通过对象来调用read方法,进行读取文件的内容,可以一次读一个,也可以使用字符数组一次读10个字符,需要声明一个记录长度的变量len并对其初始化;使用while循环来控制打印的数组长度
PrintWriter--字符缓冲输出流
字符缓冲输出流一般使用PrintWriter这个子类,而不是BufferedWriter;
因为PrintWriter这个子类的构造器比较丰富
PrintWriter(File file ):直接传入一个File 类型的对象;
PrintWriter(Stirng pathname ) :直接传入文件的路径
PrintWriter(OutputStream os ): 传入字节流对象
PrintWriter(Writer wirter ): 传入Writer类型的对象;
此外,还有两种重载方法,在后面两种构造器中添加true,表示autoFulsh,自动强制从缓冲区输出
try(PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream("test07/char.txt"), "GBK"),true)){
//写出一个字符数组
char[] chars = "你好中国".toCharArray();
pw.write(chars);
//调用print方法,写出去"床前明月光"
pw.print("床前明月光"); //print不带ln的方法,没有自动行刷新的方法
// pw.flush();
//调用println的方法,写出“疑是地上霜”
//当缓冲流构造器中指定了行刷新功能,也就是设计为true,此时ln的方法才会在行末添加换行符,并强制从缓存里写出
pw.println("疑是地上霜");
pw.println("举头望明月");
首先利用小括号在try模块后面创建PrintWriter对象pw,利用构造器对其进行赋值,传入的参数是字符输出流的构造器,传入的参数是字节输出流的构造器,传入的参数是文件的地址,在字符输出流的构造器中添加字符编码集,并在字符缓冲输出流中添加true,表示可以自动重刷缓存区;
如果print方法不带有ln,则不具有自动行刷新的功能,当输入疑是地上霜后,字符串的结尾才会带有换行符,输入举头望明月才会换行。
BufferedReader--字符缓冲输入流
内部也维护了一个缓存区,尽可能一次性读满然后存入缓存区;
常用的方法有readLine()
构造器: BufferedReader(Reader reader )
try(BufferedReader br= new BufferedReader(
new InputStreamReader(new FileInputStream("test07/char.txt"),"GBK"))){
char ch = (char)br.read();
System.out.println("ch:"+ch);
//调用读取一行的方法
String s = null;
while((s=br.readLine())!=null){
System.out.println("line:"+s);
}
}catch(Exception e){
e.printStackTrace();
}
还是利用小括号的方式来创建BufferedReader对象br,调用构造器,传入的参数是字符输入流的构造器,传入的参数是字节输入流的构造器;传入的参数是文件的地址,还需要再字符输入流的构造器中传入字符编码集GBK;
我们可以使用对象来调用read( )的方法来读取第一个字符,需要注意该方法的返回值是int类型,我们想获取字符则需要强转;
如果想获取一行的化,就需要先声明一个字符串,赋值为空,之后通过while循环,只要把输入流对象调用readline方法后的结果赋给s,s不是null的话,就可以打印。
FileWriter--文件输出流
相当于字符输出流和字节文件输出流合在一起的功能;但是不能够指定字符集
FileWriter(File file )
FileWriter(File file ,boolean append)
FileWriter(String pathname )
FileWriter(String pathname , boolean append )
我们可以直接输出字符并且可以进行追加操作;缺点就是不能指定字符集
try(FileWriter fw = new FileWriter(new File("./test07/char.txt"),true)){
//将“中国好男儿”写出去
fw.write("中国好男儿");
fw.write("华夏好儿女");
//append():追加方法,追加到文件的后面
fw.append("北京上海");
}catch (Exception e){
e.printStackTrace();
我们在小括号中创建一个FileWriter对象fw并使用构造器对其赋值,传入参数是File类型的构造器,传入文件的地址,并在FileWriter构造器中添加true,表示可以进行追加操作;利用对象名调用write方法可以写出内容,利用对象名调用append方法可以在内容后面追加新的内容,我们把程序运行两边,文件内容中会出现两遍写入的内容;
FileReader--文件输入流
相当于字符输入流和字节文件输入流合起来的作用;但是不能指定字符集;
常用构造器:
FileReader(File file )
FileReader(String pathname )
try(FileReader fr = new FileReader("./test07/char.txt")){
char i = (char)fr.read();
System.out.println(i);
int len = -1;
char[] chars = new char[10];
while((len=fr.read(chars))!=-1){
String a = new String(chars, 0, len);
System.out.println(a);
}
}
catch (Exception e){
e.printStackTrace();
}
先调用构造器来创建对象,传入文件的地址即可,在获取第一个字符时,可以选择在声明变量时强转,也可以在打印时强转;之后如果想一次获取10个字符,可以定义一个char数组,声明一个int类型的变量,调用对象的read方法,将文件中的字符读入数组中,只要数组的长度不是-1,就将数组中的元素赋值给字符串,长度是数组中新添加进的变量的长度,之后打印字符串即可;
四.系统类--System
系统类中封装了一些本地方法和静态的属性;
1.静态属性:
-PrintStream out : 标准输出流,默认目的地是控制台;
-PrintStream err : 标准错误输出流,默认目的地是控制台;
-PrintStream in : 标准输入流,默认数据源是控制台;
我们在使用System.out的时候,相当于创建了一个标准输出流对象,调用println方法,即调用的是PrintStream的方法,将结果打印到控制台;
我们可以对标准输入流和标准输出流的数据源和目的地进行修改;
我们先测试System.out
首先创建一个静态的无返回值的方法,先将默认的目的地保存起来,以免我们无法修改回去
//测试System.out
public static void testSystemOut() throws Exception {
System.out.println("Hello World");
/**
* 修改out这个流的目的地
* 1.将默认的目的地临时保存起来
*/
PrintStream ps = System.out;
//使用IO流,来重新定位
PrintStream pw = new PrintStream("./test07/char.txt");
//将上述的流对象赋值给System.out属性
System.setOut(pw);
//调用out的println方法,写出数据
System.out.println("你好世界,我是。。。。。。");
//改回来
System.setOut(ps);
System.out.println("我回来了");
}
之后我们可以使用IO流,来重新定位System.Out的目的地,创建一个新的对象pw,通过new调用构造器把新的文件地址赋给对象pw,之后将流对象的文件地址赋给System.out属性,之后就可以调用方法,打印一个字符串,如果想要修改回来,只需要将之前储存的默认目的地对象通过set来给System.out属性赋值回去就可以了;
之后我们可以在main方法中进行测试;直接调用方法名就可以;
对于修改System.in;方法大致也是如此;
//测试System.in
public static void testSystemIn() throws Exception {
//将System.in 临时保存到一个变量中
InputStream in = System.in;
//修改数据源
FileInputStream fis = new FileInputStream("./test07/char.txt");
//将数据源从控制台改为指定文件
System.setIn(fis);
//使用扫描类
Scanner sc = new Scanner(System.in); //扫描类会从文件中读取数据
//与迭代器类似,先问是否有下一行
while(sc.hasNextLine()) {
//取下一行数据
String line = sc.nextLine();
System.out.println(line);
}
}
我们需要先定义一个输入流对象来储存System.in的数据源;之后可以对数据源进行修改,使用文件输入流创建一个对象,填入我们想修改的数据来源,然后调用set方法将数据源从控制台改为指定的哪个文件;接下来创建一个扫描器对象sc来从文件中扫描数据;之后对文件的内容进行遍历,与迭代器类似,先询问是否还有下一行,然后定义一个字符串类型的变量来接收并将该字符串进行打印;
就会将之前录入的文件内容打印在控制台上;
五.拷贝操作的实现
我们可以利用IO流来实现对文件的拷贝工作;在下面的案例中,我们分别使用字节流和字符流两种方式来拷贝纯文本文件和视频文件;
与之前的案例不同的是,我们需要在一个方法中同时封装输入流和输出流,我们可以先处理字节流的拷贝方法;
public static void copyFile1(File fromFile, File toFile) throws IOException {
try(FileInputStream fis =
new FileInputStream(fromFile);
FileOutputStream fos =
new FileOutputStream(toFile)){
byte[] buffer = new byte[1024];
int len ;
while((len = fis.read(buffer))!=-1){
fos.write(buffer, 0 ,len);
}
}catch (Exception e){
e.printStackTrace();
}
}
首先在try模块的小括号中完成创建输入流和输出流,调用FileInput的构造器,传入我们需要的文件路径,输入流对象创建完毕,之后调用FileOutputStream的构造器,传入我们需要拷贝到的文件路径,输出流对象创建完毕;我们需要创建一个字节数组用于储存传入和传出的字节长度,然后定义一个int类型的变量len,作为长度,在while循环结构中,循环条件是字节数组中是否有新添加进的元素,在循环体中调用输出流对象的write方法来输出数组中的元素,之后就可以在main方法中调用该方法。传入的形参是两个File类型的对象;
/**
* 测试1,调用copyFile1方法拷贝一个纯文本文件
*/
File f1 = new File("./Exercise/111/a.txt");
File f2 = new File("./Exercise/abc/a.txt");
copyFile1(f1,f2);
/**
* 测试2,调用copyFile1方法拷贝一个视频文件
*/
File f3 = new File("./Exercise/111/1.mp4");
File f4 = new File("./Exercise/abc/1.mp4");
copyFile1(f3,f4);
至此使用字节流拷贝文件的案例就完毕了
使用字符流拷贝的方式与上述基本一致;
public static void copyFile2(String fromPath, String toPath) throws Exception {
try(FileReader fr =
new FileReader(fromPath);
FileWriter fw =
new FileWriter(toPath)){
char[] buffer = new char[1024];
int len = -1 ;
while((len =fr.read(buffer))!=-1){
fw.write(buffer,0,len);
}
}catch(Exception e){
e.printStackTrace();
}
}
在创建流的对象和字符数组与上述的案例有些许不同,需要注意。
/**
* 测试3,调用copyFile2方法拷贝一个纯文本文件
*/
copyFile2("./Exercise/111/a.txt","./Exercise/def/a.txt");
/**
* 测试4,调用copyFile2方法拷贝一个视频文件
*/
copyFile2("./Exercise/111/1.mp4","./Exercise/def/1.mp4");