Java IO学习

一、Java IO

  Java 的IO包含了java的输入输出,所有的类都在java.io包中。其主要内容就是java的输入输出流,又可以分为字符流和字节流。其中字符流以字符来处理输入输出操作,字节流则以字节为单位来处理输入输出流。接下来将分别介绍几个重要的IO操作类。

二、File类

  File类是java.io包下与平台无关的文件和目录处理类,对文件和目录可以实现新建,删除,重命名等操作,可以查看文件和目录的各种属性信息,但是不能对文件内容进行访问,文件内容的访问需要输入输出流来完成,后面介绍。

  1、对象的创建

  下面介绍文件对象的创建方法:
  1、File file = new File(“”); 创建一个File类对象,括号中字符串可以是文件名也可以是目录路径(存不存在不重要)。
  2、createNewFile(); 创建对象对应的文件
  3、mkdir(); 创建对象对应的目录(对象必须对应一个路径)
  4、delete(); 删除对象对应的文件或目录

  2、文件名的访问方法

  1、String getName(); 返回对象对应的文件名或目录名
  2、String getPath(); 返回对象对应的路径名
  3、File getAbsoluteFile(); 返回对象绝对路径文件
  4、String getAbsolutePath(); 返回对象绝对路径名
  5、String getParent(); 返回上一级目录
  6、boolean renameTo(File newName); 重命名

  3、文件相关属性查看

  1、boolean exists(); 文件或目录是否存在
  2、boolean canWrite(); 能否写
  3、boolean canRead(); 能否读
  4、boolean isFile(); 是文件么
  5、boolean isDirectory(); 是目录么

  4、目录查看

  1、String[] list(); 返回对象目录下所有子文件名和子文件夹名
  2、File[] listFiles(); 返回对象目录下所有子文件路径和文件夹路径
  3、static File listRoots(); 返回系统所有根目录(可以直接用File类调用)  

//部分代码
import java.io.*;
public class FileTest {
    public static void main(String[] args) throws IOException{
        File file = new File("F:\\Myfile\\Picture");
        File file2 = new File("F:\\test1");
        file2.mkdir();
        file2.createNewFile();
        String[] ss = file.list();
        File[] sss = file.listFiles();
        File[] fs = File.listRoots();
        System.out.println(file.getName());
        System.out.println(file.getPath());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getAbsoluteFile());
        System.out.println(file.getParent());
        System.out.println(file.exists());
    }
}

三、IO流概念

  1、流的分类

  Java把数据的输入输出抽象为流,可以把流看作是有序的一串数据。Java的IO流按照不同的分类方式可以分成不同的流类型。

  按照数据流的流向可以分为输入流和输出流,输入流只能从中读取数据,不能写入;输出流只能写入,不能读取。这里的写入读取主体可以看成是程序内存,即输入流内存只能从中读取数据,要想将数据写入硬盘,则需要输出流。
  
  按照流数据操作单元的不同,IO流又可以分为字节流和字符流,字节流操作的数据单元是八位的字节,字符流操作的单位是十六位的字符。 

  Java的IO流中包含许多的类,但是都是由4个基类派生而来的,4个基类分别为:InputStream,OutputStream,Reader,Writer 分别对应字节流输入,字节流输出,字符流读取,字符流写入。(输入和读取是一个概念,输出和写入也是一样的)下面是java流中比较常见的一些流分类:
  这里写图片描述

  2、InputStream和Reader

  InputStream和Reader都是进行数据输入的流,用法基本都差不多,这里放在一块讲。在InputStream中提供了下面3个方法:
  1、int read(); 读取单个字节,返回数据
  2、int read(byte[] b); 读取b.length个字节,返回读取字节数
  3、int read(byte[] b, int off, int len); 最多读取len个字节,从位置off处开始存入b,返回读取字节数。
  相同的在Reader类中也有相似的3个函数,将相应的byte换成char就可以了,读取的是字符。下面分别用FileInputStream和FileReader实现文件的读取(文件test.txt在当前代码目录下):

public class FileTest {
    public static void main(String[] args) throws IOException{
        int num;
        //create a FileInputStream
        FileInputStream fi = new FileInputStream("test.txt");  
        FileReader fr = new FileReader("test.txt");//create a FileReader
        num = 10;
        byte[] finput = new byte[num];   //create array
        char[] fread = new char[num];
        fi.read(finput);
        fr.read(fread);
        //transform the array to string
        System.out.println("finput: "+new String(finput,0,num));   
        System.out.println("fread: "+new String(fread,0,num));
        fi.close();    //close the obj
        fr.close();
    }   
}
//result
finput: 你好abcdef
fread: 你好abcdefgh

  从结果来看,对于中文一个字代表一个字符,但是占两个字节。这里的流对象最后都用close进行关闭,因为程序中打开的IO资源不属于内存资源,Java的垃圾回收不能自动回收,需要显示的关闭。然而在Java7之后,这些类都实现了AutoCloseable接口,因此可以通过try…catch来实现关闭。

  3、OutputStream和Writer

  和前面的InputStream,Reader一样,OutputStream和Writer也有相似的功能,只不过对应处理的数据单元不一样。这两个类是用来进行数据写入(输出)的,OutputSteam中提供了下面3个数据输出方法:
  1、void write(int c); 指定字节输出到输出流中
  2、void write(byte[] buf); 字节数组输出到指定输出流中
  3、void write(byte[] buf, int off, int len); 字节数组从位置off开始,输出长度len的字节到输出流中
  和输入流中相似,对于Writer来说也有与上面3个函数对于的写入函数,byte换成char。除此之外,Writer还提供了基于String类型的字符串写入函数:
  1、void write(String str); 写入字符串
  2、void write(String str, int off, int len); 从str的off位置开始写入len长度字符串
  下面通过文件的输出和写入类对这几个函数进行测试:

import java.io.*;
public class FileTest{
    public static void main(String[] args) throws IOException{
            //create outputstream/writer
        FileOutputStream fo = new FileOutputStream("test1.txt");
        FileWriter fw = new FileWriter("test2.txt");
        FileWriter fw1 = new FileWriter("test3.txt");

        byte[] foutput = {13,35,52,32,31,'d','s','r','e','w'};
        char[] fwrite = {'a','b','c','d','e','f','g','h','i','j'};
        String str = "la,hello,the world!";

        fo.write(foutput);   //write the data to file
        fw.write(fwrite);
        fw1.write(str,3,16);

        fo.close();       //close the stream/writer
        fw.close();
        fw1.close();
    }
}
//txt文件中的数据
test1.txt: #4 dsrew
test2.txt: abcdefghij
test3.txt: hello,the world!

  在上面的代码中,通过创建输出流对文件写入数据,当文件不存在时,会自动建立相应的文件,对于字节写入,虽然写入的一部分是数字,但在文件中是不会显示相应的数字的,因为它是按字节写入的,文件打开显示的只是相应数字对应的符号,要在文件中显示数字,则需要把数字当作字符进行写入。最后注意要close流,因为有些流是有缓冲的,不进行close操作的话,有些数据还在缓存中并没有写入文件,最终就会造成数据丢失了。 
  
  4、节点流和处理流

  前面介绍了流的几种分类方法,这里再介绍节点流和处理流的概念。节点流按照基本的意思就是面对节点的流,即流是直接和设备节点进行操作的,而处理流,也可以叫做包装流,它不是直接面对节点的,它是对其他流的封装,即它处理的对象是流,它的构造参数是已经存在的流,而节点流的构造参数则是物理IO节点。相对于节点流,处理流的作用就在于它对节点流进行了包装,因此使用方式更加简单方便。上面讲的FileInputStream,FileOutputStream,FileReader和FileWriter都是节点流,即它们都是面向文件节点的,而BufferedInputStream,BufferedOutputStream,PrintStream等都是处理流,Java中的print()等函数就是类PintStream中定义的。详细可以参考Java的节点流和处理流

  5.推回输入流

  在IO流中有两个比较特殊的流,推回输入流:PushbackInputStream,PushbackReader,每个类都有下面3个函数:
  1、void unread(byte/char[] buf); 将一个数组推回
  2、void unread(byte/char[] buf, int off, int len); 将一个数组从off位置推回len个长度
  3、void unread(int c); 推回一个字节或字符(char会自动转换为int)
  这三个函数个最前面的输入流有些像,一般来说,一个输入流每次读取数据后,它都会指向还未读取数据的下一个位置,这样下一次读取的话就从当前位置开始。这几个函数意思是推回,好像是把读取过的数据推回到数据流中,实际上它是把流读取对象指向的位置进行了移动,从而使得有些数据可以重复读取,下面通过代码测试:

import java.io.*;
public class FileTest{
    public static void main(String[] args) throws IOException{
        PushbackReader pis = new PushbackReader(new FileReader("test.txt"),64);

        char[] buf1 = new char[5];
        char[] buf2 = new char[5];
        pis.read(buf1);
        pis.unread('e');   //puckback a char e
        pis.read(buf2);
        pis.close();

        System.out.println("buf1: "+new String(buf1,0,5));
        System.out.println("buf2: "+new String(buf2,0,5));
    }
}
//结果
buf1: abcde
buf2: efghi

  从上面的结果可以看出,pis读取一次后,推回了e,因此下一次再次读取到了字符e。
  

四、输入输出重定向

  在Java中系统的标准输入输出是键盘和屏幕,对应的代码是System.in和System.out,即当程序使用这两个来进行输入输出操作时,对应的默认设备是键盘和屏幕。在System中Java提供了3个重定向函数,即修改Systemi.in和System.out的默认流对象。
  1、static void setErr(PrintStream err); 重定向标准错误输出流
  2、static void setIn(InputStream in); 重定向标准输入流
  3、static void setOut(PrintStream out); 重定向标准输出流

import java.io.*;

public class FileTest{
    public static void main(String[] args) throws IOException{
        //create a PrintStream
        PrintStream ps = new PrintStream(new FileOutputStream("test.txt"));
        System.out.println("standard print!");
        System.setOut(ps);  //redireting System in
        System.out.println("standard print after redirecting!");
        ps.close();  //close
    }
}

  上面是一个重定向的例子,首先定义了一个PrintStream处理流,先标准输出,然后将标准输出重定向为ps,再执行标准输出,结果就是第一个输出在屏幕上,而第二个不会输出在屏幕上,而是输出到文件test.txt中。下面再看一个重定向输入的例子:

import java.io.*;

public class FileTest{
    public static void main(String[] args) throws IOException{
        //create a InputStream
        FileInputStream fis = new FileInputStream("test.txt");
        byte[] b = new byte[5];
        byte[] c = new byte[5];
        System.in.read(b);  //standard in
        System.setIn(fis);  //redirecting System in
        System.in.read(c);  //read again after redirecting
        System.out.println(new String(b));
        System.out.println(new String(c));

        fis.close();    
    }
}
//result
weqwe //what i print in via the keyboard
weqwe 
stand

  上面的代码中,定义了一个文件输入流,先进行了标准的输入,在键盘输入了weqwe,然后把标准输入重定向到了文件输入流fis,因此后面再用System.in就不是从键盘输入了,而是接着从文件test.txt读取。观察输出,第一个输出是我用键盘输入的,第二个是test.txt文件中的字符。标准错误输出流和标准输出流差不多,下面简单介绍一个例子:

import java.io.*;

public class FileTest{
    public static void main(String[] args) throws IOException{
        PrintStream ps = new PrintStream(new FileOutputStream("terr.txt"));
        System.err.println("fefef");
        System.setErr(ps);
        System.err.println("fefef");
        ps.close();
    }
}

五、RandomAccessFile

  1、RandomAccessFile类

  RandomAccessFile类是java IO 中一个功能最丰富的文件内容访问处理类,它不属于前面的InputStream,OutputStream等,是一个独立的类。具有随机访问的功能,即程序可以直接跳转到文件的任意位置进行数据的读写。对于文件的操作来说,RandomAccessFile是最好的选择,当然它也只适用于文件的操作。对于一个文件来说,它就像一个巨大的数组,RandomAccessFile类对象具有一个文件指针,可以指向数据的任意位置。

  2、RandomAccessFile操作函数
  
  构造函数,类中有两个构造函数来实现对象的实例化分别为:
  RandomAccessFile(String name, String mode)
  RandomAccessFile(File file, String mode)
  区别就在于一个是直接通过文件的路径进行操作,一个是利用File类型的对象,两个函数都有String mode,代表访问模式,其有4个不同的值:
   - “r”:只读方式打开文件
   - “rw”:读写方式打开文件,文件不存在则创建
   - “rws”:对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
   - “rwd”:对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。

  在类中具有和InputStream一样的3个read()函数以及和OutputStream一样的3个write()函数。利用这些函数可以对文件进行简单的读写操作,除此还有下面一些函数:
  1、void seek(long pos);将文件记录指针定位到pos位置
  2、long length();返回文件字节长度
  3、long getFilePointer();返回文件记录指针当前位置
  4、 char readChar();读一个字母
  5、double readDouble();读一个double数据
  6、String readLine();读一行数据
  。。。
  类似于上面的函数还有很多都是读取或写入特定数据类型的,这里就不再一一介绍了。下面看一个读数据的例子:

import java.io.*;
public class FileTest{
    public static void main(String[] args) throws IOException{
        //create a objection of read only
        RandomAccessFile raf = new RandomAccessFile("test.txt","r");
        System.out.println((char)raf.read());

        byte[] buf1 = new byte[5];
        raf.seek(10);  //move the pointer to position:10
        raf.read(buf1, 0, 5);
        //read the data from tenth character
        System.out.println("buf1: " + new String(buf1));  
         //return the pointer now = (10+5)
        System.out.println("pointer: " + raf.getFilePointer()); 
        //return the length of the file
        System.out.println("file length: "+raf.length()); 
        raf.seek(28);  //move to next line
        System.out.println("line: "+raf.readLine());  //read a line

        raf.close();
    }
}
//文件test.txt中的数据:
abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
//输出结果:
a
buf1: klmno
pointer: 15
file length: 56
line: abcdefghijklmnopqrstuvwxyz

  通过例子对于这些函数的意义和使用方法基本是比较明确了,下面看一个读写的例子:

import java.io.*;

public class FileTest{
    public static void main(String[] args) throws IOException{
        //创建一个读写对象
        RandomAccessFile raf = new RandomAccessFile("test.txt","rw");
        long len = raf.length();
        raf.seek(len);  //指针指向文件末尾

        byte[] buf = {'h','e','l','l','o'};
        raf.write(buf, 0, buf.length);  //向文件末尾写入hello

        raf.seek(len);
        System.out.println("buf:"+raf.readLine());   //输出写入的数据

        raf.close();
    }
}
//文件原始内容:
abcdefghijklmnopqrstuvwxyz
//执行后文件内容:
abcdefghijklmnopqrstuvwxyz
hello
//输出结果:
buf:hello

  注意:文件的写入若写入位置及之后存在数据,则写入会覆盖原来的数据,因此想要在文件中间插入数据,需要先将插入点位置后面的数据读入缓冲区,在写入完毕后再将这一部分写回去。

六、对象序列化

  1、对象序列化的定义
  
  对象序列化就是将对象转换成平台无关的二进制流,从而实现对象在磁盘的存储或在网络上的传输。一旦程序获得了这种二进制流,则可以通过反序列化恢复得到原来的对象。对象的序列化机制使对象可以脱离程序的运行而独立存在。某个对象想要支持序列化则需要实现接口:Serializable或Externalizable,实现接口不需要实现任何方法,只是表明该类对象支持序列化。
  
  2、ObjectOutputStream,ObjectInputStream

  对象序列化需要ObjectOutputStream,ObjectInputStream两个类,一个用来序列化对象,一个用来反序列化对象。两个类对应的构造函数参数分别是OutputStream类型和InputStream类型,对应的读写函数分别是writeObject()和readObject()。下面通过创建一个可序列化类,将该类的一个对象序列化写入一个文件,然后再反序列化读取类对象数据:

import java.io.*;

public class FileTest{
    public static void main(String[] args) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        TestClass test = new TestClass(3,6);
        oos.writeObject(test);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        try {
            TestClass t = (TestClass)ois.readObject();
            System.out.println("t1: "+t.x+"  "+t.y);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        ois.close();    
    }
}

class TestClass implements Serializable{
    int x ;
    int y ;
    public TestClass(int a,int b){
        this.x = a;
        this.y = b;
    }
}

  反序列化恢复Java对象时,必须提供该对象所属的class文件,还有反序列化是不需要通过构造函数的。

  3、对象引用的序列化

  Java中除了上面那种对象的数据类型是Java基本类型外,还有一种类型:引用数据类型,即对象的数据成员是引用数据类型 ,这时候该类若是想要支持序列化,则它的引用数据类型对应的类也需要支持序列化,否则无法进行序列化操作。在序列化过程中,保存在磁盘中的每个对象都会有一个序列化编号,每次需要序列化一个对象时,程序都会先检查该对象是否已经序列化过,如果有则只需要输出该序列化编号即可。若是一个对象在序列化之后,对其变量进行修改,再将之序列化,则输出的还是它对应的编号,修改后的结果并不会更新到序列化数据中。

  若是有些变量数据不想被序列化,则可以通过transient关键字将之排除在序列化机制之外,也可以通过重写writeObject等函数自定义序列化,这里不再详述。之前提到的接口Externalizable也是用来自定义序列化的,程序员需要自己实现接口函数来决定序列化的数据。

  4、版本

  在反序列化时需要提供对象的class文件,但是有时class文件会进行更新,为了保持class文件的兼容性,Java可以为序列化类提供一个private static final 的serialVersionID值,用于标识该序列化类版本,这要这个值不变,序列化机制会把class文件升级前后的文件当成一个版本。这个值可以通过在Java安装路径jkd下bin目录下的serialver.exe获得。
  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值