IO详解

2.1 IO详解

本文将介绍Java语言中与I/O操作相关的内容,主要是流式I/O,关于NIO,我们之后会介绍。

2.1.1 流式IO

JavaI/O中的一个基本概念是流,包括输入流和输出流。简单的来说,流是一个连续的字节序列,可以从输入流读取这个字节序列,也可以将字节序列写入到输出流。输入流和输出流所操纵的基本单元就是字节,每次读取和写入单个字节或是字节数组。基本字节流所提供的一般是更通用、更底层的读写功能:比如InputStream只提供了顺序读取、跳过部分字节和标记、重置的支持,而OutputStream只能顺序写入。一般我们会将其包装一下使用:

  1. 如果操作的是Java的基本数据类型,可以使用DataInputStream和DataOutputStream,它们所提供的类似readFloat和writeFloat这样的方法,以简化会基本数据类型的读写
  2. 如果操作的是Java中的对象类型,可以使用ObjectInputStream和ObjectOutputStream,它们与对象的序列化机制一起实现Java对象状态的持久化和数据传递

资源管理

由于I/O操作都是有限的资源,每个打开的流都需要被正确的关闭以释放资源,一般谁打开谁释放。如果一个方法只是作为流的使用者,就不需要考虑流的关闭问题,典型的情况是在servlet实现中并不需要关闭HttpServletResponse中的输出流。

在使用输入流的过程中,经常会遇到需要复用一个输入流的情况,即多次读取一个输入流中的内容。为了保证每个使用流的对象都能获取到正确的内容,需要对流进行一定的处理。通常有两种解决的办法:

  1. 一种是利用InputStream的标记支持,如果一个流支持标记的话(通过markSupported方法判断),就可以在流开始的地方通过mark方法添加一个标记,当完成一次对流的使用之后,通过reset方法就可以把流的读取位置重置到上次标记的位置,如此就可以复用这个输入流。大部分输入流是不支持标记的,可以通过BufferedInputStream进行包装来支持标记。在NanoHttpD源码里关于Http头部解析使用的就是该方法。
private InputStream prepareStream(InputStream is) {
    BufferedInputStream buffered = new BufferedInputStream(is);
    buffered.mark(Integer.MAX_VALUE);
    return buffered;
}
private void resetStream(InputStream is) throws IOException {
    is.reset();
    is.mark(Integer.MAX_VALUE);
}

如上面的代码所示,通过prepareStream方法可以用一个BufferedInputStream来包装基本的InputStream,通过mark方法在流开始的时候添加一个标记,允许读入Integer.MAX_VALUE个字节。每次流使用完成之后,通过reset方法重置即可。

  1. 另外一种做法是把输入流的内容转换成字节数组,如下所示:
private byte[] saveStream(InputStream input) throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    ReadableByteChannel readChannel = Channels.newChannel(input);
    ByteArrayOutputStream output = new ByteArrayOutputStream(32 * 1024);
    WritableByteChannel writeChannel = Channels.newChannel(output);
    while ((readChannel.read(buffer)) > 0 || buffer.position() != 0) {
        buffer.flip();
        writeChannel.write(buffer);
        buffer.compact();
    }
    return output.toByteArray();
}

上面的代码中saveStream方法把一个InputStream保存为字节数组。

这两种做法的思路其实是相似的,BufferedInputStream在内部也创建了一个字节数组来保存从原始输入流中读入的内容。使用字节数组不需要考虑资源相关的问题,可以尽早的关闭原始的输入流,而无需等待所有使用流的操作完成。

缓冲区

由于流背后的数据有可能比较大,在实际的操作中,通常会使用缓冲区来提高性能。传统的缓冲区一般是使用数组来实现的的。比如经典的从InputStream到OutputStream的复制的实现,就是使用一个字节数组作为中间的缓冲区。

总结

流式IO:两个对应一个桥梁。字节流和字符流的对应(IOStream和Reader/Writer);输入流和输出流的对应。一个桥梁:从字节流到字符流的桥梁(InputStreamReader和OutputStreamWriter)。流的具体类又可以分为:介质流和过滤流。介质流是一些基本流,主要是从具体的介质上读取数据:例如文件,内存缓冲区(Byte数组、Char数组)等。过滤流:主要是装饰器类装饰具体的介质流。需要注意的是:流是一维单向(RandomAccessFile就是解决单向的问题)

这里通过一个表格简单介绍一下常用的IO流,这里以输入字节流为例,输出字节流与其相对应,这里就不再详述了

  • 基本输入字节流:
功能 具体使用
ByteArrayInputStream 将内存中的Byte数组适配为一个InputStream 作为数据源,可以和其他装饰流配合,建议加入缓冲功能
FileInputStream 最基本的文件输入流。主要用于从文件中读取信息 作为数据源,可以和其他装饰流配合
PipedInputStream 读取从对应PipedOutputStream写入的数据,在流中实现了管道的概念 在多线程程序中作为数据源,可以和其他装饰流配合
  • 装饰输入字节流:
功能 具体使用
BufferedInputStream 正常情况下,每读取一个字节都要进行一次IO,使用该对象后可以进行一次IO将字节流读取缓存区中,从缓存区读取 使用InputStream的方法读取,只是背后多一个缓存的功能,设计模式中透明装饰器的应用。
DataInputStream 一般和DataOutputStream配对使用,完成基本数据类型的读写 提供了大量的读取基本数据类新的读取方法
ObjectInputStream 一般和ObjectOutputStream配对使用,完成Java对象类型的读写 对象的序列化机制一起,可以实现Java对象状态的持久化和数据传递

2.1.2 字符与编码

字节和字符:字节是计算机数据表示(8位二进制数据,可以认为是二进制码),而字符就是用户直接看见的信息。
字符集和编码:字符集就是字符的集合,一个字符集中所包含的字符通常与地区和语言有关,常见的字符集有ASCII和Unicode等(这里需要两者的区别,ASCII不仅是字符集,还指定了编码方案,而Unicode仅仅是一个字符集)。对于字符集中的每个字符,为了在计算机中存储和网络的传递,都需要转换某种字节的序列(比如ASCII编码中字符A的编码就是0x41),即该字符的编码,同一个字符集可以有不同的编码方式(比如Unicode的编码方案有UTF-8和UTF-16等)。如果某种编码格式产生的字节序列,用另外一种编码格式来解码的话,就可能会得到错误的字符,从而产生乱码的情况。所以将一个字节序列转换成字符串的时候,需要知道正确的编码格式。
NIO中的java.nio.charset包提供了与字符集相关的类,可以用来进行编码和解码。其中的CharsetEncoder和CharsetDecoder允许对编码和解码过程进行精细的控制,如处理非法的输入以及字符集中无法识别的字符等。通过这两个类可以实现字符内容的过滤,比如应用程序在设计的时候就只支持某种字符集,如果用户输入了其它字符集中的内容,在界面显示的时候就是乱码,对于这种情况,可以在解码的时候忽略掉无法识别的内容。

String input = "你123好";
Charset charset = Charset.forName("ISO-8859-1");
CharsetEncoder encoder = charset.newEncoder();
encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
CharsetDecoder decoder = charset.newDecoder();
CharBuffer buffer = CharBuffer.allocate(32);
buffer.put(input);
buffer.flip();
try {
    ByteBuffer byteBuffer = encoder.encode(buffer);
    CharBuffer cbuf = decoder.decode(byteBuffer);
    System.out.println(cbuf); //输出123
} catch (CharacterCodingException e) {
    e.printStackTrace();
}

上面的代码中,通过使用ISO-8859-1字符集的编码和解码器,就可以过滤掉字符串中不在此字符集中的字符。
JavaI/O在处理字节流之外,还提供了处理字符流的类,即Reader/Writer类及其子类,它们所操纵的基本单位是char类型。在字节和字符之间的桥梁就是编码格式,通过编码器来完成这两者之间的转换。在创建Reader/Writer子类实例的时候,应该总是使用两个参数的构造方法,即显式指定使用的字符集或编码解码器。如果不显式指定,使用的是JVM的默认字符集,有可能在其它平台上产生错误。

2.1.3 辅助工具类

File,RandomAccessFile以及FileDescriptor类

由于JavaIO使用过于繁琐,更多使用的是一些第三方的类库进行操作:例如流式IO方面,一般多用来操作本地文件,使用Apache的CommonsIO。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容