文件对象 File
用过 Linux 的一定知道,“一切皆文件”和“一切皆文本流”的思想。在 Java 中,普通文件和文件夹都是文件对象,用File
表示。
文件对象的常用方法
new 一个文件对象,用getAbsoluteFile()
返回这个File对象形式的路径,用getAbsolutePath()
返回字符串形式的路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import java.io.File;
public class IO { public static void main(String[] args) { File txt = new File("D:\\JavaTest\\t.txt"); System.out.println(txt.getAbsoluteFile());
File dir = new File("D:\\JavaTest"); System.out.println(dir.getAbsolutePath());
File xml = new File(dir,"run.bat"); System.out.println(xml.getAbsolutePath()); } }
|
我们用 new 创建的文件,并不一定在物理磁盘上存在,用exists()
判断是否真实存在。
1 2 3
| File txt = new File("D:\\JavaTest\\t.txt"); System.out.println(txt.exists());
|
用isDirectory()
判断是否是一个目录,用isFile()
判断是否是一个普通文件,用length()
获取文件长度。
先在 D:\JavaTest 创建一个 t.txt 文件,然后里面写 hello, 保存
1 2 3 4 5 6 7
| public static void main(String[] args) { File txt = new File("D:\\JavaTest\\t.txt"); if (txt.exists()){ System.out.println(txt.length()); } }
|
输出 5
,因为 hello 正好长度是 5 。如果文件在磁盘上不存在,则没有输出。如果是目录,输出 0
。
- 用
getParent()
以 字符串 形式获取文件所在目录,用getParentFile()
以 File对象 形式获取文件所在目录。
- 用
dir.list()
以 字符串数组 形式获取目录下所有文件(不包含子文件(夹)),当然,有dir.listFile()
,相信你知道如何用。
- 用
f.mkdir()
创建文件夹,若父文件夹不存在,则创建无效。用f.mkdirs()
创建文件夹,若父文件夹不存在,则先创建父文件夹。
- 用
f.delete()
删除文件。用f.deleteOnExit()
在JVM结束的时候删除文件(通常是临时文件)
例子:遍历找出最大文件
遍历文件夹下的文件和目录,并找出最大的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static void main(String[] args) { File folder = new File("D:\\Documents");
File[] foldersAndFiles = folder.listFiles(); long length = 0; String name = "";
if (foldersAndFiles != null){
for (File eachFile : foldersAndFiles) { if (eachFile.length() > length){ length = eachFile.length(); name = eachFile.getName(); } } } System.out.printf("最大的文件是:%s \n 其大小为:%d",name,length); }
|
例子:遍历输出目录下的文件(包括子目录里的文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import java.io.File; import java.io.IOException;
public class IO { public static void main(String[] args) { File parentFolder = new File("D:\\Documents"); printSub(parentFolder); }
private static void printSub (File parentFolder) throws NullPointerException{ File[] folders = parentFolder.listFiles();
for (File f : folders) {
if (f.isFile()){ System.out.println(f); }
if (f.isDirectory()){ printSub(f); } } } }
|
输入输出流
如果说, File 是表示 文件 的对象, 那么流就是表示 数据在Java程序和文件之间流动 (流动可以是流出,也可以是流入)的对象。
流的概念
在 Java API 中,可以从Java程序向外部写入字节序列的对象叫输出流
,相反,可以从外部向Java程序读入字节序列的对象叫输入流
。
- 输出流:Java → 外部
- 输入流:外部 → Java
这里的外部,通常是指文件,当然也可以是网络,甚至内存。
Java中定义了两个抽象类,InputStream
和OutputStream
,是 Java IO 的基础。这两个抽象类都有一个抽象方法read()
和write()
,用于读入和写出一个字节并返回该字节(当遇到结尾时返回-1)。因此,实现这两个抽象类的子类,都必须重写read()
或write()
方法。
1 2 3 4 5 6 7
| abstract int read(){
}
abstract void write(int b){
}
|
举个实现的例子,比如 FileInputStream 就实现了从某个文件中读入一个字节。
下面是我们常用的 read()
实现方法,它读入一个字节数组,并返回实际读入的字节数。或者在碰到流的结尾时返回-1.
read()
和write()
方法在执行时是阻塞的(通常是因为网络延迟)。可以用available()
方法检查当前可读入的字节数量。
当我们读写完毕后,切记用close()
方法来关闭IO流,以释放系统资源。
例子:向文件写字节
创建文件 -> 判断父目录在不在 -> 写入字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void main(String[] args) throws IOException { File parentFolder = new File("D:\\JavaTest"); writeByte(parentFolder); }
private static void writeByte (File parentFolder) throws NullPointerException, IOException{ File txt = new File(parentFolder,"how2j\\jj\\test.txt");
if (!txt.getParentFile().exists()){ txt.getParentFile().mkdirs(); }
FileOutputStream outputStream = new FileOutputStream(txt); byte[] all = {75,79}; outputStream.write(all); outputStream.close(); }
|
不止字节
继承于InputStream
和OutputStream
的实现类可以让我们很方便的读写字节。但是,我们很多文件都是 Unicode 字符编码的,不是单个的字节。因此,Java又定义了Reader
和Writer
两个抽象类,专门处理 Unicode 字符。
因此,在 Java 中, Stream 结尾的都是字节流, reader 或 writer结尾都是字符流。 两者的区别是:读写的时候一个是按字节读写,一个是按字符。
相比字节,我们更感兴趣的是数字、字符串和对象,而不是一个一个的字节。Java 当然也提供了很多让我们读取常用格式的数据,而不仅仅是字节!
缓存流
如果我们自己从硬盘中读取或写入数据,每次都要读写磁盘。如果读写的频率比较高的时候,其性能表现不佳。为了解决这一问题,Java提供了BufferedReader
和BufferedWriter
两个缓存流。
当我们要从硬盘读数据的时候,BufferedReader
缓存流会先从硬盘中一次性读取较多的数据,然后我们的Java程序直接按需从缓存里取出。这样就不用每次都跟硬盘打交道了。
利用 BufferedWriter 写数据到文件例子
- new 一个
BufferedWriter
,参数里面 new 一个 FileWriter
- 用
foreach
循环,遍历集合
- 如果有必要,做一下类型转换
- 写数据,写分隔符
- 刷新
注意,FileWrite 接收第二个参数,为 true 时,不覆盖原有内容。否则原有内容会被覆盖。
1 2 3
| FileWriter fw = new FileWriter("C:\\Users\\JerrySheh\\exception.dat" , true); BufferedWriter bw = new BufferedWriter(fw);
|
完整例子
- 产生5555个随机数
- 写入到文件data.txt中
- 从文件data.txt中读取这5555个随机数,写入到data2.txt中
randomDoubleNumber.java
1 2 3 4 5 6 7 8 9 10 11 12
| package com.jerrysheh;
import java.math.BigDecimal;
public class randomDoubleNumber {
public static double getRandomDoubleNumber(int range){ BigDecimal b = new BigDecimal(Math.random() * range); return b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
|
test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import java.io.*; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List;
public class main { public static void main(String[] args){ ArrayList<Double> l = addDataToList(new ArrayList<>()); writeToFile(l); readFromFileAndWrite(l); }
public static ArrayList<Double> addDataToList(ArrayList<Double> randomNumberList){ double d; for (int i = 0; i < 5555; i++) { d = com.jerrysheh.randomDoubleNumber.getRandomDoubleNumber(1000); randomNumberList.add(d); } return randomNumberList; }
public static void writeToFile(List<Double> randomNumberList){ DecimalFormat df = new DecimalFormat("0.00");
try (BufferedWriter bw = new BufferedWriter(new FileWriter("data.txt"))) { for (double dd: randomNumberList) { String s = df.format(dd); bw.write(s); bw.write(","); bw.flush(); } } catch (IOException e){ e.printStackTrace(); } }
public static void readFromFileAndWrite(List<Double> randomNumberList){ try( BufferedReader br = new BufferedReader(new FileReader("data.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("data2.txt")) ){ String s = br.readLine(); String ss[] = s.split(","); for (String each: ss) { bw.write(each); bw.write("\r\n"); bw.flush(); } } catch (IOException e){ e.printStackTrace(); } } }
|
序列化
什么是序列化(Serialization)?
变量从内存中变成可存储或传输的过程称之为序列化(或持久化)。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
Java中的序列化
在 Java 中,java.io.Serializable 是一个标记接口。要序列化一个对象,只需要实现该接口。但是,对象中并不是所有字段都可以被序列化,使用时需要注意。
当然,也有一些字段本身是可以被序列化的,但是我们不希望它被序列化,这时可以使用 transient
关键字让它不被序列化。
一个支持序列化的类
1 2 3 4 5 6 7 8 9 10 11 12
| public class Employee implements java.io.Serializable { public String name; public String address; public transient int SSN; public int number; public void mailCheck() { System.out.println("Mailing a check to " + name + " " + address); } }
|
在 Java 中,我们使用 ObjectOutputStream 类来将一个对象转换成输出流。它的 writeObject(Object x)
方法用于序列化一个对象,并将它发送到输出流。
序列化过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class SerializeDemo { public static void main(String [] args) { Employee e = new Employee(); e.name = "Reyan Ali"; e.address = "Phokka Kuan, Ambehta Peer"; e.SSN = 11122333; e.number = 101; try { FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut); out.writeObject(e); out.close(); fileOut.close(); System.out.printf("Serialized data is saved in /tmp/employee.ser"); }catch(IOException i) { i.printStackTrace(); } } }
|
Java约定序列化的文件后缀名为 .ser ,我们将该对象存入磁盘/tmp/employee.ser文件中
反序列化过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class DeserializeDemo { public static void main(String [] args) { Employee e = null; try {
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close(); fileIn.close(); }catch(IOException i) { i.printStackTrace(); return; }catch(ClassNotFoundException c) { System.out.println("Employee class not found"); c.printStackTrace(); return; } } }
|
此时,Employee对象即被“复活”了。但是注意,变量SSN是 transient 的,因此不会被还原。
为什么一个类实现了Serializable接口,它就可以被序列化?
查看 ObjectOutputStream 的源码,可以看到,其 writeObject0方法 中,是通过判断该类是否可以转型为 String、Enum 或 Serializable 来为其决定进行何种序列化方式的。实现Serializable接口就用 writeOrdinaryObject 方式。
如果该类没有实现 Serializable 接口,就抛出 NotSerializableException
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private void writeObject0(Object obj, boolean unshared) throws IOException { ... if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException(cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } ... }
|