浅析Java文件IO

前言:提到文件先了解一下编码

  • 在windows下,直接创建txt文本文件,我的默认是ANSI编码,但文本文件编码可任意修改;
  • 我使用的是IDEA,所以我的项目默认编码是UTF-8编码,UTF-8编码中,中文占3byte,英文占1byte;

根据结构图理解

IO流的三种分类方式:

  • 按照流的方向分为:输入流和输出流
  • 按照流的数据单位:字节流和字符流
  • 按照流的不同功能:节点流和处理流

我是按照字节流和字符流的结构顺序来逐个理解相关概念及用法(图源网络)
在这里插入图片描述
一、File:

  1. java.io.file类代表系统文件(文件和目录)
  2. 访问文件属性步骤:
  • 先创建/获取文件对象
File file = new File("D:\\Program Files (x86)\\Java\\TestIO\\test2.txt");//way1
File file = new File(new File("D:\\Program Files (x86)\\Java"),"TestIO\\test3.txt");//way2
URI uri = tmp.toURI();
File file = new File(uri);//way3
  • 然后调用方法
  1. 常用方法:
    (1)boolean exists() //表示当前路径是否存在
    (2)boolean isFile() //判断当前路径是否是文件
    (3)boolean isDirectory() //判断当前是否为目录
    (4)boolean isHidden() //判断是否是隐藏文件
    (5)void deleteOnExit() //在程序退出时删除
    (6)boolean delete() //直接删除
    (7)boolean createNewFile() //创建新文件 在当前文件不存在的情况下,创建成功返回true,文件存在返回false
    (8)Mkdirs() 和mkdir() //创建目录
    Mkdirs 和mkdir之间的关系
    new File("/tmp/one/two/three").mkdirs();
    执行后, 会建立tmp/one/two/three父级目录
    new File("/tmp/one/two/three").mkdir();
    则不会建立任何目录, 因为找不到/tmp/one/two目录, 结果返回false
    (9)file.getName();// 获取文件名
    (10)File.getPath(); //获取相对路径
    (11)getParent()//获取文件的父路径
    (12)file.lastModified() //获取文件最后一次修改的时间
    (13)file.getAbsolutePath();//获取绝对路径
    (14)File.Length() //获取文件长度
    (15)public boolean renameTo(File dest)

上面是对文件的基本操作,下面再了解对文件内容的基本读写操作!
二、字节流:

  • 输入流(InputStream):
    输入流下有FileInputStream类,用来将文件内容读取到Java程序中:
public class fileInputStreamTest {
    public static void main(String[] args) {
        FileInputStream in = null;
        try {
            in = new FileInputStream("D:\\Program Files (x86)\\Java\\TestIO\\test4.txt");
            int i = 0;
            byte[] bytes = new byte[9];
            //in.read();会乱码,中文占两个字节,这样每次读半个字
            while(in.read(bytes) != -1){
                System.out.print(new String(bytes,"gbk"));//gbk是编码形式
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            //防止多线程情况下引发的异常
            if(in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

  • 输出流(OutputStream):
    输出流下有FileOutputStream类,将内容从Java程序写入指定文件中:
public class FileOutStreamTest {
    public static void main(String[] args) {
        OutputStream out = null;
        try {
            //第二个参数不写或者写为false,会覆盖之前的内容;如果写为true的话,会追加在原文档的后面
            out = new FileOutputStream("D:\\Program Files (x86)\\Java\\TestIO\\test.txt",true);
            String str = "写入文件的内容啊";
            out.write(str.getBytes("utf-8"));//插入的类型与源文件编码形式不同会出现乱码

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

以上两种方法是将内容直接写入或者读出的,只要运行程序,相应的IO操作就会完成,但是我们知道的,IO操作很耗时,如果频繁的进行IO操作,事实上很浪费时间,因此我们引入缓冲区的概念。
缓冲区的作用
1、IO耗时,使用缓冲区减少IO次数,一次读出缓冲区的大小的内容,再次去读的时候操作的是缓冲区,将缓冲区内容读完后会刷新缓冲区的数据。–flush():在写入数据过程中,写了一部分就要同步到文件里 --close():在操作结束时调用;
2、字节和字符间的转换在缓冲区实现;

  • 带有缓冲区的输入流(BufferedInputStream):
public class BufferInputStreamTest {
    public static void main(String[] args) {
        try {
            BufferedInputStream in = new BufferedInputStream(new FileInputStream( "D:\\Program Files (x86)\\Java\\TestIO\\test.txt"));
            byte[] bytes = new byte[2];
            int read = in.read(bytes);//将文件中内容读出来,并且放到数组里
            System.out.println(read);
            System.out.println(Arrays.toString(bytes));
            while(in.read(bytes) != -1){
                System.out.print(Arrays.toString(bytes));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  • 带缓冲区的输出流(BufferedOutputStream):
public class bufferedOutStreamTest {
    public static void main(String[] args) {
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(
                    new FileOutputStream("D:\\Program Files (x86)\\Java\\TestIO\\test.txt",true) );
            String str = "是不是傻";
            out.write(str.getBytes());
            out.flush();//flash写完后被读之后还能继续向里面写内容,但close之后想要再写的话就还需要重新开启资源
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(out != null){
                try {//如果没有写满8M的话,就要调用close方法或者flash
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

字符和字节本质传输都是字节;
区别是:字节流以一个字节为单位进行数据传输;而字符流以两个字节二为单位进行数据传输。
三、字符流

  • 转换流:字节流与字符流之间的桥梁
    InputStreamReader按照指定编码将字节流转换成字符流 解决读里的乱码问题
    InputStreamWriter按照指定编码将字符流转换成字节流 解决写里的乱码问题

  • 输入流(reader):从文件读取内容转换为字节传输到Java程序再转换为字符输出,所以字符的输入输出流中间都会经过一个转换流

public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fileReader = null;
        try {
            fileReader = new FileReader("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt");
                    char[] chars = new char[10];
            int len = 0;
            while((len = fileReader.read(chars)) > 0) {
                for(int i = 0;i < len;i++) {
                    System.out.print(chars[i]);
                }
            }
   } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fileReader != null){
                try {
                    fileReader.close();
                    fileReader = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 输出流(writer):
public class FileWriterTest {
    public static void main(String[] args) {
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt",true);
           String str = "也罢--鲁向卉";
            fileWriter.write(str);
       } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileWriter != null) {
                try {
                    fileWriter.close();
                    fileWriter = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 带缓冲区的输入流:
public class BufferedReaderTest {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt"),1024);
            String str = null;
            reader.skip(2);//跳过前n个字符从第n+1个开始读

            while((str=reader.readLine()) != null){
                System.out.println(str);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  • 带缓冲区的输出流:

public class BufferedWriterTest {
    public static void main(String[] args) {
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter("D:\\Program Files (x86)\\Java\\TestIO\\writerTest.txt"));
//            writer.write(123);
            writer.write("-我们的总和-");
            String str = "-也罢-";
            writer.write(str,0,3);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(writer != null){
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

四、序列化和反序列化
ObjectInputStream:
ObjectOutputStream:
序列化:对象–>字节序列的过程 存放在文件中
反序列化:字节序列–>对象的过程

  • 序列化和反序列化的作用:
    (1)实现对象持久化。对象–>字节序列的过程 存放在文件中
    对象==》项目终止 对象也就随之消失,无法实现反复利用 例如:MySQL
    (2)网络传输。不能将消息直接从A转到B的,需要对它序列化再进行网络传输
  • 序列化/反序列化流程:
    (1)让序列化对象实现Serializable接口
    (2)序列化调用ObjectOutputStream—out.writeObject( p)方法;
    (3)反序列化调用ObjectInputStream—in.readObject( p)方法;
  • transient:可以让指定关键字不被序列化
  • transient关键字的作用:
    (1)避免不必要的序列化操作
    (2)避免敏感信息被序列化

常见的I/O模型

(可能理解还不成熟,有错还望指出)
I/O分两步

  • 用户层PAI的调用
  • 内核层完成系统调用

I/O模型的分类

  • 阻塞IO模型
  • 非阻塞IO模型
  • IO复用模型
  • 信号驱动IO模型
  • 异步IO模型

阻塞 IO模型(BIO)
阻塞IO模型先是等待数据就绪,然后将内核空间的数据拷贝一份到用户空间;
它的特点是一个用户单独创建一个线程,比如说小李子去火车站买票,但暂时不售票,他在火车站等了三天,然后买到票回去;它的缺点是:用户请求量和服务端线程成正比关系,服务端线程是并发的瓶颈,限制了用户数量;
非阻塞IO模型
soctet设置为NONBLOCK,当IO请求无法完成时,先返回一个错误码,然后IO测试操作持续进行,直到数据准备好,然后再将数据拷贝到内核空间;就像小李子去买票,但还未开始售票,他先回去隔两小时再去火车站买一次,直到买到票;
IO复用模型
IO复用模型用了select和poll函数,这两个函数会使线程阻塞,但与BIO的区别是,IO多复用模型可以同时对多个线程阻塞,还可以同时对多个文件IO数据读写操作进行检测,直到有数据准备就绪,才调用真正的IO函数。
过程是:调用select/poll方法由一个用户线程轮流查询多个Channel,直到某个阶段数据准备就绪,再通知实际用户执行下一阶段的拷贝,通过一个专职的用户进程执行非阻塞IO轮流查询,模拟实现阶段1的异步化。就像小李子想买票,他让黄牛去买,然后等黄牛买到后他再去黄牛那里拿票。
信号驱动IO模型首先socket进行信号驱动IO,并安装一个信号处理函数,线程继续运行并不阻塞,当数据准备好时,线程会收到一个信号,可以在信号处理函数中调用IO操作函数处理数据。就像小李子想去买票,他给售票处打电话,等由票的时候通知他,然后他再去火车站直接买好票回来;
异步IO模型
调用aio_read函数,告诉内核描述字,缓冲区大小和缓冲区指针,文件偏移和通知的方式,然后立即返回,当内核数据拷贝到缓冲区后,再调用应用程序。就像小李子买票的时候,他告诉售票员他的具体信息,等票出售的时候,售票员把他的票送货上门。所以异步IO模式下,阶段1和阶段2皆由内核完成,用户不参与。

发布了45 篇原创文章 · 获赞 11 · 访问量 4822

猜你喜欢

转载自blog.csdn.net/weixin_44187963/article/details/99622772
今日推荐