Java IO

Java  IO

 

IO问题是整个人机交互的核心问题,因为IO是机器获取和交换信息(包括人机交互和机器与机器交互等)的主要渠道。Java的IO操作主要分为下列几类:

1)     基于字节操作的IO接口:InputStream和OutputStream;

2)     基于字符操作的IO接口:Writer和Reader;

3)     基于磁盘操作的IO接口:File;

4)     基于网络操作的IO接口:Socket;

前面两组讨论的是传输数据的格式(字节或字符),后两组讨论的是传输数据的方式(磁盘IO、网络IO)。

1、       IO接口

1.1、字节IO接口

基于字节IO操作的接口分别对应InputStream和OutputStream,InputStream类层次结构图如下:


输入流根据数据类型和操作方式划分为若干子类,每个子类分别处理不同操作类型。OutputStream类层次结构与InputStream类似,如下:



1.2、字符IO接口

不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以IO操作的最终对象都是字节。在应用程序中操作数据一般采用字符的形式,为了操作方便,Java在字节IO的基础上提供了字符IO。

字符IO接口分别对应Reader和Writer类,Reader类主要读字符的接口为intread(char cbuf[],int off,int len),返回成功读到的字符个数,层次结构如下图所示:


类也提供了一个抽象方法用来写字符voidwrite(char cbuf[],int off,int len),其类结构图如下:


Reader和Writer类只是定义了读取或写入字符的方式,也就是怎么写或读;但是并没有规定数据要写到哪里(是磁盘文件还是网络)。

1.3、字节、字符转换

数据持久化或网络传输都是以字节为单位进行的,所以必须要有字符到字节或字节到字符的转换。字节和字符的转换如下所示:


InputStreamReader类是字节到字符的转换桥梁,InputStreamer到Reader的过程要指定编码字符集,否则将采用操作系统的默认字符集(由于Java是跨平台的,最好指定编码字符集,否则容易出现乱码问题)。StreamDecoder是完成字节到字符解码的实现类。


OutputStreamWriter类完成字符到字节的编码过程,由StreamEncoder完成编码过程。

 

2、       磁盘IO机制

读写文件都是调用操作系统的API(read、write接口)完成的,因为磁盘设备是由操作系统统一管理的,应用程序要访问物理设备只能通过系统调用的方式来工作。发生系统调用时就可能存在内核地址空间和用户地址空间切换的问题,进而导致数据从内核空间向用户空间复制的问题。

如果遇到非常耗时的操作,数据从磁盘复制到内核空间,再从内核空间复制到用户空间,将会非常缓慢。操作系统为了加速IO的访问,在内核空间采用缓存机制将读取的文件按照一定的组织方式进行缓存,如果用户程序访问的是同一段磁盘地址空间的数据,直接将内核缓存中的数据返回给用户程序。

2.1、标准访问文件方式

标准访问文件方式就是当应用程序调用read()接口时,OS会检查内核的高速缓存中有没有需要的数据,如果缓存中存在数据,直接将缓存中的数据返回;如果没有,则从磁盘中读取,然后在内核中缓存并返回给用户程序。

当写入时,用户程序调用write()接口将数据从用户地址空间复制到内核地址空间的缓存中。这时对用户程序来说写操作已完成,至于什么时候再写到磁盘中由OS决定。

 

2.2、直接IO方式

所谓直接IO方式就是应用程序直接访问磁盘数据,而不经过OS内核数据缓冲区,这样可以减少从内核缓冲区到用户程序缓存的数据复制。直接IO也存在缺点,如果访问的数据不在应用程序缓存中,则每次都直接从磁盘加载,增加IO响应时间。通常将直接IO和异步IO结合使用,并在应用层增加缓存机制,会得到较好的性能。


2.3、同步访问文件方式

同步访问文件方式比较容易理解,就是数据的读取和写入都是同步操作。它与标准访问文件方式的不同时,只有当数据被成功写回磁盘时才返回给应用程序成功的标志。这种同步访问文件方式性能比较差,只有在一些对数据安全性要求比较高的场景中使用,而且通常这种操作方式的硬件都是定制的。


2.4、异步访问文件方式

异步访问文件方式就是当访问数据的线程发出请求后,线程接着处理其他事情,而不是阻塞等待;当请求的数据返回后请求线程继续处理下面的操作。这种访问文件的方式可以明显提高应用程序的效率,但并不会改变访问文件的效率。


2.5、内存映射方式

内存映射方式是指操作系统将内存中的某一块区域与磁盘的文件关联起来,当要访问内存中的一段数据时,转换为访问文件中的某一段数据。这种方式同样是减少数据从内核空间缓存到用户空间缓存的数据复制,因为这两个空间的数据是共享的。


2.5、Java访问磁盘文件

数据在磁盘中的访问单位就是文件,也就是说应用程序只能通过文件来操作磁盘上的数据。

在Java中,File是用来处理文件和文件夹,如新建、删除等操作;而并不能来读写文件,File只是指定了读写操作的目标位置,至于如何读取或写入文件,需要相关的输入或输出流来实现。Java读取一段文本文件的过程如下


此外,File类的实例是不可变的;也就是说,一旦创建File对象,File对象表示的抽象路径将不会改变。由于File既可以表示文件,也可以表示目录,在实际应用中可能会有过滤某些文件、文件夹的操作,如下

l  过滤文件

在遍历某个文件夹下所有的文件时,可以创建FileNameFilter实例来对特定的文件进行过滤。

FileNameFilter是个接口,需要提供此接口的实现类,重写accept方法来对特定的文件进行过滤。

l  过滤文件夹

在遍历某个文件夹的文件时,该文件夹可能存在子文件夹,若要实现对文件夹的过滤,可提供FileFilter实现类。

 

3、       网络IO机制

Socket描述了计算机之间完成通信的一种抽象功能,Socket可以有多种实现,如基于UDP的套接字和基于TCP/IP的流套接字。大部分情况下,我们使用的都是基于TCP/IP的流套接字,它是一种稳定的通信协议。

采用基于TCP/IP的流套接字进行通信,首先需要建立连接。当连接已经建立成功,服务端和客户端都会拥有一个Socket实例,每个Socket实例都有一个InputStream和OutputStream实例,并通过这两个对象来交换数据,以实现数据的传输。

网络IO都是基于字节流的,当创建Socket对象时,操作系统会为Socket分配一定大小的缓冲区,数据的读取和写入都是通过缓冲区来完成的。写入端将数据写到OutputStream对应的SendQ队列中,当队列填满时,数据被转移到另一端InputStream的RecvQ队列中,如果RecvQ已满,那么OutputStream的write方法将阻塞直到RecvQ队列有足够的空间容纳SendQ发送的数据。缓冲区的大小以及写入端的速度和读取端的速度都会影响数据传输率,由于可能会发生阻塞,如果通信两端同时传送数据可能会产生死锁的情况。

3.1、BIO

标准IO的各种流是阻塞的(阻塞IO),也就是说当一个线程调用read或write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。在读取或写入期间该线程不能在干其他任何事情了。


BIO(BlockingIO)即阻塞IO,不管是磁盘IO还是网络IO,数据在写入OutputStream或者从InputStream读取数据时都有可能会阻塞,一旦阻塞,线程将失去CPU控制权。

当调用ServerSocket对象的accept方法时,将会一直阻塞到有客户端连接才会返回,每个客户端连接请求,服务器端都回启动一个线程去处理请求。若存在大规模访问量,虽然可以采用线程池技术,为每个请求对应一个处理线程,但线程的创建和回收也会消耗大量的资源。此外线程数量并不是越多越好,在一定范围内增加线程数量,可以提高应用的性能;当线程数量超过一定值后,应用程序的性能将会下降,甚至瘫痪。

此外,如果应用需要保持大量的HTTP长连接(如网页即时聊天工具),若每个长连接对应一个线程,会是怎样的开销。

3.2、NIO

NIO(NewIO)也称非阻塞IO。在标准IO中,所有的操作都是基于流的;而NIO是面向通道和缓冲区的。标准IO基于流是指每次从流中读取一个或多个字节,找到读取所有的字节;此外它不能移动流中的数据,如果需要前后移动从流中读取数据,需要将它缓存到一个缓冲区中。NIO将数据读取到一个缓冲区中,需要时可以在缓冲区中前后移动。

此外,NIO是非阻塞模式的,当一个线程从某个通道发送请求读取数据时,它仅能得到目前可用的数据;如果目前没有可用的数据,就什么都不会获取;但并不会阻塞该线程,所以直到数据变的可读之前,该线程可以继续做其他事情。当一个线程请求写数据到某通道时,不需要等待完全写入,该线程同时可以处理其他事情。线程通常将非阻塞IO的空闲时间用在其他通道上执行IO操作,所以一个单独的线程就可以管理多个输入和输出通道。

 

3.3、总结

在实际应用中,需要根据具体的需求和复杂度来确定采用哪种IO操作方式。并不是说NIO就比保准IO好,各有各的实际用途。

NIO虽然可以只使用单个线程来管理多个通道(网络连接或文件),但其解析数据比保准IO更复杂。如果需要同时管理的连接数达到成千上万个,而且每个连接只是发送少量的数据,如聊天服务器,采用NIO实现是一个比较好的选择。如果连接数较少,且每次需要传输大量的数据,标注IO将是比较好的选择。

4、       总结

 

在Java中IO可按照多个方面进行分类,如下

l  方向:IO流分为输入和输出流;

l  类型:字节流和字符流

l  操作方式:节点流、过滤流

l  转换流

4.1、字节流

字节流在读写文件时,以字节为单位,适合处理二进制文件,如图片、压缩包等。在Java中字节流的基类为InputStream和OutputStream,常用的类(以输入流为例)有FileInputStream、DataInputStream、ObjectInputStream等。

4.1.1、DataInputStream

数据输入流允许程序以与机器无关的方式从底层输入流中读取Java基本数据类型,如可以从二进制文件中读取boolean、byte、int等基本数据类型。

为什么需要DataInputStream和DataOutputStream来进行基本数据类型的读写操作呢?我们知道对于整形数123456,若以字符流保存需要6个字节;而采用字节流,整形只需要4个字节即可,可有效节省空间。

4.1.2、ObjectInputStream

ObjectInputStream对使用ObjectOutputStream写入的基本数据和对象进行反序列化。

总结:在Java中字节流通常以XXXInputStream、XXXOutputStream来命名。

 

4.2、字符流

字符流以字符为基本单位进行读写数据,基类为Reader和Writer。其直接子类有BufferedReader、StringReader、InputStreamReader,FileReader。

总结:

l  输入字符流中BufferedReader比较常用,而字符输入流中,PrintWriter则比较常用,PrintWriter可以格式化输出的数据,如System.out即为PrintWriter实例;

l  在Java中字符流通常以XXXReader、XXXWriter来命名。

4.3、节点流

可直接创建的流,并不需要依赖其他流对象,如FileInputStream。

4.4、过滤流

在Java中,过滤流是采用装饰模式,基于节点流或其他过滤器而创建的功能增强的流,如输入输出缓冲流等(在节点流的基础上增加缓冲区,提高效率)。因此过滤流是不能直接创建实例的,需要依赖其他流实例,以增强流的功能。

4.5、转换流

转换流是将字节流转换为字符流的流,在字符流的基础上提供增强功能。转换流也是过滤器,依赖字节流而存在。如FileReader为InputStreamReader的子类,而InputStreamReader为转换流,所以FileReader也为转换流。

System.in为标准字节流,不方便操作,所以在使用System.in时往往将其装转换为字符流,再转换为BufferedReader来操作。

在Java中,InputStreamReader,OutputStreamWriter来实现字节流和字符流的转换,如下

 

参考资料:

1:http://www.ibm.com/developerworks/cn/linux/l-cn-directio/

2:深入分析Java Web技术内幕/许令波著

 


<think>好的,我现在需要处理用户关于Java IO的问题。用户希望了解Java IO的概念或者解决相关问题,特别是询问教程和故障排除的方法。首先,我得回顾一下Java IO的基础知识,确保自己理解正确,然后组织回答的结构。 首先,Java IO主要涉及输入输出流,用于处理数据的读写。核心包是java.io,里面有很多类,比如File、InputStream、OutputStream、Reader、Writer等。用户可能需要知道这些类的层次结构和使用方法。 接下来,常见的IO操作包括文件读写、网络通信、序列化等。比如,FileInputStream和FileOutputStream用于字节流,而FileReader和FileWriter用于字符流。缓冲流(BufferedInputStream等)可以提高效率,这部分应该提到。 然后,异常处理是Java IO中很重要的一部分,比如IOException,需要正确处理try-with-resources来自动关闭流,避免资源泄漏。这部分用户可能在故障排除时会遇到问题,比如未正确关闭流导致文件锁定或资源耗尽。 关于教程,Oracle的官方文档是权威资源,应该引用。比如引用[3]提到了JSP的信息,但用户需要的是IO的教程,可能需要检查是否有其他引用相关。不过用户给出的引用中,引用[1]是关于JDK 7的故障排除工具,可能和调试IO问题相关,比如使用jdb进行调试。引用[5]提到字节码信息,可能和IO关系不大,但故障排除时可能需要查看异常堆栈,这可能涉及字节码或类加载的问题。 对于故障排除,常见问题包括文件路径错误、权限问题、字符编码不一致导致的乱码,或者流未正确关闭。比如,文件找不到可能是因为相对路径的当前工作目录与预期不符,或者文件被其他进程占用。这时候需要检查路径是否正确,是否有读取权限,使用绝对路径测试,或者使用工具如Process Explorer查看文件句柄。 此外,NIO(New IO)在Java 1.4引入,提供了非阻塞IO和通道等高效处理方式,用户可能也需要了解NIO和传统IO的区别,比如使用Channel和Buffer的方式,或者使用Files类简化操作。 在组织回答时,需要分步骤讲解,比如先介绍核心概念,然后常见操作,接着是故障排除技巧,最后提供学习资源和相关工具。同时,生成的问题要围绕Java IO的进一步学习,比如NIO、性能优化、常见异常处理等。 需要确保回答中的LaTeX格式正确,比如类名和方法名可能需要用代码块,而数学表达式则用$...$。例如,文件路径的转义可能需要讨论,比如在字符串中使用反斜杠需要转义,写成`"C:\\\\path\\\\to\\\\file"`,但这里可能不需要数学表达式,所以可能不需要用到LaTeX的公式部分。 检查用户提供的引用,是否有与IO相关的资源。引用[4]是关于安卓USB驱动的,可能不相关。引用[2]关于反射,可能也不直接相关,但故障排除时可能会用到反射来检查类路径或资源加载,这可能间接相关,但可能不需要特别提到。 总结回答结构: 1. Java IO核心概念:流、字节流与字符流、缓冲流、NIO。 2. 常见操作:文件读写示例代码。 3. 故障排除:常见问题及解决方法,如路径、权限、编码、资源泄漏。 4. 学习资源:官方教程、工具(如jdb)。 5. 相关问题:如何选择IO和NIO,处理大文件,字符编码问题,关闭资源方法,常见异常处理。 需要确保回答中的引用正确,比如引用[1]关于JDK工具,可能在故障排除时使用jdb或者jstack等工具分析问题,可以提到。引用[3]的链接虽然是JSP,但Oracle的Java EE教程可能包含IO相关内容,需要检查链接是否有效,但用户提供的链接可能已失效,需要注意。不过用户给出的引用可能有误,比如引用[3]的网址是javaee.github.io,可能是正确的,但需要确认是否有IO相关部分。 最后,代码示例需要正确,比如使用try-with-resources来读取文件,展示正确的异常处理方式。同时,确保所有技术术语准确,例如InputStreamReader处理字符编码,BufferedReader提高读取效率。 可能的问题点:用户提供的引用中是否有直接关于Java IO的内容?引用[1]提到JDK工具,可以用于调试IO问题,比如使用jdb调试应用程序,或者在出现IO异常时分析线程状态。引用[5]关于字节码信息,可能与IO问题关系不大,但类加载时找不到资源文件可能会涉及,需要看具体情况。 因此,在故障排除部分,可以建议使用JDK工具如jstack检查是否有线程阻塞在IO操作,或者使用jdb进行调试,引用[1]。同时,处理资源泄漏时,可以使用try-with-resources来自动关闭流,这是Java 7的特性,可能需要提到。 综上所述,回答的结构应该是先介绍核心概念,然后示例代码,接着故障排除步骤,引用相关资源,最后生成相关问题。</think>### Java IO 概念与问题排查指南 #### 一、核心概念 Java IO(输入输出)用于处理数据读写,核心包为 `java.io`,主要分为两类: 1. **字节流**:处理二进制数据,基类为 `InputStream` 和 `OutputStream`,例如 `FileInputStream` 和 `BufferedOutputStream`。 2. **字符流**:处理文本数据,基类为 `Reader` 和 `Writer`,例如 `FileReader` 和 `BufferedWriter`。 3. **NIO(New IO)**:Java 1.4 引入,通过 `Channel` 和 `Buffer` 实现高效非阻塞操作,例如 `Files.readAllLines()`[^1]。 #### 二、常见操作示例 1. **文件读取(字符流)**: ```java try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } ``` 2. **文件写入(字节流)**: ```java try (OutputStream out = new BufferedOutputStream(new FileOutputStream("output.dat"))) { out.write("Hello".getBytes()); } catch (IOException e) { e.printStackTrace(); } ``` #### 三、故障排查技巧 1. **文件路径问题** - 使用绝对路径或检查工作目录:`System.getProperty("user.dir")`。 - 路径分隔符兼容性:使用 `File.separator` 替代硬编码的 `/` 或 `\`。 2. **权限与资源占用** - 确认文件可读/写:`file.canRead()` 和 `file.canWrite()`。 - 确保流正确关闭:使用 **try-with-resources**(Java 7+)避免资源泄漏。 3. **字符编码问题** - 指定编码格式:例如 `new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8)`。 4. **调试工具** - 使用 `jdb`(Java Debugger)追踪 IO 异常堆栈[^1]。 - 通过 `jstack` 分析线程阻塞(如网络 IO 超时)。 #### 四、学习资源 1. **官方教程** - [Oracle Java IO 教程](https://docs.oracle.com/javase/tutorial/essential/io/)(涵盖基础与 NIO)。 2. **进阶内容** - Java NIO.2(Java 7+)的 `Path` 和 `Files` 类[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值