java 使用IO流进行文件读写

时间:2022-06-22 00:26:52

 

概念

IO流用来处理设备之间的数据传输

java对数据的操作是通过流的方式

java用来操作流的类都在IO包中

流按流向分为两种:输入流、输出流

流按操作类型分为两种:字节流、字符流

 

IO流常用父类

字节流的抽象父类:InputStream、OutputStream

字符流的抽象父类:Reader、Writer

 

IO流的使用注意

使用前:要导入IO包中的类

使用时:要进行IO异常处理

使用后:要释放资源

流对象尽量晚开早关

 

FileInputStream进行文件读取

public class Test {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("test.txt");
        
        //从硬盘上读取一个字节
        int x = fis.read();
        
        System.out.println(x);
        
        //关闭流资源
        fis.close();
                
    }
}

文件的结束标记是  -1

public class Test {
    public static void main(String[] args) throws IOException {
        
        FileInputStream fis = new FileInputStream("test.txt");
        
        int b;
        while((b = fis.read()) != -1) {
            System.out.println(b);
        }
        
        //关闭流资源
        fis.close();
                
    }
}

 

为什么read返回的是int类型而不是byte类型?

应为FileInputStream可以读取任意类型的文件,如果用byte类型,中间可能出现11111111,而这就是byte类型的-1

程序就会停止执行,后面的就读不到了,如果用int,前面高位不会变成1,也就变成了255,可以保证整个数据的读写

 

FileOutputStream向文件中写入

将打开的文件清空再重新写入

FileOutputStream在创建时,如果没有这个文件,则会自动创建一个

如果有这个文件,则会先将这个文件清空

public class Test {
    public static void main(String[] args) throws IOException {

        //如果没有此文件则会自动创建一个
        FileOutputStream fos = new FileOutputStream("test.txt");
        
        fos.write(97);
        fos.write(98);
        fos.write(99);
        fos.write(100);
        
        fos.close();
                
    }
}

如果想以追加的方式操作文件,要给第二个参数为true

public class Test {
    public static void main(String[] args) throws IOException {

        FileOutputStream fos = new FileOutputStream("test.txt", true);
        
        fos.write(97);
        fos.write(98);
        fos.write(99);
        fos.write(100);
        
        fos.close();
                
    }
}

 

使用FileInputStream和FileOutputStream进行拷贝

逐个字节拷贝

public class Test {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("test.jpg");
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");
        
        int b;
        while((b = fis.read()) != -1) {
            fos.write(b);
        }
        
        fis.close();
        fos.close();
                
    }
}

 

FileInputStream里的available方法:

返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数

使用字节数组将文件一次性读取和写入

public class Test {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("9.jpg");
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");
        
        //获取文件的字节数
        int len = fis.available();
        //创建和文件一样大小的字节数组
        byte[] arr = new byte[len];
        //将文件一次性读进来
        fis.read(arr);
        //将文件一次性写入
        fos.write(arr);
        
        fis.close();
        fos.close();
                
    }
}

 

而对于某些大文件,需要创建很大的字节数组,有可能导致内存溢出

所以可以创建合适大小的字节数组,一部分一部分读取

public class Test {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("test.jpg");
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");
        
        //小文件一般定义为16的整数倍,大文件大小一般定义为1024的整数倍
        byte[] arr = new byte[1024]; //存放每次读取的字节
        int len; //记录每次读取的字节个数
        
        while((len = fis.read(arr)) != -1) {
            fos.write(arr, 0, len);
        }
        
        fis.close();
        fos.close();
                
    }
}

 

 

 

使用BufferedInputStream和BufferedOutputStream

这两个是对FileInputStream和FileOutputStream的包装

在进行读取时,会预先将一部分读入内存,然后其实是在内存中读取

读完了在进行下一部分的读取

所以同样是一个字节一个字节读取,但是使用Buffered会更快

因为内存的速度比硬盘快

public class Test {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("9.jpg");
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        
        int b;
        while ((b = bis.read()) != -1) {
            bos.write(b);
        }
        
        //仅需要关闭包装后的对象即可
        bis.close();
        bos.close();
    }
}

 

定义小数组和使用buffer的比较

小数组会比buffer稍微快一点点

 

flush和close方法的区别

flush用来刷新缓冲区

close用来关闭流

 

close方法具备刷新的功能,在关闭流之前会进行刷新,刷新之后不能继续写入

flush方法仅仅是刷新缓冲区的功能,刷新完之后可以继续写入

 

字节流读写中文的问题

字节流读取中文会出现各种乱码,因为不确定每个字符所占的字节数

public class Test {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("test.txt");
        byte[] arr = new byte[2];
        int len;
        
        while((len = fis.read(arr)) != -1) {
            System.out.println(new String(arr, 0, len));
        }
        
    }
}

写出中文时要将字符串转换成字节数组

public class Test {
    public static void main(String[] args) throws IOException {

        FileOutputStream fos = new FileOutputStream("test.txt");
        
        fos.write("你好啊".getBytes());
        
        fos.close();
        
    }
}

 

 

流的标准异常处理代码

1.6

public class Test {
    public static void main(String[] args) throws IOException {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("9.jpg");
            fos = new FileOutputStream("test_copy.jpg");
            
            int b;
            while((b = fis.read()) != -1) {
                fos.write(b);
            }
        }  finally {
            //关闭时出现异常,则能关一个就关一个
            try {
                if(fis != null)
                    fis.close();
            } finally {
                if(fos != null)
                    fos.close();
            }
        }
    }
}

1.7版本的写法,这种写法可以自动关闭流的功能

public class Test {
    public static void main(String[] args) throws IOException {
    
        try(
                FileInputStream fis = new FileInputStream("test.jpg");
                FileOutputStream fos = new FileOutputStream("test_copy.jpg");
        ) {
            int b;
            while((b = fis.read()) != -1) {
                fos.write(b);
            }
        }

    }
}

 

字符流

字符流是能够直接读取字符的IO流

对字节流进行了编码表的转换

 

使用FileReader读取

public class Test {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("test.txt");
        
        int x;
        while((x = fr.read()) != -1) {
            System.out.println((char)x);
        }
        
        fr.close();
    }
}

 

使用FileWriter进行文件写入

public class Test {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("test.txt");
        
        fw.write("大家好啊");
        
        fw.close();
    }
}

Writer类中有一个2k的小缓冲区,如果不关闭流,写入的内容最后不会刷新进去,可能写入不完全

 

使用字符流定义小数组进行拷贝

public class Test {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("xxx.txt");
        FileWriter fw = new FileWriter("yyy.txt");
        
        char[] arr = new char[1024];
        int len;
        while((len = fr.read()) != -1) {
            fw.write(arr);
        }
        
        fr.close();
        fw.close();
    }
}

 

使用BufferedReader和BufferedWriter进行字符流读写

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("xxx.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("yyy.txt"));
        
        int c;
        while((c = br.read()) != -1) {
            bw.write(c);
        }
        
        br.close();
        bw.close();
    }
}

 

带缓冲的字符流的readLine和newLine方法

readLine方法每次读取一行

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("xxx.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("yyy.txt"));
        
        String line;
        
        while((line = br.readLine()) != null ) {
            bw.write(line);
        }
        
        br.close();
        bw.close();
    }
}

 

newLine方法写入一个行分隔符,不加的话,所有读取的会被写在一行

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("xxx.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("yyy.txt"));
        
        String line;
        
        while((line = br.readLine()) != null ) {
            bw.write(line);
            bw.newLine();
        }
        
        br.close();
        bw.close();
    }
}

newLine与\r\n的区别:

  newLine是跨平台的,\r\n只是windows系统的

 

LineNumberReader跟踪行号的缓冲区输入流

public class Test {
    public static void main(String[] args) throws IOException {
        LineNumberReader lnr = new LineNumberReader(new FileReader("test.txt"));
        String line;
        
        lnr.setLineNumber(0);  //设置当前行号,默认为零
        while((line = lnr.readLine()) != null) {
            System.out.println(lnr.getLineNumber() + ":" + line);
        }
        
        lnr.close();
    }
}

 

InputStreamReader和OutputStreamWriter

使用指定编码表进行读写

public class Test {
    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"), "utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test_x.txt"), "gbk");
        
        int c;
        while((c = isr.read()) != -1) {
            osw.write(c);
        }
        
        isr.close();
        osw.close();
    }
}

 

进一步更高效的包装

public class Test {
    public static void main(String[] args) throws IOException {
        BufferedReader br = 
                new BufferedReader(new InputStreamReader(new FileInputStream("utf8.txt"), "utf-8"));
        BufferedWriter bw = 
                new BufferedWriter(new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk"));
        
        int c;
        while((c = br.read()) != -1) {
            bw.write(c);
        }
        
        br.close();
        bw.close();
    }
}

 

 

序列流

整合两个输入流

public class Test {
    public static void main(String[] args) throws IOException {
        FileInputStream fis1 = new FileInputStream("xxx.txt");
        FileInputStream fis2 = new FileInputStream("yyy.txt");
        SequenceInputStream sis = new SequenceInputStream(fis1, fis2);
        FileOutputStream fos = new FileOutputStream("zzz.txt");
        
        int b;
        while((b = sis.read()) != -1) {
            fos.write(b);
        }
        
        sis.close();
        fos.close();
    }
}

 

整合多个输入流

public class Test {
    public static void main(String[] args) throws IOException {
        FileInputStream fis1 = new FileInputStream("xxx.txt");
        FileInputStream fis2 = new FileInputStream("yyy.txt");
        FileInputStream fis3 = new FileInputStream("zzz.txt");
        
        Vector<FileInputStream> v = new Vector<FileInputStream>();
        v.add(fis1);
        v.add(fis2);
        v.add(fis3);
        
        Enumeration<FileInputStream> en = v.elements();
        SequenceInputStream sis = new SequenceInputStream(en);
        
        FileOutputStream fos = new FileOutputStream("out.txt");
        int b;
        while((b = sis.read()) != -1) {
            fos.write(b);
        }
        
        sis.close();
        fos.close();
    }
}

 

 

内存输出流ByteArrayOutputStream

public class Test {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("xxx.txt");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        int b;
        while((b = fis.read()) != -1) {
            baos.write(b);
        }
        
        byte[] arr = baos.toByteArray();
        System.out.println(new String(arr));
        
        fis.close();
        baos.close(); //内存用完了会自动释放,所以可以不用关闭
        
    }
}

 

也可以直接toString,但这样就用的是默认编码,

如果转换要用指定编码,需要先toByteArray再使用String类创建字符串

 

随机访问流RandomAccessFile

public class Test {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile("xxx.txt", "rw");
        
        //
        raf.read();
        
        //
        raf.write(97);
        
        //定位
        raf.seek(0);
        
        //关闭
        raf.close();
        
    }
}

 

对象操作流ObjectOutputStream和ObjectInputStream

可以将对象写入文件,或者读取一个对象到程序中

也就是执行了序列化和反序列哈的功能

对象需要可以被序列化,也就是需要实现Serializabel接口

对象写入,序列化

public class Test {
    public static void main(String[] args) throws IOException {
        XXX m1 = new XXX();
        XXX m2 = new XXX();
        
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        
        //写入对象到文件中
        oos.writeObject(m1);
        oos.writeObject(m2);
        
        oos.close();
        
    }
}

class XXX implements Serializable {
    
}

读取方法,反序列化

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        
        XXX m1 = (XXX)ois.readObject();
        XXX m2 = (XXX)ois.readObject();
        
        ois.close();
    }
}

class XXX implements Serializable {
    
}

批量写入和读取只要将对象存入集合,然后将集合进行操作即可

public class Test {
    public static void main(String[] args) throws IOException {
        XXX m1 = new XXX();
        XXX m2 = new XXX();
        
        ArrayList<XXX> list = new ArrayList<XXX>();
        list.add(m1);
        list.add(m2);
        
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        
        oos.writeObject(list);
        
        oos.close();
        
    }
}

class XXX implements Serializable {
    
}

 

在进行对象的序列化操作时,对象的类中可以加一个id号,

可以有利于类的结构被修改时引发的版本不一致的错误

 

数据输入输出流DataInputStream和DataOutputStream

数据写入

public class Test {
    public static void main(String[] args) throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("test.txt"));
        
        dos.writeInt(999);
        
        dos.close();
    }
}

数据读取

public class Test {
    public static void main(String[] args) throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("test.txt"));
        
        int x = dis.readInt();
        
        dis.close();
    }
}

 

打印流PrintStream(字节)和PrintWriter(字符)

可以将对象的toString结果输出,并且可以使用自动刷出的模式

System.io就是一个打印流

public class Test {
    public static void main(String[] args) throws IOException {
        PrintStream ps = System.out;
        
        ps.println("aaa");
        ps.write(98);
    }
}

 

PrintWriter的自动刷出功能

public class Test {
    public static void main(String[] args) throws IOException {
        //第二个参数加上了true,代表使用了自动刷出功能,但是只是针对println方法才会触发
        PrintWriter pw = new PrintWriter(new FileOutputStream("test.txt"), true);
        
        pw.println("xxx");
        pw.write("a");
        
        pw.close();
    }
}

 

标准输入输出流System.in和System.out

略。。。。。

public class Test {
    public static void main(String[] args) throws IOException {
        InputStream is = System.in;
        OutputStream os = System.out;
        
        int x = is.read();
        System.out.println(x);
        
        os.write(98);
        
        //标准输入输出流可以不用关闭
    }
}

 

修改标准输入流和标准输出流

public class Test {
    public static void main(String[] args) throws IOException {
        System.setIn(new FileInputStream("test.txt"));
        System.setOut(new PrintStream("xxx.txt"));
        
        InputStream is = System.in;
        OutputStream os = System.out;
        
        int b;
        while((b = is.read()) != -1) {
            os.write(b);
        }
        
    }
}

 

Properties和IO流的交互

这个类是Hashtable的子类,是一个双列集合

Properties 类表示了一个持久的属性集

Properties 可保存在流中或从流中加载

public class Test {
    public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
        
        prop.put("abs", 123);
        prop.put("sada", 123123);
        
        System.out.println(prop);
        
    }
}

还可以使用,setProperty设置属性和属性值

 

和IO流的交互

首先要有一个配置文件,比如xxx.properties

name=asd
tel=123123123
qq=345345345

接着是读取的方法

public class Test {
    public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
        
        prop.load(new FileInputStream("xxx.properties"));
        
        System.out.println(prop);
        
    }
}

在程序中对属性进行修改,并将结果写入回配置文件