Java的IO系统(3) - 字符流类

发表于2019-12-25,长度7923, 1655个单词, 22分钟读完
Flag Counter

字节流虽然方便也强大,但是不支持Unicode。字符流就是为了解决这个问题的,所以除了面对的对象不一样(一个是字节,一个是字符),他们的类和行为很相仿。

Reader 类

这是字符输入流的积累,也是一个抽象类。主要有以下方法:

由于和字节流类似,本篇相对简略一些

方法 描述
abstract void close( ) 关闭输入源。如果试图继续读取,将产生IOException异常
void mark(int mumChars) 在输入流的当前位置放置标记,该标记在读入numChars个字符之前都一直有效
boolean markSupported( ) 如果这个流支持mark或reset方法,就返回true
int read() 返回一个表示调用输入流中下一个可用字符的整数。如果到达文件末尾,就返回-1
int read(char buffer[ ]) 尝试读取buffer.length个字符到buffer中,并且返回成功读取的实际字符数。如果到达文件末尾,则返回-1
int read(CharBuffer buffer) 尝试读取字符到buffer中,并且返回成功读取的实际字符数。如果到达文件末尾,则返回-1
abstract int read(char buffer[ ],int offset,int numChars) 尝试读取numChars个字符到buffer中,从buffer[offset]开始保存读取的字符,返回成功读取的字符数。如果到达文件末尾,就返回-1
boolean ready( ) 如果下一个输入请求不等待,就返回true;否则返回false
void reset( ) 将输入指针重新设置为前面设置的标记位置
long skip(long numChars) 略过numChars个输入字符,返回实际略过的字符数

Writer 类

主要有以下方法:

方法 描述
Writer append(char ch) 将ch追加到调用输出流的末尾,返回对调用流的引用
Writer append(CharSequence chars) 将chars追加到调用输出流的末尾,返回对调用流的引用
Writer append(CharSequence chars,int begin, int end) 将chars中从begin到end- 1之间的字符追加到调用输出流的末尾,返回对调用流的引用
abstract void close( ) 关闭输出流。如果试图继续向其中写入内容,将产生IOException异常
abstract void flush( ) 完成输出状态,从而清空所有缓冲区
void write(int ch) 向调用输出流写入单个字符。注意参数是int类型,从而可以直接使用表达式调用write方法,而不必将之强制转换回char类型。但是只会写入低阶的16位
void write(char buffer[]) 将整个字符数组写入调用输出流中
abstract void write(char buffer[],int offset,int numChars) 将buffer数组中从buffer[offset]开始的numChars个字符写入调用输出流中
void write(String str) 将str写入调用输出流中
void write(String str, int offset,int numChars) 将字符串str中从offset开始的numChars个字符写入调用输出流中

FileReader

这是FileInputStream的字符流对应类。做为对比,我们把代码放一起:

int size;
try (FileInputStream f = new FileInputStream("/Users/sheldon/IdeaProjects/QLExpress/README.md")) {
    System.out.println("文件总字节数" + (size = f.available()));
    int n = size / 400;
    System.out.println("分400份,头" + n + "个字节:");
    for (int i = 0; i < n; i++) {
        System.out.print((char) f.read());
    }
    System.out.println("\n剩余字节数: " + f.available());
    System.out.println("接下来" + n + "个字符:");
    byte b[] = new byte[n];
    if (f.read(b) != n) {
        System.err.println("不足" + n + "个字符。");
    }
    System.out.println(new String(b, 0, n));
    System.out.println("\n剩余字节:" + (size = f.available()));
    System.out.println("跳过总的一半");
    f.skip(size / 2);
    System.out.println("剩余字节 " + f.available());
} catch (IOException e) {
    e.printStackTrace();
}

System.out.println("-------");
try (FileReader f = new FileReader("/Users/sheldon/IdeaProjects/QLExpress/README.md")){
    size = 0;
    while (size < 60) {
        System.out.print((char)f.read());
        size++;
    }
}

输出内容:

文件总字节数21885
分400份,头54个字节:
# QLExpress基本语法

[![Join the chat at https://g
剩余字节数: 21831
接下来54个字符:
itter.im/QLExpress/Lobby](https://badges.gitter.im/QLE

剩余字节:21777
跳过总的一半
剩余字节 10889
-------
# QLExpress基本语法

[![Join the chat at https://gitter.im/QLExp

可以看到,中文正常打印了出来。写法一样,都是把读出来的int以char获取即可。但Reader是两个字节两个字节的获取。

FileWriter 类

这个是FileOutputStream的对应类,对比代码如下

String s = "A distributed transaction solution with high performance and ease of use for microservices architecture.\n" +
        "Distributed Transaction Problem in Microservices\n" +
        "Let's imagine a traditional monolithic application. Its business is built up with 3 modules. They use a single local data source.\n" +
        "Naturally, data consistency will be guaranteed by the local transaction.哈哈哈哈哈哈哈哈哈";
try (FileOutputStream fos1 = new FileOutputStream("t1.txt");
        FileOutputStream fos2 = new FileOutputStream("t2.txt");) {
    byte[] bytes = s.getBytes();
    int l = bytes.length;
    fos1.write(bytes);
    fos2.write(bytes, l - 10, 10);
}

try (FileWriter fw = new FileWriter("t3.txt");) {
    fw.write(s.toCharArray());
}

能够在t3.txt中看到完整正确的字符串。

CharArrayReader、CharArrayWriter

分别是ByteArrayInputStream和ByteArrayOutputStream的对应类。

BufferedReader

这是BufferedInputStream的对应类。我们按行处理文件的readLine()方法就来自该类。从Java8开始,该类增加了一个用于按行生成迭代流的方法lines(),返回值类型是java.util.stream.Stream。

BufferedWriter

无他。

PushbackReader 类

先看对比代码:

String s = "if (a==1) {a = 2;}";
try (ByteArrayInputStream is = new ByteArrayInputStream(s.getBytes());
        PushbackInputStream pis = new PushbackInputStream(is);) {
    byte c;
    while ((c = (byte) pis.read()) != -1 ) {
        if (c == '=') {
            c = (byte) pis.read();
            if (c == '=') {
                System.out.print(" 是 ");
            } else {
                System.out.print(" 赋值为 ");
                pis.unread(c);
            }
        } else {
            System.out.print((char)c);
        }
    }
}

System.out.println();
System.out.println("---------");
try (PushbackReader pr = new PushbackReader(new CharArrayReader(s.toCharArray()))){
    int i;
    while ((i =  pr.read()) != -1 ) {
        char c = (char) i;
        if (c == '=') {
            c = (char) pr.read();
            if (c == '=') {
                System.out.print(" 正是 ");
            } else {
                System.out.print(" 写值为 ");
                pr.unread(c);
            }
        } else {
            System.out.print((char)c);
        }
    }
}
System.out.println();
System.out.println(s.getBytes().length);
System.out.println(s.toCharArray().length);

输出:

if (a 是 1) {a  赋值为  2;}
---------
if (a 是 1) {a  赋值为  2;}
18
18

可见结果一样,而且字节数和字符数也一样。如果有汉字呢?

String s = "if (a==你) {a = 我;}";
try (ByteArrayInputStream is = new ByteArrayInputStream(s.getBytes());
        PushbackInputStream pis = new PushbackInputStream(is);) {
    byte c;
    while ((c = (byte) pis.read()) != -1 ) {
        if (c == '=') {
            c = (byte) pis.read();
            if (c == '=') {
                System.out.print(" 是 ");
            } else {
                System.out.print(" 赋值为 ");
                pis.unread(c);
            }
        } else {
            System.out.print((char)c);
        }
    }
}

System.out.println();
System.out.println("---------");
try (PushbackReader pr = new PushbackReader(new CharArrayReader(s.toCharArray()))){
    int i;
    while ((i =  pr.read()) != -1 ) {
        char c = (char) i;
        if (c == '=') {
            c = (char) pr.read();
            if (c == '=') {
                System.out.print(" 是 ");
            } else {
                System.out.print(" 赋值为 ");
                pr.unread(c);
            }
        } else {
            System.out.print((char)c);
        }
    }
}
System.out.println();
System.out.println(s.getBytes().length);
System.out.println(s.toCharArray().length);

输出:

if (a 是 ¦ᄑᅠ) {a  赋值为  ₩ネム;}
---------
if (a 是 你) {a  赋值为  我;}
22
18

原因是Java把latin1字符和utf16区别对待。一个latin1字符无论是字节还是字符都是1,而utf16的字节是2,字符是1。

PrintWriter

无他

Console类

Console类是Java6加入到java.io包的,用于操作控制台:读取控制台、写到控制台。该类的大部分功能都可以通过System.in和System.out获得,但也有自己独特的地方,比如不显示输入的密码。

Console没有公开的构造方法,只能通过System.console()获取实例。没有可用的控制台会返回null。 主要方法如下:

方法 描述
void flush( ) 将缓冲的输出物理地写到控制台
Console format(String fmtString,Object..args) 使用fmtString指定的格式将args 写到控制台
Console printf(String fmtString,Object..args) 使用fmtString指定的格式将args 写到控制台
Reader reader( ) 返回对连接到控制台的Reader对象的引用
String readLine( ) 读取并返回从键盘输入的字符串。当用户按下回车键时,输入结束。如果已经到达控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
String readLine(String fmtString,Object…args) 按照fmtString和args指定的格式显示提示性字符串,然后读取并返回从键盘输入的字符串。当用户按下回车键时,输入结束。如果已经到达控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
char[ ] readPassword() 读取从键盘输入的字符串。当用户按下回车键时,输入结束。输入的字符串并不显示。如果已经到达控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
char[ ] readPassword(String fmtString,Object..args) 按照fmtString和args指定的格式显示提示性字符串,然后读取从键盘输入的字符串。当用户按下回车键时,输入结束。输入的字符串并不显示。如果已经到达控制台输入流的末尾,就返回null;如果失败,就抛出IOError异常
PrintWriter writer( ) 返回对连接到控制台的Writer对象的引用
Written on December 25, 2019
分类: dev, 标签: java
如果你喜欢,请赞赏! davelet