首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >猿进化系列7——一文搞懂IO

猿进化系列7——一文搞懂IO

作者头像
山旮旯的胖子
发布2020-07-28 16:43:14
2900
发布2020-07-28 16:43:14
举报
文章被收录于专栏:猿人工厂猿人工厂猿人工厂

看完上一个章节,你已经算一个小javaer了——知道了基本语法,学会了面向对象,开始抽象世界百态,学会了拿来主义,懂得了使用别人的程序...今天,我们将打开JAVA世界的另一扇大门—— 文件和I/O.

猿人进化是一个原创系列文章,帮助你从一只小白快速进化为一个猿人,更多精彩内容,敬请大家关注公主号猿人工厂,点击猿人养成获取

为了方便文件读写,java提供了java.io.File用于文件的读写操作,它能创建、查找、删除文件、能够获取文件路径(绝对路径和相对路径)。下面是File类的构造函数:

所谓递归,就是方法(也叫函数)中,自己调用自己的行为。当我们遇到一个问题的子问题和原始问题的解决办法是一致的时候,我们考虑使用递归来解决问题。比如遍历某个文件夹。文件夹下有文件,也有子文件夹,子文件夹下还有文件夹或者文件。

接下来给你几个小考验,检验你的学习成果:

1.编写一个方法,用于拷贝某个文件夹

2.使用递归计算n以内的阶乘,不知道阶乘是什么的,自行百度。

输入(input)和输出(output)的缩写就是IO。IO是两种不同的行为,我们把文件数据读取到内存中的行为是一种输入行为,我们把内存中的数据写入到文件是一种输出行为。Java设计了庞大的类库来支撑,I/O行为——

小朋友,是不是像儿歌一样简单?从数据流向上看,java的IO设计分为输入和输出,从数据流体上看,分为字节和字符:

输入流: InputStream,Reader

输出流:OutputStream,Writer

字节流:InputStream,OutputStream

字符流:Reader,Writer

Java的IO设计中,所有的其他输入流都是继承InputStream或 Reader,所有的输出流都继承自OutputStream或Writer。

java.io.InputStream 和java.io.OutputStream 是抽象类,是所有输入流和输出流的超类,它抽象了使用字节的方式操作数据流的功能和方法,具体的实现方式,由具体的实现类提供,所以在使用的时候,我们都是使用由对应功能的实现类。下面就是文件读写的一个例子:

注意:如果创建文件流对象时,如果文件不存在会创建一个新的文件,如果文件存在,会清空文件的内容重新写入。

对象是可以在存储和读取的!对象的传输可能要贯穿你的整个职业生涯,尤其是当你面对大型分布式系统的时候,对象的序列化和反序列化是一切的基础:

序列化:将对象的信息写入设备的过程叫做序列化。

反序列化:将对象的信息从设备中恢复的过程叫反序列化。

对象的序列化和反序列化的一些要求:

1.要求被序列化对象的类实现java.io.Serializable接口

2.被transient关键字修饰的成员变量是不会被序列化和反序列化的

java.io.ObjectOutputStream 是OutputStream的一个子类。可以对java对象实现序列化操作,将java对象的信息写入文件。构造方法:

public ObjectOutputStream(OutputStream out)

java.io.ObjectInputStream 是IutputStream的一个子类。可以对java对象实现反序列化操作,java对象的信息从文件或者是设备上恢复过来。

构造方法:

public ObjectInputStream(InputStream in) :通过输入流创建对象输入流。

下面是一个对象序列化和反序列化的例子:

/**
 *序列化对象

 * @param file

 * @param obj

 */
private static void serializeObject(File file,Object obj){
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream(file));
        //将对象写入文件
        oos.writeObject(obj);
    } catch (FileNotFoundException e) {
               e.printStackTrace();

    } catch (IOException e) {
               e.printStackTrace();
    } finally {
        try {
            if(null!=oos){
                oos.close();
            }
        } catch (IOException e) {
               e.printStackTrace();
        }
    }
}
/**

 *序列化对象

 * @param file

 * @param obj

 */

private static void serializeObject(File file,Object obj){
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream(file));
        //将对象写入文件
        oos.writeObject(obj);
    } catch (FileNotFoundException e) {
        e.printStackTrace();

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(null!=oos){
                oos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

假如我们需要使用这两个方法来,序列化和反序列化对象,那么被操作的对象必须实现java.io.Serializable接口。

我们都知道,一个中文字符可能占用多个字节存储,有时候使用字节流读取文本文件时,可能导致中文字符显示不完整,为此, Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。

java.io.Reader 是一个抽象类,是所有字符输入类的父类,它提供了使用字符的方式将数据从设备读入内存的基本功能和方法:

public void close() :关闭输入流,释放系统资源。

public int read() :读取一个字符到内存中。

public int read(char[] cbuf) :读取一批字符到并存储到字符数组中。

abstractpublic int read(char cbuf[], int off, int len) 以偏移量的形式将字符数组的内容读取到输入流,int len代表写入多少字符,int off代表偏移量,表示从字符数组的第几位开始读

java.io.Writer是一个抽象类,是所有字符输出类的超类,它提供了使用字符的方式输出数据流到设备的基本功能和方法:

void write(int c) 在写入一个字符。

void write(char[] cbuf) 在写入字符数组。

abstract void write(char[] cbuf, int off, int len) 以偏移量的形式将字符数组的内容写入到输出流,int len代表写入多少字符,int off代表偏移量,表示从字符数组的第几位开始写。

void write(String str) 就字符串写入输出流。

void write(String str, int off, int len) 以偏移量的形式将字符串的内容写入到输出流,int len代表写入多少字符,int off代表偏移量,表示从字符字符串的第几位开始写。

void flush() 刷新输出流,并强制将缓冲区内的数据输出。

void close() 关闭输出流,释放系统资源,记得在关闭前刷新输出流

注意:完成所有输出流或输出流的操作后,一定要释放系统资源。close()方法一但调用,就不能使用当前对象处理数据了。

java.io.FileReader是Reader的一个子类,可以非常方便的读取字符文件。

java.io.FileWriter是Writer的一个子类,可以非常方便的写入字符文件。

他们都是以字符的方式读取或写入文件,而且在创建对象的时候,都支持文件对象和文件路径两种方式。他们创建对象时,如果时windows系统,默认字符编码为gbk。下面是字符文件读写的例子:

/**

 *使用Reader读取文件内容

 * @param file

 * @return

 */

private static String readFileByFileReader(File file){
    if(null==file||!file.exists()||!file.isFile()){
        return null;
    }
    Reader reader=null;
    try {
        reader=new FileReader(file);
        char[] ch=new char[512];
        int len=0;
        StringBuilder builder = new StringBuilder();
        while((len=reader.read(ch))!=-1){
            builder.append(new String(ch));
        }
        return builder.toString();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally{
        try {
            if(null!=reader){
                reader.close();
            }

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

    }
    return null;
}
/**

 *使用writer写入数据

 * @param file

 * @param content

 */
private static void writeStringToFileByFileWriter(File file, String content) {
    if(null==content||"".equals(content)){
        return;
    }
    Writer writer = null;
    try {
        writer = new FileWriter(file);
        //将字符串写入文件
        writer.write(content);

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

    } catch (IOException e) {
               e.printStackTrace();
    } finally {
        try {
            if(null!=writer){
                writer.flush();
                writer.close();
            }
        } catch (IOException e) {
                       e.printStackTrace();
        }

    }
}

我们已经提到gbk的字符编码了,相信字符编码你也有一些了解:字符编码(Characterencoding)也称字集码,是把字符集中的字符以某种格式进行编排,以便文本在计算机中存储和通过通信网络的传递。常见的编码方式有ASCII、UTF-8、GBK、GB2312等等。如果文件写入和文件读取时使用的编码不一致(尤其是中文),就可能导致字符无法正常显示。

字符集(Charset) :是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,字符集告诉计算机以什么样的方式识别字符,。常见的字符集有ASCII、UTF-8、Unicode等等。

如果要对字符集进行编码转换,那么你可能需要用到java.io.InputStreamReader和java.io.OutputStreamReader:

java.io.InputStreamReader ,是Reader的子类,可以用指定字符集的方式来指定字节流的解码方式来读取数据,java.io.OutputStreamWriter ,是Writer的子类,可以用指定字符集的方式来指定字节流的编码方式来写入数据,从而实现字符的编码和转码。

接下来送你两个方法,分别读取和写入指定的字符集的数据,你稍作改造,应该可以轻松的完成字符的编码转换吧?

/**

 *使用指定的字节读取文件

 * @param file

 * @param charSet

 * @return

 */

private  static  String readFileByInputStreamReader(File file,String charSet){

    Reader reader =null;

    try {

        reader= new InputStreamReader(new FileInputStream(file),charSet);

        char[] ch=new char[512];

        int len=0;

        StringBuilder builder = new StringBuilder();

        while((len=reader.read(ch))!=-1){

            builder.append(new String(ch));

        }

        return builder.toString();

    } catch (UnsupportedEncodingException e) {

        e.printStackTrace();

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }finally{

        try {

            if(null!=reader){

                reader.close();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }
    return null;
}
/**

 *使用指定的字节读取文件

 * @param file

 * @param content

 * @param charSet

 * @return

 */
private  static  void writeFileByOutputStreamReader(File file,String content,String charSet){

    Writer writer =null;

    try {

        writer= new OutputStreamWriter(new FileOutputStream(file),charSet);

        writer.write(content);

    } catch (UnsupportedEncodingException e) {

        e.printStackTrace();

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }finally{

        try {

            if(null!=writer){

                writer.flush();

                writer.close();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }
}

我们之前使用字节流和字符流来读取数据时,每一次读写操作,对于系统来说都是一次I/O操作,读写的次数越频繁,效率越低(文件小的时候没啥区别,文件越大区别越大),为了优化这个性能瓶颈,我们使用数组作为缓冲区,减少读写次数,从而提高读写的效率。

其实吧,java时提供了对应的处理办法的——java提供了缓冲流来应对这种场景:

字节缓冲流:BufferedInputStream , BufferedOutputStream(用于增强InputStream OutputStream)

字符缓冲流:BufferedReader , BufferedWriter(用于增强Reader Writer)

我们先看字节缓冲流:

public BufferedInputStream(InputStream in) :通过字节输入流创建字节缓冲输入流。

public BufferedOutputStream(OutputStream out) :通过字节输出流创建字节缓冲输出流。

下面是例子:

   /**

 *使用缓冲区读取文件

 * @param file

 * @return

 */
private static String readFileBuffered(File file){

    BufferedInputStream bis=null;

    try {

        bis=new BufferedInputStream(new FileInputStream(file));

        int len;

        byte[] b = new byte[1024];

        StringBuilder builder=new StringBuilder();

        while((len=bis.read(b))!=-1){

            String str = new String(b);

            builder.append(str);

        }
        return builder.toString();

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }finally{

        try {

            if(null!=bis){

                bis.close();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }
    return null;
}
/**

 *使用缓冲流写入文件

 * @param file

 * @param content

 */
private static void StringWriteFileBuffered(File file,String content){

    BufferedOutputStream bos=null;

    try {

        bos=new BufferedOutputStream(new FileOutputStream(file));

        byte[] b = content.getBytes();

        bos.write(b);



    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }finally{

        try {

            if(null!=bos){

                bos.flush();

                bos.close();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }
}

我们再看看字符缓冲流:

public BufferedReader(Reader in) :通过Reader创建字符缓冲输入流。

public BufferedWriter(Writer out) :通过Writer创建按字符缓冲输出流。

下面是例子:

/**

 *使用使用字符缓冲Reader读取文件

 * @param file

 * @return

 */

private static String readFileBufferedReader(File file){

    BufferedReader bReader=null;

    try {

        bReader=new BufferedReader(new FileReader(file));



        int len;

        char[] ch = new char[1024];

        StringBuilder builder=new StringBuilder();

        while((len=bReader.read(ch))!=-1){

            String str = new String(ch);

            builder.append(str);

        }

        return builder.toString();



    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }finally{

        try {

            if(null!=bReader){

                bReader.close();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

    return null;

}

/**

 *使用缓冲流写入文件

 * @param file

 * @param content

 */

private static void writeFileBuffered(File file,String content){
    BufferedOutputStream bos=null;

    try {

        bos=new BufferedOutputStream(new FileOutputStream(file));

        byte[] b = content.getBytes();

        bos.write(b);

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }finally{

        try {

            if(null!=bos){

                bos.flush();

                bos.close();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }
}

字符缓冲流还有个特殊的方法——可以读取和写入一行字符串

BufferedReader:public StringreadLine() : 读一行文字。

BufferedWriter:public voidnewLine() : 写一行行分隔符,由系统属性定义符号。

我们看下例子:

// 创建流对象

 BufferedWriter bw = new BufferedWriter(new FileWriter("outBufferedWriter.txt"));

// 写出数据

 bw.write("疫情");

// 写出换行

 bw.newLine();

 bw.write("快点");

 bw.newLine();

 bw.write("结束吧");

 bw.newLine();

 //释放资源

 bw.close();



 //创建流对象

 BufferedReader br = new BufferedReader(new FileReader("outBufferedWriter.txt"));

 //定义字符串,保存读取的一行文字

 String line = null;

 //循环读取,读取到最后返回null

 while ((line = br.readLine())!=null) {

     System.out.print(line);

     System.out.println("‐‐‐‐‐‐");

 }

 //释放资源

 br.close();

我们在设计程序的时候,为了程序的可配置性。会有一些可以配置的变量,放在外部文件中,这些变量一般是xxx=xxx的形式(键值对)存放。也就时传说中的*.Properties文件。java.util.Properties可以非常方便的读取外部配置文件,并且通过System.setPropertites方法将这些键值对设置为系统变量,然后方便程序随时获取,下面是一个例子:

/**

 * Properties例子,注意先创建文件要不会报错

 */

private static void showProperties() {



    Properties propertites = new Properties();



    try {

        propertites.load(new FileInputStream("D:/filetest/data.Properties"));

    } catch (FileNotFoundException e) {

        e.printStackTrace();

    } catch (IOException e) {

        e.printStackTrace();

    }



    System.setProperties(propertites);



    System.out.println(System.getProperty("password"));



}

小练习:

找一个大一点的文件,分别用字节流和字节缓冲流复制下试试,看看谁比较快。

创建10000个对象,序列化和反序列化。

自己构建一个propertites文件,读取下试试

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 猿人工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档