IO流
即使再小的帆也能远航~
一.目录
二.内容
概述
File是java.io包下代表与平台无关的文件和目录,则程序中操作文件和目录,都可以通过File类来完成。File能新建、删除、重命名文件和目录。File不能访问文件内容本身,如果访问文件内容本身,则需要使用输入/输出流。
构造方法
File(File parent, String child)
根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
File(String pathname)
通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
File(String parent, String child)
根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例
字段摘要
static String pathSeparator
与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar
与系统有关的路径分隔符。
static String separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
static char separatorChar
与系统有关的默认名称分隔符。
常用方法
boolean canExecute()
测试应用程序是否可以执行此对象表示的文件
boolean createNewFile()
当且仅当不存在具有此对象指定名称的文件时,不可分地创建一个新的空文件。
boolean delete()
删除此对象表示的文件或目录。
boolean exists()
测试此对象表示的文件或目录是否存在
String getAbsolutePath()
返回此对象的绝对路径名字符串
File getAbsoluteFile()
返回此对象的绝对路径名形式。
String getName()
返回由此对象表示的文件或目录的名称
String getPath()
将此对象转换为一个路径名字符串。
String getParent()
返回此对象父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
File getParentFile()
返回此对象父目录的对象;如果此路径名没有指定父目录,则返回 null
boolean isDirectory()
测试此对象表示的文件是否是一个目录。
long length()
返回由此对象表示的文件的长度。
long lastModified()
返回此对象表示的文件最后一次被修改的时间。
String[] list()
返回一个字符串数组,这些字符串指定此对象表示的目录中的文件和目录。
String [] list(FilenameFilter filter)
返回一个字符串数组,这些字符串指定此对象表示的目录中满足指定过滤器的文件和目录。
boolean mkdir()
创建此对象指定的目录。
boolean mkdirs()
创建此对象指定的目录,包括所有必需但不存在的父目录。
boolean renameTo(File dest)
将对象剪切至dest位置
File[] listFiles()
返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
File[] listFiles(FileFilter filter)
返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录
boolean isFile()
测试此抽象路径名表示的文件是否是一个标准文件。
父路径+文件名=绝对路径;即:getParent()+getName()=getAbsolutePath();
获取dir下"第一级别"下的所有文件和目录的名字的数组
public class FileDemo {
public static void main(String[] args) {
File dir=new File("D:/Package");
String[] names = dir.list();
for (String name : names) {
System.out.println(name);
}
}
}
利用String [] list(FilenameFilter filter)方法获取某一目录下第一级别符合要求的的文件名和目录名
- FilenameFilter是一个接口,其内有一个方法:
boolean accept(File dir, String name)
测试指定文件是否应该包含在某一文件列表中。
- 利用匿名内部类 覆盖重写accept()方法;
代码示例:
public class FileDemo {
public static void main(String[] args) {
File dir=new File("D:/Package");
String[] names = dir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if (name.endsWith(".zip")):{
return true;
}
return false;
}
});
for (String name : names) {
System.out.println(name);
}
}
}
获取某目录下第一级别的文件和目录对象
public class FileDemo {
public static void main(String[] args) {
File dir = new File("D:/Package");
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()){
System.out.println(file.getName());
}
}
}
}
获取某目录下第一级别的符合要求文件和目录对象
1.FileFilter是一个接口,其内有一个方法:
an accept(File pathname)
测试指定对象是否应该包含在某个路径名列表中。
- 利用匿名内部类 覆盖重写accept()方法;
代码示例:
public class FileDemo {
public static void main(String[] args) {
File dir = new File("D:/Package");
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return true;
}
return false;
}
});
for (File file : files) {
System.out.println(file.getName());
}
}
}
文件移动(rename()方法)
背景:在D盘ABC文件夹下,有六个.txt文件,文件名前半部分是固定的,叫"世界上最美丽的地方之"; 任务:把六个.txt文件从D:/ABC移动的D:/ABC&把文件名前半部分去掉
public class FileDemo {
public static void main(String[] args) {
File dir = new File("D:/ABC");
String goout="世界上最美丽的地方之";
int len=goout.length();
File[] files = dir.listFiles();
for (File file : files) {
String filename = file.getName();
String newName = filename.substring(len);
File newFile = new File(dir, newName);
file.renameTo(newFile);
}
}
}
递归删除
递归:方法调方法
public class FileDemo {
public static void main(String[] args) {
File dir = new File("D:/ABC");
FileDemo.deleteRecursion(dir) ';
}
private static void deleteRecursion(File dir) {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile()) {
System.out.println(file.getName() + "正在被删除--->");
file.delete();
}else{
deleteRecursion(file);
}
}
System.out.println(dir.getName()+"==>目录正在被删除");
dir.delete();
}
}
概述
Java的IO通过java.io包下的类和接口来支持,在java.io包下主要包括输入、输出两种IO流,每种输入、输出流又可分为字节流和字符流两大类。其中字节流以字节为单位来处理输入、输出操作,而字符流则以字符来处理输入、输出操作。
流的分类
- 输入流和输出流
按照流向来分,可以分为输入流和输出流 输入流:只能从中读取数据,而不能向其写入数据;基本上是从磁盘文件到系统内存。 输出流:只能向其写入数据,而不能从中读取数据;基本上是从系统内存到磁盘
Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类。他们都是一些抽象基类,不能直接创建实例对象。 - 字节流与字符流
字节流和字符流的用法几乎一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的数据单元是一个byte字节,而字符流操作的数据单元是一个char字符。 字节流主要由InputStream和OutputStream作为基类,字符流主要由Reader和Writer作为基类。
InputStream字节输入流
InputStream是所有字节输入流的抽象基类,本身并不能创建实例来执行输入,它是所有字节输入流的模板,它的方法是所有字节输入流都可以使用的方法。
stract int read()
从输入流中读取数据的下一个字节。
int read(byte[] b)
从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
int read(byte[] b, int off, int len)
将输入流中最多 len 个数据字节读入 byte 数组。
Reader字符输入流
Reader是所有字符输入流的抽象基类,任何一个字符输入流的子类都可以使用Reader上定义的方法。使用字符输入流的可以更加便利的对文本文件进行处理,而且也不会出现中文乱码等情况。因为使用Reader字符流时是按照字符来进行读取,一次读取到一个完整字符数据。
int read()
读取单个字符。
int read(char[] cbuf)
将字符读入数组。
abstract int read(char[] cbuf, int off, int len)
将字符读入数组的某一部分。
OutputStream字节输出流
OutputStream字节输出流提供了对外输出功能的操作,可以将程序运行产生的数据保存到指定的文件中去,是实现数据的持久化操作。
void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b, int off, int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b)
将指定的字节写入此输出流
void close()
关闭此输出流并释放与此流有关的所有系统资源。
void flush()
刷新此输出流并强制写出所有缓冲的输出字节。
Writer字符输出流
riter是Java提供的字符输出流的抽象基类,所有的字符输出流实现类都是以Writer为基础进行扩展。
Writer append(char c)
将指定字符添加到此 writer。
abstract void close()
关闭此流,但要先刷新它。
abstract void flush()
刷新该流的缓冲。
void write(char[] cbuf)
写入字符数组。
abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String str)
写入字符串。
void write(String str, int off, int len)
写入字符串的某一部分。
FileInputStream
构造方法
FileInputStream(File file)
通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
public class IOTest01 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("F:\\a.txt");
int readData = 0;
while((readData=fis.read())!=-1){
System.out.println(readData);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class IOTest01 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("a.txt");
int readData = 0;
byte[] bytes = new byte[4];
while((readData=fis.read(bytes))!=-1){
System.out.println(new String(bytes,0,readData));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
对 if(fis!=null) 的解释:
这个判断条件很有必要加,加完之后。
假设fis为空,那么就不需要关闭了,就不会执行 if{ } 中关闭流这些代码;如果fis不为空,就关闭流。
这样做的目的是防止空指针异常。
你想想,如果不加的话,fis为空,即fis这个引用没有指向任何对象,那它怎么调用close()方法,这样就会发空指
针异常。
对read()方法的解释:
read()方法就是“读取”,每次读取一个字节大小的数据,返回一个int类型的字面量。
每读取一个,光标就会往下移动一个字节,以便读取下一个字节。
当读取的返回值是-1时,表示文本已经读完了。
read(byte[] b)的解释:
这个方法,是将读取到的多个字节存放到字节数组中,然后一次性搬到内存。这就好比外卖小
哥学聪明了,他用大袋子装了很多饭盒,这就不用往复跑很多次了。这样,效率就变高了。
注意:read()的返回值是一个小于256的int类型的数字;read(byte[] b)的返回值表示读到了几个字节数量,比如我们 定义一个byte[1024],也就是能存1024个字节的数组,然后我们的文件大小是1025个字节,那么使用循环读取,第 一次返回的int是1024,第二次是1,第三次是-1。
FileOutputStream
构造方法
FileOutputStream(File file)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
FileOutputStream(File file, boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
public class IOTest01 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("new.txt");
byte[] bytes = {97,98,99,100};
fos.write(bytes);
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
执行以上代码之后,我们可以看到在 javase 文件夹下,多了一个新的 new.txt 文件,且文本内容是 abcd 。此时,我们想在 new.txt 中,再加上 efg,使文本内容变成 abcdefg,这里就不再演示了,大家改改代码验证一下就行。
FileOutputStream fos = new FileOutputStream("new.txt",true);
汉字转换成字节:使用getBytes()方法。
try{
fos = new FileOutputStream("new.txt",true);
byte[] bytes = "我爱中国".getBytes();
fos.write(bytes);
fos.flush();
}
try{
fos = new FileOutputStream("new.txt",true);
fos.write("我爱中国".getBytes());
fos.flush();
}
文件复制
已经学习了FileInputStream和FileOutputStream,输入流是将数据从硬盘读取到内存,输出流是将数据从内存写入到硬盘,那这样的话,我们就能实现文件的复制了。这次不操作文本文件,我们来试一下图片的复制。
public class IOTest01 {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("01.jpg");
fos = new FileOutputStream("IO流\\01.jpg");
byte[] bytes = new byte[1024];
int len=-1;
while(len=fis..read(bytes))!=-1){
fis.read(bytes);
fos.write(bytes);
fos.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
有了前面的学习,这里的代码就不难了。执行程序,图片就复制成功了。 注意这里前面说过,字节流能操作任何类型的文件,所以这里除了复制图片,复制其他类型的文件也是可以的,只不过如果是比较大的视频,效率可能会比较低。
FileReader、FileWriter
字节流的read()和write(),是一个字节一个字节地读取和写入,那么字符流的read()和write(),自然就是一个字符一个字符地读取和写入。比如我们读取一个内容为 我爱abc 的文本文件,会先读 ‘我’ ,然后光标移动一位,下次调用read()方法的时候,就是读 ‘爱’。
FileReader
public class IOTest01 {
public static void main(String[] args) {
FileReader fr = null;
try {
fr = new FileReader("a.txt");
int len=-1;
char []gyu=new char[1023];
while ((len=reader.read(gyu))!=-1){
System.out.println(gyu);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fr!=null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
FileWriter
public class IOTest01 {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("a.txt",true);
char[] chars = {'我','爱','中','国'};
fw.write(chars);
fw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fw!=null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
有四个缓冲流,分别是 BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream ,和文件流中的字节流和字符流类似,缓冲流中的字节流和字符流之间基本的方法也都是类似的,因此这里只介绍 BufferedReader和BufferedWriter。
所谓缓冲流,就是自带字节数组或者字符数组,我们不需要像之前那样去定义字节数组或者字符数组了,因为缓冲流中是自带的。
BufferedReader和BufferedWriter特有方法(创建对象不能再用多态了)
String readLine()
读取一个文本行
void newLine()
写入一个行分隔符。
BufferedReader
public class IOTest01 {
public static void main(String[] args) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("a.txt"));
String str = null;
while((str=br.readLine())!=null){
System.out.println(str);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这里还有两个概念,就是节点流和包装流,很好理解。当我们将一个流对象A作为参数传给另一个流B的构造方法时, A就是节点流,B就是包装流。 节点流和包装流是相对而言的,比如下面的代码中,InputStreamReader对于FileInputStream来说是包装流,对于 BufferedReader来说是节点流。
BufferedWriter
BufferedWriter就不多介绍了,你想想,BufferedReader可以读取一行,且读取到的一行是字符串,那么相对的,BufferedWriter的write()方法,也可以写入一行字符串
public class IOTest01 {
public static void main(String[] args) {
BufferedReader br = null;
try {
br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a.txt")));
br.write("张三\n");
br.write("李四");
br.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
flush()何时使用?
IO流的四个基类:InputStream,OutputStream,Reader,Writer;
只有OutputStream和Writer有flush()方法=>flush()在OutputStream和Writer的子类中使用
输入/输出体系中提供了两个转换流InputStreamReader和OutputStreamWriter,这两个转换流用于实现将字节流转换成字符流,其中InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流。
InputStreamReader
在处理文本文件的数据时,字符流往往要比字节流方便了很多,但是在某种情况下,处理数据时,并不会直接对数据源(文件)操作,接收到的有可能是一个字节流。那么在这种情况下就希望对字节流进行一次包装,能把字节流包装成一个字符流。
InputStreamReader就是一个包装流,创建这个流对象时需要传入一个字节流对象作为参数,并将这个字节流包装成一个字符流对象返回,包装后的字符流跟普通的字符流对象操作方式完全一样。流对象操作完毕后,进行关闭时,只需要关闭包装流即可,因为关闭包装流时会同时将内部的字节流关闭。
OutputStreamWriter
OutputStreamWriter是一个字符输出的转换流,可以将一个字节输出流转换成一个字符输出流。创建转换流OutputStreamWriter对象时,需要将一个字节输出流对象作为参数传递进去,并包装成一个字符输出流返回。输出操作结束后,关闭字符流时,同时也会将内部的字节输出流同时关闭。使用包装流进行输出时,希望原有数据文件中的数据不被覆盖掉,那就需要传入给OutputStreamWriter的字节流就是以追加的方式进行输出的。因为OutputStreamWriter仅仅是个包装流,将字节流进行了一次封装,底层还是字节流的机制运行的。
步骤:
- 将字节流转换成字符流(也就是InputStreamReader或者 OutputStreamWriter)
InputStreamReader是InputStream的子类,FileReader的父类,InputStreamReader是一个具体类(非抽象类),那几个read方法是它从InputStream那继承来的,又传给了FileReader==>我们用FileReader来完成的常规操作,用InputStreamReader也可以完成(两者功能上等效(此时是节点流),且InputStreamReader同时还可以起转换作用(此时是包装流))==>FileReader可以被缓冲流修饰,InputStreamReader也可以
?OutputStreamWriter是OutputStream的子类,FileWriter的父类, OutputStreamWriter是一个具体类(非抽象类),那几个write方法是它从OutputStream那继承来的,又传给了FileWriter==>我们用FileWriter来完成的常规操作,用 OutputStreamWriter也可以完成(两者功能上等效(此时是节点流),且 OutputStreamWriter同时还可以起转换作用(此时是包装流))==>FileWriter可以被缓冲流修饰, OutputStreamWriter也可以 - 利用字符流的方法进行相关操作
- 关闭InputStreamReader或者 OutputStreamWriter(两者是包装流),因为关闭包装流时会同时将内部的字节流关闭。
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("syt"));
==>进一步 BufferedWriter bw = new BufferedWriter(osw);
==>即: BufferedWriter bw= new BufferedWriter(new OutputStreamWriter(new FileOutputStream("syt")));
学习怎么向文件中写入java对象。
为什么我们要向文件中写入java对象呢?
java对象中可以保存数据,假设我们定义了一个User类(用户)来存放用户的一些信息,比如账号、密码,以及个人 信息等,这些在用户注册完账号后,都要先把数据作为参数保存在User对象中,再将User对象写入到文件中,这样就 能将用户信息永久地保存在硬盘中了。 当下一次用户登陆的时候,我们就去文件中找到对应的账号,只有用户输入的密码是正确的,才能成功地登陆。
什么是“序列化”和“反序列化”?
这两个流因为能写入和读取文件中的java对象,因此称为“对象流”。其中,ObjectOutputStream又称为“序列化流”, ObjectInputStream又称为“反序列化流”。 序列化是指将对象从内存写入到硬盘的过程,反序列化是指将对象从硬盘读取到内存的过程。
构造方法
构造方法仍然是需要传入FileOutputStream和FileInputStream(多态,再次强调!)
ObjectInputStream(InputStream in)
创建从指定 InputStream 读取的 ObjectInputStream。
ObjectOutputStream(OutputStream out)
创建写入指定 OutputStream 的 ObjectOutputStream
先创建一个User类,等会就来存这个类的对象
public class User {
private long account;
private String password;
public User(long account, String password) {
this.account = account;
this.password = password;
}
public long getAccount() {
return account;
}
public void setAccount(long account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"account=" + account +
", password='" + password + '\'' +
'}';
}
}
-
序列化流将对象写入文件 现在,我们来实际操作一下,首先是序列化流ObjectOutputStream public class IOTest01 {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
User user = new User(123456, "abc");
oos.writeObject(user);
oos.flush();
oos.close();
}
}
执行以上程序,结果出异常了 解决方法很简单,我们修改一下User类如下:也就是去实现Serializable这个接口 public class User implements Serializable{
}
我们查看这个Serializable接口,发现这个接口里面竟然什么都没有写! 这里扩展一下,向Serializable这种什么内容都没有的接口,称为’标识接口’。 实现Serializable这个接口,并不是想要实现它的某些方法,或者遵从它的某些规范,它仅仅只是一个标识的作用, 它仅仅只是要告诉程序员,实现了它的类,就是可以序列化的类。 也就是说,实现了Serializable之后,现在我们的User对象可以序列化了。 继续执行将其序列化的代码,发现成功了,而且生成了一个user文件,且打开之后都是乱码。想要知道里面存的是什么,方法就是使用反序列化流来读取。 -
反序列化流读取文件中的对象 其实,我们发现,加上序列化流中上述代码,和之前相比只多了两个新的方法,writeObject()和readObject(),所以要掌握的新内容也不多,下面直接给代码,很容易看懂 接下来,开始反序列化流ObjectInputStream的代码: public class IOTest02 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
Object obj = ois.readObject();
System.out.println(obj);
ois.close();
}
}
成功读取出了对象。
-
存入和读取多个对象向文件中写入多个对象,不能向其他流一样,通过 new FileOutputStream(" ",true) 的方式,也就是在文件末继续写入对象来实现(原因比较复杂,我会重新开坑,写一篇专门介绍这个原因的博客)。 而是采用集合的方式,向集合对象中添加入多个User对象,再将集合对象写入到文件中,来实现一次性存入多个User对象。如果你还没学过集合,就先理解成集合是一个容器,可以存放对象。当然,这不是唯一的实现方式,且这是较差的一种方式,我都会在新坑里做介绍的。 我们先看看代码,注意要先把原来的 user 文件删除掉。 写入集合对象的代码如下: public class IOTest01 {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
List<User> list = new ArrayList<>();
list.add(new User(111,"abc"));
list.add(new User(222,"abc"));
list.add(new User(333,"abc"));
oos.writeObject(list);
oos.flush();
oos.close();
}
}
读取集合对象: public class IOTest02 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
List list = (ArrayList) ois.readObject();
User user1 = (User) list.get(0);
User user2 = (User) list.get(1);
User user3 = (User) list.get(2);
System.out.println(user1);
System.out.println(user2);
System.out.println(user3);
ois.close();
}
}
这样,我们就取出了User对象 这一小节的开头我有说到,使用 new FileOutputStream(" ",true) 作为参数传给 ObjectOutputStream 的构造方法,来实现添加对象的方式是行不通的。因此,如果我们还要写入更多的User对象,只能将集合读取出来,往集合中存入新的User对象,再将集合继续写入文件来实现。我们来看下面的代码,稍微修改一下IOTest02即可。 public class IOTest02 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
List list = (ArrayList) ois.readObject();
System.out.println(list);
list.add(new User(444,"abc"));
list.add(new User(555,"abc"));
ois.close();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
oos.writeObject(list);
oos.flush();
oos.close();
}
}
再执行一下以下程序,验证我们的文件中的集合中已经有5个User对象了。 public class IOTest03 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
List list = (ArrayList) ois.readObject();
System.out.println(list);
}
}
这种方式,显然过于繁琐,所以我才说这是一种比较差劲的方式。当然,这里你也可以不用深究,也没有必要深究。如果想深究的话,可以去看看其他博客,或者等我把填坑的博客写完,我会在这里加上链接
-
transient关键字很简单,比如你给 User 类中的 password 变量加上 transient,(private transient String password;)那么序列化 User 类对象的时候,变量 password 将不会被写入到文件中。 以下代码可以直接复制粘贴(记得删除 user 文件),没有学习的必要,主要是要知道 transient 关键字的作用即可。 public class IOTest01 {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user"));
oos.writeObject(new User(111,"abc"));
oos.flush();
oos.close();
}
}
public class IOTest02 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user"));
User user = (User) ois.readObject();
System.out.println(user);
}
}
我们取出来的 User 对象,其变量 password 的值是null,说明我们在给 User 类的 password 加上关键字 transient 之后,其就没有被写入到文件了。 -
序列化版本号了解序列化版本号之前,我们还是先来试试代码。 我们把 User 类中的 transient 去掉,然后运行第4节中的两段代码,此时我们的 user 文件中就有一个对象,User{account=111, password=‘abc’}。现在,我们修改一下 User 类,给它新增一个成员变量 email,如下,请大家直接复制代码试验即可 public class User implements Serializable {
private long account;
private String password;
private String email;
public User(long account, String password, String email) {
this.account = account;
this.password = password;
this.email = email;
}
public long getAccount() {
return account;
}
public void setAccount(long account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"account=" + account +
", password='" + password + '\'' +
", email='" + email + '\'' +
'}';
}
}
改完之后,我们再去执行第4节中的第二段代码,来读取我们的 User 类对象,发现报错了。错误如下: java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = 3094711356857122676, local class serialVersionUID = 4110144175829258817 原来报错的根本原因是类的序列化版本号不一致。 那么这个序列化版本号究竟是什么呢? Java虚拟机在看到 User 类实现了 Serializable 这个标识接口之后,就会给这个类自动生成一个序列化版本号。这个版本号的作用,是确保流中的类和文件中的类是同一个类,因为只有同一个类,序列化之后,才能被反序列化。 由于我们修改了 User 类,导致 Java虚拟机 重新为 User 类生成了一个新的序列化版本号,所以导致不一致。 但是,我们自己清楚,这两个类确实都是 User 类。所以,我们不需要 Java虚拟机 为我们自动生成序列化版本号,我们可以自己定义,且当我们自己定义的时候,便不会再自动生成。 自定义序列化版本号非常简单,只需要在类中加上 serialVersionUID 这个常量即可,如下: public static final long serialVersionUID = 16516516543215L;
【小总结】:我们想要序列化和反序列化的对象,都要实现 Serializable 这个接口,且最好手动给它加上一个自定义的序列化版本号。 另外,我们在 IDEA 中也可以设置自动生成序列化对象的代码,设置方法如下:将√取消掉,然后apply,OK。 设置完之后,只要实现了 Serializable 接口的类,可以将光标停在该类上,然后按 “Alt + Enter”,就能手动添加序列化版本号了。
你还记得我们学习java的第一个程序“Hello World”吗?打印字符串的语句:
System.out.println("Hello world");
这我们都会写。但是你有没有去想过,为什么System.out.println(),就能将字符串打印在控制台上吗?我来解释给你听。 1)首先了解System,它也是一个类,在java.lang包下面,java规定,lang包下的类都是不用导入就能直接使用的。因此我们不导包,也能直接使用System类。 2)其次了解System.out。我们来看System类的部分源代码。System类中有一个引用out,其类型是就是PrintStream,我们还发现,这是一个用static修饰的引用,因此我们能直接用 System.out 进行访问。 这里的 out 是 null ,但往下的代码中会对其进行赋值,这里涉及到的有些方法是用C++或者C代码写的,我们没有必要再深究了。只要知道,其最后会被赋值一个PrintStream对象即可。 3)接着了解整句,System.out.println()。我们看PrintStream的源码。我们发现,原来println()是PrintStream类的一个方法。关于这个方法为什么能将字符串打印到控制台上,我们依旧没有必要再深究了。
来,也就是说我们得到以下的代码,两者是完全等价的。
public class IOTest02 {
public static void main(String[] args) throws IOException {
System.out.println("张三");
PrintStream ps = System.out;
ps.println("张三");
}
}
理解了System.out.println()之后,先转个脑回路,我们思考一个问题:凭什么只能输出在控制台上,我输出到其他地方不可以吗?我们来看看System类的源码。我们发现一个setOut()方法,根据字面意思,就是设置输出,这应该是就是我们要用到的方法了。
setOut()方法调用了两个方法,我们来看setOut0()方法。发现它是个本地方法(用关键字 native 修饰的方法称为本地方法,底层是用C++写的),那就不深究了,我们会用setOut()方法就行。
setOut()方法需要传入一个PrintStream对象,因此我们需要先创建一个PrintStream对象,PrintStream的构造方法又需要传入一个OutputStream,OutputStream是抽象类,因此我们要使用其子类FileOutputStream来创建(利用多态特性)。 万事俱备了,我们来看代码
public class IOTest01 {
public static void main(String[] args) throws IOException {
PrintStream ps = new PrintStream(new FileOutputStream("a.txt"));
System.setOut(ps);
System.out.println("张三");
System.out.println("李四");
System.out.println("王五");
}
}
那输出到文件有什么作用呢?作用大着呢,我们可以利用上述代码来写一个日志类,通过日志类来生成日志。(日志文件,就是专门用来记录程序执行情况的文件) 日志类的代码如下:(这个类的代码是垃圾代码,不规范,只是想让大家知道日志是个什么东西)
public class Logger {
public static void log(String str){
try {
PrintStream ps = new PrintStream(new FileOutputStream("log.txt",true));
System.setOut(ps);
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String time = sdf.format(date);
System.out.println(time + ":" + str);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
创建好Logger类之后,我们执行以下程序。日志文件就生成了,打开看文件中的内容,你就了解日志文件是个什么玩意儿了。
public class IOTest01 {
public static void main(String[] args) throws IOException {
Logger.log("调用了login()方法,用户开始登陆");
Logger.log("用户登陆成功");
}
}
略
略
略
略
|