Java学习之文件io流篇
Java学习之文件io流篇
0x00 前言
在平时的一些开发中,普遍都会让脚本运行的时候,去存储一些脚本运行结果的数据,例如开发一个爬虫,这时候我们爬取下来的内容,就需要存储到本地,那么这时候就会用到一些操作文件的类。
0x01 File 类
File类主要用于文件和目录创建、查找、删除等操作的。
先来查看他的构造方法
public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。
常用方法:
public String getAbsolutePath() :返回此File的绝对路径名字符串。
public String getPath() :将此File转换为路径名字符串。
public String getName() :返回由此File表示的文件或目录的名称。
public long length() :返回由此File表示的文件的长度。
代码实例:
public static void main(String[] args) {
String Pathname = "a.txt";
File abc = new File(Pathname);
System.out.println(abc.getAbsolutePath());
String Pathname1 = "a\\a.txt";
File abc1 = new File(Pathname1);
System.out.println(abc1.getAbsolutePath());
System.out.println(abc1.getPath());
System.out.println(abc1.length());
}
判断方法:
public boolean exists() :此File表示的文件或目录是否实际存在。
public boolean isDirectory() :此File表示的是否为目录。
public boolean isFile() :此File表示的是否为文件。
public static void main(String[] args) {
String Pathname = "a.txt";
File abc = new File(Pathname);
boolean a = abc.exists();
System.out.println(a);
System.out.println(abc.isFile());
System.out.println(abc.isDirectory());
}
增删功能方法
public boolean createNewFile() :当前仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() :删除由此File表示的文件或目录。
public boolean mkdir() :创建由此File表示的目录。
public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。
public static void main(String[] args) throws IOException {
String Pathname = "a.txt";
File abc = new File(Pathname);
boolean file = abc.createNewFile();
System.out.println(file+"\n"+abc.getAbsolutePath());
File abc1 = new File("abc");
abc1.mkdir();
File abc2 = new File("abc\\abc");
abc2.mkdirs();
}
如果是对目录进行删除,删除的目录必须为空才能进行删除。
目录遍历
在file里面给我们提供了,可以直接获取file目录下面所有子文件或目录。
public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
代码实例:
public static void main(String[] args) throws IOException {
String Pathname = "../";
File abc = new File(Pathname);
String[] name = abc.list();
for (String s : name) {
System.out.println(s);
}
}
使用list方法获取所有文件,然后使用增强for循环进行遍历。
0x02 IO流概述
IO流概述
java里面的io流指的是对一些文件内容做一个输入输出的作用。也就是input和output,对文件进行读取和输入数据的操作。
input:把数据从其他设备上读取到内存的流
output:把数据从内存写出到其他设备的流。
字节流
在计算机里面所有的文本数据包括图片、文本、视频这些在存储的时候,都是以二进制的形式进行村粗的。字节流是以一个字节为单位,读写数据的流。
字符流
以一个字符为单位,读写数据的流
0x03 字节流输出流
OutputStream抽象类是字节输入流的超类,他定义了几个共性的方法。
public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输
出到此输出流。
public abstract void write(int b) :将指定的字节输出流。
在操作完成后,必须使用close方法将资源释放。
FileOutputStream 类
FileOutputStream类是文件输出流,用于将数据些出到文件当中。
查看构造方法:
public FileOutputStream(File file) :创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name) : 创建文件输出流以指定的名称写入文件。
如果创建一个io流的对象,必须传入文件的路径,,如果没有该文件就会创建该文件,如果有就会清空原本有的数据。
代码:
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
FileOutputStream FileoutDate = new FileOutputStream(file);
FileoutDate.write(97);
FileoutDate.close();
}
这里写进入一个97,但是打开文件会发现写进入的变成了a,这是因为我们这里是以字节进行写入的,,而97的ascii转换为字符后,就是a这个字符。
这里还可以指定写出的数据长度。
public class FileOutput {
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
FileOutputStream FileoutDate = new FileOutputStream(file);
byte[] b = "abce3".getBytes();
FileoutDate.write(b,2,2);
FileoutDate.close();
}
}
这里要先获取字符的字节类型数据,使用write写入,从第二位开始索引,写入2个字节。
在程序开发中,有些数据可能没法一次执行获取所有结果,这时候我们如果以上面的方式来循环写入运行结果的话,每次循环就都会被清空一次,只获得最后一次的执行结果。
那么这时候我们就可以使用到追加,把它追加进入,而不是直接覆盖重写。
public FileOutputStream(File file, boolean append) : 创建文件输出流以写入由指定的 File对象表示的
文件。
public FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件。
代码:
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
FileOutputStream FileoutDate = new FileOutputStream(file,true);
byte[] b = "abce3".getBytes();
FileoutDate.write(b,2,2);
FileoutDate.close();
}
这几行代码和前面的相同,只是在FileOutputStream 构造方法里面传入一个ture,表示使用追加模式,该模式默认为false。
0x04 字节输入流
InputStream抽象类是字节输入流的超类。可以读取字节数据到内存中。
共性方法:
public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read() : 从输入流读取数据的下一个字节。
public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
FileInputStream 类
FileInputStream是文件输入流,从文件中读取字节到内存中。
构造方法:
FileInputStream(File file) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系
统中的 File对象 file命名。
FileInputStream(String name) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件
系统中的路径名 name命名。
代码:
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
FileInputStream fileinputdata = new FileInputStream(file);
int read = fileinputdata.read();
System.out.println((char) read);
read = fileinputdata.read();
System.out.println((char) read);
read = fileinputdata.read();
System.out.println(read);
fileinputdata.close();
}
使用read方法读取完后,地址会往后推一位,知道读取到没有,会返回-1。
以上的方法都是读取单个字节,我们可以定义一个字节类型的数值,然后让他每次读取我们指定的长度。
代码:
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
FileInputStream fileinputdata = new FileInputStream(file);
int len;
byte[] b = new byte[2];
while ((len=fileinputdata.read(b))!=-1){
System.out.println(new String(b,0,len));
}
fileinputdata.close();
}
这里定义了b变量用来接收每次读取的数据产的长度,然后定义一个len变量,接收每次读取的数据,这里可以直接把赋值放在循环条件里面,如果赋值的变量不等于-1,就一直循环,知道读取到-1,停止循环,前面说到如果没有数据读取会输出返回一个-1,结束循环。
0x05 字符流
在字节读写的时候,一些中文字符读写可能会显示乱码。因为一个中文字符可能占用多个字节。所以在一些读写的是字符数据的话,可以使用字符流来处理该数据。
字符输入流
Reader抽象类是表示用于读取字符流的超类,可以读取字符信息到内存中。
共性方法:
public void close() :关闭此流并释放与此流相关联的任何系统资源。
public int read() : 从输入流读取一个字符。
public int read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
FileReader 类
构造方法:
FileReader(File file) : 创建一个新的 FileReader ,给定要读取的File对象。
FileReader(String fileName) : 创建一个新的 FileReader ,给定要读取的文件的名称。
创建一个流对象,必须传入一个文件路径。
构造方法定义代码:
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
FileReader filereader = new FileReader(file);
}
或者:
public static void main(String[] args) throws IOException {
FileReader filereader = new FileReader("a.txt");
}
读取单个字符数据:
public static void main(String[] args) throws IOException {
FileReader filereader = new FileReader("a.txt");
int b;
while ((b = filereader.read())!=-1){
System.out.println((char)b);
}
}
读取多个字符数据:
public static void main(String[] args) throws IOException {
FileReader filereader = new FileReader("a.txt");
int b;
char[] buf = new char[2];
while ((b = filereader.read(buf))!=-1){
System.out.println(new String(buf));
}
}
字符输出流
Writer抽象类是字符输出流的超累,将知道的字符信息写出到指定地方。
共性方法:
void write(int c) 写入单个字符。
void write(char[] cbuf) 写入字符数组。
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len
写的字符个数。
void write(String str) 写入字符串。
void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个
数。
void flush() 刷新该流的缓冲。
void close() 关闭此流,但要先刷新它。
FileWriter 类
FileWriter类是写出字符到文件中的一个类,,构造时候使用默认的字符编码和默认的字节缓冲区。
构造方法:
FileWriter(File file) : 创建一个新的 FileWriter,给定要读取的File对象。
FileWriter(String fileName) : 创建一个新的 FileWriter,给定要读取的文件的名称。
写出数据代码:
public static void main(String[] args) throws IOException {
FileWriter filewriter = new FileWriter("a.txt");
filewriter.write("二狗");
filewriter.write(97);
filewriter.write(156);
filewriter.flush();
filewriter.close();
}
这里的代码,如果不写flush方法,也不写close是没法写入数据的。
来看看这2个方法的区别:
flush :刷新缓冲区,流对象可以继续使用。
close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
0x06 缓存流
字节流和字符流每次读写都会访问硬盘,当读写频率增加其访问效率不高
而使用缓存流读取时会将大量数据先读取到缓存中,以后每次读取先访问缓存,直到缓存读取完毕再到硬盘读取,缓存流写入数据也是一样,先将数据写入到缓存区,直到缓存区达到一定的量,才把这些数据一起写道硬盘中去,这样减少了IO操作。
字节缓冲流: BufferedInputStream , BufferedOutputStream
字符缓冲流: BufferedReader , BufferedWriter
字节缓存输出流
构造方法:
public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。
代码:
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A.txt"));
int b ;
while ((b = bis.read())!=-1){
bos.write(b);
}
}
上面的代码使用了字节缓存流的方式来进行了一个文件的复制粘贴,如果是使用字节输出流的方式是很慢的。而使用字节缓存流的方式读写的速度会很快。
如果我们想让读写更快的话,可以定义一个每次读写的长度,让他每次读写固定到的长度。
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A.txt"));
int b ;
byte[] bytes = new byte[8*1024];
while ((b = bis.read(bytes))!=-1){
bos.write(bytes,0,b);
}
}
每次读写8*1024个字节,减少文件读写的时间。
字符缓存流
构造方法:
public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。
代码:
public static void main(String[] args) throws IOException {
BufferedReader bis = new BufferedReader(new FileReader("a.txt"));
String b = null ;
while ((b = bis.readLine())!=null){
System.out.println(b);
}
bis.close();
}
使用readline读取当行数据。
0x07 序列化与反序列化
概述
java提供了一种对象序列化的机制,用一个字节序列表示一个对象,该字节包含对象的数据、对象的类型、对象的存储属性。字节序列写出到文件后,相当于可以持久报错了一个对象信息,这过程叫做序列化。
而反过来,将存储在文件的字节序列从文件中读取出来,重构对象,重新用来创建对象,这步骤叫做反序列化。
总结:简单来讲就是将一个对象,写入文件当中,而反序列化就是将写入文件的对象,读取出来。
ObjectOutputStream 类
ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。也就是对对象进行序列化的一个类。
构造方法:
public ObjectOutputStream(OutputStream out)
注意事项:
1.使用该类必须实现Serializable 接口,Serializable是一个标记接口,不实现此接口的类将不会时任何状态序列化或者是放序列化,会抛出NotSerializableException的异常。
2.该类的所有的属性必须时可以序列化的,如果有一个属性不需要序列化的,测该属性必须标明时瞬态的,使用transient修饰符。
代码:
创建一个类:
public class Method implements Serializable {
public String name;
public String address;
public transient int age;
public void method(){
System.out.println(name +address);
}
}
main方法:
public static void main(String[] args) {
Method e = new Method();
e.name = "zhangsan";
e.address = "beiqinglu";
e.age = 20;
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt"));
out.writeObject(e);
out.close();
System.out.println("ok");
} catch (IOException ioException) {
ioException.printStackTrace();
}finally {
}
}
查看a.txt文件可以看到显示的字符都是乱码,这是因为将对象序列化了,写进入的是一个对象,而不是一些字符。
ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象
构造方法:
public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
反序列化方法1
如果我们需要对序列化的对象,进行反序列化,将他从文件读取出来还原成对象那么我们这里调用ObjectOutputStream的readobject方法,读取一个对象。
public final Object readObject () : 读取一个对象。
代码:
public static void main(String[] args) {
Method e = null;
try {
FileInputStream fis = new FileInputStream("a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
e = (Method) ois.readObject();
ois.close();
fis.close();
} catch (IOException | ClassNotFoundException ioException) {
ioException.printStackTrace();
}
System.out.println("name="+e.name);
System.out.println("address ="+e.address);
System.out.println("age="+e.age);
}
反序列化方法2
当jvm方序列化对象的时候,能找到class属性,,但是class文件在序列化之后修改了,那么反序列化操作肯定是失败的,会抛出InvalidClassException。
抛出InvalidClassException的一次原因有3种:
1.该类的序列版本号与从流中读取的类描述符的版本号不匹配
2.该类包含未知数据类型
3.该类没有可访问的无参数构造方法
Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序
列化的对象和对应类是否版本匹配。
public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " ‐‐ " + address);
}
}
0x08 总结
File类:用于创建删除文件与文件夹。
FileoutputStream:字节输出流,用于将字节写出到文件中。
FileinputStream:字节输出流,用于读取文件中的字节。
FileReader类是表示用于读取字符流,可以读取字符信息到内存中。
FileWriter类是写出字符到文件中的一个类,,构造时候使用默认的字符编码和默认的字节缓冲区。
字节缓冲流: BufferedInputStream , BufferedOutputStream
字符缓冲流: BufferedReader , BufferedWriter
0x09 结尾
这篇文章的内容比较多,在后面也写了个简单的小总结,这篇也写了我2天左右。后面去深入研究一下历史爆出的一些反序列化漏洞。