一、java基础--IO/NIO

一、JAVA IO


流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

1.JAVA IO流的分类

  • 根据处理数据类型的不同分为:字符流和字节流
  • 根据数据流向不同分为:输入流和输出流

2. 输入字节流 InputStream

  • InputStream 是所有的输入字节流的父类,它是一个抽象类。
  • ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。
  • ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。

3.输出字节流 OutputStream

  • OutputStream 是所有的输出字节流的父类,它是一个抽象类。
  • ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据,
  • ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。

4.输入字符流 Reader

  • Reader 是所有的输入字符流的父类,它是一个抽象类。
  • CharReader、StringReader 是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
  • BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
  • FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
  • InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。

5.输出字符流 Writer

  • Writer 是所有的输出字符流的父类,它是一个抽象类。
  • CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据,
  • BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
  • PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。
  • OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。

6.处理流

处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

常用的处理流

  • 缓冲流:BufferedInputStrean 、BufferedOutputStream、 BufferedReader、 BufferedWriter 增加缓冲功能,避免频繁读写硬盘。
  • 转换流:InputStreamReader 、OutputStreamReader实现字节流和字符流之间的转换。
  • 数据流: DataInputStream 、DataOutputStream 等-提供将基础数据类型写入到文件中,或者读取出来。

7.转换流

InputStreamReader 、OutputStreamWriter 要InputStreamOutputStream作为参数,实现从字节流到字符流的转换。

构造函数

InputStreamReader(InputStream);        //通过构造函数初始化,使用的是本系统默认的编码表GBK。
InputStreamWriter(InputStream,String charSet);   //通过该构造函数初始化,可以指定编码表。
OutputStreamWriter(OutputStream);      //通过该构造函数初始化,使用的是本系统默认的编码表GBK。
OutputStreamwriter(OutputStream,String charSet);   //通过该构造函数初始化,可以指定编码表。

8.File类

File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。

9.RandomAccessFile类

该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点:

  • 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
  • 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)

注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。

10.文件操作常用示例

①FileInputStream

public static void main(String [] args) throws IOException{
		int i=0;
		FileInputStream in=null;
		try {
			in=new FileInputStream("C:\\Users\\15080\\Desktop\\aaaa.txt");//从文件外读数据
		} catch (FileNotFoundException e) {
			// TODO: handle exception
			System.out.println("找不到文件位置");
			System.exit(-1);
		}
		try {
			int num=0;//用于字节个数的计数
			while((i=in.read())!=-1){
				System.out.println((char)i);//将得到的ASCII码值转换成字符型
				num++;
			}
			in.close();
			System.out.println("传输字节个数:"+num);
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println("读取文件错误");
		}
		
	}

②FileOutputStream

public static void main(String []args){
		int i=0;
		FileInputStream in=null;
		FileOutputStream out=null;
		try {
			//实例化FileInputStream,FileOutputStream对象
			in=new FileInputStream("C:\\Users\\15080\\Desktop\\aaaa.txt");
			out=new FileOutputStream("C:\\Users\\15080\\Desktop\\bbbb.txt");
			while((i=in.read())!=-1){
				out.write(i);
				System.out.println((char)i);
			}
			in.close();
			out.close();
			System.out.println("文件已复制");
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println("文件复制错误");
			System.exit(-1);
		} 
		
		
	}

③FileRead、FileWriter

public static void main(String []args){
	FileReader fr=null;
	FileWriter fw=null;
	try {
		fr=new FileReader("C:\\Users\\15080\\Desktop\\eeee.txt");//要读取的文件
		fw=new FileWriter("C:\\Users\\15080\\Desktop\\ffff.txt");//要复制的目的文件
		int i=0;
		while((i=fr.read())!=-1){
			fw.write(i);
			System.out.println((char)i);//将得到的ASCII码值转换成字符型
		}
		fr.close();
		fw.close();
	} catch (Exception e) {
		// TODO: handle exception
	}
}

二、JAVA NIO

NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO 基于字节流和字 符流进行操作,而NIO基于 Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区 中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开, 数据到达)。因此,单个线程可以监听多个数据通道。 

NIO和传统IO 之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 

1.NIO的缓冲区 

 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何 地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓 存到一个缓冲区。

NIO的缓冲导向方法不同。数据读取到一个它稍后处理的缓冲区,需要时可在 缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所 有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的 数据。 

2.NIO的非阻塞 

IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有 一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

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

3.Channel 

首先说一下Channel,国内大多翻译成“通道”。Channel和IO 中的Stream(流)是差不多一个 等级的。只不过Stream是单向的,譬如:InputStream, OutputStream,而Channel是双向 的,既可以用来进行读操作,又可以用来进行写操作。

NIO中的Channel的主要实现有: 

① FileChannel

② DatagramChannel

③ SocketChannel

④ ServerSocketChannel

这里看名字就可以猜出个所以然来:分别可以对应文件IO、UDP和TCP(Server和Client)。 下面演示的案例基本上就是围绕这4个类型的Channel进行陈述的。 

4. Buffer 

Buffer,故名思意,缓冲区,实际上是一个容器,是一个连续数组。Channel提供从文件、 网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer。 

上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送 数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必 须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。 

在NIO中,Buffer是一个顶层父类,它是一个抽象类,常用的Buffer的子类有: ByteBuffer、IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、FloatBuffer、 ShortBuffer 

5.Selector 

Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用 函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护 多个线程,并且避免了多线程之间的上下文切换导致的开销。 

三、IO模型

1.阻塞 IO 模型 

最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。当用户线程发出IO 请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。典型的阻塞IO模型的例子为:data = socket.read();如果数据没有就 绪,就会一直阻塞在read方法。

2.非阻塞 IO 模型 

当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个 error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备 好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。 所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO 不会交出CPU,而会一直占用CPU。典型的非阻塞IO模型一般如下: 

while(true){  
 data = socket.read();  
 if(data!= error){
  处理数据
  break;
 } 
}

但是对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问内核数据是否就 绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。 

3.多路复用 IO 模型 

多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。在多路复用IO 模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作

因为在多路复用IO模型中,只需要使用一个线程就可以管理多个 socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有 socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。

在Java NIO中,是通 过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这 种方式会导致用户线程的阻塞。多路复用IO 模式,通过一个线程就可以管理多个socket,只有当 socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO 比较适合连 接数比较多的情况。 

另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO 中,不断地询问socket状态 时通过用户线程去进行的,而在多路复用IO 中,轮询每个socket状态是内核在进行的,这个效 率要比用户线程要高的多。 

不过要注意的是,多路复用IO 模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件 逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件 迟迟得不到处理,并且会影响新的事件轮询

4.信号驱动 IO 模型 

在信号驱动IO 模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函 数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到 信号之后,便在信号函数中调用IO 读写操作来进行实际的IO 请求操作。 

5.异步 IO 模型 

异步IO模型才是最理想的IO 模型,在异步IO 模型中,当用户线程发起read操作之后,立刻就 可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后, 它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内 核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程 发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何 进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接 去使用数据了。 

也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完 成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO 函数进行具体的 读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据 已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号 表示IO操作已经完成,不需要再在用户线程中调用IO函数进行实际的读写操作。 

注意,异步IO 是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO。 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值