Java输入输出(I/O)
Java I/O 系统很复杂。Java语言定义了许多类专门负责各种方式的输入或者输出,这些类主要被放在java.io包和java.nio包(意思是"new I/O")中,但这还不是全部,如可以用java.util.Scanner类来从控制台和文件中读取文本和数值,java.lang.System类提供了用于文本控制台程序(字符界面程序)的标准输入,标准输出和错误输出流。【关于java.util.Scanner类和java.lang.System类,参见 “Java基础类库(系统包)”https://blog.csdn.net/cnds123/article/details/111879459 一文有关部分。】
Java I/O 系统很复杂,原因在于对于一门编程语言,创建一套好的输入输出(I/O)系统,是一项难度极高的任务。这个问题难就难在它要面对的可能性太多了。不仅是因为有那么多I/O的源和目地(文件,控制台,网络连接等等),而且还有很多方法(顺序的[sequential],随机的[random-access],缓存的[buffered],二进制的[binary],字符方式的[character],行的[by lines],字的[by words],等等)。Java类库的设计者们用"创建很多类"的办法来解决这个问题。Java在1.0版之后又对其I/O类库作了重大的修改,原先是面向byte的,现在又补充了面向Unicode字符的类库。为了提高性能,完善功能,JDK 1.4又加了一个nio(意思是"new I/O")。这么以来,如果你想对Java的I/O类库有个全面了解,并且做到运用自如,你就得先学习了解大量的类,这一点确实比较难。本文将选择部分相关知识介绍。
本文将重点放在java.io包这一部分。
官网 https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/package-tree.html
中文 https://www.runoob.com/manual/jdk11api/java.base/java/io/package-tree.html
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。其体系结构分为三个部分:
1)流式部分――IO的主体部分;
2)非流式部分――主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类;
3)其他类——文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
Java输入输出(I/O)体系结构示意图:
IO流定义:
流的本质是一组单向有序,分起始和终止的数据传输过程。
一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。Java中的数据流分为两种,一种是字节流,另一种是字符流。这两种流主要由4个抽象类来表示,分别为InputStream,OutputStream,Reader,Writer,输入输出各两种。其中InputStream和OutputStream表示字节流,Reader和Writer表示字符流,其他各种各样的流均是继承这4个抽象类而来的。
【输入流就是把数据(键盘输入、鼠标、扫描仪等等外设设备)读入到内存(程序)中,输出流就是把内存(程序)中的数据输出到外设或其他地方,从文件角度简单总结就是,输入流就是读数据,输出流就是写数据。在这个过程中,始终把内存作为参考点。】
IO流分类:
按数据类型分为:字节流和字符流
字节流:
按字节进行读取(可以处理任意类型数据)
字符流
字节流 + 编码表(处理纯文本数据优先考虑)
【字符流与字节流的区别
字节流操作的基本单元为字节;字符流操作的基本单元为Unicode编码。
字节流默认不使用缓冲区;字符流使用缓冲区。
字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode编码;字符流通常处理文本数据,它支持写入及读取Unicode编码。】
按数据流向分为:输入流和输出流
输入流
有Reader(字符输入流)、InputStream(字节输入流)
输出流
有Writer(字符输出流)、OutputStream(字节输出流)
File类主要用于命名文件、查询文件属性和处理文件目录。特别指出,java.io提供了一个File类,注意这个类很容易让人产生误会,它表示的是一个文件名或者目录名,而不是文件本身,所以通过这个类没法对文件里面的数据进行操作,File类提供了一序列对文件操作的功能:删除文件,创建目录,查询文件大小等等。
Java字节流类的层次结构和字符流类的层次结构:
InputStream字节输入流的基类(父类),OutputStream字节输出流的基类;Reader字符输入流的基类,Writer字符输出流的基类。其它Io流的40多个类都是从由这4个抽象类基类中派生出来的。
java输入/输出流体系中常用的流的分类表:
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 |
|
| StringReader | StringWriter |
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 |
|
| InputStreamReader | OutputStreamWriter |
对象流 | ObjectInputStream | ObjectOutputStream |
|
|
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 |
| PrintStream |
| PrintWriter |
推回输入流 | PushbackInputStream |
| PushbackReader |
|
其中,表中粗体字所标出的类代表节点流,必须直接与指定的物理节点关联:用下划线标出的类代表抽象基类,无法直接创建实例。
☆InputStream类中的常用方法:
1. public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。
2. public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数。该方法实际上是调用下一个方法实现的。
3. public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中。
4. public int available( ):返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。
5. public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取。
6. public int close( ) :我们在使用完后,必须对我们打开的流进行关闭。
☆OutputStream类中的常用方法:
1. public void write(byte b[ ]):将参数b中的字节写到输出流。
2. public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。
3. public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。
4. public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。
5. public void close( ) : 关闭输出流并释放与流相关的系统资源。
注意:
(1). 上述各方法都有可能引起异常。
(2). InputStream和OutputStream都是抽象类,不能创建这种类型的对象。
☆FileInputStream类
FileInputStream类是InputStream类的子类,用来处理以文件作为数据输入源的数据流。使用方法:
方式1:
File fin=new File("d:/abc.txt");
FileInputStream in=new FileInputStream(fin);
方式2:
FileInputStream in=new
FileInputStream("d: /abc.txt");
方式3:
构造函数将 FileDescriptor()对象作为其参数。
FileDescriptor() fd=new FileDescriptor();
FileInputStream f2=new FileInputStream(fd);
☆FileOutputStream类
FileOutputStream类用来处理以文件作为数据输出目的数据流;一个表示文件名的字符串,也可以是File或FileDescriptor对象。
创建一个文件流对象方法:
方式1:
File f=new File("d:/abc.txt");
FileOutputStream out=new FileOutputStream (f);
方式2:
FileOutputStream out=new
FileOutputStream("d:/abc.txt");
方式3:构造函数将 FileDescriptor()对象作为其参数。
FileDescriptor() fd=new FileDescriptor();
FileOutputStream f2=new FileOutputStream(fd);
方式4:构造函数将文件名作为其第一参数,将布尔值作为第二参数。
FileOutputStream f=new FileOutputStream("d:/abc.txt",true);
注意:
(1). 文件中写数据时,若文件已经存在,则覆盖存在的文件;
(2). 的读/写操作结束时,应调用close方法关闭流。
☆DataInputStream类和DataOutputStream类
DataInputStream类对象可以读取各种类型的数据。
DataOutputStream类对象可以写各种类型的数据。
创建这两类对象时,必须使新建立的对象指向构造函数中的参数对象。例如:
FileInputStream in=new FileInputStream("d:/abc.txt");
DataInputStream din=new DataInputStream(in);
☆BufferInputStream类和bufferOutputStream类
BufferInputstream定义了两种构造函数
(1). BufferInputStream b= new BufferInputstream(in);
(2). BufferInputStream b=new BufferInputStream(in,size) //第二个参数表示指定缓冲器的大小。
同样BufferOutputStream也有两种构造函数。
☆printstream类
用于写入文本或基本类型。
两种构造函数方法:
PrintStream ps=new PrintStream(out);
PrintStream ps=new PrintStream(out, autoflush)
☆FileReader类
FileReader主要用来读取字符文件,使用缺省的字符编码,有三种构造函数:
--将文件名作为字符串
FileReader f=new FileReader(“c:/temp.txt”);
--构造函数将File对象作为其参数。
File f=new file(“c:/temp.txt”);
FileReader f1=new FileReader(f);
--构造函数将FileDescriptor对象作为参数
FileDescriptor() fd=new FileDescriptor()
FileReader f2=new FileReader(fd);
☆ charArrayReader
将字符数组作为输入流,构造函数为:
public CharArrayReader(char[] ch);
☆StringReader
读取字符串,构造函数如下:
public StringReader(String s);
☆ InputStreamReader
从输入流读取字节,在将它们转换成字符。
Public inputstreamReader(inputstream is);
☆FilterReader
允许过滤字符流
protected filterReader(Reader r);
☆BufferReader
接受Reader对象作为参数,并对其添加字符缓冲器,使用readline()方法可以读取一行。
Public BufferReader(Reader r);
☆FileWrite
将字符类型数据写入文件,使用缺省字符编码和缓冲器大小。
Public FileWrite(file f);
☆chararrayWrite()
将字符缓冲器用作输出。
Public CharArrayWrite();
☆ PrintWrite
生成格式化输出
public PrintWriter(outputstream os);
☆ filterWriter
用于写入过滤字符流
protected FilterWriter(Writer w);
例1、下面的程序向文件scores.txt中写两行文本
public class WriteDataTest {
public static void main(String[] args) throws java.io.IOException {
java.io.File file = new java.io.File("d:/scores.txt");
if (file.exists()) {
System.out.println("文件已存在");
System.exit(0);
}
// 创建一个文件
java.io.PrintWriter output = new java.io.PrintWriter(file);
// 写入文件
output.print("李萌 ");
output.println(90.5);
output.print("赵一男 ");
output.println(85);
// 关闭文件
output.close();
}
}
运行完毕,在d盘文件scores.txt中的内容如下:
李萌 90.5
赵一男 85
使用blue演示如下图
附录、java.nio包
官网 https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/package-tree.html
中文 https://www.runoob.com/manual/jdk11api/java.base/java/nio/package-tree.html
java.nio与java.io之间有什么区别
IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无选择器 选择器
(1)面向流与面向缓冲
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
(2)阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
(3)选择器(Selectors)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。IO 没有选择器。