文件与IO

start time:2020年10月26日

01 文件File类

  • 程序代码和文件目录的关系:主要就是对文件和目录进行增删改查,俗称CRUD;简单了解IO,即Input/Output:
  • 把内存的中数据存储到持久化设备到上的动作称为输出,Output操作;
  • 把持久化设备的数据读取到内存中的动作称为输入,Input操作;
  • 一般把输入和输出的动作称为IO操作,IO也分网络IO、文件IO;

  • java文件类File:主要是计算机文件和目录的操作,即对文件的和目录的增删改查;
  • File类表示磁盘中存在的文件和目录;
  • File类的包名是java.io,也实现了SerializableComparable两大接口以便于其对象可序列化和比较;
package java.io;
public class File implements Serializable, Comparable<File> {}
  • File.separator目录分隔符,在不同的系统下不一样,windows和mac/Linux;

  • 常见的构造函数:

// Creates a new File instance by converting the given pathname string into an abstract pathname.
public File(String pathname) {}

// Creates a new File instance from a parent pathname string and a child pathname string.
public File(String parent, String child) {}
  • 常见的方法:
public class FileTest {

    public static void main(String[] args) {
        String dir = "F:\\00-harvest";
        String fileName = "test.txt";
//        File file = new File(dir, fileName);
        File file = new File(dir);

        System.out.println("文件分隔符: " + File.separator);         // 文件分隔符: \
        System.out.println("基本路径: " + file.getPath());           // 基本路径: F:\00-harvest
        System.out.println("文件名: " + file.getName());            // 文件名: 00-harvest
        System.out.println("绝对路径: " + file.getAbsolutePath());  // 绝对路径: F:\00-harvest
        System.out.println("父路径: " + file.getParent());          // 父路径: F:\
        System.out.println("是否绝对路径: " + file.isAbsolute());    // 是否绝对路径: true
        System.out.println("是否一个目录: " + file.isDirectory());   // 是否一个目录: true
        System.out.println("文件是否存在: " + file.exists());       // 文件是否存在: true
        System.out.println("目录中的文件和目录的名称所组成字符串数组list");
        /*
         * 输出:
         * 00 学习书籍
         * 01 小滴class
         * 02 运维&容器
         * 2020年8月17日.md
         * ......
         */
        String[] list = file.list();
        for (String str : list) {
            System.out.println(str);
        }
        // 创建指定目录
        File mkdirFile = new File(dir + "\\testdir");
        mkdirFile.mkdir();

        // 创建多个层级目录
        File mkdirsFile = new File(dir + "\\testdir\\test");
        mkdirsFile.mkdirs();

        // 创建一个新的文件
        File newFile = new File(dir + "\\testdir\\test.txt");
        try {
            newFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
//        // 删除文件
//        newFile.delete();
    }
}

note:删除文件;当前文件必须是最终的文件才可以删除,即如果是文件夹,需要把文件夹里面的内容删除完后才能删除文件夹。

note:File的构造函数只是创建一个File实例,即使目录错误也不出报错,因为没对文件进行操作;

02 Input/OutputStream流

IO:Input/Ouput即输入输出;

  • 输出流:程序(内存) -> 外界设备;
  • 输入流:外界设备 -> 程序(内存);

处理数据类型分类

  • 字符流:处理字符相关,如处理文本数据(如txt文件),Reader/Writer
  • 字节流: 处理字节相关,如声音或者图片等二进制,InputStream/OutputStream

两者区别:

  • 字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节;
  • 字节流可以处理几乎所有文件,字符流只能处理字符类型的数据;
  • 功能不同,但是具有共性内容,通过不断抽象形成4个抽象类,抽象类下面有很多子类是具体的实现;
    • 字符流 Reader/Writer
    • 字节流 InputStream/OutputStream

2.1 InputStream

InputStream是输入字节流的父类,它是一个抽象类(一般用它的子类);

int read()
// 讲解:从输入流中读取单个字节,返回0到255范围内的int字节值,字节数据可直接转换为int类型,如果已经到达流末尾而没有可用的字节,则返回-1;

int read(byte[] buf)
// 讲解:从输入流中读取一定数量的字节,并将其存储在缓冲区数组buf中,返回实际读取的字节数,如果已经到达流末尾而没有可用的字节,则返回-1;

long skip(long n)
// 讲解:从输入流中跳过并丢弃n个字节的数据;

int available()
// 讲解:返回这个流中有多少个字节数,可以把buf数组长度定为这个;

void close() throws IOException
// 讲解:关闭输入流并释放与该流关联的系统资源

抽象类InputStream用来具体实现类的创建对象,文件字节输入流,对文件数据以字节的形式进行读取操作;

  • 常见子类FileInputStream,及构造函数:
public class FileInputStream extends InputStream {}
// 传入文件地址
public FileInputStream(String name) throws FileNotFoundException {
    ......
}
// 传入文件对象
public FileInputStream(File file) throws FileNotFoundException {
    ......
}
  • 使用范例
public class InputStreamTest {

    public static void main(String[] args) throws IOException {
        String dir = "F:\\00-harvest";
        String fileName = "test.txt";
        File file = new File(dir, fileName);
        InputStream inputStream = new FileInputStream(file);

        // testRead(inputStream);
        // testSkip(inputStream);
        testReadByteArr(inputStream);
    }

    /**
     * “F:\00-harvest\test.txt”中的内容为:
     *  hello world!
     *  2020年10月26日16:32:06
     */

    private static void testRead(InputStream inputStream) throws IOException {
        // 对于汉字等unicode中的字符则不能正常读取,只能以乱码的形式显示;
        int read = inputStream.read();
        System.out.println(read);
        System.out.println((char) read);
    }

    private static void testSkip(InputStream inputStream) throws IOException {
        long skipSize = inputStream.skip(2);
        System.out.println(skipSize);
        int read = inputStream.read();
        System.out.println(read);
        System.out.println((char) read);
    }

    private static void testReadByteArr(InputStream inputStream) throws IOException {
        // 如果buf的长度为0,则不读取任何字节并返回0;每次读取的字节数最多等于buf的长度;
        byte[] buf = new byte[1024];
        int length;
        /*
        循环读取文件内容,输入流中将最多buf.length个字节的数据读入到一个buf数组中,返回类型是读取到的字节数;
        如果这个缓冲区没有满的话,则返回真实的字节个数;
        当文件读取到结尾时返回-1,循环结束;
         */
        while ((length = inputStream.read(buf)) != -1) {
            // System.out.println(new String(buf, 0, length));
            // 遇到乱码问题,解决方案是保证编码一致;
            System.out.println(new String(buf, 0, length, "UTF-8"));
        }
        inputStream.close();
    }
}

编码小知识(节省空间):

  • 操作的中文内容多则推荐GBK:GBK中英文也是两个字节,用GBK节省了空间;
  • UTF-8编码的中文使用了三个字节,如果是英文内容多则推荐UFT-8:因为UFT-8里面英文只占一个字节,UTF-8编码的中文使用了三个字节;

2.2 OutputStream

OutputStream是输出字节流的父类,它是一个抽象类;

void write(int b)
// 讲解:将指定的字节写入输出流;

void write(byte[] b)throws IOException
// 讲解:将b.length个字节的byte数组写入当前输出流;

void flush() throws IOException
// 讲解:write是写到缓冲区中,可以认为是内存中,当缓冲区满时系统会自动将缓冲区的内容写入文件,
// 但是一般还有一部分有可能会留在内存这个缓冲区中,所以需要调用flush清空缓冲区数据;

void close() throws IOException
// 讲解:关闭输入流并释放与该流关联的系统资源;

抽象类OutputStream用来具体实现类的创建对象,文件字节输出流,对文件数据以字节的形式进行输出的操作;

  • 常见子类FileOutputStream,及构造函数:
public class FileOutputStream extends OutputStream {
    // 传入输出文件地址
    public FileOutputStream(String name)

    // 传入目标输出的文件对象
    public FileOutputStream(File file)

    // 传入目标输出的文件对象,是否可以追加内容
    public FileOutputStream(File file, boolean append)
}
  • 使用范例
public class OutputStreamTest {

    public static void main(String[] args) throws IOException {
        String dir = "F:\\00-harvest";
        String fileName = "test.txt";
        String target = "abc.txt";
        File file = new File(dir, fileName);
        InputStream inputStream = new FileInputStream(file);
        // 会自动创建文件,但是不会创建多级目录下的文件;
        OutputStream outputStream = new FileOutputStream(dir + "\\" + target);

        // 不覆盖文件,追加数据;
        // OutputStream outputStream = new FileOutputStream(dir + target, true);

        // testOut(inputStream, outputStream);
        testOutBuf(inputStream, outputStream);


    }

    // 单个字节读取,中文会有乱码问题
    private static void testOut(InputStream inputStream, OutputStream outputStream) throws IOException {
        int value = 0;
        while (value != -1) {
            value = inputStream.read();
            outputStream.write(value);
        }
        // 最后记得关闭流
        inputStream.close();
        outputStream.close();
    }

    private static void testOutBuf(InputStream inputStream, OutputStream outputStream) throws IOException {
        byte[] buf = new byte[1024];
        int length;
        while ((length = inputStream.read(buf)) != -1) {
            outputStream.write(buf, 0, length);
        }
        inputStream.close();
        outputStream.close();
    }
}

2.3 BufferInputStream/BufferOutputStream

什么是缓冲Buffer?

  • 它是内存空间的一部分,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分空间就叫做缓冲区,缓冲区是具有一定大小的;

为啥要用缓冲?

  • 缓冲,缓和冲击,例如操作磁盘比内存慢的很多,所以不用缓冲区效率很低;
  • 数据传输速度和数据处理的速度存在不平衡,比如你每秒要写100次硬盘,对系统冲击很大,浪费了大量时间在忙着处理开始写和结束写这两个事件,用buffer暂存起来,变成每10秒写一次硬盘,数据可以直接送往缓冲区,高速设备不用再等待低速设备,对系统的冲击就很小,写入效率高了;(通过减少和磁盘的IO来提升效率-ddh 2020年10月26日17:56:14)

Java IO包里面的两个缓冲类(高级流)

  • BufferInputStreamBufferOutputStream
  • 采用包装设计模式(锦上添花的意思)(即在外包装一层,使得功能增强-ddh 2020年10月26日17:57:31)

BufferInputStream缓冲字节输入流:

  • BufferedInputStream通过预先读入一整段原始输入流数据至缓冲区中,而外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,如果读取的数据超过了缓冲区的范围,那么BufferedInputStream负责重新从原始输入流中载入下一截数据填充缓冲区,然后外界继续通过缓冲区进行数据读取;

  • 好处:避免了大量的磁盘IO,原始的InputStream类实现的read是即时读取的,每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),如果数据量巨大,这样的磁盘消耗非常可怕;

  • 缓冲区的实现: 读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,下一次读取一般情况就直接可以从缓冲区读取,减少了磁盘IO;

  • 默认缓冲区大小是8k;int DEFAULT_BUFFER_SIZE = 8192;

  • BufferedInputStream,及构造函数:

public class BufferedInputStream extends FilterInputStream {
    // 默认缓冲区大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
    
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}    
  • 常用的方法
// 从输入流中读取一个字节
public int read();

// 从字节输入流中给定偏移量处开始将各字节读取到指定的byte数组中
public int read(byte[] buf,int off,int len);

// 关闭释放资源,关闭的这个流即可,InputStream会在里面被关闭
void close();
  • BufferedOutputStream,及构造函数:
public class BufferedOutputStream extends FilterOutputStream {
    protected byte buf[];
    
    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);
    }
    
    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}
  • 常用的方法
// 向输出流中输出一个字节
public void write(int b);

// 将指定byte数组中从偏移量off开始的len个字节写入缓冲的输出流
public void write(byte[] buf,int off,int len);

// 刷新此缓冲的输出流,强制使所有缓冲的输出字节被写出到底层输出流中
public void flush();

// 关闭释放资源,关闭这个流即可,OutputStream会在里面被关闭,JDK7新特性try(在这里声明的会自动关闭){}
void close();
  • 缓冲输入输出流-Java文件拷贝范例
public class BufferedStreamTest {

    public static void main(String[] args) throws IOException {
        String dir = "F:\\00-harvest";
        String fileName = "test.txt";
        String target = "b.txt";
        File file = new File(dir, fileName);
        InputStream inputStream = new FileInputStream(file);
        // 会自动创建文件,但是不会创建多级目录下的文件;
        OutputStream outputStream = new FileOutputStream(dir + "\\" + target);

        // 包装了一层FileInput/OutputStream
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        BufferedOutputStream bos = new BufferedOutputStream(outputStream);
        int size;
        byte[] buf = new byte[1024];
        while ((size = bis.read(buf)) != -1) {
            bos.write(buf, 0, size);
        }
        // 刷新此缓冲的输出流,才可以保证数据全部输出完成,close会自动关闭;
        // 如果缓存区是8k,缓存区满了就会写入到磁盘,如果只剩6k数据,则需要用flush把剩余的数据写入磁盘
        // bos.flush();
        bis.close();
        bos.close();
    }
}
  • 注意:BufferedOutputStream在close()时会自动flush
public synchronized void flush() throws IOException {
    flushBuffer();
    out.flush();
}
  • BufferedOutputStream在不调用close()的情况下,缓冲区不满,又需要把缓冲区的内容写入到文件或通过网络发送到别的机器时,才需要调用flush;
  • 流的关闭顺序:后开先关,如果A依赖B,先关闭B;

2.4 ObjectInputStream/ObjectOutputStream(保存对象)

  • ObjectOutputStreamTest
public class ObjectOutputStreamTest {
    public static void main(String[] args) {
        String dir = "F:\\00-harvest";
        String target = "object.txt";

        File file = new File(dir, target);
        User user = new User();
        user.setAge(18);
        user.setName("dawson");
        Order order = new Order();
        order.setId(1);
        order.setNumber("abc");
        order.setPrice(10.1);
        List<Object> objectList = new ArrayList<>();
        objectList.add(user);
        objectList.add(order);
        FileOutputStream out;
        try {
            out = new FileOutputStream(file);
            ObjectOutputStream objOut = new ObjectOutputStream(out);
            objOut.writeObject(objectList);
            objOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • ObjectInputStreamTest
public class ObjectInputStreamTest {
    public static void main(String[] args) {
        String dir = "F:\\00-harvest";
        String target = "object.txt";

        File file = new File(dir, target);
        List<Object> objectList;
        FileInputStream in;
        try {
            in = new FileInputStream(file);
            ObjectInputStream objIn = new ObjectInputStream(in);
            objectList = (List<Object>) objIn.readObject();
            System.out.println(objectList.get(0).toString());
            System.out.println(objectList.get(1).toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出:

User{age=18, name='dawson'}
Order{id=1, number='abc', price=10.1}

2.5 获取目录下的所有文件

  • GetFileByTimeTest
/**
 * @author ddhhdd
 * @date 2020/10/27 17:16
 */
public class GetFileByTimeTest {
    public static void main(String[] args) {
        String path="F:\\00-harvest";
        List<File> fileList = getFileSort(path);
        for (File file : fileList) {
            System.out.println(file.getName());
        }
    }

    /**
     * 获取目录下的所有文件(按时间排序)
     * @param path
     * @return
     */
    private static List<File> getFileSort(String path) {
        List<File> fileList = getFiles(path, new ArrayList<>());
        if (fileList != null && fileList.size() > 0) {
            Collections.sort(fileList, new Comparator<File>() {
                @Override
                public int compare(File file, File newFile) {
                    // 大于,升序;小于,降序;
                    if (file.lastModified() > newFile.lastModified()) {
                        return 1;
                    } else if (file.lastModified() == newFile.lastModified()) {
                        return  0;
                    } else {
                        return -1;
                    }
                }
            });
        }
        return fileList;
    }
    /**
     * 获取目录下的所有文件
     * @param path
     * @param fileList
     * @return
     */
    private static List<File> getFiles(String path, List<File> fileList) {
        File file = new File(path);
        if (file.isDirectory()) {
            File[] subFiles = file.listFiles();
            for (File f : subFiles) {
                if (f.isDirectory()) {
                    getFiles(f.getAbsolutePath(), fileList);
                } else {
                    fileList.add(f);
                }
            }
        }
        return fileList;
    }
}
  • Comparator
public interface Comparator<T> {
    /**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.<p>
     /
}
posted @ 2020-12-15 10:11  ddhhdd  阅读(98)  评论(0)    收藏  举报