Java IO流(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yerenyuan_pku/article/details/78237371

本篇文章主要围绕字符编码展开,为了能够更好地讲述这一主题,我将从字节流操作中文数据开始。

字节流操作中文数据

假设编写有如下程序,代码贴出如下:

public class ReadCNDemo {

    public static void main(String[] args) throws IOException { 
        writeCNText();
    }

    public static void writeCNText() throws IOException {
        FileOutputStream fos = new FileOutputStream("tempfile\\cn.txt");

        fos.write("你好".getBytes()); // 按照默认编码表(GBK表)编码

        fos.close();
    }

}

此时运行以上程序,可以发现在cn.txt文本文件中有你好两字,并且文件大小仅有4字节。其原因是String类的getBytes()方法是使用平台的默认字符集(即GBK码表,而在GBK码表里面,一个中文占2个字节)将你好字符串编码为了一个字节数组。
上面程序将你好两字写入cn.txt文本文件之后,我们需要使用字节流将其读取出来,由于该文本文件大小仅有4字节,可使用字节输出流的read()方法一个字节一个字节地读取出来,代码如下:

public class ReadCNDemo {

    public static void main(String[] args) throws IOException {
        readCNText();
    }

    public static void readCNText() throws IOException {        
        FileInputStream fis = new FileInputStream("tempfile\\cn.txt");

        int by = fis.read();
        System.out.println(by);
        int by1 = fis.read();
        System.out.println(by1);
        int by2 = fis.read();
        System.out.println(by2);
        int by3 = fis.read();
        System.out.println(by3);
        int by4 = fis.read();
        System.out.println(by4);

        fis.close();
    }

}

运行以上程序,可发现Eclipse控制台打印如下,截图如下。
这里写图片描述
上面输出的东西是一堆数字,我们可能看不懂,而我们想要看到的是你好两字,那么问题归纳为:使用字节输出流读取中文时,是按照字节形式,但是一个中文在GBK码表中是2个字节,而且字节输出流的read()方法一次读取一个字节,如何可以获取到一个中文呢?解决方案就是别读一个就操作,多读一些存起来,再操作,存到字节数组中,将字节数组转成字符串就哦了。

public class ReadCNDemo {

    public static void main(String[] args) throws IOException {
        readCNText();
    }

    public static void readCNText() throws IOException {        
        FileInputStream fis = new FileInputStream("tempfile\\cn.txt");

        byte[] buf = new byte[1024];
        int len = fis.read(buf);
        String s = new String(buf, 0, len); // 将字节数组转成字符串,而且是按照默认的编码表(GBK)进行解码。
        System.out.println(s);

        fis.close();
    }

}

须知String类的String(byte[] bytes, int offset, int length)构造方法是通过使用平台的默认字符集(即GBK码表)解码指定的byte子数组,构造一个新的String。接下来,就要引出编码表这一概念了。

编码表

编码表的由来

计算机只能识别二进制数据,早期由来是电信号来表示,为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。

常见的编码表

常见的编码表有:

  • ASCII:美国标准信息交换码,用一个字节的7位可以表示。
  • ISO8859-1:拉丁码表或欧洲码表,用一个字节的8位表示。
  • GB2312:中国的中文编码表。
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号。
  • Unicode:国际标准码,融合了多种文字,所有文字都用两个字节来表示,Java语言使用的就是Unicode。
  • UTF-8:最多用三个字节来表示一个字符。

编码与解码

所谓编码就是字符串变成字节数组(String→byte[]),代表方法为str.getBytes(charsetName);;而所谓解码就是字节数组变成字符串(byte[]→String),代表方法为new String(byte[], charsetName);。关于编码与解码,有一个结论,即由UTF-8编码,自然要通过UTF-8来解码;同理,由GBK来编码,自然也要通过GBK来解码,否则会出现乱码。下面我会通过若干个案例来详述编码与解码。
首先运行以下程序,给出程序代码如下:

public class Test {

    public static void main(String[] args) throws Exception {
        method();
    }

    public static void method() throws Exception {
        String s = "你好";
        byte[] b1 = s.getBytes("GBK"); // 默认编码方式就是GBK
        System.out.println(Arrays.toString(b1));
        String s1 = new String(b1, "GBK"); // 通过GBK来解码
        System.out.println("s1 = " + s1);
    }

}

运行以上程序,可发现Eclipse控制台打印如下,截图如下。
这里写图片描述
发现输出结果并无乱码。
然后再运行以下程序,观察运行后的结果,给出程序代码如下:

public class Test {

    public static void main(String[] args) throws Exception {
        method1();
    }

    public static void method1() throws Exception {
        String s = "你好";
        byte[] b1 = s.getBytes("GBK"); // 默认编码方式就是GBK
        System.out.println(Arrays.toString(b1));
        String s1 = new String(b1, "UTF-8"); // 通过UTF-8来解码
        System.out.println("s1 = " + s1);
    }

}

运行以上程序,可发现Eclipse控制台打印如下,截图如下。
这里写图片描述
发现输出了乱码——???。那怎么不是像上面那样输出???三个问号呢?method1()方法的代码应修改为如下,即可解决乱码问题。

public class Test {

    public static void main(String[] args) throws Exception {
        method1();
    }

    public static void method1() throws Exception {
        String s = "你好";
        byte[] b1 = s.getBytes("UTF-8"); // 默认编码方式就是UTF-8
        System.out.println(Arrays.toString(b1));
        String s1 = new String(b1, "UTF-8"); // 通过UTF-8来解码
        System.out.println("s1 = " + s1);
    }

}

再次运行以上程序,可发现Eclipse控制台打印如下,截图如下。
这里写图片描述
接着再运行以下程序,观察运行后的结果,给出程序代码如下:

public class Test {

    public static void main(String[] args) throws Exception {
        method2();
    }

    public static void method2() throws Exception {
        String s = "你好";
        byte[] b1 = s.getBytes("UTF-8"); // 默认编码方式就是UTF-8
        System.out.println(Arrays.toString(b1));
        String s1 = new String(b1, "GBK"); // 通过GBK来解码
        System.out.println("s1 = " + s1);
    }

}

运行以上程序,可发现Eclipse控制台打印如下,截图如下。
这里写图片描述
发现输出结果出现了乱码,至于怎么解决我就不说了。
这里有一种特殊的情况,先对字符串你好进行GBK编码,再通过ISO8859-1解码,程序代码如下:

public class Test {

    public static void main(String[] args) throws Exception {
        method3();
    }

    public static void method3() throws Exception {
        String s = "你好";
        byte[] b1 = s.getBytes("GBK"); // 默认编码方式就是GBK
        System.out.println(Arrays.toString(b1));
        String s1 = new String(b1, "ISO8859-1"); // 通过ISO8859-1来解码
        System.out.println("s1 = " + s1);
    }

}

运行以上程序,输出结果肯定会出现乱码——????。如果要输出正确的字符串内容,可对s1进行ISO8859-1编码,然后再通过GBK解码,所以method3()方法的代码应修改为如下,即可解决乱码问题。

public static void method3() throws Exception {
    String s = "你好";
    byte[] b1 = s.getBytes("GBK"); // 默认编码方式就是GBK
    System.out.println(Arrays.toString(b1));
    String s1 = new String(b1, "ISO8859-1"); // 通过ISO8859-1来解码
    System.out.println("s1 = " + s1);

    // 对s1进行ISO8859-1编码
    byte[] b2 = s1.getBytes("ISO8859-1");
    System.out.println(Arrays.toString(b2));
    String s2 = new String(b2, "GBK"); // 通过GBK来解码
    System.out.println("s2 = " + s2);
}

不理解以上代码没关系,下面我会图解,如下:
这里写图片描述
现在来思考这样一个问题:先对字符串你好进行GBK编码,再通过UTF-8解码,编写代码如下:

public class Test {

    public static void main(String[] args) throws Exception {
        method1();
    }

    public static void method1() throws Exception {
        String s = "你好";
        byte[] b1 = s.getBytes("GBK"); // 默认编码方式就是GBK
        System.out.println(Arrays.toString(b1));
        String s1 = new String(b1, "UTF-8"); // 通过UTF-8来解码
        System.out.println("s1 = " + s1);
    }

}

运行以上程序,此时肯定会输出乱码——???,如果想要输出正确的字符串内容,可不可以对s1进行一次UTF-8编码,然后再通过GBK解码呢?即method1()方法的代码应修改为如下:

public static void method1() throws Exception {
    String s = "你好";
    byte[] b1 = s.getBytes("GBK"); // 默认编码方式就是GBK
    System.out.println(Arrays.toString(b1));
    String s1 = new String(b1, "UTF-8"); // 通过UTF-8来解码
    System.out.println("s1 = " + s1);

    // 对s1进行UTF-8编码
    byte[] b2 = s1.getBytes("UTF-8");
    System.out.println(Arrays.toString(b2));
    String s2 = new String(b2, "GBK");
    System.out.println("s2 = "+s2);
}

答案是不可以。如若不信,读者不妨试着调用以上方法,可以看到Eclipse控制台打印如下,给出截图。
这里写图片描述
发现依然输出乱码——锟斤拷锟?,其原因又是什么呢?不妨画张图来解释一下。
这里写图片描述

字符流

为了能更好地操作中文数据,我们就需要学习一下字符流了,这样就不可避免地要学习InputStreamReader和OutputStreamWriter这两个类了。

字节通向字符的桥梁

InputStreamReader是字节流通向字符流的桥梁:它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集(GBK)。
如果我们想将内容为你好的cn.txt文本文件(本文一开始操作的文本文件)使用字符流将其读取出来,那么可以编写如下程序,代码如下所示。

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {
        // 字节通向字符的桥梁。将读到的字节进行解码。
        readCNText();
    }

    public static void readCNText() throws IOException {

        // 1、操作字节流的字符流对象,必须先有字节流。
        FileInputStream fis = new FileInputStream("tempfile\\cn.txt");

        // 2、建立字节向字符的桥梁。
        InputStreamReader isr = new InputStreamReader(fis);

        int ch = isr.read();
        System.out.println((char)ch);
        int ch1 = isr.read();
        System.out.println((char)ch1);
        int ch2 = isr.read();
        System.out.println(ch2);

        isr.close();
    }

}

运行以上程序,可发现Eclipse控制台打印如下,给出截图。
这里写图片描述

字符通向字节的桥梁

OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
如果想要使用字符流往一个文本文件中写入中文数据,那么可以编写如下程序,代码如下所示。

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {
        // 字符通向字节的桥梁。
        writeCNText();
    }

    public static void writeCNText() throws IOException {
        // 1、创建字节流对象。
        FileOutputStream fos = new FileOutputStream("tempfile\\GBK.txt");

        // 2、字符通向字节的桥梁。
        OutputStreamWriter osw = new OutputStreamWriter(fos);

        // 3、使用osw的write方法直接写中文字符串。写入数据时会存储到缓冲区中,因为要查码表。
        osw.write("你好");

        // 4、需要刷新缓冲区,将数据弄到目的地中。
        // osw.flush();

        // 5、关闭资源。flush()刷新完,流可以继续使用,close()刷新完,直接关闭,流结束,无法使用。
        osw.close();

        // osw.write(""); // java.io.IOException: Stream closed(流已关闭)
    }

}

转换流的编码应用

字符流的出现是为了方便操作字符,更重要的是加入了编码转换,通过两个转换流——InputStreamReader和OutputStreamWriter来完成,在两个对象进行构造的时候可以加入字符集。下面我就来重点讲讲这两个转换流的编码应用。
可以将字符以指定编码格式存储,然后对文本数据指定编码格式来解读,指定编码表的动作由构造函数完成。这里再提醒一点,由UTF-8编码,自然要通过UTF-8来解码;同理,由GBK来编码,自然也要通过GBK来解码,否则会出现乱码。下面我分四种情况来分别阐述之。
第一种情况,先用UTF-8编码,再通过UTF-8来解码。示例代码如下:

public static void writeText() throws IOException {
    FileOutputStream fos = new FileOutputStream("tempfile\\utf-8.txt");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
    osw.write("你好");
    osw.close();
}

public static void readText() throws IOException {
    FileInputStream fis = new FileInputStream("tempfile\\utf-8.txt");
    InputStreamReader isr = new InputStreamReader(fis, "utf-8");
    char[] buf = new char[1024];
    int len = isr.read(buf);
    String content = new String(buf, 0, len);
    System.out.println(content);
    isr.close();
}

这样,输出结果为你好,显示正常,并无乱码。
第二种情况,先用UTF-8编码,再通过GBK来解码。示例代码如下:

public static void readText() throws IOException {
    FileInputStream fis = new FileInputStream("tempfile\\utf-8.txt");
    InputStreamReader isr = new InputStreamReader(fis, "gbk");
    char[] buf = new char[1024];
    int len = isr.read(buf);
    String content = new String(buf, 0, len);
    System.out.println(content);
    isr.close();
}

public static void writeText() throws IOException {
    FileOutputStream fos = new FileOutputStream("tempfile\\utf-8.txt");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
    osw.write("你好");
    osw.close();
}

这样一来,输出结果就为浣犲ソ,出现乱码。其原因又是什么呢?不妨画张图来解释一下。
这里写图片描述
第三种情况,先用GBK编码,再通过GBK来解码。示例代码如下:

public static void readText() throws IOException {
    FileInputStream fis = new FileInputStream("tempfile\\cn_gbk.txt");
    InputStreamReader isr = new InputStreamReader(fis, "gbk");
    char[] buf = new char[1024];
    int len = isr.read(buf);
    String content = new String(buf, 0, len);
    System.out.println(content);
    isr.close();
}

public static void writeText() throws IOException {
    FileOutputStream fos = new FileOutputStream("tempfile\\cn_gbk.txt");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
    osw.write("你好");
    osw.close();
}

这样一来,输出结果为你好,显示正常,并无乱码。
最后看一下第四种情况,先用GBK编码,再通过UTF-8来解码。示例代码如下:

public static void readText() throws IOException {
    FileInputStream fis = new FileInputStream("tempfile\\cn_gbk.txt");
    InputStreamReader isr = new InputStreamReader(fis, "utf-8");
    char[] buf = new char[1024];
    int len = isr.read(buf);
    String content = new String(buf, 0, len);
    System.out.println(content);
    isr.close();
}

public static void writeText() throws IOException {
    FileOutputStream fos = new FileOutputStream("tempfile\\cn_gbk.txt");
    OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
    osw.write("你好");
    osw.close();
}

这样一来,输出结果就为??,出现乱码。其原因又是什么呢?不妨画张图来解释一下。
这里写图片描述

转换流的子类——用于操作字符文件的便捷类

通过查阅API帮助文档可知,两个转换流——InputStreamReader和OutputStreamWriter分别有其对应的子类——FileReader和FileWriter,它俩是用于操作字符文件的便捷类,但是它俩有局限性,只能操作字符文件,而且是默认编码,如果不操作字符文件,而且编码不是默认的,需要使用转换流。已经都说到这里了,就要用例子来演示一下它俩的使用了。
现在有需求:在硬盘上,创建一个文件并写入一些文字数据。类似这样的需求,我废话不多说了,直接给出示例代码,如下。

public class SubTransStreamDemo {

    public static void main(String[] args) throws IOException {
        /*
         * 转换流的子类。
         * 专门用于操作文本文件的流对象。
         */
        writeText();
    }


public static void writeText() throws IOException {
        // 1、创建一个用于操作文本文件的字符输出流对象。
        FileWriter fw = new FileWriter("tempfile\\fw.txt"); // 内部使用了默认的码表,而且只能操作文件。
        // 等效于:
        // FileOutputStream fos = new FileOutputStream("tempfile\\fw.txt");
        // OutputStreamWriter osw = new OutputStreamWriter(fos);

        fw.write("你好");

        fw.close();
    }

}

从以上示例代码中可以清楚地看到下面这句代码:

FileWriter fw = new FileWriter("tempfile\\fw.txt");

等效于:

FileOutputStream fos = new FileOutputStream("tempfile\\fw.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos);

接着再来看这样一个需求:从硬盘的一个文件中读取内容。这里就直接给出示例代码了,如下。

public class SubTransStreamDemo {

    public static void main(String[] args) throws IOException {
        /*
         * 转换流的子类。
         * 专门用于操作文本文件的流对象。
         */
        readText();
    }

    public static void readText() throws IOException {
        FileReader fr = new FileReader("tempfile\\fw.txt");
        // 等效于:
        // FileInputStream fis = new FileInputStream("tempfile\\fw.txt");
        // InputStreamReader isr = new InputStreamReader(fis);

        int ch = 0;
        while ((ch = fr.read()) != -1) {
            System.out.println((char)ch);
        }

        fr.close();
    }

}

从以上示例代码中可以清楚地看到下面这句代码:

FileReader fr = new FileReader("tempfile\\fw.txt");

等效于:

FileInputStream fis = new FileInputStream("tempfile\\fw.txt");
InputStreamReader isr = new InputStreamReader(fis);

接下来就用转换流的子类来复制一个文本文件。

复制一个文本文件

之前我们就用字节流复制过一个文本文件,而现在就要用字符流来解决这个需求了。啥都不说了,直接给出示例代码,如下。

public class SubTransStreamDemo2 {

    public static void main(String[] args) throws IOException {
        // 复制一个文本文件。
        copyText();
    }

    public static void copyText() throws IOException {
        // 1、明确数据源,定义字符读取流和数据源关联。
        FileReader fr = new FileReader("IO流.txt");
        // 2、明确数据目的地,定义字符输出流,创建存储数据的目的。
        FileWriter fw = new FileWriter("tempfile\\copy_IO流.txt");

        // 3、创建缓冲区。
        char[] buf = new char[1024];
        int len = 0;
        while ((len = fr.read(buf)) != -1) {
            fw.write(buf, 0, len);
        }

        fw.close();
        fr.close();
    }

}

字符流的缓冲区

缓冲区的出现是为了提高流的操作效率而出现的,所以在创建缓冲区之前,必须要先有流对象。

BufferedWriter

该缓冲区中提供了一个跨平台的换行符:newLine()。如果想要在硬盘上创建一个文件并写入一些文字数据的话,就可以使用该类了,代码应该类似于下:

public class CharStreamBufferDemo {

    public static void main(String[] args) throws IOException {
        /*
         * 演示字符流的缓冲区。
         * BufferedReader
         * BufferedWriter
         */
        writeTextByBuffer();
    }


    public static void writeTextByBuffer() throws IOException {
        // 1、明确目的。
        FileWriter fw = new FileWriter("tempfile\\bufw.txt");

        // 2、创建缓冲区对象,明确要缓冲的流对象。
        BufferedWriter bufw = new BufferedWriter(fw);

        for (int i = 1; i <= 4; i++) {
            bufw.write(i + "abc");
            bufw.newLine();
            bufw.flush();
        }

        bufw.close();
    }

}

BufferedReader

字符读取流缓冲区,该缓冲区提供了一个一次读一行的方法:readLine(),方便于对文本数据的获取,当返回null时,表示读取到文件末尾。注意:readLine()方法返回的时候只返回回车符之前的数据内容,并不返回回车符(行终止符)
如果现在我们想要从硬盘的一个文本文件中读取内容,那么就能使用到该类了,代码应该类似于下:

public class CharStreamBufferDemo {

    public static void main(String[] args) throws IOException {
        /*
         * 演示字符流的缓冲区。
         * BufferedReader
         * BufferedWriter
         */
        readTextByBuffer();
    }

    public static void readTextByBuffer() throws IOException {
        FileReader fr = new FileReader("tempfile\\bufw.txt");
        BufferedReader bufr = new BufferedReader(fr);

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

        bufr.close();
    }

}

System类对IO的支持

System.out对应的是标准输出设备,即控制台;System.in对应的是标准输入设备,即键盘。
现在有这样一个需求:通过键盘录入数据,当录入一行数据后,就将该行数据进行打印,如果录入的数据是over,那么录入停止。这里直接给出示例代码,如下。

public class ReadIn {

    public static void main(String[] args) throws IOException {
        InputStream in = System.in;

        StringBuilder sb = new StringBuilder();

        while (true) {
            int ch = in.read();
            if (ch == '\r') {
                continue;
            }
            if (ch == '\n') {
                String s = sb.toString();
                if ("over".equals(s)) {
                    break;
                }
                System.out.println(s);
                sb.delete(0, sb.length()); // 清空缓冲区。
            } else {
                sb.append((char)ch);
            }
        }
    }

}

通过以上的键盘录入一行数据并打印,发现其实就是读一行数据的原理,也就是readLine()方法,那么能不能直接使用readLine()方法来完成键盘录入的一行数据的读取呢?readLine()方法是字符流BufferedReader类中的方法,而键盘录入的read()方法是字节流InputStream的方法,那么能不能将字节流转成字符流,再使用字符流缓冲区的readLine()方法呢?当然可以啦,此时就需要用到转换流了。如此一来,以上程序代码就要修改为下面这样了。

public class TransStreamDemo {

    public static void main(String[] args) throws IOException {
        /*
        // 获取键盘录入对象。
        InputStream in = System.in;

        // 将字节流对象转成字符流对象,使用转换流————InputStreamReader。
        InputStreamReader isr = new InputStreamReader(in);

        // 为了提高效率,将字符流进行缓冲区技术高效操作,使用BufferedReader。
        BufferedReader bufr = new BufferedReader(isr);
        */

        // 简写格式,键盘录入的最常见写法。
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

        // PrintStream out = System.out; // 屏幕输出。
        // OutputStreamWriter osw = new OutputStreamWriter(out); // 字符流输出对象转换成字节流输出对象。
        // BufferedWriter bufw = new BufferedWriter(osw);
        BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

        String line = null;

        while ((line = bufr.readLine()) != null) {
            if ("over".equals(line)) {
                break;
            }
            bufw.write(line);
            bufw.newLine();
            bufw.flush();
        }

        bufw.close();
        bufr.close();
    }

}

注意:更专业地读取键盘录入的方式是下面这样的。

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
bufr.readLine();

而Scanner类就是流+正则表达式,它的方法都是按照某种规则在读取数据。接下来,我们就来做两个练习,以巩固之前学习到的知识点。

练习一

学完字符流的缓冲区对象之后,为了趁热打铁,赶紧做这样一个练习:建立指定文件的清单文件,即将指定目录下(包含子目录)的指定文件的绝对路径写入到一个文件中,该文件就作为指定文件的清单文件。
分析:其实这个练习的绝大部分我们都已做完了,就只剩将指定文件的绝对路径写入到一个文件中了。如若不信,可以回头去看一下Java IO流(一)——递归综合练习这一章节。所以我们只须编写一个将集合中符合条件的文件对象的绝对路径写入到一个文件中的函数即可。

public static void write2File(List<File> list, File destFile) throws IOException {

    // 1、需要一个流对象,既然是写入字符。
    BufferedWriter bufw = null;
    try {
        bufw = new BufferedWriter(new FileWriter(destFile));
        // 2、遍历集合
        for (File file : list) {
            bufw.write(file.getAbsolutePath());
            bufw.newLine();
            bufw.flush();
        }
    } finally {
        if (bufw != null) {
            try {
                bufw.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭失败");
            }
        }
    }

}

最后,我们即可编写测试代码进行测试了。

public static void main(String[] args) {

    File dir = new File("F:\\Java\\java_bxd");

    List<File> list = fileList(dir, ".java");

    /*
    for (File file : list) {
        System.out.println(file);
    }
    */

    // 将集合中符合条件的文件对象的绝对路径写入到一个文件中。
    File destFile = new File("javalist.txt");
    write2File(list, destFile);

}

练习二——学生总分排序

有这样一个练习,键盘录入多名学生的信息,格式为姓名,数学成绩,语文成绩,英文成绩,按总分由高到低,将学生的信息进行排列到文件中。
分析:1,描述学生对象;2,定义一个可以操作学生对象的工具类。本题的思路还是蛮明确的,如下:

  1. 通过获取键盘录入的一行数据,并将该行中的信息取出封装成学生对象。
  2. 因为学生对象有很多,那么就需要存储,故使用到集合。因为要对学生的总分进行排序,所以可以使用TreeSet。
  3. 将集合的信息写入到一个文件中。

首先描述一个学生类,因为学生对象要存储到TreeSet集合中,所以要实现Comparable接口,学生对象也有可能存储到HashSet集合中,所以最好覆写hashCode和equals方法。

public class Student implements Comparable<Student> {

    private String name;
    private int ma,cn,en;
    private int sum;

    public Student() {
        super();
    }

    public Student(String name, int ma, int cn, int en) {
        super();
        this.name = name;
        this.ma = ma;
        this.cn = cn;
        this.en = en;
        this.sum = ma + cn + en;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + sum;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (sum != other.sum)
            return false;
        return true;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMa() {
        return ma;
    }

    public void setMa(int ma) {
        this.ma = ma;
    }

    public int getCn() {
        return cn;
    }

    public void setCn(int cn) {
        this.cn = cn;
    }

    public int getEn() {
        return en;
    }

    public void setEn(int en) {
        this.en = en;
    }

    public int getSum() {
        return sum;
    }

    public void setSum(int sum) {
        this.sum = sum;
    }

    @Override
    public int compareTo(Student o) {
        int temp = this.sum - o.sum;
        return temp == 0 ? this.name.compareTo(o.name) : temp;
    }

}

接着再定义一个可以操作学生对象的工具类。其中一个函数功能是通过获取键盘录入的一行数据,并将该行中的信息取出封装成学生对象,存储进集合;另一个函数功能是将集合的信息写入到一个文件中。

public class GetInfoTool {

    /**
     * 获取所有学生对象集合,按照学生对象的自然顺序排序。
     * @throws IOException 
     */
    public static Set<Student> getStudents() throws IOException {
        return getStudents(null);
    }

    /**
     * 获取所有学生对象集合,按照指定的比较器排序。
     * @throws IOException 
     */
    public static Set<Student> getStudents(Comparator<Student> comp) throws IOException {
        // 1、键盘输入
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

        // 创建一个容器,存储学生对象。
        Set<Student> set = null;
        // 如果比较器存在,就创建带有比较器的对象。
        if (comp != null) {
            set = new TreeSet<Student>(comp);
        } else {
            set = new TreeSet<Student>();
        }

        // 2、获取键盘录入的信息
        String line = null;
        while ((line = bufr.readLine()) != null) {
            // 键盘录入结束标记。
            if ("over".equals(line)) {
                break;
            }

            // 因为录入的数据是有规则的。可以通过指定的规则进行分割。
            String[] strs = line.split(",");
            // 将数组中的元素封装成对象。
            Student stu = new Student(strs[0], Integer.parseInt(strs[1])
                    , Integer.parseInt(strs[2])
                    , Integer.parseInt(strs[3]));

            // 将学生对象存储到集合中。
            set.add(stu);
        }

        // 关闭键盘录入,须知如果后面不再使用键盘录入,是可以关闭的,如果后面还要使用,就不要关闭,继续通过System.in就可以获取。
        // bufr.close();
        return set;
    }

    /**
     * 将集合中的学生信息写入到文件当中。
     * @throws IOException 
     */
    public static void write2File(Set<Student> set, File destFile) throws IOException {
        BufferedWriter bufw = null;
        try {
            bufw = new BufferedWriter(new FileWriter(destFile));

            // 遍历集合。
            for (Student student : set) {
                bufw.write(student.getName() + "\t" + student.getSum());
                bufw.newLine();
                bufw.flush();
            }
        } finally {
            if (bufw != null) {
                try {
                    bufw.close();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }
    }

}

最后,我们即可编写测试代码进行测试了。

public class Test {

    public static void main(String[] args) throws IOException {
        /*
         * 练习:键盘录入多名学生的信息:格式为姓名,数学成绩,语文成绩,英文成绩。
         * 按总分由高到低,将学生的信息进行排列到文件中。
         */

        /*
        aa,40,40,40
        mm,10,10,10
        pp,50,50,50
        qq,90,90,90
        zz,80,80,40
        */
        // 创建一个逆序的比较器。
        Comparator<Student> comp = Collections.reverseOrder();

        // 使用操作学生信息的工具类。
        Set<Student> set = GetInfoTool.getStudents(comp);

        File destFile = new File("tempfile\\info.txt");
        GetInfoTool.write2File(set, destFile );
    }

}

没有更多推荐了,返回首页