Java IO

来源:互联网 发布:阿里巴巴好还是淘宝好 编辑:程序博客网 时间:2024/05/20 09:43

这里写图片描述

一、File类

表示文件(目录)
只用于表示文件(目录)的信息(名称/大小等),不能用于文件内容等访问。

package com.mook.String.com.mook.io.file;import java.io.File;public class FileExample{    public static void main(String[] args) {        createFile();    }    /**     * 文件处理示例     */    public static void createFile() {        File f = new File("/Users/mook/jar/files/create.txt");        try{            System.out.println("文件是否存在:" + f.exists());            System.out.println("创建文件路径上的目录是否成功:" + f.mkdirs());//            f.isDirectory();//            f.isFile();//            f.mkdirs();  //创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。//            f.delete(); //  删除此抽象路径名表示的文件或目录            //如果,路径不存在,即不存在/Users/mook/jar/files,会抛出异常。            // 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件,返回true,否则,返回false。            f.createNewFile();            System.out.println("该分区大小"+f.getTotalSpace()/(1024*1024*1024)+"G"); //返回由此抽象路径名表示的文件或目录的名称。            System.out.println("文件名  "+f.getName());  //  返回由此抽象路径名表示的文件或目录的名称。            System.out.println("文件父目录字符串 "+f.getParent());// 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。        }catch (Exception e) {            e.printStackTrace();        }    }}

二、字节流

1.字节流有输入和输出流,我们首先看输入流InputStream,我们首先解析一个例子(FileInputStream)。

    /**     * Reads a byte of data from this input stream. This method blocks     * if no input is yet available.     *     * @return     the next byte of data, or <code>-1</code> if the end of the     *             file is reached.     * @exception  IOException  if an I/O error occurs.     */    public int read() throws IOException {        return read0(); //读取一个字节,无符号填充到int低8位    }    private native int read0() throws IOException;    /**     * Reads a subarray as a sequence of bytes.     * @param b the data to be written     * @param off the start offset in the data     * @param len the number of bytes that are written     * @exception IOException If an I/O error has occurred.     */    private native int readBytes(byte b[], int off, int len) throws IOException;    /**     * Reads up to <code>b.length</code> bytes of data from this input     * stream into an array of bytes. This method blocks until some input     * is available.     *     * @param      b   the buffer into which the data is read.     * @return     the total number of bytes read into the buffer, or     *             <code>-1</code> if there is no more data because the end of     *             the file has been reached.     * @exception  IOException  if an I/O error occurs.     */    public int read(byte b[]) throws IOException {        return readBytes(b, 0, b.length);    }    /**     * Reads up to <code>len</code> bytes of data from this input stream     * into an array of bytes. If <code>len</code> is not zero, the method     * blocks until some input is available; otherwise, no     * bytes are read and <code>0</code> is returned.     *     * @param      b     the buffer into which the data is read.     * @param      off   the start offset in the destination array <code>b</code>     * @param      len   the maximum number of bytes read.     * @return     the total number of bytes read into the buffer, or     *             <code>-1</code> if there is no more data because the end of     *             the file has been reached.     * @exception  NullPointerException If <code>b</code> is <code>null</code>.     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,     * <code>len</code> is negative, or <code>len</code> is greater than     * <code>b.length - off</code>     * @exception  IOException  if an I/O error occurs.     */    public int read(byte b[], int off, int len) throws IOException {        return readBytes(b, off, len);    }
import java.io.*;public class FileCount {    /**     * 我们写一个检测文件长度的小程序,别看这个程序挺长的,你忽略try catch块后发现也就那么几行而已。     */    public static void main(String[] args) {        int count=0;  //统计文件字节长度        InputStream streamReader = null;   //文件输入流        try{            streamReader = new FileInputStream(new           File("/Users/mook/Desktop/1210.png"));//          FileInputStream是有缓冲区的,所以用完之后必须关闭,否则可能导致内存占满,数据丢失。            while(streamReader.read()!= -1) {  //读取文件字节,并递增指针到下一个字节                count++;            }            System.out.println("---长度是: " + count + " 字节");        }catch (IOException e) {            e.printStackTrace();        }finally{            try{                streamReader.close();            }catch (IOException e) {                e.printStackTrace();            }        }    }}

2.FileOutputStream

write(int b); //写出一个字节,b的低8位write(byte[] b);write(byte[] b, int start, int size);
close(): - 让流对象变成垃圾,就可以被垃圾回收器回收 - 通知系统去释放该文件相关的资源
追加操作:public FileOutputStream(String name, boolean append)public FileOutputStream(File file, boolean append)

OutputStream是所有字节输出流的父类,子类有ByteArrayOutputStream,FileOutputStream,ObjectOutputStreanm,这些我们在后面都会一一说到。先说FileOutputStream:
我以一个文件复制程序来说,顺便演示一下缓存区的使用。(Java I/O默认是不缓冲流的,所谓“缓冲”就是先把从流中得到的一块字节序列暂存在一个被称为buffer的内部字节数组里,然后你可以一下子取到这一整块的字节数据,没有缓冲的流只能一个字节一个字节读,效率孰高孰低一目了然。有两个特殊的输入流实现了缓冲功能,一个是我们常用的BufferedInputStream(继承FilterOutputStream).)

package inputstream;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class FileCopy {    public static void main(String[] args) {        byte[] buffer = new byte[512];   //一次取出的字节数大小,缓冲区大小        int numberRead = 0;        FileInputStream input = null;        FileOutputStream out = null;        try {            input = new FileInputStream("/Users/mook/Desktop/1210.png");            out = new FileOutputStream("/Users/mook/Desktop/1211.png"); //如果文件不存在会自动创建            while ((numberRead = input.read(buffer)) != -1) {  //numberRead的目的在于防止最后一次读取的字节小于buffer长度,                out.write(buffer, 0, numberRead);       //否则会自动被填充0            }        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } finally {            if (input != null && out != null) {                try {                    input.close();                    out.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    }}

3.读写对象:ObjectInputStream 和ObjectOutputStream ,该流允许读取或写入用户自定义的类,但是要实现这种功能,被读取和写入的类必须实现Serializable接口,其实该接口并没有什么方法,可能相当于一个标记而已,但是确实不合缺少的。

package inputstream;import java.io.*;public class ObjetStream {    public static void main(String[] args) {        ObjectOutputStream objectwriter = null;        ObjectInputStream objectreader = null;        try {            objectwriter = new ObjectOutputStream(new FileOutputStream("/Users/mook/Desktop/student.txt"));            objectwriter.writeObject(new Student("gg", 22));            objectwriter.writeObject(new Student("tt", 18));            objectwriter.writeObject(new Student("rr", 17));            objectreader = new ObjectInputStream(new FileInputStream("/Users/mook/Desktop/student.txt"));            for (int i = 0; i < 3; i++) {                System.out.println(objectreader.readObject());            }        } catch (IOException | ClassNotFoundException e) {            e.printStackTrace();        }finally{            try {                objectreader.close();                objectwriter.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}class Student implements Serializable {    private static final long serialVersionUID = -6849794470754660000L;    private String name;    private transient int age; //不支持序列化    public Student(String name, int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}
Student{name='gg', age=0}Student{name='tt', age=0}Student{name='rr', age=0}
1transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。2、被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。3、一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化。

transient

4.有时没有必要存储整个对象的信息,而只是要存储一个对象的成员数据,成员数据的类型假设都是Java的基本数据类型,这样的需求不必使用到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream(继承FilterOutputStream)来写入或读出数据。下面是一个例子:(DataInputStream的好处在于在从文件读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时将停止,使用对应的readUTF()和readInt()方法就可以正确地读入完整的类型数据。)

public class Member {      private String name;      private int age;      public Member() {      }     public Member(String name, int age) {          this.name = name;          this.age = age;      }      public void setName(String name){          this.name = name;      }      public void setAge(int age) {          this.age = age;      }      public String getName() {          return name;      }      public int getAge() {          return age;      }  }

打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象。下面的代码简单示范了如何实现这个需求。

package inputstream;import java.io.*;public class DataStreamDemo{    public static void main(String[] args)    {        Member[] members = {new Member("Justin",90), new Member("momor",95), new Member("Bush",88)};        try        {            DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("/Users/mook/Desktop/member.txt"));            for(Member member : members)            {                //写入UTF字符串                dataOutputStream.writeUTF(member.getName());                //写入int数据                dataOutputStream.writeInt(member.getAge());            }            //所有数据至目的地            dataOutputStream.flush();            //关闭流            dataOutputStream.close();            DataInputStream dataInputStream = new DataInputStream(new FileInputStream("/Users/mook/Desktop/member.txt"));            //读出数据并还原为对象            for(int i=0; i < members.length; i++)            {                //读出UTF字符串                String name = dataInputStream.readUTF();                //读出int数据                int score = dataInputStream.readInt();                members[i] = new Member(name,score);            }            //关闭流            dataInputStream.close();            //显示还原后的数据            for(Member member : members)            {                System.out.printf("%s\t%d%n", member.getName(), member.getAge());            }        }        catch(IOException e)        {            e.printStackTrace();        }    }}

5.PushbackInputStream类继承了FilterInputStream类。提供可以将数据插入到输入流前端的能力(当然也可以做其他操作)。简而言之PushbackInputStream类的作用就是能够在读取缓冲区的时候提前知道下一个字节是什么,其实质是读取到下一个字符后回退的做法,这之间可以进行很多操作,这有点向你把读取缓冲区的过程当成一个数组的遍历,遍历到某个字符的时候可以进行的操作,当然,如果要插入,能够插入的最大字节数是与推回缓冲区的大小相关的,插入字符肯定不能大于缓冲区吧!下面是一个示例。

import java.io.ByteArrayInputStream; //导入ByteArrayInputStream的包import java.io.IOException;import java.io.PushbackInputStream;/** * 回退流操作 * */public class PushBackInputStreamDemo {    public static void main(String[] args) throws IOException {        String str = "hello,rollenholt";        PushbackInputStream push = null; // 声明回退流对象        ByteArrayInputStream bat = null; // 声明字节数组流对象        bat = new ByteArrayInputStream(str.getBytes());        System.out.println(str.getBytes().length); //16        push = new PushbackInputStream(bat); // 创建回退流对象,将拆解的字节数组流传入        int temp = 0;        while ((temp = push.read()) != -1) { // push.read()逐字节读取存放在temp中,如果读取完成返回-1            if (temp == ',') { // 判断读取的是否是逗号                push.unread(temp); //回到temp的位置                temp = push.read(); //接着读取字节                System.out.print("(回退" + (char) temp + ") "); // 输出回退的字符            } else {                System.out.print((char) temp); // 否则输出字符            }        }    }}

6.SequenceInputStream:有些情况下,当我们需要从多个输入流中向程序读入数据。此时,可以使用合并流,将多个输入流合并成一个SequenceInputStream流对象。SequenceInputStream会将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 合并流的作用是将多个源合并合一个源。其可接收枚举类所封闭的多个字节流对象。

package inputstream;import java.io.*;import java.util.Enumeration;import java.util.Vector;public class SequenceInputStreamTest {    /**     * @param args     *            SequenceInputStream合并流,将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,     *            直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。     *            合并流的作用是将多个源合并合一个源。可接收枚举类所封闭的多个字节流对象。     */    public static void main(String[] args) {        doSequence();    }    private static void doSequence() {        // 创建一个合并流的对象        SequenceInputStream sis = null;        // 创建输出流。        BufferedOutputStream bos = null;        try {            // 构建流集合。            Vector<InputStream> vector = new Vector<InputStream>();            vector.addElement(new FileInputStream("/Users/mook/Desktop/1310.png"));            vector.addElement(new FileInputStream("/Users/mook/Desktop/1210.png"));            Enumeration<InputStream> e = vector.elements();            sis = new SequenceInputStream(e);            bos = new BufferedOutputStream(new FileOutputStream("/Users/mook/Desktop/1212.png"));            // 读写数据            byte[] buf = new byte[1024];            int len = 0;            while ((len = sis.read(buf)) != -1) {                bos.write(buf, 0, len);                bos.flush();            }        } catch (FileNotFoundException e1) {            e1.printStackTrace();        } catch (IOException e1) {            e1.printStackTrace();        } finally {            try {                if (sis != null)                    sis.close();            } catch (IOException e) {                e.printStackTrace();            }            try {                if (bos != null)                    bos.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

7.PrintStream 说这个名字可能初学者不熟悉,如果说System.out.print()你肯定熟悉,System.out这个对象就是PrintStream

8.BufferedInputStream和BufferedOutputStream提供字节缓冲区(装饰设计模式

package java.io;/** * The class implements a buffered output stream. By setting up such * an output stream, an application can write bytes to the underlying * output stream without necessarily causing a call to the underlying * system for each byte written. * * @author  Arthur van Hoff * @since   JDK1.0 */publicclass BufferedOutputStream extends FilterOutputStream {    /**     * The internal buffer where data is stored.     */    protected byte buf[];    /**     * The number of valid bytes in the buffer. This value is always     * in the range <tt>0</tt> through <tt>buf.length</tt>; elements     * <tt>buf[0]</tt> through <tt>buf[count-1]</tt> contain valid     * byte data.     */    protected int count;    /**     * Creates a new buffered output stream to write data to the     * specified underlying output stream.     *     * @param   out   the underlying output stream.     */    public BufferedOutputStream(OutputStream out) {        this(out, 8192);    }    /**     * Creates a new buffered output stream to write data to the     * specified underlying output stream with the specified buffer     * size.     *     * @param   out    the underlying output stream.     * @param   size   the buffer size.     * @exception IllegalArgumentException if size &lt;= 0.     */    public BufferedOutputStream(OutputStream out, int size) {        super(out);        if (size <= 0) {            throw new IllegalArgumentException("Buffer size <= 0");        }        buf = new byte[size];    }    /** Flush the internal buffer */    private void flushBuffer() throws IOException {        if (count > 0) {            out.write(buf, 0, count);            count = 0;        }    }    /**     * Writes the specified byte to this buffered output stream.     *     * @param      b   the byte to be written.     * @exception  IOException  if an I/O error occurs.     */    public synchronized void write(int b) throws IOException {        if (count >= buf.length) {            flushBuffer();        }        buf[count++] = (byte)b;    }    /**     * Writes <code>len</code> bytes from the specified byte array     * starting at offset <code>off</code> to this buffered output stream.     *     * <p> Ordinarily this method stores bytes from the given array into this     * stream's buffer, flushing the buffer to the underlying output stream as     * needed.  If the requested length is at least as large as this stream's     * buffer, however, then this method will flush the buffer and write the     * bytes directly to the underlying output stream.  Thus redundant     * <code>BufferedOutputStream</code>s will not copy data unnecessarily.     *     * @param      b     the data.     * @param      off   the start offset in the data.     * @param      len   the number of bytes to write.     * @exception  IOException  if an I/O error occurs.     */    public synchronized void write(byte b[], int off, int len) throws IOException {        if (len >= buf.length) {            /* If the request length exceeds the size of the output buffer,               flush the output buffer and then write the data directly.               In this way buffered streams will cascade harmlessly. */            flushBuffer();            out.write(b, off, len);            return;        }        if (len > buf.length - count) {            flushBuffer();        }        System.arraycopy(b, off, buf, count, len);        count += len;    }    /**     * Flushes this buffered output stream. This forces any buffered     * output bytes to be written out to the underlying output stream.     *     * @exception  IOException  if an I/O error occurs.     * @see        java.io.FilterOutputStream#out     */    public synchronized void flush() throws IOException {        flushBuffer();        out.flush();    }}

字符缓冲区流仅仅提供缓冲区,为高校而设计的,真正的读写操作是通过基本的流对象来实现的。


IO 流中的 flush


三、字符流

package inputstream;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class FileInputStreamDemo {    public static void main(String[] args) {        FileInputStream in = null;        FileInputStream inputStream = null;        try {            in = new FileInputStream("/Users/mook/Desktop/abc");            inputStream = new FileInputStream("/Users/mook/Desktop/abc");            int by = 0;            while ((by = in.read()) != -1) {                System.out.println(by);                System.out.println((char) by); // 一个中文(两个字节)被拆单独的两个字节进行转换输出,会出现乱码,无法正确解析                System.out.println("--------");            }            byte[] bys = new byte[1024];            int len = 0;            while ((len = inputStream.read(bys)) != -1) {                System.out.println(new String(bys, 0, len)); //中文可以解析,因为当byte[]足够大时,不会拆分中文的字节            }        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                in.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

编码问题

Unicode是字符集存储方式,而UTF-8是字符的编码显示方式,它是Unicode的一种实现方式。

计算机内只能保存101010等二进制数据,那么页面上显示的字符是如何显示出来的呢?
一:字符集(Charset)charset = char + set,char 是字符,set是集合,charset就是字符的集合。字符集就是是这个编码方式涵盖了哪些字符,每个字符都有一个数字序号。
二:编码方式(Encoding)编码方式就是一个字符要怎样编码成二进制字节序,或者反过来怎么解析。也即给你一个数字序号,要编码成几个字节,字节顺序如何,或者其他特殊规则。
三:字形字体(Font)根据数字序号调用字体存储的字形,就可以在页面上显示出来了。所以一个字符要显示出来,要显示成什么样子要看字体文件。
综上所述,Unicode 只是字符集,而没有编码方式。UTF-8 是一种 Unicode 字符集的编码方式,其他还有 UTF-16,UTF-32 等。而有了字符集以及编码方式,如果系统字体是没有这个字符,也是显示不出来的。

package inputstream;import java.io.UnsupportedEncodingException;import java.util.Arrays;public class StringDemo {    public static void main(String[] args) throws UnsupportedEncodingException {        String s = "你好";        byte[] bys = s.getBytes();        System.out.println(Arrays.toString(bys)); //[-28, -67, -96, -27, -91, -67]  UTF-8        byte[] bytes = s.getBytes("GBK");        System.out.println(Arrays.toString(bytes)); //[-60, -29, -70, -61]  gbk        String ss = new String(bys);        System.out.println(ss);        System.out.println(new String(bytes));        System.out.println(new String(bytes, "GBK"));    }}

1.java 使用Unicode存储字符串,在写入字符流时我们都可以指定写入的字符串的编码。前面介绍了不用抛异常的处理字节型数据的流ByteArrayOutputStream,与之对应的操作字符类的类就是CharArrayReader,CharArrayWriter类,这里也会用到缓冲区,不过是字符缓冲区,一般讲字符串放入到操作字符的io流一般方法是CharArrayReader reader = new CharArrayReader(str.toCharArray()); 可以使用CharArrayReader访问字符串的各个元素以执行进一步读取操作。

package cn.itcast_02;import java.io.FileOutputStream;import java.io.IOException;import java.io.OutputStreamWriter;/* * OutputStreamWriter(OutputStream out):根据默认编码把字节流的数据转换为字符流 * OutputStreamWriter(OutputStream out,String charsetName):根据指定编码把字节流数据转换为字符流 * 把字节流转换为字符流。 * 字符流 = 字节流 +编码表。 */public class OutputStreamWriterDemo {    public static void main(String[] args) throws IOException {        // 创建对象        // OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(        // "osw.txt")); // 默认GBK        // OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(        // "osw.txt"), "GBK"); // 指定GBK        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(                "osw.txt"), "UTF-8"); // 指定UTF-8        // 写数据        osw.write("中国");        osw.flush();        // 释放资源        osw.close();    }}
package cn.itcast_02;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;/* * InputStreamReader(InputStream is):用默认的编码读取数据 * InputStreamReader(InputStream is,String charsetName):用指定的编码读取数据 */public class InputStreamReaderDemo {    public static void main(String[] args) throws IOException {        // 创建对象        // InputStreamReader isr = new InputStreamReader(new FileInputStream(        // "osw.txt"));        // InputStreamReader isr = new InputStreamReader(new FileInputStream(        // "osw.txt"), "GBK");        InputStreamReader isr = new InputStreamReader(new FileInputStream(                "osw.txt"), "UTF-8");        // 读取数据        // 一次读取一个字符        int ch = 0;        while ((ch = isr.read()) != -1) {            System.out.print((char) ch);        }        // 释放资源        isr.close();    }}

2.我们用FileReader ,PrintWriter来做示范

package Reader;import java.io.FileReader;import java.io.IOException;import java.io.PrintWriter;public class Print {    public static void main(String[] args) {        char[] buffer = new char[512];   //一次取出的字节数大小,缓冲区大小        int numberRead = 0;        FileReader reader = null;        //读取字符文件的流        PrintWriter writer = null;    //写字符到控制台的流        try {            reader = new FileReader("/Users/mook/Desktop/student.txt");            writer = new PrintWriter(System.out);  //PrintWriter可以输出字符到文件,也可以输出到控制台            while ((numberRead = reader.read(buffer)) != -1) {                writer.write(buffer, 0, numberRead);            }        } catch (IOException e) {            e.printStackTrace();        }finally{            try {                reader.close();            } catch (IOException e) {                e.printStackTrace();            }            writer.close();       //这个不用抛异常        }    }}

3.相对我们前面的例子是直接用FileReader(继承自InputStreamReader)打开的文件,我们这次使用链接流,一般比较常用的都用链接流,所谓链接流就是就多次对流的封装,这样能更好的操作个管理数据,(比如我们利用DataInputStream(BufferedInputStream(FileInputStream))将字节流层层包装后,我们可以读取readByte(),readChar()这样更加具体的操作,注意,该流属于字节流对字符进行操作,)字符流用CharArrayReader就可以了。使用BufferedWriter 和BufferedReader用文件级联的方式进行写入,即将多个文件写入到同一文件中(自带缓冲区的输出输出流BufferedReader和BufferedWriter,该流最常用的属readLine()方法了,读取一行数据,并返回String)。

package Reader;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;public class FileConcatenate {    /**     * 包装类进行文件级联操作     */    public static void main(String[] args) {        try {            concennateFile("/Users/mook/Desktop/student.txt");        } catch (IOException e) {            e.printStackTrace();        }    }    public static void concennateFile(String...fileName) throws IOException {        String str;        //构建对该文件您的输入流        BufferedWriter writer = new BufferedWriter(new FileWriter("/Users/mook/Desktop/student2.txt"));        for(String name : fileName){            BufferedReader reader = new BufferedReader(new FileReader(name));            while ((str = reader.readLine()) != null) {                writer.write(str);                writer.newLine();            }        }    }}
原创粉丝点击