输入输出——下

  • File类
  • 处理压缩文件
  • GZIP
  • ZIP
  • 对象序列化
  • Serializable 接口
  • Externalizable 接口
  • 随机读写
  • 构造方法


File类

File 可以帮助操作磁盘文件,其中定义了一些与平台无关的方法来操作文件。包括

  • 创建、删除文件
  • 重命名文件
  • 判断文件读写权限
  • 判断文件是否存在
  • 设置和查询文件最近修改时间

以下方法实现了创建一个名为 hello.txt 文件的功能,首先判断有无这个文件,有则删除这个文件,否则创建一个同名的空文件。其中 Sepatator 属性相当于文件路径的分隔符 “\”。

void createOrDeleteFile(){
    File f = new File("doc"+File.separator+"hello.txt");
    if(f.exists() && f.isFile()) f.delete();
    else{
        try{
            f.createNewFile();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

File 类可以使用 exists() 判断文件是否已经存在,也可以使用 isFile() 判断是否是文件或者目录。以下是一个复制文件的例子。

void copyBytes(String[] args){
   	DataInputStream in;
    DataOutputStream out;
    if(args.length != 2){
        System.out.println("Please Enter file names");
        return ;
    }
    File inFile = new File(args[0]);
    File outFile = new File(args[1]);
    if(outFile.exists() && outFile.isFile()){
        System.out.println(args[1]+" already exists");
        return;
    }
    if(!inFile.exists() && outFile.isFile()){
        System.out.println(args[0]+" does not exist");
    }

    try{
        in = new DataInputStream(new BufferedInputStream(
                new FileInputStream(inFile)));
        out = new DataOutputStream(new BufferedOutputStream(
                new FileOutputStream(outFile)));
        try{
            int data;
            while(true){
                data = in.readUnsignedByte();
                out.writeByte(data);
            }
        }catch (EOFException eof){
            out.close();
            in.close();
            return;
        }
    }catch (FileNotFoundException fe){
        System.out.println("Problem opening files");
    }catch (IOException ioe){
        System.out.println("IO Problems");
    }
}

处理压缩文件

这里介绍一下如何压缩和解压gzip文件和zip文件。java.util.zip 包中提供了一些可以用于在压缩格式对流进行读写的流。这些流都继承自 OutputStream 和 InputStream。

  • 压缩流类 GZIPOutputStream 和 ZipOutputStream 可以将数据压缩成 GZIP 格式和 ZIP 格式
  • GZIPInputStream 和 ZipInputStream 可以将 GZIP 和 ZIP 格式的数据解压缩回复原状

GZIP

  • GZIPOutputStream 的父类是 DeflaterOutputStream,可以把数据压缩成 GZIP 格式
  • GZIPInputStream 的父类是 InflaterInputStream,可以将 GZIP 格式文件解压缩

以下演示以下如何将 GZIP 格式文件压缩和解压缩

void gzipStreamTest() throws IOException{
	// 压缩
    FileInputStream in = new FileInputStream("./doc/hello.txt");
    GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream("./doc/hello.gz"));
    System.out.println("Compressing files from doc/hello.txt to doc/test.gz");

    int c;
    while((c = in.read()) != -1) out.write(c); //读写低位一个字节

    in.close();
    out.close();

	// 读取
    System.out.println("Reading file from doc/hello.gz to minitor");
    BufferedReader in2 = new BufferedReader(new InputStreamReader(
            new GZIPInputStream(new FileInputStream("doc/hello.gz"))));

    String s;
    while((s=in2.readLine())!=null) System.out.println(s);
    in2.close();
    
	// 解压缩
    System.out.println("Writing decompression to doc/newHello.txt");
    GZIPInputStream in3 = new GZIPInputStream(new FileInputStream("doc/hello.gz"));
    FileOutputStream out2 = new FileOutputStream("doc/newHello.txt");
    while((c=in3.read())!=-1) out2.write(c);
    in3.close();
    out2.close();
}
  • read() 方法每次读取一个字节,遇到文件结尾返回 -1,否则将其转化为 [0, 255] 之间的一个整数,返回一个 int 类型的数值。
  • write() 方法将一个 int 类型的数值的低 8 位写入文件,忽略高 24 位,等于写入一个字节。read() 和 write() 配合可以完成任何文件的复制。

ZIP

ZIP 可以由多个文件压缩而成,我们称这些文件的路径称为 ZIP 文件的入口 ( Entry ) ,我们压缩的时候不仅需要完成文件的压缩,而且需要记录入口。同样,解压缩的时候要读取入口,恢复最初的路径。

  • Zip 文件的每个文件对应一个入口,入口记录了其路径信息,解压缩的时候需要入口信息完成路径的恢复。每个入口使用一个 ZipEntry 对象表示,对象的 getName() 方法返回文件的路径名称。
  • ZipOutputStream,父类是 DeflaterOutputStream,可以把数据压缩成 ZIP 格式。
  • ZipInputStream,父类是 InflaterInputStream,可以将 ZIP 格式的文件解压缩。

ZIP 文件的压缩与解压缩举例

void zipStreamTest(String[] args) throws IOException{
	// 压缩
    ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
            new FileOutputStream("./doc/test.zip")));

    for(int i=0; i<args.length; i++){
        System.out.println("Writing file e" + args[i]);
        BufferedInputStream in = new BufferedInputStream(
                new FileInputStream(args[i]));
        out.putNextEntry(new ZipEntry(args[i])); // putNextEntry() 可以记住路径信息,从而可以还原路径关系信息
        int c;
        while((c = in.read()) != -1) out.write(c);
        in.close();
    }
    out.close();
    
    // 读取
    System.out.println("Reading file");
    ZipInputStream in2 = new ZipInputStream(new BufferedInputStream(
            new FileInputStream("./doc/test.zip")));
    ZipEntry ze;
    while((ze=in2.getNextEntry())!=null){
        System.out.println("Reading file "+ze.getName());
        int x;
        while((x=in2.read())!=-1) System.out.write(x);
        System.out.println();
    }
    in2.close();
}

解压缩 ZIP 文件并回复原来的路径。

class Unzip{
    byte doc[] = null;
    String Filename = null; // 压缩文件路径
    String UnZipPath = null; // 解压缩路径
    public Unzip(String filename, String unZipPath){
        this.Filename = filename;
        this.UnZipPath = unZipPath;
        this.setUnZipPath(this.UnZipPath);
    }
	
	// 如果只传入压缩文件路径,默认解压缩到当前文件夹下面。
    public Unzip(String filename){
        this.Filename = new String(filename);
        this.UnZipPath = null;
        this.setUnZipPath(this.UnZipPath);
    }
	
	// 统一解压缩路径的格式,以文件分割符结尾
    private void setUnZipPath(String unZipPath){
        if(unZipPath==null || !unZipPath.endsWith("\\"))
            this.UnZipPath = new String(unZipPath+"\\");
        else
            this.UnZipPath = new String(unZipPath);
    }

    public void doUnZip(){
        try{
            ZipInputStream zipin = new ZipInputStream(new FileInputStream(Filename));
            ZipEntry fEntry = null;
            while((fEntry=zipin.getNextEntry())!=null){ // getNextEntry() 方法获得下一个入口的路径,是一个相对路径
                if(fEntry.isDirectory())
                    checkFilePath(UnZipPath+fEntry.getName()); // 加上解压缩的目录地址获得完整的解压缩路径
                else{
                    String fname = new String(UnZipPath + fEntry.getName());
                    try{
                        FileOutputStream out = new FileOutputStream(fname);
                        doc = new byte[512];
                        int n;
                        while((n=zipin.read(doc, 0, 512)) != -1) // 一次读取 512 个字节, read()返回读取到的实际字节数,实现文件结尾的读取
                            out.write(doc, 0, n);
                        out.close();
                        out=null;
                        doc=null;
                    }catch (Exception ex) {ex.printStackTrace();}
                }
            }
            zipin.close();
        }catch (IOException ioe){
            System.out.println(ioe);
            ioe.printStackTrace();
        }
    }
	// 检查是否是目录文件,如果是且没有这个目录,就创建这个目录
    private void checkFilePath(String dirName) throws IOException {
        File dir = new File(dirName);
        if(!dir.exists()) dir.mkdirs(); // 可以多级创建目录
    }

}

public class Exp9 {
    public static void main(String[] args) {
        String zipFile = args[0];
        String unZipPath = args[1];
        Unzip myZip = new Unzip(zipFile, unZipPath);
        myZip.doUnZip();
    }
}

对象序列化

我们可以将多个对象存储到一个二进制文件中,需要的时候再从文件夹中恢复。存储对象实际上是存储对象属性的值,而且我们可以决定哪些属性存,哪些不必存。所以我们可以在类中定义一个密码,将其定义为不被存储的类型,然后其它属性序列化之前使用其加密,然后恢复的时候用其解密,这样就可以实现对象的加密。

Serializable 接口

对象序列化的时候不保存被 transient 和 static 关键字修饰的变量。对于需要序列化的对象,其类必须实现 Serializable 接口,实际上 Serializable 接口是一个空接口,只需要声明实现改接口,但是不要求实现任何的方法,只是帮助让大家了解到这个类要有这个特性。实际上,所有的 Java 中定义的类都实现了 Serializable 接口,所以可以直接将它们序列化,而我们自己定义的类需要主动声明实现该接口。

对象序列化的输入输出流为 ObjectInputStream 和 ObjectOutputStream 类,可以将对象从磁盘上读取出来或者将对象存储到磁盘上。

以下是一个对象存盘的例子

void writeObject() throws Exception{
    FileOutputStream out = new FileOutputStream("./doc/theTime");
    ObjectOutputStream s = new ObjectOutputStream(out);
    s.writeObject("Today");
    s.writeObject(new Date(2019, 12, 25));
    s.flush();
    s.close();

}

以下是一个读取对象的例子

void readObject() throws Exception{
    FileInputStream in = new FileInputStream("./doc/theTime");
    ObjectInputStream s = new ObjectInputStream(in);
    String today = (String)s.readObject();
    Date date = (Date)s.readObject();
    System.out.println(today);
    System.out.println(date);
    s.close();
}

创建一个 Book 对象并存盘和读取

class Book implements Serializable{
    int id;
    String name;
    String author;
    float price;
    public Book(int id, String name, String author, float price){
        this.id = id;
        this.name = name;
        this.author = author;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

class Test{
    void testBook() throws Exception{
        Book book = new Book(100032, "Java Language", "Wang Sir", 30);
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("./doc/book.dat"));
        oout.writeObject(book);
        oout.close();

        book = null;
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream("./doc/book.dat"));
        book = (Book)oin.readObject();
        oin.close();
        System.out.println(book);
    }

}


public class Exp10 {
    public static void main(String[] args) throws Exception{
        Test t = new Test();
        t.testBook();
    }
}

结果为:

Book{id=100032, name=‘Java Language’, author=‘Wang Sir’, price=30.0}

Externalizable 接口

Externalizable 接口继承自 Serializable 接口,但是多了两个方法,writeExternal() 和 readExternal() 方法需要实现,当实现了 Externalizable 接口的类被序列化的时候,在写入和读取时会自动调用 writeExternal() 和 readExternal() 。从而我们可以在序列化之前和序列化之后完成一些操作。

class User implements Externalizable {

    private String name;
    private transient String password;

    // 必须要有无参构造器
    public User() {
        System.out.println("constructor");
    }


    // 序列化User对象, 可以在这里使用密钥加密
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("writeExternal");
        out.writeObject(name);
        out.writeObject(password);
    }

    // 反序列化User对象, 可以在这里用密钥解密
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("readExternal");
        name = (String) in.readObject();
        password = (String) in.readObject();
    }

    // 不会被执行
    private void writeObject(ObjectOutputStream out) throws IOException {
        System.out.println("writeObject");
    }

    // 不会被执行
    private void readObject(ObjectInputStream in) throws IOException {
        System.out.println("readObject");
    }

    // 在writeExternal之前执行,只是为了说明执行顺序,实际不需要写。
    private Object writeReplace() throws ObjectStreamException {
//        this.setName("kobe");
        System.out.println("writeReplace");
        return this;
    }

    // 在readExternal之后执行,只是为了说明执行顺序,实际不需要写。
    private Object readResolve() throws ObjectStreamException {
//        this.setName("kobe");
        System.out.println("readresolve");
        return this;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

class Test{
	void testExternal() throws Exception{
        User u = new User();
        u.setName("Lucas");
        u.setPassword("32sdfs");

        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream("./doc/user.dat"));
        oout.writeObject(u);
        oout.close();

        u = null;
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream("./doc/user.dat"));
        u = (User) oin.readObject();
        oin.close();
        System.out.println(u);
    }
}

public class Exp10 {
    public static void main(String[] args) throws Exception{
        Test t = new Test();
        t.testExternal();
    }
}

结果如下:

constructor
writeReplace
writeExternal
constructor
readExternal
readresolve
User{name=‘Lucas’, password=‘32sdfs’}

随机读写

虽然 Java 对象化的方法将输入输出看作顺序化的流,但也可以实现在 C 语言中可以完成的随机读写。实现的类是 RandomAccessFile 类,可以使用 seek(), skipBytes(),getFilePointer() 等方法将文件指针定位到文件中的任意地方,从而实现文件的随机读写,而不破坏文件。

构造方法

java 判断文件已经存在就不创建 java 判断文件是否正在写入_System


java 判断文件已经存在就不创建 java 判断文件是否正在写入_zip_02


以下是实现随机读写文件的一个例子

class Employee {
    // \\u 表示后面是一个 unicode 编码
    char[] name = {'\u0000', '\u0000', '\u0000', '\u0000',
            '\u0000', '\u0000', '\u0000', '\u0000'};
    int age;
    public Employee(String name, int age) throws Exception{
        if(name.toCharArray().length > 8)
            System.arraycopy(name.toCharArray(), 0, this.name, 0, 8);
        else
            System.arraycopy(name.toCharArray(), 0, this.name, 0, name.toCharArray().length);

        this.age = age;
    }

}

public class Exp11 {
    String FileName;
    public Exp11(String fileName){
        this.FileName = fileName;
    }

    public void writeEmployee(Employee e, int n) throws Exception{
        RandomAccessFile ra = new RandomAccessFile(FileName, "rw");
        ra.seek(n*20);
        for(int i=0; i<8; i++) ra.writeChar(e.name[i]);
        ra.writeInt(e.age);
        ra.close();
    }

    public void readEmployee(int n) throws Exception{
        char buf[] = new char[8];
        RandomAccessFile ra = new RandomAccessFile(FileName, "r");
        ra.seek(n*20);
        for(int i=0; i<8; i++) buf[i] = ra.readChar();
        System.out.print("name: ");
        System.out.println(buf);
        System.out.println("age: "+ra.readInt());
        ra.close();
    }

    public static void main(String[] args) throws Exception{
        Exp11 ep = new Exp11("./doc/random.txt");
        Employee e1 = new Employee("LucasP", 23);
        Employee e2 = new Employee("李华", 19);
        Employee e3 = new Employee("张力", 33);

        ep.writeEmployee(e1, 0);
        ep.writeEmployee(e3, 2);
        System.out.println("第一个雇员信息:");
        ep.readEmployee(0);
        System.out.println("第二个雇员信息:");
        ep.readEmployee(2);
        ep.writeEmployee(e2, 1);
        System.out.println("第三个雇员信息: ");
        ep.readEmployee(1);
    }
}

结果如下:

第一个雇员信息:
name: LucasP
age: 23
第二个雇员信息:
name: 张力
age: 33
第三个雇员信息:
name: 李华
age: 19