`
yimi128
  • 浏览: 55438 次
  • 来自: ...
社区版块
存档分类
最新评论

[zz]JAVA I/O

    博客分类:
  • WEB
阅读更多

      在应用程序中,通常会涉及到两种类型的计算:CPU计算和I/O计算。对于大多数应用来说,花费在等待I/O上的时间是占较大比重的。通常需要等待速度较 慢的磁盘或是网络连接完成I/O请求,才能继续后面的CPU计算任务。因此提高I/O操作的效率对应用的性能有较大的帮助。本文将介绍Java语言中与I /O操作相关的内容,包括基本的Java I/O和Java NIO,着重于基本概念和最佳实践。

Java语言提供了多个层次不同的概念来对I/O操作进行抽象。Java I/O中最早的概念是流,包括输入流和输出流,早在JDK 1.0中就存在了。简单的来说,流是一个连续的字节的序列。输入流是用来读取这个序列,而输出流则构建这个序列。InputStreamOutputStream 所操纵的基本单元就是字节。每次读取和写入单个字节或是字节数组。如果从字节的层次来处理数据类型的话,操作会非常繁琐。可以用更易使用的流实现来包装基本的字节流。如果想读取或输出Java的基本数据类型,可以使用DataInputStreamDataOutputStream 。它们所提供的类似readFloat和writeDouble这样的方法,会让处理基本数据类型变得很简单。如果希望读取或写入的是Java中的对象的话,可以使用ObjectInputStreamObjectOutputStream 。它们与对象的序列化 机制一起,可以实现Java对象状态的持久化和数据传递。基本流所提供的对于输入和输出的控制比较弱。InputStream只提供了顺序读取、跳过部分字节和标记/重置的支持,而OutputStream则只能顺序输出。

 

流的使用

由于I/O操作所对应的实体在系统中都是有限的资源,需要妥善的进行管理。每个打开的流都需要被正确的关闭以释放资源。所遵循的原则是谁打开谁释放。如果一个流只在某个方法体内使用,则通过finally语句或是JDK 7中的try-with-resources 语 句来确保在方法返回之前,流被正确的关闭。如果一个方法只是作为流的使用者,就不需要考虑流的关闭问题。典型的情况是在servlet实现中并不需要关闭 HttpServletResponse中的输出流。如果你的代码需要负责打开一个流,并且需要在不同的对象之间进行传递的话,可以考虑使用Execute Around Method 模式。如下面的代码所示:

public void use(StreamUser user) {
    InputStream input = null;
    try {
        input = open();
        user.use(input);
    } catch(IOException e) {
        user.onError(e);
    } finally {
        if (input != null) {
            try { 
                input.close();
            } catch (IOException e) {
                user.onError(e);
            }
        }
    }
 } 

如上述代码中所看到的一样,由专门的类负责流的打开和关闭。流的使用者StreamUser并不需要关心资源释放的细节,只需要对流进行操作即可。

在使用输入流的过程中,经常会遇到需要复用一个输入流的情况,即多次读取一个输入流中的内容。比如通过URL.openConnection方法打 开了一个远端站点连接的输入流,希望对其中的内容进行多次处理。这就需要把一个InputStream对象在多个对象中传递。为了保证每个使用流的对象都 能获取到正确的内容,需要对流进行一定的处理。通常有两种解决的办法,一种是利用InputStream的标记支持。如果一个流支持标记的话(通过markSupported 方法判断),就可以在流开始的地方通过mark 方法添加一个标记,当完成一次对流的使用之后,通过reset 方法就可以把流的读取位置重置到上次标记的位置,即流开始的地方。如此反复,就可以复用这个输入流。大部分输入流的实现是不支持标记的。可以通过BufferedInputStream 进行包装来支持标记。

private InputStream prepareStream(InputStream ins) {
    BufferedInputStream buffered = new BufferedInputStream(ins);
    buffered.mark(Integer.MAX_VALUE);
    return buffered;
} 
private void resetStream(InputStream ins) throws IOException {
    ins.reset();
    ins.mark(Integer.MAX_VALUE);
}  

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

另外一种做法是把输入流的内容转换成字节数组,进而转换成输入流的另外一个实现ByteArrayInputStream 。 这样做的好处是使用字节数组作为参数传递的格式要比输入流简单很多,可以不需要考虑资源相关的问题。另外也可以尽早的关闭原始的输入流,而无需等待所有使 用流的操作完成。这两种做法的思路其实是相似的。BufferedInputStream在内部也创建了一个字节数组来保存从原始输入流中读入的内容。

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保存为字节数组。

缓冲区

由于流背后的数据有可能比较大,在实际的操作中,通常会使用缓冲区来提高性能。传统的缓冲区的实现是使用数组来完成。比如经典的从InputStream到OutputStream的复制的实现 ,就是使用一个字节数组作为中间的缓冲区。NIO中引入的Buffer 类及其子类,可以很方便的用来创建各种基本数据类型的缓冲区。相对于数组而言,Buffer类及其子类提供了更加丰富的方法来对其中的数据进行操作。后面会提到的通道也使用Buffer类进行数据传递。

在Buffer上进行的元素添加和删除操作,都围绕3个属性positionlimitcapacity 展 开,分别表示Buffer当前的读写位置、可用的读写范围和容量限制。容量限制是在创建的时候指定的。Buffer提供的get/put方法都有相对和绝 对两种形式。相对读写时的位置是相对于position的值,而绝对读写则需要指定起始的序号。在使用Buffer的常见错误就是在读写操作时没有考虑到 这3个元素的值,因为大多数时候都是使用的是相对读写操作,而position的值可能早就发生了变化。一些应该注意的地方包括:将数据读入缓冲区之前, 需要调用clear 方法;将缓冲区中的数据输出之前,需要调用flip 方法。

ByteBuffer buffer = ByteBuffer.allocate(32);
CharBuffer charBuffer = buffer.asCharBuffer();
String content = charBuffer.put("Hello ").put("World").flip().toString();
System.out.println(content);  

上面的代码展示了Buffer子类的使用。首先可以在已有的ByteBuffer上面创建出其它数据类型的缓冲区视图,其次Buffer子类的很多方法是可以级联的,最后是要注意flip方法的使用。

字符与编码

在程序中,总是免不了与字符打交道,毕竟字符是用户直接可见的信息。而与字符处理直接相关的就是编码。相信不少人都曾经为了程序中的乱码问题而困 扰。要弄清楚这个问题,就需要理解字符集和编码的概念。字符集,顾名思义,就是字符的集合。一个字符集中所包含的字符通常与地区和语言有关。字符集中的每 个字符通常会有一个整数编码与其对应。常见的字符集有ASCII、ISO-8859-1和Unicode等。对于字符集中的每个字符,为了在计算机中表 示,都需要转换某种字节的序列,即该字符的编码。同一个字符集可以有不同的编码方式。如果某种编码格式产生的字节序列,用另外一种编码格式来解码的话,就 可能会得到错误的字符,从而产生乱码的情况。所以将一个字节序列转换成字符串的时候,需要知道正确的编码格式。

NIO中的java.nio.charset 包提供了与字符集相关的类,可以用来进行编码和解码。其中的CharsetEncoderCharsetDecoder 允 许对编码和解码过程进行精细的控制,如处理非法的输入以及字符集中无法识别的字符等。通过这两个类可以实现字符内容的过滤。比如应用程序在设计的时候就只 支持某种字符集,如果用户输入了其它字符集中的内容,在界面显示的时候就是乱码。对于这种情况,可以在解码的时候忽略掉无法识别的内容。

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字符集的编码和解码器,就可以过滤掉字符串中不在此字符集中的字符。

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

通道

通道作为NIO中的核心概念,在设计上比之前的流要好不少。通道相关的很多实现都是接口而不是抽象类。通道本身的抽象层次也更加合理。通道表示的是 对支持I/O操作的实体的一个连接。一旦通道被打开之后,就可以执行读取和写入操作,而不需要像流那样由输入流或输出流来分别进行处理。与流相比,通道的 操作使用的是Buffer而不是数组,使用更加方便灵活。通道的引入提升了I/O操作的灵活性和性能,主要体现在文件操作和网络操作上。

文件通道

对文件操作方面,文件通道FileChannel 提供了与其它通道之间高效传输数据的能力,比传统的基于流和字节数组作为缓冲区的做法,要来得简单和快速。比如下面的把一个网页的内容保存到本地文件的实现。

FileOutputStream output = new FileOutputStream("baidu.txt");
FileChannel channel = output.getChannel();
URL url = new URL("http://www.baidu.com");
InputStream input = url.openStream();
ReadableByteChannel readChannel = Channels.newChannel(input);
channel.transferFrom(readChannel, 0, Integer.MAX_VALUE);   

文件通道的另外一个功能是对文件的部分片段进行加锁。当在一个文件上的某个片段加上了排它锁之后,其它进程必须等待这个锁释放之后,才能访问该文件 的这个片段。文件通道上的锁是由JVM所持有的,因此适合于与其它应用程序协同时使用。比如当多个应用程序共享某个配置文件的时候,如果Java程序需要 更新此文件,则可以首先获取该文件上的一个排它锁,接着进行更新操作,再释放锁即可。这样可以保证文件更新过程中不会受到其它程序的影响。

另外一个在性能方面有很大提升的功能是内存映射文件 的支持。通过FileChannel的map 方法可以创建出一个MappedByteBuffer 对象,对这个缓冲区的操作都会直接反映到文件内容上。这点尤其适合对大文件进行读写操作。

套接字通道

在套接字通道方面的改进是提供了对非阻塞I/O和多路复用I/O的支持。传统的流的I/O操作是阻塞式的。在进行I/O操作的时候,线程会处于阻塞状态等待操作完成。NIO中引入了非阻塞I/O的支持,不过只限于套接字I/O操作。所有继承自SelectableChannel 的通道类都可以通过configureBlocking 方法来设置是否采用非阻塞模式。在非阻塞模式下,程序可以在适当的时候查询是否有数据可供读取。一般是通过定期的轮询来实现的。

多路复用I/O是一种新的I/O编程模型。传统的套接字服务器的处理方式是对于每一个客户端套接字连接,都新创建一个线程来进行处理。创建线程是很 耗时的操作,而有的实现会采用线程池。不过一个请求一个线程的处理模型并不是很理想。原因在于耗费时间创建的线程,在大部分时间可能处于等待的状态。而多 路复用I/O的基本做法是由一个线程来管理多个套接字连接。该线程会负责根据连接的状态,来进行相应的处理。多路复用I/O依靠操作系统提供的select 或相似系统调用的支持,选择那些已经就绪的套接字连接来处理。可以把多个非阻塞I/O通道注册在某个Selector 上,并声明所感兴趣的操作类型。每次调用Selector的select 方法,就可以选择到某些感兴趣的操作已经就绪的通道的集合,从而可以进行相应的处理。如果要执行的处理比较复杂,可以把处理转发给其它的线程来执行。

下面是一个简单的使用多路复用I/O的服务器实现。当有客户端连接上的时候,服务器会返回一个Hello World作为响应。

private static class IOWorker implements Runnable {
    public void run() {
        try {
            Selector selector = Selector.open();
            ServerSocketChannel channel = ServerSocketChannel.open();
            channel.configureBlocking(false);
            ServerSocket socket = channel.socket();
            socket.bind(new InetSocketAddress("localhost", 10800));
            channel.register(selector, channel.validOps());
            while (true) {
                selector.select();
                Iterator iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (!key.isValid()) {
                        continue;
                    }
                    if (key.isAcceptable()) {
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel sc = ssc.accept();
                        sc.configureBlocking(false);
                        sc.register(selector, sc.validOps()); 
                    }
                    if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        Charset charset = Charset.forName("UTF-8");
                        CharsetEncoder encoder = charset.newEncoder();
                        CharBuffer charBuffer = CharBuffer.allocate(32);
                        charBuffer.put("Hello World");
                        charBuffer.flip();
                        ByteBuffer content = encoder.encode(charBuffer);
                        client.write(content);
                        key.cancel();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上面的代码给出的只是非常简单的示例程序,只是展示了多路复用I/O的基本使用方式。在开发复杂网络应用程序的时候,使用一些Java NIO网络应用框架会让你事半功倍。目前来说最流行的两个框架是Apache MINANetty 。在使用了Netty之后,Twitter的搜索功能速度提升达到了3倍之多 。网络应用开发人员都可以使用这两个开源的优秀框架。

分享到:
评论

相关推荐

    ZZ18000/25.5/50型四柱支撑掩护式支架的研制与应用

    介绍了ZZ18000/25.5/50型四柱支撑掩护式支架的主要技术参数、结构特点以及该支架作为快速搬家专用巷道支护支架在回撤巷道中的应用。该支架可实现对回撤巷道的提前支护,缩短了搬家倒面的时间,提高了综采设备的利用率...

    ZZ8500/20/40型液压支架中缸筒设计及工艺的改进

    针对ZZ8500/20/40型液压支架中缸筒设计不合理部分进行改进。分别列举了中缸筒按原设计和改进后设计进行加工的工艺过程,并对二者进行了比较。改进前设计加工工艺繁琐,费料费时,效率低;改进后省工省料,可降低成本,提高...

    ZZ8200/22/45型四柱支撑掩护式液压支架研制与应用

    根据掌石沟煤业有限公司15号煤层的地质...分析了ZZ8200/22/45型四柱支撑掩护式液压支架的结构组成、适用范围和技术参数等,对支架的顶梁、底座、掩护梁进行了阐述。该支架的使用保证了较高的支护效率和人员生产安全。

    ZZ13000/28/60重型液压支架井下整体快速搬运的研究

    随着晋华宫矿最近几年来...针对新型大采高液压支架ZZ13000/28/60在井下搬运过程中出现的诸多现场问题,进行分析研究,总结出一整套整体快速搬运重型液压支架的搬运方法。有效地解决了在普通条件下特大型设备的搬家难题。

    ZZ13000/28/60型液压支架稳定性研究

    通过对ZZ13000/28/60型液压支架结构的介绍,重点对四连杆机构力的纵向稳定性进行了分析,验证了液压支架四连杆结构的设计是合理的、正确的,能确保工作面的稳定性,为煤矿高效、安全生产提供重要的保障。

    ZZ6400/22/45型液压支架的选型及支护强度验算

    综采工作面液压支架架型是否与工作面煤层...根据丁集煤矿1121(3)回采工作面地质条件,选用ZZ6400/22/45型液压支架。为验证液压支架的支护强度,对其进行支护强度验算,验算结果表明,所选支架的支护强度满足顶板压力要求。

    ZZ5900/14/28型液压支架设计探讨

    介绍了大同青磁窑煤矿ZZ5900/14/28型支撑掩护式液压支架的设计过程、设计内容、创新点、社会效益及总体性能指标与国内外的比较,特别是对该支架在焊接性能、材质选用、工艺选择方面进行了详尽的叙述。

    综采面回撤三角区ZZ7200/18/36型支架电液系统设计

    为了提高综采工作面的搬家速度和工效,研制了回撤三角区ZZ7200/18/36型支架。介绍了支架布置情况和使用特点,重点说明了其技术参数、电液控制系统特点及组成。实践证明该支架的使用,取得了显著的经济效益。

    ZZ3200/11/21型液压支架改造

    ZZ3200/11/21型液压支架投用后,由于盘江支护条件的改变顶梁护帮板结构不能适应盘江煤层条件,液压系统反冲洗过滤器通液孔径很小,对水质要求极高,容易堵塞,致使支架无法正常工作,原ZZ3200/11/21型液压系统是本架操纵...

    ZZ9000/23/48型液压支架柱窝铸造工艺的优化

    潞安机械公司在ZZ9000/23/48型液压支架柱窝铸件实际生产过程中,由于传统工艺的局限,柱窝铸件冒口处无法得到有效补缩,导致柱窝在切割冒口后发现十字结构热节处存在严重的缩孔缺陷,直接导致铸件报废,无法使用。...

    ZZ10800/22/45液压支架强度及疲劳分析

    将偏载工况下液压支架的三维几何模型导入到有限元分析软件中进行静力学结构分析,得出整架的应力分布规律。通过Fatigue模块,分析得到整架的疲劳寿命、疲劳损伤和疲劳安全系数,这样不仅为液压支架轻量化设计提供理论...

    ZZ8800/25/50型液压支架立柱强度计算

    以某矿用280型双伸缩立柱拟定的各个技术参数为基础,通过建立二维模型,根据双伸缩立柱的结构特点,通过对壁厚、稳定性和活塞杆强度的计算验证技术参数能否满足使用要求。计算结果表明:计算方法合理,该型号双伸缩立柱...

    base zz zz zz zz

    base zz zz zz zz zz base zz zz zz zz zz base zz zz zz zz zz base zz zz zz zz zz

    在页面处理分页

    zz[i]=a[i].innerHTML; } //div的字符串数组付给zz var pageno=1; //当前页 var pagesize=3; //每页多少条信息 if(zz.length%pagesize==0){ var pageall =zz.length/pagesize; }else{ var pageall =parseInt(zz....

    ZZ6000/17/32型支撑掩护式液压支架设计

    根据晋城长平公司王台铺煤矿的煤层赋存情况以及与煤层相关的围岩条件进行支架的选型计算,并针对15号煤层工作面顶板坚硬稳定、底板极软的地质特点,确定液压支架的具体结构,设计ZZ6000/17/32型支撑掩护式液压支架。

    ZZ9900/29.5/50型支架护帮装置限压阀板的改进

    对ZZ9900/29.5/50型支撑掩护式大采高液压支架护帮装置限压控制系统限压阀板存在的缺陷进行了分析、改进。

    阿里巴巴java笔试zz.

    关于java的笔试题,大家共享。阿里巴巴java笔试,希望对大家有用。

    BC480/22/42支撑掩护式液压支架的改造

    已造成相当数量的结构件变形,影响了支架的整体稳定性,目前支架的状况已不能满足煤矿安全生产要求,全部报废会造成资源的极大浪费,通过对该支架进行改造,使整体性能超过原BC480/22/42支架,整机性能和ZZ5200/19.5/4支架...

    ZZ13000/28/60型液压支架选型设计初步研究

    大同煤矿集团公司晋华宫矿南山区域402盘区12#层为侏罗系厚煤层赋存区。通过对该区域的地质情况、开采矿压总结分析,初步对支架的架型、工作阻力进行设计研究,实现了"两硬"条件下6 m厚煤层一次采全高综采工作面的安全...

Global site tag (gtag.js) - Google Analytics