美文网首页技术干货
Java 操作目录,文件,流的读、写,低级流与高级流,异常

Java 操作目录,文件,流的读、写,低级流与高级流,异常

作者: 两分与桥 | 来源:发表于2018-08-02 12:56 被阅读169次

中文编码,GBK 2个字节,UTF-8 3个字节。

在关闭流的时候,直接关闭高级流就行了,如果先关闭低级流再关闭高级流,有可能导致数据的丢失。

File,操作文件,不能访问文件数据

* java.io.File
* File的每一个实例都可以表示文件系统中的一个文件或目录
* 使用File可以:
* 1. 访问文件或目录的属性(如:大小,名字,)
* 2. 操作文件或目录(创建,删除文件和目录)
* 3. 访问目录中的所有内容
*
* 但是不可以:访问文件数据

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Demo4 {
    public static void main(String[] args){
        File file = new File("."+File.separator+ "test.txt");
        String name = file.getName();

        System.out.println("文件名称:"+name);
        long length = file.length();
        System.out.println("文件字节:"+length);

        boolean isFile = file.isFile();
        System.out.println("是否是文件:"+isFile);

        long time = file.lastModified();
        System.out.println(time);

        Date date = new Date(time);
        System.out.println("修改时间:"+date);

        SimpleDateFormat sdf = new SimpleDateFormat(
                "yyyy年MM月dd日 HH:mm:ss"
        );
        System.out.println(sdf.format(date));
    }
}

打印:
文件名称:test.txt
文件字节:5
是否是文件:true
1533181020052
修改时间:Thu Aug 02 11:37:00 CST 2018
2018年08月02日 11:37:00

内部的一些方法

* boolean exists()
* 判断该文件是否真实存在

if(!file.exists()){
  file.createNewFile();
  System.out.println("创建完毕");
}else{
  System.out.print("该文件已存在");
}

file.delete();
        
 file.mkdir()  ------创建目录
 file.mkdirs() ------创建多级目录
 * delete方法可以删除一个目录,但是前提是该目录必须是一个空目录
           
File[] subs = dir.listFiles(); ----- 获取当前目录中的所有子项

递归遍历文件和目录

     * 使用递归遍历所有子目录和文件
    @Test
    public void test2(){
        String path = "E:\\TestCase";
        File file = new File(path);
        if(file.exists()) {
            getPaths(file);
        }
    }
    
    public void getPaths(File file) {
        if(file.isDirectory()) {
            for(File f : file.listFiles()) {
                System.out.println(f.getPath());
                getPaths(f);
            }
        }
    }

递归删除文件和目录

import java.io.File;

public class Demo5 {
    public static void main(String[] args) {
        File dir = new File("."+File.separator+"a");
        deleteDirs(dir);
    }
    public static void deleteDirs(File sub){
        if(sub.isDirectory()){
            for(File s:sub.listFiles()){
                deleteDirs(s);
            }
        }
        sub.delete();
    }
}

listFiles 方法使用过滤器

import java.io.File;
import java.io.FileFilter;

* 获取一个目录中符合条件的部分子项,使用重载的listFiles方法,
* 需要传入一个额外的文件过滤器
* 文件过滤器是一个接口:FileFilter

public class Demo5 {
    public static void main(String[] args) {
        File dir = new File(".");
        File[] subs = dir.listFiles(new FileFilter() {
            @Override
            -- 获取名字以"."开头的子项
            public boolean accept(File pathname) {
                return pathname.getName().startsWith(".");
            }
        });
        for(File sub:subs){
            System.out.println(sub.getName());
        }
    }
}

打印:
.idea

FileReader,FileWriter复制文件,字符流

package io;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyFile {
    public static void main(String[] args) {
        FileReader reader = null;
        FileWriter writer = null;
        try {
            reader = new FileReader("E://TestCase//demo.txt");
            writer = new FileWriter("E://TestCase//demoCopy.txt");
            char[] buffer = new char[2];
            int len = -1;
            while((len = reader.read(buffer)) != -1) {
                System.out.println(new String(buffer, 0, len));
                writer.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {   
                if(reader != null) {
                    reader.close();
                }
                if(writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("复制结束");
        }
    }
}

RandomAccessFile读写文件,覆盖模式,基于指针

* java.io.RandomAccessFile
* 用来读写文件数据
* RAF是基于指针进行读写的,即,RAF总是在指针指向的位置读写字节,
* 并且读写后指针会自动向后移动,RAF可以读写文件数据。

* RandomAccessFile(String path, String mode)
* RandomAccessFile(File file, String mode)
* 第二个参数为模式:常用的有
* r:只读模式
* rw:读写模式

* 随机访问文件,支持随机读写
* 1.seek(),绝对的量
* 2.skip(),相对值

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class Demo6 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile("demo.txt","rw");

        * void write(int d)
        * 写出给定的int值对应的2进制的低八位
        raf.write(97);  --写到文件中为a

        * int read()
        * 读取一个字节,并以10进制的int型返回
        * 若返回值为-1,则表示读取到了文件末尾
        int i = raf.read();         ------这里读取文件一直是-1,因为这里的文件指针就已经到了末尾
        System.out.println(i);
        raf.close();
    }
}

一次读取一个字节,复制文件

* 创建一个RAF读取原文件,再创建一个RAF向目标文件中写出。
* 顺序从从原文件读取每一个字节并写入到目标文件中即可

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile src = new RandomAccessFile("demo.txt","r");
        RandomAccessFile desc = new RandomAccessFile("copy-demo.txt","rw");

        int i = src.read();
        long start = System.currentTimeMillis();
        while(i != -1){
            desc.write(i);
            i = src.read();
        }
        long end = System.currentTimeMillis();
        src.close();
        desc.close();
        System.out.println("耗时:"+(end-start));
    }
}
打印:
耗时:158

用byte数组,读取复制文件

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

* 若想提高读写效率,可以通过提高每次读写的数据量来减少读写次数达到。

public class Demo8 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile src = new RandomAccessFile("demo.txt","r");
        RandomAccessFile desc = new RandomAccessFile("copy-demo.txt","rw");

        * int read(byte[] data)
        * 一次性尝试读取给定的字节数组总长度的字节量并存入到该数组中,
        * 返回值为实际读取到的字节量,
        * 若返回值为-1,则表示本次没有读取到任何数据(文件末尾)

        byte[] buf = new byte[1024*10];
        int len = -1;
        long start = System.currentTimeMillis();
        while((len = src.read(buf)) != -1){  -- len为读取到的字节量

            * void write(byte[] data)
            * 一次性将给定的字节数组中的所有字节写出

            * void write(byte[] d, int start, int len)
            * 将给定数组中从下表start处开始的连续将len个字节一次性写出

            desc.write(buf,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start));
        src.close();
        desc.close();
    }
}

打印:
耗时:0

getFilePointer 获取指针位置,seek,设置指针位置

* RAF提供了方便读写基本类型数据的方法

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class Demo9 {
    public static void main(String[] args) throws IOException {
        RandomAccessFile raf = new RandomAccessFile("copy.txt","rw");

        -----------getFilePointer 返回的是位置字节
        System.out.println("pos:"+raf.getFilePointer());   

        raf.writeInt(Integer.MAX_VALUE);
        System.out.println("pos:"+raf.getFilePointer());
        raf.writeBoolean(true);
        System.out.println("pos:"+raf.getFilePointer());

        raf.seek(0);
        System.out.println(raf.read());
        System.out.println(raf.readBoolean());
        System.out.println("pos:"+raf.getFilePointer());

        raf.close();
    }
}

打印:
pos:0
pos:4
pos:5
127
true
pos:2

Stream 流,基于流模式

默认创建的FOS是覆盖写操作,FOS会先将文件数据(若有数据)全部删除,然后再开始写

文件输出流,OutputStream,字节流

* 节点流:可以从或向一个特定的地方(节点)读写数据。
* 处理流:是对一个已存在的流的连接和封装,通过所封装的流的
* 功能调用实现数据读写。
*
* 流根据方向不同分为输入流与输出流,参照点为当前程序。
* 输入流用来读取数据,输出流用来写出数据。
*
* java.io.OutputStream则是所有字节输出流的父类。
*
* 流分节点流和处理流
* 节点流,也叫低级流。是负责读写数据的的流
* 读写操作中必须要有低级流,数据源明确
*
* 处理流,也叫高级流。读写可以没有高级流,高级流也不能独立存在,必须用于
* 处理其他流,处理其他流的目的是简化读写数据中的操作。
*
* java.io.FileOutputStream
* 文件输出流,是一个低级流,作用是向文件中写出字节。


import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("demo.txt");
        String str = "中文测试写入";

        * byte getBytes()
        * 将当前字符串按照系统默认的字符集转换为一组字节
        *
        * byte getBytes(String csn)
        * 按照给定的字符集将当前字符串转换为一组字节

        byte[] data = str.getBytes();
        fos.write(data);
        System.out.println("写出完毕");
    }
}

文件输出流,OutputStream,字节流,追加写操作

* 文件输出流,追加写操作

import java.io.FileOutputStream;
import java.io.IOException;

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

        * 在创建FOS时,若指定第二个参数,并且该值为true时,
        * 则是追加写操作,那么本次通过FOS写出的内容就会被追加到该文件末尾。

        FileOutputStream fos = new FileOutputStream("demo.txt",true);
        fos.write(" hello".getBytes());
        System.out.println("写出完毕");
        fos.close();
    }
}

文件输入流,FileInputStream,字节流

* java.io.FileInputStream
* 文件输入流,是一个低级流,用于从文件中读取字节

import java.io.FileInputStream;
import java.io.IOException;

public class Demo3 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("demo.txt");
        byte[] data = new byte[100];

        int len = fis.read(data);

        String str = new String(data,0,len);
        System.out.println(str);
        fis.close();
    }
}

字节流,复制文件

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo4 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("demo.txt");
        FileOutputStream fos = new FileOutputStream("copy-demo.txt");
        byte[] data = new byte[1024*10];

        int len = -1;
        while ((len=fis.read(data)) != -1){
            fos.write(data,0,len);
        }
        System.out.println("复制完毕");
        fis.close();
        fos.close();
    }
}

缓冲流

* java.io.BufferedInputStream
* java.io.BufferedOutputStream
* 缓冲字节输入输出流是一对高级流,使用它们可以加快读写效率
* 高级流可以处理其他流,但是无论添加了多少高级流,最底下都要有低级流,
* 因为低级流是真实读写数据的流,高级流都是处理数据的。
* 高级流处理其他流就形成了流的链接,并且有效的组合不同的高级流
* 可以得到叠加的效果。

import java.io.*;
import java.nio.Buffer;

public class Demo5 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("demo.txt");
        FileOutputStream fos = new FileOutputStream("copy-demo.txt");

        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        * 缓冲流内部有一个缓冲区
        * 当bis.read方法读取第一个字节时,
        * 实际上BIS会一次性读取一组字节并存入内部的字节数组中,
        * 然后将第一个字节返回,当再调用read方法时,BIS直接从字节数组中将第二个字节返回,
        * 直到字节数组中所有字节全部返回后,才会再次读取一组字节。
        * 所以缓冲流也是依靠提高一次读写的数据量减少读写次数来达到提高读写效率的

        int d = -1;
        long start = System.currentTimeMillis();
        while((d = bis.read()) != -1){
            bos.write(d);
        }
        long end = System.currentTimeMillis();
        bis.close();
        bos.close();
        System.out.println("复制完毕,耗时:"+(end-start)+"ms");
    }
}

效率相比字节流快许多

缓冲流存在一个问题,就是写入数据的时候也并不是马上就写入的,而是积攒到一定字节数再一次性写入,必要时候可以调用 flush() 强制写入。

* 缓冲输出流写出数据的缓冲区问题

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo6 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("demo-test.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        bos.write("libai22222".getBytes());
        ---------- 强制将缓冲区中的字节一次性写出
        bos.flush();
        bos.close();
    }
}

对象序列化

串行化使用场景
    将对象用于网络间传输或者是本地化存储。

串行化(序列化)
    将java对象转换成某种格式(JVM定义的)的字节数组。
    
反串行化(反序列化)
    将字节数组恢复成java对象。

java.io.Serializable
    1.可串行化接口
    2.标识性接口
    3.JVM定义
    
    
问题:反序列化对象时,是否经过构造函数?为什么?
    反序列化对象时,没有经过构造函数,因为构造函数的作用就是将
    数据初始化,并赋值给成员变量,重要的是成员变量中的数据
    所以没有必要经过构造函数。

对象序列化 ObjectOutputStream,也就是将对象存储到文件中

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

 * 该类用于测试作为对象流读写对象使用
 * 当一个类需要被对象流读写,那么该类必须实现 java.io.Serializable接口
 * 该接口不用实现任何方法,是一个签名接口

class Person implements Serializable {
    private String name;
    private int age;
    private String gender;
    private List<String> otherInfo;
}
-- 省略一堆的get/set方法


* 对象流
* 对象流是一对高级流,作用是方便存储和获取java中的对象。
* java.io.ObjectOutputStream
* 对象输出流,可以将给定的对象转换为一组字节后写出。

public class Demo7{
    public static void main(String[] args) throws IOException {
        Person p = new Person();
        p.setName("libai");
        p.setAge(21);
        p.setGender("man");

        List<String> otherinfo = new ArrayList<String>();
        otherinfo.add("a student from university");
        otherinfo.add("a practice worker");
        p.setOtherInfo(otherinfo);

        FileOutputStream fos = new FileOutputStream("person.detail");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        * ObjectOutputStream 的 writeObject
        * 方法可以将给定对象转换为一组字节后写出,这些字节比
        * 该对象实际内容要大,因为处理数据外还有结构等描述信息
        *
        * 下面的代码实际上经历了两个操作:
        * 1. oos将Person对象转换为一组字节
        *   将一个对象转换一组字节的过程称为:对象序列化
        *
        * 2. 再通过fos将这组字节写入到硬盘
        *   将该对象转换的字节写入到硬盘做长久保存的过程称为:对象持久化

        oos.writeObject(p);
        System.out.println("写出对象完毕");
        oos.close();
    }
}

对象反序列化 ObjectInputStream

 * java.io.ObjectInputStream
 * 对象输入流,作用是可以进行对象的反序列化
 * 读取一组字节并还原为对象
 * OIS读取的字节必须是由OOS将对象序列化得到的字节,否则会抛出异常。

public class Demo7{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("person.detail");
        ObjectInputStream ois = new ObjectInputStream(fis);
        
        ----------- 对象反序列化
        Person p = (Person)ois.readObject();
        System.out.println(p.toString());
        
        ois.close();
    }
}

transient,对象"瘦身",serialVersionUID,版本号ID

 * java.io.ObjectInputStream
 * 对象输入流,作用是可以进行对象的反序列化
 * 读取一组字节并还原为对象
 * OIS读取的字节必须是由OOS将对象序列化得到的字节,否则会抛出异常。

 * transient关键字用来修饰属性
 * 当被修饰后,该类实例在使用OOS进行对象序列化时,该属性被忽略
 * 从而达到对象"瘦身"的目的

class Person implements Serializable {

     * 当一个类实现了Serializable接口后,应当添加一个常量:serialVersionUID
     * 该常量为当前类的序列化版本号,若不定义,系统会根据当前类的结构生成,
     * 但是只要类的结构发生改变,版本号也会相应发生改变。
     * 
     * 版本号影响这反序列化的结果。即:
     * 当OIS对一个对象进行反序列化时,会检查该对象与类的版本号是否一致,
     * 若一致,反序列化成功,但是若该对象与类的机构不一致时,则采用兼容模式,将能还原的属性都还原。
     * 若不一致,反序列化直接抛出版本不一致异常。

    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private String gender;
    private List<String> otherInfo;
--- 省略 get set 方法
}

使用ByteArrayInputStream + ByteArrayOutputStream实现对象图的深度复制,深度复制是用序列化接口实现的

     * 使用ByteArrayInputStream + ByteArrayOutputStream实现对象图的深度复制
     * 也即是将所有关联的对象都复制一遍
     * 基于内存的实现方式
    @Test
    public void deeplyCopyInBAOs() throws Exception {
        Dog dog = new Dog();
        dog.setName("中秋快乐");
        dog.setAge(2);
        
        Owner own = new Owner();
        own.setName("libai");       
        dog.setOwn(own);
        
        /串行化过程,串行化到内存中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        
        oos.writeObject(dog);
        
        oos.close();
        baos.close();
        
        byte[] bytes = baos.toByteArray();
        
        /反串行化过程,提取到内存中的数据
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        
        Dog ddd = (Dog) ois.readObject();
        System.out.println(ddd);
        
        if(dog != ddd) {
            System.out.println("深度复制后,两个对象的内存地址不相等");
        }
        
        ois.close();
        bais.close();
    }

打印结果:(深度复制)
Dog [name=中秋快乐, age=2, own=Owner [name=libai]]
深度复制后,两个对象的内存地址不相等

ObjectOutputStream,持续写入对象

     * 持续写入对象
    @Test
    public void continueWrite() throws Exception {
        Dog dog = new Dog();
        dog.setName("中秋快乐");
        dog.setAge(2);
        
        Owner own = new Owner();
        own.setName("中秋快乐");
        
        /串行化过程,串行化到内存中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        
        oos.writeObject(dog);
        oos.writeObject(own);
        
        oos.close();
        baos.close();
        
        byte[] bytes = baos.toByteArray();
        
        /反串行化过程,提取到内存中的数据
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        
        Dog dog_read = (Dog) ois.readObject();
        Owner own_read = (Owner) ois.readObject();
        
        ois.close();
        bais.close();
        
        System.out.println(dog_read);
        System.out.println(own_read);   
    }

打印:
Dog [name=中秋快乐, age=2, own=null]
Owner [name=中秋快乐]

字符流

 * 字符流的读写单位为字符,是高级流
 * 虽然以字符为单位读写数据,但是实际底层还是读写字节,
 * 只是从字节与字符的转换工作交给了字符流来完成。
 * java.io.Reader:字符输入流的顶级父类
 * java.io.Writer:字符输出流的顶级父类
 * 
 * 转换流
 * java.io.OutputStreamWriter
 * 特点是可以按照指定的字符集写出字符

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("demo.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
        osw.write("北京");
        System.out.println("写出完毕");
        osw.close();
    }
}
输出流不指定编码格式的话,会按照系统默认编码方式来输出,最好指定编码
UTF-8 每个中文占三个字节,GBK每个中文占两个字节

字符输入流,字符输出流,称为转换流

转换流
将字节流到字符流的转换,使用特定字符集读取byte并解码成字符。
底层是字节流,如果需要将其转换成字符内容处理的话,就可以使用转换流。
也是装饰模式实现。
1.InputStreamReader(InputStream is, charset)
2.OutputStreamWriter(OutputStream out, charset)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

 * java.io.InputStreamReader
 * 字符输入流,可以按照给定的字符集读取字符
 * 
 * 之所以称OutputSteamWriter与InputSteamReader为转换流,
 * 是因为大对数的字符流都只处理字符流,而低级流又是字节流,
 * 这就导致字符流不能处理字节流的问题,
 * 转换流相当于一个转换器的作用,它们可以将字节流先转变为字符流,
 * 这样其他的字符流就可以处理了。

public class Demo2 {
    public static void main(String[] args) throws IOException {
    FileInputStream fis = new FileInputStream("demo.txt");
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    int d = -1;
    while((d=isr.read())!=-1) {
        System.out.print((char)d);
    }
    }
}

PrintWriter,按行写字符串

import java.io.FileNotFoundException;
import java.io.PrintWriter;

 * 缓冲字符流
 * BufferedWriter,BufferedReader
 * 特点是可以按行读写字符串
 * 
 * java.io.PrintWriter
 * 具有自动行刷新的缓冲字符输出流
 * 创建PW时,它一定会在内部创建BufferedWriter,作为缓冲功能的叠加

public class Demo3 {
    public static void main(String[] args) throws FileNotFoundException {
         * 提供了多种构造方法
         * 其中有两个可以直接对文件进行写出操作的构造方法:
         * PrintWriter(File file)
         * PrintWriter(String path)

        PrintWriter pw = new PrintWriter("demo.txt");
        pw.println("java");
        pw.println("python");
        
        System.out.println("写出完毕");
        pw.close();
    }
}

PrintWriter 自动刷新写入,设置编码格式

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

 * PrintWriter也提供了可以处理其他流的构造方法
 * 提供的方法可以传入字节流,也可以处理字符流
 * 并且,当使用这类构造方法时,可以再传入第二个参数,
 * 该参数为boolean值,该值为true时,则具有了自动行刷新的功能。

public class Demo4 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("demo.txt");
        -------- 通过字符流设置编码格式
        OutputStreamWriter fow = new OutputStreamWriter(fos,"UTF-8");  
        
        PrintWriter pw = new PrintWriter(fow,true);

        pw.println("呵呵");
        pw.println("嘿嘿");
        System.out.println("输出完毕");
        pw.close();
    }
}

BufferedReader,按行读取字符串readLine()

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

 * java.io.BufferedReader
 * 缓冲字符输入流,特点:按行读取字符串

public class Demo6 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("demo.txt");
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
        BufferedReader br = new BufferedReader(isr);
        
         * BufferedReader提供了按行读取方法
         * String readLine()
         * 连续读取若干字符,直到读取到换行符为止,
         * 并将换行符之间读取的字符以一个字符串返回。
         * 注意:该字符串不包含最后的换行符
         * 若返回值为NULL,则表示读取到文件末尾

        String line = null;
        while((line = br.readLine())!=null) {
            System.out.println(line);
        }
        br.close();
    }
}

一个归档文件的例子
也就是将各个小文件整合成一个大文件,用输入流读取文件,获取文件的扩展名(用一个字节的特定编码来表示),文件名称用特定的64个字节来存储,还有文件的大小,分别存入。
还有解归档的功能,读取文件名,文件扩展名,文件大小,读取文件内容时只需要读取特定的大小即可。

package archiver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Archiver {
    
    public void newArchiveFile(File[] srcPaths, String yarPath) {
        File file = new File(yarPath);
        file.delete(); //这里删除文件老是出现删除失败的问题
        System.out.println("yarPath:" + yarPath);
        FileOutputStream fos = null;
        int fileNumber = 0;
        try {
            fos = new FileOutputStream(yarPath, false);
            for(File srcPath : srcPaths) {
                addFile(srcPath.toString(), fos);
                fileNumber++;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("归档结束,将" + fileNumber + "个文件归档");
        }
    }
    
     * 向归档文件添加子文件
    public void append2Yar(String srcPath, String aimPath) {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(aimPath, true);
            addFile(srcPath, fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private void addFile(String srcPath, FileOutputStream fos) {
        FileInputStream fis = null;
        try {
            //1.取出文件类型
            int fileType = getFileType(srcPath);
            
            //2.取出文件名称
            String fileName = getFileName(srcPath);
            System.out.println("文件名称:" + fileName);
            
            //3.取出文件大小
            fis = new FileInputStream(srcPath);
            int fileSize = fis.available();
            
            //4.写入文件类型
            fos.write(int2Array(fileType));
            
            //5.写入文件名称
            fos.write(createFixByte(fileName));
            
            //6.写入文件大小
            fos.write(int2Array(fileSize));
            System.out.println("文件大小:" + fileSize);
            
            //5.写入文件内容
            byte[] buffer = new byte[1024];
            int len = -1;
            while((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
     * 为文件名称创建固定字节存放
    private byte[] createFixByte(String fileName) {
        if(fileName.getBytes().length < 64) {
            String kong = "";
            for(int i=0; i<64-fileName.getBytes().length; i++) {
                kong = kong + " ";
            }
            fileName = fileName + kong;
            return fileName.getBytes();
        }else {
            throw new RuntimeException("文件名称过长,请修改");
        }
    }

     * 获取路径中的文件名称
    private String getFileName(String srcPath) {
        return srcPath.substring(srcPath.lastIndexOf("\\")+1, srcPath.lastIndexOf("."));
    }

     * 将int转成byte[]
    private byte[] int2Array(int number) {
        byte[] b = new byte[4];
        b[0] = (byte)number;
        b[1] = (byte) (number>>>8);
        b[2] = (byte) (number>>>16);
        b[3] = (byte) (number>>>24);
        return b;
    }
    
     * 将byte[]转成int
    private int array2Int(byte[] b) {
        
        int i0 = b[0] & 0xff;
        int i1 = (b[1] & 0xff) << 8;
        int i2 = (b[2] & 0xff) << 16;
        int i3 = (b[3] & 0xff) << 24;
        int number = i0 | i1 | i2 | i3;
        return number;
    }
    
    
     * 由后缀名得到文件类型编号
     * 0--txt
     * 1--jpg
     * 2--avi
     * 3--gif
     * 4--exe
    private int getFileType(String srcPath) {
        String ext = srcPath.substring(srcPath.lastIndexOf("."));
        int type = -1;
        if(".txt".equals(ext)) {
            type = 0; 
        }else if(".jpg".equals(ext)) {
            type = 1;   
        }else if(".avi".equals(ext)) {
            type = 2; 
        }else if(".gif".equals(ext)) {
            type = 3; 
        }else if(".exe".equals(ext)) {
            type = 4; 
        }
        return type;
    }
    
     * 由文件类型编号得到后缀名
     * 0--txt
     * 1--jpg
     * 2--avi
     * 3--gif
     * 4--exe
    @SuppressWarnings("unused")
    private String getFileSuffixName(int type) {
        String suffixName = null;
        if(type == 0) {
            suffixName = ".txt";
        }else if(type == 1) {
            suffixName = ".jpg";
        }else if(type == 2) {
            suffixName = ".avi";
        }else if(type == 3) {
            suffixName = ".gif";
        }else if(type == 4) {
            suffixName = ".exe";
        }
        return suffixName;
    }

     * 解档文件
    public void unarchive(String yarPath, String destDir) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(yarPath);
            //循环读取下一个文件
            while(readNextFile(fis, destDir)) {
                //System.out.println("循环中");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

     * 解归档文件
    private boolean readNextFile(FileInputStream fis, String destDir) throws IOException {
        byte[] b = new byte[4];
        int type = fis.read(b);
        if(type == -1) {
            return false;
        }
        
        System.out.print("读取一个文件,");
        
        //1.处理文件类型
        int fileType = array2Int(b);
        String fileSuffixName = getFileSuffixName(fileType);
        System.out.print("文件类型:" + fileType);
        
        //2.获取文件名称固定64个字节
        byte[] fileNameByte = new byte[64];
        fis.read(fileNameByte);
        String fileName = new String(fileNameByte).trim();
        System.out.print(" 获取到文件名称:" + fileName);
        
        //3.获取文件大小
        fis.read(b);
        int fileSize = array2Int(b);
        System.out.println(" 文件大小:" + fileSize);
        
        FileOutputStream fos = new FileOutputStream(destDir + fileName + fileSuffixName);
        
        byte[] data = new byte[1024];

        while(true) {
            if(fileSize > 1024) {
                fis.read(data);
                fos.write(data);
                fileSize = fileSize - 1024;
//              System.out.println("循环:" + fileSize);
            }else {
                byte[] end = new byte[fileSize];
                fis.read(end);
                fos.write(end);
                break;
            }
        }
        fos.close();
        return true;
    }
}

junit单元测试文件

package archiver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.junit.Test;

public class test {
    @Test
    public void many2One(){
        Archiver archiver = new Archiver();
        File file = new File("E:\\TestCase\\Archive");
        
        archiver.newArchiveFile(file.listFiles(), "E:\\TestCase\\Archive\\end.yar");
        System.out.println("程序运行结束");
    }
    
     * 向归档文件中添加文件
    @Test
    public void append2Yar() {
        Archiver archiver = new Archiver();
        archiver.append2Yar("E:\\TestCase\\Archive\\demo.txt", "E:\\TestCase\\Archive\\end.yar");
        System.out.println("程序运行结束");
    }
    
     *解档到某个文件夹
    @Test
    public void unArchiver() {
        Archiver archiver = new Archiver();
        archiver.unarchive("E:\\TestCase\\Archive\\end.yar", "E:\\TestCase\\Archive\\undo\\");
        System.out.println("程序运行结束");
    }
}

运行junit中的many2One单元测试,看下图


归档.png

解归档,运行unArchiver


解归档.png

标准IO
输入默认是键盘,输出默认是控制台

     * 改变系统的out输出流向,默认是console
    @Test
    public void setSysOut() {
        PrintStream ps = null;
        try {
            ps = new PrintStream("E://TestCase//day14//log.log");
            System.setOut(ps);
            System.out.println("你好,控制台输出");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ps != null) {
                ps.close();
            }
            System.out.println("关闭输出流");
        }
    }
    
     * 获取控制台输入,写到文件中
    @Test
    public void getSystemIn() {
        try {
            System.setIn(new FileInputStream("E://TestCase//day14//System.in.txt"));
            BufferedReader bir = new BufferedReader(new InputStreamReader(System.in));
            String line = null;
            while(true) {
                line = bir.readLine();
                if("exit".equals(line)) {
                    bir.close();
                    System.exit(-1);
                }
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }   

note

java.io.FileWriter
    1.new FileWriter(String path);
    2.write(String str);
        写入字符数据到流中
    3.flush()
        清理流,将流中的数据写入目标设备上。
    4.close()
        关闭流,隐含了flush()操作。
    
char是十六位,无符号,0-65535

字节流
    1.FileInputStream
    支持skip()方法,skip向后跳的时候不能文件头地址,可以
    超过文件末尾地址。
    2.FileOutputStream
    不支持skip
    3.RandomAccessFile
    随机访问文件,定位到文件的任意位置

IO
1.流向 InputStream | OutputStream
2.类型划分 
    a.字符流(文本文件,字符编码问题)
           Reader | Writer
       FileReader | FileWriter
    b.字节流(任何文件)
      InputStream | OutputStream
  FileInputStream | FileOutputStream
3.性能划分
    BufferedWriter:缓冲区流(装饰模式,flush清理)
    BufferedReader

java例子,File和IO流复制整个文件夹及其下所有文件

     * 使用递归遍历所有子目录
     * 复制文件夹及下所有文件
    @Test
    public void copyAllFile(){
        String srcPath = "E:\\TestCase";
        String aimPath = "E:\\TestCase2";
        long start = System.currentTimeMillis();
        File file = new File(srcPath);
        int number = 0;
        if(file.exists()) {
            number = getPaths(file, number, srcPath, aimPath);
        }
        long end = System.currentTimeMillis();
        System.out.println("一共复制了" + number + "个文件,共花费" + (end-start) + "ms");
    }
    
    public int getPaths(File file, int number, String srcPath, String aimPath) {
        if(file.isDirectory()) {
            for(File f : file.listFiles()) {
                if(f.isFile()) {
                    number++;
                    System.out.println("第" + number + "个文件:" + f.getAbsolutePath());
                    copyFile(f.getAbsolutePath(), f.getAbsolutePath().replace(srcPath, aimPath));
                }else {
                    String newPath = f.getPath().replace(srcPath, aimPath);
                    File fNew = new File(newPath);
                    fNew.mkdirs();
                }
                number = getPaths(f, number, srcPath, aimPath);
            }
        }
        return number;
    }
    
    public void copyFile(String srcPath, String aimPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(srcPath);
            File file = new File(aimPath);
            if(!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            fos = new FileOutputStream(aimPath);
            
            int len = -1;
            while((len = fis.read()) != -1) {
                fos.write(len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if(fis != null) {
                    fis.close();
                }
                if(fos != null) {
                    fos.close();
                }
            } catch(Exception e) {
                e.printStackTrace();
            }
            System.out.println("复制一个文件结束");
        }
    }
copyFile.png

try--catch 捕获异常,finally

 * java异常捕获机制中的try-catch
 * try块是用来包含上可能出错的代码片段
 * catch块是用来捕获try块中代码抛出的错误,并解决

public class Demo7 {
    public static void main(String[] args) {
        System.out.println("this program begin");
        try {
            String str = null;

             * JVM在执行代码的过程中若发现了一个异常,则会
             * 实例化这种情况的异常实例,并将代码整个执行过程
             * 完整设置到异常中来表示该错误报告,设置完毕后将该异常抛出。

             * 若抛出异常的这句代码有被try包围,则JVM会检查catch
             * 中是否有关注该异常,关注则抛给catch解决,否则会将
             * 异常抛出到当前方法外,由调用当前方法的代码解决该异常。 
            
            System.out.println(str.length());
        }catch(NullPointerException e) {
            System.out.println("空指针异常 "+e);
        }catch(Exception e) {

             * 应当养成一个好习惯,在最后一个catch中捕获Exception,避免
             * 因未捕获异常导致程序中断。
             * 
             * 当多个catch捕获不同的异常时,这些异常间存在继承关系,那么
             * 子类异常要在上先行捕获,父类异常在下。

            System.out.println("program error as"+e);
        }
    }
}

打印:
this program begin
空指针异常 java.lang.NullPointerException


 * finally块
 * finally块定义在异常捕获机制的最后
 * 可以直接跟在try块之后或者最后一个catch块之后。
 * finally块中的代码一定执行,无论try块中的代码是否抛出异常。
 * 所以通常会把释放资源等操作放在finally中,例如关闭流。

重写父类一个含有throws异常抛出声明的方法时,子类该方法的throws的重写原则
      不允许抛出额外异常
      不允许抛出比父类异常还大的异常

throw与throws,实例异常与抛出异常

 * 用来测试throw与throws

class Person {
    private int age;

    public int getAge() {
        return age;
    }
    
     * 当一个方法中使用throw抛出一个异常时就要在方法上
     * 使用throws声明该类异常的抛出以通知调用者解决。
     * 
     * 只有RuntimeException及其子类异常使用throw抛出
     * 时不强制要求必须使用throws声明,其他异常都是强制
     * 要求的,否则编译不通过。

    public void setAge(int age) throws Exception {
        if(age<0 || age>100) {
            throw new Exception("this age is illegal");
        }
        this.age = age; 
    }
}


----------- 永远不要在main方法抛出异常
public class Demo9{
    public static void main(String[] args) throws Exception {
        Person p = new Person();

         * 当调用一个含有throws声明异常抛出的方法时,编译器要求必须处理该异常。
         * 处理手段有两种:
         * 1. 使用try-catch捕获并处理
         * 2. 在当前方法上继续使用throws声明该异常的抛出。

        p.setAge(200);
        System.out.println("age is "+p.getAge());
    }
}

打印抛出的异常信息

public class DemoException {
    public static void main(String[] args){
        String str = "abc";
        try{
            Integer.parseInt(str);
        }catch(Exception e){
            System.out.println(e.getMessage());
            System.out.println("-------------------------");
            e.printStackTrace();
        }
    }
}
打印:
For input string: "abc"
-------------------------
java.lang.NumberFormatException: For input string: "abc"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at day07.DemoException.main(DemoException.java:7)

自定义异常

* 年龄不合法异常
*
* 自定义异常,通常是用来描述某个业务逻辑上出现的问题。
* 自定义异常的名字应当做到见名知义

class IllegalAgeException extends Exception{      -----该类底下代码可以直接生成,不需要修改
    public IllegalAgeException() {
    }

    public IllegalAgeException(String message) {
        super(message);
    }

    public IllegalAgeException(String message, Throwable cause) {
        super(message, cause);
    }

    public IllegalAgeException(Throwable cause) {
        super(cause);
    }

    public IllegalAgeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

public class Demo{
    public static void main(String[] args){
        Person p = new Person();
        try{
            p.setAge(120);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

class Person{
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) throws IllegalAgeException{
        if(age<0 || age>100){
            throw new IllegalAgeException("年龄不合法");
        }
        this.age = age;
    }
}

打印:
day07.IllegalAgeException: 年龄不合法
    at day07.Person.setAge(Demo.java:50)
    at day07.Demo.main(Demo.java:33)

补充一个很容易忽略的问题,困扰我许久----
关于字符串的 getBytes() 方法,和 (byte)强转的区别,都是对int的操作

@Test
public void test1() {
    int n = 10;
    System.out.println((byte)n);
    byte[] b = (n+"").getBytes();
    System.out.println("--------------------------");
    for(byte bb : b) {
        System.out.println(bb);
    }
}

运行结果如下


字符问题.png

实际上,String 的 getBytes() 方法是将字符串中的所有字符依据编码表(基于ascii表)一个一个转换成对应的数字,
(byte) 是将类型和值都转换成了 byte 类型

在将int类型写入文件时,用 String 的 getBytes() 方法写入时,需要用字符流来读;

用 int 转换成 byte 写入时,需要注意 byte 的数字区间,避免数据丢失,也可以自定义将 int 转换成 byte 类型,读取时需要用字节流,读取的 byte[] 大小需要依据你写入时的 byte[] 大小来确定。

还有一点,字符写入模式是依据文本文件来说的,一般的媒体文件都是用字节流来处理。

相关文章

  • Java 操作目录,文件,流的读、写,低级流与高级流,异常

    中文编码,GBK 2个字节,UTF-8 3个字节。 在关闭流的时候,直接关闭高级流就行了,如果先关闭低级流再关闭高...

  • 文件与流-1

    文件与流 持久化操作:(文件里、数据库里)Java.io 文件分隔符 目录操作 文件操作

  • IO流

    IO 输入是读取,输出是写出,流是单向的 节点流,也叫低级流 节点流有明确的来源与去向 处理流,也叫高级流 处理流...

  • 输入输出--Java IO流

    1. IO流分类 节点流:又叫低级流 处理流:高级流 字节流和字符流的区别在于数据处理单位的不同。 字节流:一次读...

  • Java IO流

    标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流等等,java中将输入输出抽象称为流,就好...

  • Java中Stream操作

    Java中Stream操作 流 此处的流(Stream)与io中的输入流(InputStream)与输出流(Out...

  • Java-IO流(三)操作文件中的数据

    对数据进行输入输出操作(往文件中写入数据) 字节输出流-数据写入到文件中 字节输出流-续写&换行 IO异常处理 字...

  • Java 学习笔记(14)—— 文件操作

    java文件操作主要封装在Java.io.File中,而文件读写一般采用的是流的方式,Java流封装在 java....

  • Android 基础之文件和数据库

    Java 文件流 1. 字节流文件操作(读写)的代码 2. 字符流文件操作(读写)的代码 3. 按行(读写)的代码...

  • java文件流操作!

    一、概念 在Java中,文件的输入和输出是通过流(Stream)来实现的。一个流,必有源端和目的端,它们可以是计算...

网友评论

    本文标题:Java 操作目录,文件,流的读、写,低级流与高级流,异常

    本文链接:https://www.haomeiwen.com/subject/tjbjvftx.html