文章目录
JAVA I/O流
一、I/O流简要概述
I/O(input/output)流,即输入输出流,是java中实现输入输出的基础。在java.io包中存在的额四个顶级类,分别为:InputStream和OutputStream、Read和Write,其中InputStream和Read是输入流OutputStream和Write是输出流。
根据操作的数据单位不同:字节流、字符流
根据流传输的方向:输入流、输出流
输入流:只能从流中读取数据
输出流:只能向流中写入数据
根据流的不同功能功能:节点流、处理流
节点流又被称为低级流,是指可以向一个特定的可以向I/O设备中写入数据的流,只能连接数据源,进行数据的读写操作;
处理流又被称为高级流,用于对一个已经存在的节点流进行连接和封装,来实现流的读写能力,不能直接和实际的数据源连接。
返回顶部
1、字节流
1.1 字节流概述
在计算机中,无论是文本、图片、音频还是视频,所有的文件都是以二进制(字节)形式存在,I/O流中针对字节的输入输出提供了一系列的流,统称为字节流。其中InputStream和OutputStream是字节输入、输出流中的顶级父类。
InputStream常用方法
方法
| 说明
|
int read()
| 读取一个8位的字节,将其转换为0-255之间的整数并返回,当前没有字节可用时,返回-1
|
int read(byte[] b)
| 将若干个字节保存在数组中,一次性输出,返回的整数表示读取的字节数目
|
int read(byte[] b,int off,int len)
| off指定开始保存数据的其实下标 ,len表示读取的字节数目
|
void close()
| 关闭此输入流并释放相关联的系统资源
|
OutputStream常用方法
方法
| 说明
|
int write()
| 读取一个8位的字节,将其转换为0-255之间的整数并返回,当前没有字节可用时,返回-1
|
int write(byte[] b)
| 将若干个字节保存在数组中,一次性输出,返回的整数表示读取的字节数目
|
int write(byte[] b,int off,int len)
| off指定开始保存数据的其实下标 ,len表示读取的字节数目
|
void flush()
| 刷新此输出流并强制写出所有缓冲的输出字节
|
void close()
| 关闭此输出流并释放相关联的系统资源
|
1.1.2 注意
InputStream和OutputStream只是抽象类,不能够被实例化,具体的实现需要实现关系下的不同实现类来完成。下面是输入、输出流的一些基本的实现子类:
返回顶部
1.2 字节流读写文件(FileOutputStream、FileIutputStream)>
从名称就可以看出来File是针对文件进行操作的输入输出流,其中input主要针对文件读取,output主要针对文件的写入。接下来上一个小案例:
FileIutputStream
package File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class input {
public static void main(String[] args) throws IOException {
// 创建一个文件字节输入流对象来读取文件
FileInputStream in = new FileInputStream("src/File/text.txt");
// 定义int类型的变量
int a = 0;
// 遍历循环读取文件,直到返回值为-1的时候停止
while((a=in.read())!= -1){
System.out.print(a+" ");
}
// 关闭流
in.close();
}
}
这里我们创建了一个输入流对象和txt文本文件,通过输入流的read()方法,遍历读取文本字符,将其转化为字节0-255的对应数,输出。
FileOutputStream
package File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class output {
public static void main(String[] args) throws IOException {
// 定义一个字符串对象
String a = "星星会不会趁着人间烟火坠落时,偷偷溜下来见想见的人。";
//String b = "我想在山谷开一家店,贩卖猫的脚步声。";
// 创建文件输出流对象
FileOutputStream out = new FileOutputStream("src/File/out.txt");
FileOutputStream output = new FileOutputStream("src/File/out.txt",true);
// 将字符串转化为字节数组进行写入操作
out.write(a.getBytes());
//output.write(b.getBytes());
// 关闭流
out.close();
}
}
在这里我们定义了文件输出流对象和一个字符串,用于写入文件。通过文件输出流对象的write()方法,把字符串的的字节存入数组当做参数传入write()。
注意:通过文件输出流对象向一个已存在的文件中写入的时候,会首先清除原文件中的数据,再重新写入新的内容。如果希望在原有的数据基础上追加新的内容,则需要在创建的文件输出流对象的时候调用其含参构造方法FileOutputStream(File file, boolean append) ,将append值设定为true即可。
返回顶部
1.3 文件的拷贝
类似于Map集合中的 key/value一样,在应用程序中,I/O流通常也都是成对出现的。文件的拷贝就需要通过输入流先来读取数据源文件中的数据,并通过输出流将数据重新写入新文件。
package FileCopy;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class copy {
public static void main(String[] args) throws Exception{
// 创建输入流对象读取数据
FileInputStream in = new FileInputStream("src/FileCopy/平行宇宙.png");
// 创建输出数据流写入数据
FileOutputStream out = new FileOutputStream("src/FileCopy/copy.png");
// 定义一个int类型变量
int len = 0;
// 获取当前时间
long begin = System.currentTimeMillis();
// 遍历获取数据源信息并将其写入
while((len = in.read())!=-1){
out.write(len);
}
// 获取遍历后的时间
long end = System.currentTimeMillis();
System.out.println("文件读写用时"+(end-begin)+"秒");
// 关闭流
in.close();
out.close();
}
}
在拷贝过程中通过while循环将字节逐个进行拷贝。每循环一次就通过read方法读取一个字节,并通过write方法将该字节写入目标输出文件。知道读取的长度返回-1,停止读取、写入并在控制台输出读取写入文件的用时。
返回顶部
1.4 字节流缓冲区
对比上面案例是将字节一个一个读取写入,就好比去银行存钱,1000元的硬币,一元一元的往账户里存(当然实际是不可能的),这时候,为了减少存入的次数,可以先将1000元的硬币换成纸币100元的来进行打包,然后一次性好几张100的存入,这样一来节省了大量的时间。文件传输也可以进行“打包”,我们可以定义一个数组作为字节流缓冲区,在拷贝文件的时候一次性读取多个字节。
package FileCopy;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class mulCopy {
public static void main(String[] args) throws Exception{
// 创建输入流对象读取数据
FileInputStream in = new FileInputStream("src/FileCopy/平行宇宙.png");
// 创建输出数据流写入数据
FileOutputStream out = new FileOutputStream("src/FileCopy/mulcopy.png");
// 定义一个int类型变量、一个长度为1024的字节数组
int len = 0;
byte[] buff = new byte[1024];
// 获取当前时间
long begin = System.currentTimeMillis();
// 遍历获取数据源信息存入字节数组并将其写入
while((len = in.read(buff))!=-1){
// 每循环读取一次字节数组,将读取到的内容写入新文件
out.write(buff,0,len);
}
// 获取遍历后的时间
long end = System.currentTimeMillis();
System.out.println("文件读写用时"+(end-begin)+"毫秒");
// 关闭流
in.close();
out.close();
}
}
通过对比,可以发现使用字节流缓冲区可以大大节省拷贝过程消耗的时间~
程序中的缓冲区就是一块内存,该内存主要用于存放再试输入输出的数据,由于使用缓冲区减少了对文件的操作次数,所以可以提高对数据文件的读写效率。
返回顶部
1.5 字节缓冲流
在I/O包中提供了两个带缓冲的字节流,BufferedInputStream 和 BufferedOutputStream,他们在构造方法中分别接受了输入输出流类型的参数作为对象,在读取数据的时候提供缓冲功能。
通过图示我们可以发现应用程序是通过字节缓冲流来完成数据的读写,而缓冲流又是通过底层字节流来和目标设备进行关联的。
package Buffered;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class buffered {
public static void main(String[] args) throws Exception{
// 创建用于输入输出的字节缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/Buffered/黑白1.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/Buffered/黑白1copy.jpg"));
// 定义一个变量len、一个字节缓冲数组
int len = 0;
// byte[] buff = new byte[1024];
// 得到读取文件前的当前时间
long begin = System.currentTimeMillis();
// 循环遍历
while((len=bis.read())!=-1){
bos.write(len);
}
bos.flush();
// 得到读取文件后的当前时间
long end = System.currentTimeMillis();
System.out.println("花费的时间是"+(end - begin)+"毫秒");
// 关闭流
bis.close();
bos.close();
}
}
这里我们并没有定义字节流缓冲数组,但是发现运行的时间很短,其实BufferedInputStream 和 BufferedOutputStream两个流内部都已经定义过了一个大小为8192的字节数组,可以直接使用。
返回顶部
2、字符流
2.1 字符流简要概述
同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader和Writer。下面是它们常用到的子类:
2.2 字符流操作文件
与字节流读写字节不同,字符流在文件读取的时候直接读取单个或者一组字符。读取写入形式与字节流方法大致一样,只是采用输入流FileRead与输出流FileWriter。
返回顶部
2.3 字符缓冲流
类似于字节缓冲流,字符也有缓冲流,分别是BufferedReader、BufferedWriter,具体操作与字节缓冲流类似,只是创建的缓冲区不同。在BufferedReader中有方法readline()和newline(),其中readline()方法用于一次读取文本的一行,newline()用于换行写入。
package Buffered;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class charbuffered {
public static void main(String[] args) throws Exception{
// 创建对象
BufferedReader br = new BufferedReader(new FileReader("src/Buffered/科比.jpg"));
BufferedWriter bw = new BufferedWriter(new FileWriter("src/Buffered/永远的黑曼巴.jpg"));
// 声明一个字符串的变量
String str = null;
// 遍历读取写入
while ((str = br.readLine()) != null){
// 通过缓冲流写入
bw.write(str);
// 每次写入后换行
bw.newLine();
}
//关闭流
br.close();
bw.close();
}
}
注意:
以上代码运行的时候会错,生成文件会打不开。因为我这里传入的是个图片文件,非纯文本文件,通过字符缓冲流的FileWriter只能够读取纯文本文件!
返回顶部
2.4 转换流
I/O流中可分为字节流、字符流,有时候字节流和字符流之间也需要进行转换。为此,JDK中提供了两个类用于实现将字节流转换为字符流的类—InputStreamReader、OutputStreamReader,它们都可以将字节输入(出)流转换成字符输入(出)流,方便字符的直接读取与写入。
package transTream;
import java.io.*;
public class transform {
public static void main(String[] args) throws Exception{
// 创建字节输入流对象
FileInputStream in = new FileInputStream("src/transTream/reader.txt");
// 将字节流输入对象转化为字符流输入对象
InputStreamReader inputStreamReader = new InputStreamReader(in);
// 创建字符输入缓冲流对象
BufferedReader br = new BufferedReader(inputStreamReader);
// 创建字节输出流对象
FileOutputStream out = new FileOutputStream("src/transTream/writer.txt");
// 将字节输出流对象转化为字符流输出对象
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(out);
// 创建爱你字符输出缓冲流对象
BufferedWriter bw = new BufferedWriter(outputStreamWriter);
// 定义一个字符串
String str = null;
// 遍历
while ((str= br.readLine())!=null){
bw.write(str);
bw.newLine();
}
// 关闭流
br.close();
bw.close();
}
}
注意:
在使用转换流时,只能正对操作文本文件的字节流进行转换,如果字节流操作的是字节码内容的文件(图片、视频等),此时转换为字符流就会造成数据丢失。,
返回顶部
二、File类
通过前面的学习,可以了解一些对于文件内容读取写入的内部操作。File类则提供了我们对于文件自身外部的操作,简单地说就是创建、删除、重命名文件。
2.1 File类的常用方法
File类用于封装一个路径,这个路径可以是绝对路径,也可以是相对路径。File类内部封装的路径可以指向一个文件,也可以指向一个目录。
方法
| 说明
|
boolean canRead()
| 测试应用程序是否可以读取由此抽象路径名表示的文件。
|
boolean canWrite()
| 测试应用程序是否可以修改由此抽象路径名表示的文件。
|
boolean createNewFile()
| 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。
|
boolean delete()
| 删除由此抽象路径名表示的文件或目录。
|
boolean equals(Object obj)
| 测试此抽象路径名与给定对象的相等性。
|
boolean exists()
| 测试此抽象路径名表示的文件或目录是否存在。
|
String getAbsolutePath()
| 返回此抽象路径名的绝对路径名字符串。
|
String getName()
| 返回由此抽象路径名表示的文件或目录的名称。
|
String getParent()
| 返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。
|
String getPath()
| 将此抽象路径名转换为路径名字符串。
|
boolean isAbsolute()
| 测试这个抽象路径名是否是绝对的。
|
boolean isDirectory()
| 测试此抽象路径名表示的文件是否为目录。
|
boolean isFile()
| 测试此抽象路径名表示的文件是否为普通文件。
|
long length()
| 返回由此抽象路径名表示的文件的长度。
|
String[] list()
| 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
|
String[] list(FilenameFilter filter)
| 返回一个字符串数组,命名由此抽象路径名表示的目录中满足指定过滤器的文件和目录。
|
File[] listFiles()
| 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
|
boolean mkdir()
| 创建由此抽象路径名命名的目录。
|
boolean mkdirs()
| 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
|
boolean renameTo(File dest)
| 重命名由此抽象路径名表示的文件。
|
常用的文件操作:
package File;
import java.io.File;
public class file {
public static void main(String[] args) {
// 创建file文件对象
File file = new File("src/File/example.txt");
// 相关的方法
System.out.println("文件名:"+file.getName());
System.out.println("文件绝对路径:"+file.getAbsolutePath());
System.out.println("文件相对路径:"+file.getPath());
System.out.println("文件的父路径:"+file.getParent());
System.out.println(file.canRead()?"文件可读":"文件不可读");
System.out.println(file.canWrite()?"文件可写":"文件不可写");
System.out.println(file.isFile()?"是一个文件":"不是一个文件");
System.out.println(file.isDirectory()?"是一个目录":"不是一个目录");
System.out.println(file.isAbsolute()?"是绝对路径":"不是绝对路径");
System.out.println("文件最后的修改时间是:"+file.lastModified());
System.out.println("文件的大小是:"+file.length()+"bytes");
System.out.println("是否成功删除文件:"+file.delete());
}
}
遍历目录下的文件:
首先指定路径,然后判断该路径是否为文件目录,若是则调用list()方法,获得一个String类型的数组filename,该数组中存储了所有该路径下的文件名及文件夹名。通过Arrays工具类的Strean()方法,将得到的数组转化为流并进行遍历。
package File;
import java.io.File;
import java.util.Arrays;
public class fileiterate {
public static void main(String[] args) {
// 创建File对象,并指定文件路径
File file = new File("G:\\Projects\\IdeaProjects\\IO\\src\\");
// 判断是否为目录
if(file.isDirectory()){
// 如果是目录,从目录中获取所有文件的名称
String[] fileName = file.list();
// 对指定路径下的文件或目录进行遍历
Arrays.stream(fileName).forEach(f->System.out.println(f));
}
}
}
返回顶部
查询特定文件名结尾的文件:
针对文件筛选的需求,File类的list()重载的一个方法list(FilenameFilter filter),该方法接受一个FilenameFilter接口类型的参数,FilenameFilter是一个函数式接口,被定义为文件过滤器,接口中定义了一个抽象方法accept(File dir,String name),用于对指定文件的所有子目录进行迭代筛选,从而获得需要的文件。
package File;
import java.io.File;
import java.util.Arrays;
public class fileIteratetxt {
public static void main(String[] args) {
// 创建File对象,并指定文件路径
File file = new File("G:\\Projects\\IdeaProjects\\IO\\src\\bytecharstream");
// 判断是否为目录
if(file.isDirectory()){
// 如果是目录,从目录中获取所有文件的名称
String[] fileName = file.list((dir, name) -> name.endsWith(".txt"));
// 对指定路径下的文件或目录进行遍历
Arrays.stream(fileName).forEach(f->System.out.println(f));
}
}
}
返回顶部
查询所有子目录下的文件:
通常在查询文件的时候都会像俄罗斯套娃那样,在我们的文件下存在着子文件(这里比喻只针对一对一),如果想要得到子目录下的文件对象,list() 方法显然不能满足。File类提供的另一个listFile()方法就派上用场了,该方法返回一个File对象数组,当数组中的元素进行遍历的时候,如果元素下还有子目录需要遍历,就可以使用递归再次遍历其子目录。
package File;
import java.io.File;
public class filefile {
public static void fileDir(File file) {
// 获得路径下的所有文件,存到数组中
File[] listFile = file.listFiles();
// 循环遍历数组
for (File files : listFile) {
// 如果遍历的是目录,则使用递归遍历子目录
if(files.isDirectory()) {
fileDir(files);
}
System.out.println(files);
}
}
public static void main (String[]args) throws Exception {
// 创建File对象
File file = new File("G:\\Projects\\IdeaProjects\\IO\\src\\");
// 调用fileDir方法
fileDir(file);
}
}
注意:
// 如果遍历的是目录,则使用递归遍历子目录
if(files.isDirectory()) {
fileDir(files);
}
此处是遍历以获取的目录下的文件,如果该文件是目录,则对其进行迭代遍历。不能在传参的时候传入创建的file对象,否则会死循环,无限遍历路径下的文件目录,导致栈溢出异常。
返回顶部
删除文件及目录:
在实现文件删除的时候,我们可以利用File类提供的delete()方法来完成。当然如果我们要删除整个文件,首先要判断当前目录下是否存在文件,如果存在,首先需要删除内部文件,然后在将空文件夹删除。
package File;
import java.io.File;
public class filedelete {
public static void deleteDir(File file){
// 创建File对象,获取所有路径下的文件
File[] listfile = file.listFiles();
// 循环遍历文件数组
for(File files:listfile){
// 如果是文件夹,递归
if(files.isDirectory()){
deleteDir(files);
}
// 如果是文件,直接删除
files.delete();
}
// 删除主目录空文件夹
file.delete();
}
public static void main(String[] args) {
// 创建新的文件
File file = new File("src/newFile");
// 调用删除方法
deleteDir(file);
}
}
注意:
在java中删除目录操作是通过java虚拟机直接删除的,不走回收站,文件一旦被删除就不可恢复,删除的时候切记谨慎。
返回顶部
2.2 RandomAccessFile
前面介绍的I/O流都有一个特点,就是只能够按照数据的先后顺序读取数据源设备中的数据,
或者写入,但是向任意位置写入读取文件,字节流和字符流都无法实现。不慌,在I/O包中,向我们提供了一个RandomAccessFile类它不属于流类,但是具有读写文件数据的功能,可以随机从文件中的任何位置开始读写。同时,RandomAccessFile可以将文件以指定的操作权限(只读、可读写)的方式打开。
RandomAccessFile对象中包含了一个记录指针来标识当前读写处的位置。当程序新建RandomAccessFile对象的时候,该对象的文件记录指针会在文件开始处(标识为0),读取m个字节后,记录指针会自动向后移动m个字节。最重要的是RandomAccessFile对象可以自由地移动记录指针,以下是其对象常用的方法:
方法
| 说明
|
getFilePointer()
| 返回此文件中的当前偏移量。
|
readLine()
| 从此文件中读取下一行文本。
|
setLength(long newLength)
| 设置此文件的长度。
|
skipBytes(int n)
| 尝试跳过 n字节的输入丢弃跳过的字节。
|
seek(long pos)
| 设置文件指针偏移,从该文件的开头测量,发生下一次读取或写入。
|
write(byte[] b)
| 从指定的字节数组写入 b.length个字节到该文件,从当前文件指针开始。
|
模拟付费软件试用:
package RandomAccessFile;
import java.io.RandomAccessFile;
public class randomAccessFile {
public static void main(String[] args) throws Exception{
// 创建对象,并以读写模式打开文本文档
RandomAccessFile raf = new RandomAccessFile("src/RandomAccessFile/time.txt","rw");
// 读取还可以使用的次数,需要在time.txt中写入试用次数,例如:5
int times = Integer.parseInt(raf.readLine())-1;
// 判断剩余的次数
if (times>0){
// 每执行一次代表试用一次,次数就减少一次
System.out.println("你还可以试用"+times+"次!");
// 将记录指针重新指向文件开头‘
raf.seek(0);
// 将剩余次数写入文件
raf.write((times+"").getBytes());
}else{
System.out.println("您的试用次数已使用完!");
}
// 关闭流
raf.close();
}
}
// 读取还可以使用的次数
int times = Integer.parseInt(raf.readLine())-1;
返回顶部
三、对象序列化
- 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。
- 在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。但是在真实的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。
- 对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。
- 类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
- 当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出 NotSerializableException。
- 虽然Serializable接口中并没有定义任何属性和方法,但是如果一个类想要具备序列化能力也比必须要实现它。其实,主要是因为序列化在真正的执行过程中会使用instanceof判断一个类是否实现类Serializable,如果未实现则直接抛出异常。
- 如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成java.io.Serializable接口。
- 我们一般使用ObjectOutputStream的writeObject方法把一个对象进行持久化。再使用ObjectInputStream的readObject从持久化存储中把对象读取出来。
package serializable;
import java.io.*;
public class person implements Serializable {
// 为该类指定一个 serialVersionUID变量值
private static final long serialVersionUID = 1L;
// 声明变量
private String name;
private int id;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
",name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) throws Exception{
// 创建对象
person p = new person();
p.setAge(12);
p.setId(01);
p.setName("li");
System.out.println(p);
//Write Obj to File
try {
// 创建序列化对象
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("src/serializable/tempFile"));
// 写入对象
oo.writeObject(p);
}catch (IOException e) {
e.printStackTrace();
}
//Read Obj from File
File file = new File("src/serializable/tempFile");
try {
// 创建反序列化对象
ObjectInputStream oi = new ObjectInputStream(new FileInputStream(file));
// 读取对象
person ps = (person) oi.readObject();
System.out.println(ps);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
第一条是在创建对象的时候输出的,第二条是在反序列化的时候读取序列化对象生成的。
注意:
// 为该类指定一个 serialVersionUID变量值
private static final long serialVersionUID = 1L;
1. serialVersionUID是能否进行反序列化的关键。当没有人为定义时,在类实现Serializable接口序列化时,系统会默认的定义一个serialVersionUID属性(隐性),当反序列化的时候,系统会匹配当前serialVersionUID与序列化的serialVersionUID是否一致,一致才能够反序列化。当出现特殊期情况时,我们改动了类中的属性,比如添加了一个属性,再次编译时,系统会再次生成一个新的serialVersionUID,而此时的serialVersionUID已经发生了改变,当再次把当前的serialVersionUID和之前的serialVersionUID进行匹配的时候就发生错误,无法反序列化。所以在进行序列化的时候最好人为定义serialVersionUID.
2.关键字 transient — 暂时的
在序列化的时候我们不需要将某个属性一并序列化时,可以使用transient关键字来修饰。
返回顶部
四、NIO
简单的说NIO就是new I/O,在JDK1.4之后提供的一系列用于改进处理输入输出流的新功能。
与I/O不同的是,NIO采用了内存映射的方式来处理 输入输出,将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件。
五、NIO简要概述
在I/O中使用的是字节流和字符流,而在NIO中使用的是通道和缓冲区,数据总是从通道读入缓冲区,再从缓冲区写入通道。
NIO的三大主要核心:Buffer、Channel、Selector。
Buffer可以看做是一个容器,本质是一个数组缓冲区,读入或写出到channel中的所有对象都会先放入到这里面;
Channel是对传统的输入输出的模拟,在NIO中,所有的数据都需要通过通道流的形式传输;
Selecto选择器用于监听多个通道的事件(如:连接打开、数据到达等),主要用于处理多线程。
六、Buffer(缓冲器)
从结构上来说,Buffer类似于一个数组,可以保存多个类型相同的数据;
从类型上来说,Buffer是一个抽象类,子类:基本数据类型名+Buffer。其中最常用的是ByteBuffer、CharBuffer。
buffer对象的三个基本特性:
容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制
由于Buffer类的子类并没有声明构造方法,所以并不能通过构造方法来创建对象。要想创建Buffer对象,需要使用子类的 static XxxBuffer allocate(int capacity)方法来实现,其中capacity表示容量。例如创建一个容量为8 的ByteBuffer对象:ByteBuffer buffer = ByteBuffer.allocate(8)
package NIO;
import java.nio.CharBuffer;
public class Buffer {
public static void main(String[] args){
// 创建对象,指定缓冲区容量大小为6
CharBuffer charBuffer = CharBuffer.allocate(6);
System.out.println("容量" + charBuffer.capacity());
System.out.println("界限值" + charBuffer.limit());
System.out.println("初始位置" + charBuffer.position());
System.out.println("--------------------------------------------------");
// 向对象中放入3个元素
charBuffer.put('x');
charBuffer.put('y');
charBuffer.put('z');
System.out.println("加入元素后的界限值" + charBuffer.limit());
System.out.println("加入元素后的初始位置" + charBuffer.position());
System.out.println("--------------------------------------------------");
// 执行flip方法
charBuffer.flip();
System.out.println("flip后的界限值" + charBuffer.limit());
System.out.println("flip后的位置" + charBuffer.position());
System.out.println("--------------------------------------------------");
// 取出第一个元素
System.out.println("取出第一个元素" + charBuffer.get());
System.out.println("取出后的界限值" + charBuffer.limit());
System.out.println("取出后的位置" + charBuffer.position());
System.out.println("--------------------------------------------------");
// 执行clear
charBuffer.clear();
System.out.println("clear后的界限值" + charBuffer.limit());
System.out.println("clear后的位置" + charBuffer.position());
System.out.println("--------------------------------------------------");
// 取出第一个元素
System.out.println("取出的第一个元素" + charBuffer.get(0));
System.out.println("取出后的界限值" + charBuffer.limit());
System.out.println("取出后的位置" + charBuffer.position());
}
}
返回顶部
七、Channel(通道)
同样Channel也是一个接口对象,需要实现类来实现~
Channel的特点:
- 可以异步执行I/O读写操作
- 读写是双向的,既可以从channel中读取数据,又可以写数据到channel
- 可以将指定文件的部分或者全部直接映射成Buffer
- 只能与Buffer进行交互,程序不能直接读写channel中的数据
主要实现类
| 主要用途
|
DataGramChannel
| 用于支持UDP网络通信
|
FileChannel
| 用于从文件中读写数据
|
Pipe.SinkChannel、Pipe.SourceChannel
| 用于支持线程之间的通信
|
ServerSocketChannel、SocketChannel
| 用于支持TCP网络通信
|
同样的,Channel对象也不是通过构造方法来创建的,而是通过I/O的getChannel() 方法来获取的,并且不同的流所获取的Channel对象也是不同的。
FileChannel类常用方法
| 说明
|
abstract MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
| 将此频道文件的区域直接映射到内存中。
|
static FileChannel open(Path path, OpenOption… options)
| 打开或创建文件,返回文件通道以访问该文件。
|
static FileChannel open(Path path, Set<? extends OpenOption> options, FileAttribute<?>… attrs)
| 打开或创建文件,返回文件通道以访问该文件。
|
abstract long position()
| 返回此频道的文件位置。
|
abstract int read(ByteBuffer dst)
| 从该通道读取到给定缓冲区的字节序列。
|
long read(ByteBuffer[] dsts)
| 从该通道读取到给定缓冲区的字节序列。
|
abstract long read(ByteBuffer[] dsts, int offset, int length)
| 从该通道读取字节序列到给定缓冲区的子序列中。
|
abstract int read(ByteBuffer dst, long position)
| 从给定的文件位置开始,从该通道读取一个字节序列到给定的缓冲区。
|
abstract long size()
| 返回此通道文件的当前大小。
|
abstract long transferFrom(ReadableByteChannel src, long position, long count)
| 从给定的可读字节通道将字节传输到该通道的文件中。
|
abstract long transferTo(long position, long count, WritableByteChannel target)
| 将该通道文件的字节传输到给定的可写字节通道。
|
abstract int write(ByteBuffer src)
| 从给定的缓冲区向该通道写入一个字节序列。
|
long write(ByteBuffer[] srcs)
| 从给定的缓冲区向该通道写入一系列字节。
|
abstract long write(ByteBuffer[] srcs, int offset, int length)
| 从给定缓冲区的子序列将一个字节序列写入该通道。
|
abstract int write(ByteBuffer src, long position)
| 从给定的缓冲区向给定的文件位置开始,向该通道写入一个字节序列。
|
package NIO;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
public class Channel {
public static void main(String[] args) throws Exception{
// 创建RandomAccessFile对象,指定源文件
RandomAccessFile infile = new RandomAccessFile("src/NIO/科比.jpg","rw");
// 获取源文件FileChannel通道
FileChannel inchannel = infile.getChannel();
// 创建RandomAccessFile对象,指目标文件
RandomAccessFile outfile = new RandomAccessFile("src/NIO/forever.jpg","rw");
// 获取复制目标文件FileChannel通道
FileChannel outchannel = outfile.getChannel();
// 使用transferTo方法整体复制
long transferTo = inchannel.transferTo(0,inchannel.size(),outchannel);
if (transferTo > 0) {
System.out.println("复制成功!!!");
}
// 关闭资源
infile.close();
inchannel.close();
outfile.close();
outchannel.close();
}
}
使用 Randomaccessfile类的构造方法生成两个 Randomaccessfile对象, 同时还指定了拷贝文件的源文件和目标文件名称以及可执行的操作, 然后通过 rechannel方法获取对应的 Filechannel类分别用于文件读取和写入通道。接下来通过 Filechanne类的 ransfer To( long position, long count Writablebytechannel target)方法实现了整个文件的拷贝, 该方法的第1个参数表示所需转移文件的起始位置,这里表示从0开始: 第2个参数表示要传输的最大字节数,这里通过size)方法获取了文件的字节数:第3个参数表示目标通道,即要传输到的位置。最后文件拷贝完毕后,关闭了所有的资源。
返回顶部
八、NIO.2
JDK7引入了新的I/OAPI,对原有 I/O API中的功能进行改进,这个改进之后的NIO就称之为NO.2,其中最大的改进是提供了全面的文件输入输出以及文件系统的访问与支持,并且新增了jaa. nio. file 1包及其子包,而且还提供基于异步 Channel的输人输出。
九、path接口
通过前面的学习可知,File虽然可以访问文件系统,但是File类所提供的方法性能较低,大多数方法在出错时仅返回失败而不提供异常信息,不仅如此,File类还不能利用特定的文件系统的特性。为了弥补这种不足,NIO.2提供了一个Path接口,该接口是一共用在文件系统中定位文件的对象,通常表示一个依赖于系统的文件路径。除此之外NO.2还提供了Paths和 Files两个工具类,其中Paths类中提供了两个返回Path的静态方法,通过这两个方法可以创建Path对象,而Fls类中提供了大量的静态方法来操作。
package NIO;
import java.nio.file.Paths;
import java.nio.file.Path;
public class pathTest {
public static void main(String[] args) {
// 使用Paths的get()方法创建path对象
Path path = (Path) Paths.get("G:\\Projects\\IdeaProjects\\IO\\src\\NIO\\文件夹\\text.txt");
// 输出path的信息
System.out.println("path的根路径"+path.getRoot());
System.out.println("path的父路径是"+path.getParent());
System.out.println("path中的路径名称数"+path.getNameCount());
// 循环输出路径名称
for(int i=0;i<path.getNameCount();i++){
// 获取指定索引处的路径名称
Path name = path.getName(i);
System.out.println("索引为"+i+"的路径的名称为:"+name);
}
System.out.println("path的URI路径为:"+path.toUri());
System.out.println("path的绝对路径为:"+path.toAbsolutePath());
}
}
返回顶部