1 Java中有哪几种类型的流
字符流和字节流,字节流需要继承inputStream和OutputStream,字符流需要继承自Reader和Writer, InputStream,OutputStream,Reader,writer都是抽象类。所以不能直接new。
1.1 区别
字节流是最基本的,主要用在处理二进制数据,它是按字节来处理的。但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化,可以直接读取unicode码元。
缓冲区
某些情况下,如果一个程序频繁地操作一个资源性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可,读取内存速度会比较快,可以提升程序的性能。
字节流默认不使用缓冲区,字符流使用缓冲区。在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据。
对于字符流关闭流(close)之前磁盘是不会刷新的,如果我们想在字符流操作中主动将缓冲区刷新到文件则可以使用 flush() 方法操作。
数据类型
字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元
所谓Unicode码元,也就是一个Unicode代码单元,范围是0x0000~0xFFFF。在以上范围内的每个数字都与一个字符相对应,Java中的String类型默认就把字符以Unicode规则编码而后存储在内存中,
1.2 转化
这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联, 在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的。
转化示例
byte[]转化为String:public String(byte bytes[], String charsetName);
String转化为byte[]:byte[] getBytes(String charsetName)
转化原理
Java中的字符流处理的最基本的单元是Unicode码元(大小2字节),它通常用来处理文本数据。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式。实际上字符流是这样工作的:
输出字符流:把要写入文件的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列,然后再写入到文件中;
输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列)从而可以存在内存中。
1.3 选择
大多数情况下使用字节流会更好,因为大多数时候 IO 操作都是直接操作磁盘文件,这些流在传输时都是以字节的方式进行的,如果对于操作需要频繁处理字符串的情况使用字符流会好些,因为字符流具备缓冲区,提高了性能
2 节点流和处理流
节点流直接与数据源相连,用于输入或者输出;处理流在节点流的基础上对之进行加工,进行一些功能的扩展;处理流的构造器必须要传入节点流的子类
3 如何关闭流
流一旦打开就必须关闭,使用close方法;放入finally语句块中(finally 语句一定会执行);调用的处理流就关闭处理流,多个流互相调用只关闭最外层的流
4 了解BufferedReader
属于处理流中的缓冲流,可以将读取的内容存在内存里面,有readLine()方法,它,用来读取一行
5 实现Java序列化
序列化就是一种用来处理对象流的机制,将对象的内容进行流化。可以对流化后的对象进行读写操作,可以将流化后的对象传输于网络之间。序列化是为了解决在对象流读写操作时所引发的问题。
实现Serialize接口,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,再使用ObjectOutputStream对象的write(Object obj)方法就可以将参数obj的对象写出
6 PrintStream
PrintStream继承了FilterOutputStream.是"装饰类"的一种,所以属于字节流体系中
特点
不抛异常:与其他流不同的是,PrintStream流永远不会抛出异常.因为做了try{}catch(){}会将异常捕获,出现异常情况会在内部设置标识,通过checkError()获取此标识
自动刷新:PrintStream流有自动刷新机制,例如当向PrintStream流中写入一个字节数组后自动调用flush()方法, 即在调用了println()方法,或者文本中出现“\n”,就会自动flush
输出流->缓冲流->转化流->文件流->文件
PrintStream流不是直接将数据写到文件的流,需要传入底层输出流out,而且要实现指定编码方式,需要中间流OutputStreamWriter,OutputStreamWriter流实现了字符流以指定编码方式转换成字节流
BufferWriter
此外为了提高写入文件的效率,使用到了字符缓冲流BufferWriter
内部变量
autoFlush----是否自动刷新缓冲区
trouble----是否抛出异常的内部标识,当PrintStream流内部抛出异常时会捕获异常,然后将trouble的值设置成true
formatter----用于数据格式化的对象Formatter
textOut----PrintStream流本身不具备指定编码功能,BufferedWriter提供了缓冲数据的功能
OutputStreamWriter提供了按照指定编码方法将字符转化成字节的功能
内部方法
flush()----刷新流,将缓冲的数据写到底层输出流中
close()—关闭流,释放关联的资源.
checkError()—检查流中异常状态,如果PrintStream流中有异常抛出,返回true.
write(int b)----将单个字节b写到PrintStream流中.
write(byte buf[] ,int off,int len)----将字节数组buf中off位置开始,len个字节写到PrintStream流中.
printf(String format, Object … args)----将数据args按照默认的Locale值和format格式进行格式化后写到PrintStream流中,方法执行等同于out.format(format, args)
printf(Locale l, String format, Object … args)----将数据args根据Locale值和format格式进行格式化后写到PrintStream输出流中,方法执行等同于out.printf(l, format,args).
format(String format, Object … args)----根据默认的Locale值和format格式来格式化数据args.
format(Locale l, String format, Object … args)----将数据args根据Locale值和format格式进行格式化.
append(CharSequence csq, int start, int end)----将字符序列csq中start(包含)位置到end(不包含)之间的子字符序列添加到PrintStream输出流中,此方法执行等同于out.print(csq.subSequence(start, end).toString()).
append(char c)----将单个字符添加到PrintStream输出流中.此方法执行等同于out.print©.
7PrintStream与PrintWriter的区别
PrintStream是OutputStream的子类,PrintWriter是Writer的子类,两者处于对等的位置上,所以它们的API是非常相似的。PrintWriter实现了PritnStream的所有print方法
二进制和文本
PrintStream主要操作字节流,也就是说打印出来所有字符按照平台的编码转换为字节,所以一般用于二进制文件, 而PrintWriter用来操作字符流, 缺省是用UNICODE编码
字节参数
PrintStream方法包含写入单个字节和字节数组的方法, PrintWriter流中没有写入字节的方法,而有写入单个字符和字符数组的方法
在自动刷新方面
PrintStream在遇到换行符的时候就会自动刷新,即在调用了println()方法,或者文本中出现“\n”,就会自动flush;PrintWriter则不会,要在构造方法中设置自动刷新,或者手动flush
8 BufferWriter
字符缓冲输出流, 将文本写入字符输出流,缓冲各个字符从而提供单个字符,数组和字符串的高效写入。通过write()方法可以将获取到的字符输出,然后通过newLine()进行换行操作。字符流必须通过调用flush 才能将其刷出去
构造方法
BufferedWriter(Writer out) :默认缓冲区大小构造字符缓冲输出流对象;
BufferedWriter(Writer out,int size):指定缓冲区大小
常用方法
public void write(int c) throws IOException:写入单个字符(c - 指定要写入字符的 int)。
public void write(String str) throws IOException写入字符串。
public void close() throws IOException:关闭此流,但要先刷新它。
特有方法
public void newLine() throws IOException:写入一个行分隔符。
9 FileWriter和BufferedWriter区别和用法
两个都可以作为写入的流,那么两个的区别在哪里呢
参数:首先,如果要使用BufferedWriter,一定会要用到FileWriter
效率:使用的BufferedWriter的效率要比FileWriter高很多,原因很简单,前者有效的使用了缓存器,将缓存写满以后(或者close以后)才输出到文件中,然而后者是没写一次数据,磁盘就会进行一次写操作,性能差得一匹
10 PrintWriter和BufferedWriter区别和用法
方法参数
1.PrintWriter的print、println方法可以接受任意类型的参数,而BufferedWriter的write方法只能接受字符、字符数组和字符串;
自动换行
2.PrintWriter的println方法自动添加换行,BufferedWriter需要显示调用newLine方法;
抛异常
- PrintWriter的方法不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生;
自动刷新
- PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush);
构造参数
- PrintWriter的构造方法更广, PrintWriter不但能接收字符流,也能接收字节流;BufferedWriter只能对字符流进行操作,如果要对字节流操作,则使用BufferedInputStream。
11 BIO,NIO,AIO及其区别
11.1 BIO(block input output)
传统的网络通讯模型,就是BIO,同步阻塞IO
服务端和客户端
服务端创建一个ServerSocket,接收到了一个连接请求就创建一个Socket和一个线程去跟连过来的客户端Socket进行通讯。
阻塞式的通信
客户端和服务端就进行阻塞式的通信,客户端发送一个请求,服务端Socket进行处理后返回响应,在响应返回前,客户端那边就阻塞等待,上门事情也做不了。
Acceptor线程模型
传统的IO模型的网络服务的设计模式中有俩种比较经典的设计模式: 一个是多线程, 一种是依靠线程池来进行处理。
缺点(不适用大量客户端)
每次一个客户端接入都需要在服务端创建一个线程来服务这个客户端,大量客户端来的时候,可能会造成服务端过载过高(几千甚至几万),最后崩溃死掉。
11.2 NIO
NIO是一种同步非阻塞IO, 基于Reactor模型来实现的,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可
11.2.1 Channel
Channel是NIO中的数据通道,用于源节点和目标节点的连接,在java nio中负责缓冲区中数据的传输。类似流,但是又有些不同,Channel即可从中读取数据,又可以从写数据到通道中,但是流的读写通常是单向的。
异步:Channel可以异步的读写。Channel中的数据总是要先读到一个Buffer中,或者从缓冲区中将数据写到通道中,
种类: FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel。这些通道涵盖了文件IO、UDP IO和 TCP 网络IO。
获取通道的三种方式
java针对支持通道的类(本地IO: FileInputStream/FileOutputStream、RandomAccessFile;网络IO:Socket、DatagramSocket)提供了getChannel()方法:
FileChannel inChannel = new FileOutputStream(“2.jpg”).getChannel();
在JDK1.7中的NIO.2针对各个通道提供了静态方法open():
FileChannel inChannel = FileChannel.open(Paths.get(“1.jpg”), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get(“2.jpg”),StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
在JDK1.7中NIO.2的Files工具类的newByteChannels():
ByteChannel byteChannel = Files.newByteChannel(Paths.get(“1.jpg”),StandardOpenOption.READ);
通道之间的数据传输(隐含使用了直接缓存)
inChannel .transferTo(0,inChannel.size(),outChannel);
outChannel .transferFrom(inChannel,0,inChannel.size());
分散与聚集
Channel的read和write方法也支持Buffer数组,可以将数据按顺序依次读取或写入。
11.2.2 Buffer
一般来说,如果你要通过NIO写数据到文件或者网络,或者是从文件和网络读取数据出来此时就需要通过Buffer缓冲区来进行。在Java NIO中负责数据的存取,缓冲区就是数组,用于存储不同数据类型的数据(boolean除外),一般通过如ByteBuffer.allocate(1024)方法获取缓冲区,写入数据到Buffer,调用flip()方法;从Buffer中读取数据,调用clear()方法或者compact()方法
空间属性
capacity: 缓冲区容量的大小,就是里面包含的数据大小,声明后就不能改变。
limit:对buffer缓冲区使用的一个限制,从这个index开始就不能读取数据了,在写模式下表示最多能写入多少数据,此时和Capacity相同
position:代表着数组中可以开始读写的index, 类似于读写指针, 不能大于limit。
mark:是类似路标的东西,在某个position的时候,设置一下mark,此时就可以设置一个标记,后续调用reset()方法可以把position复位到当时设置的那个mark上去,把position或limit调整为小于mark的值时,就丢弃
0<=mark<=position<=limit<=capacity
方法
get([args]):读取数据
put(arg):存数据
flip():在写模式下调用flip方法,那么limit就设置为了position当前的值(即当前写了多少数据),postion会被置为0,以表示读操作从缓存的头开始读。也就是说调用flip之后,读写指针指到缓存头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小),如果已定义了标记,则丢弃该标记,比如重新读
rewind:将position置0,清除mark,它的作用在于为提取Buffer的有效数据做准备
clear():调用clear()方法:position将被设回0,limit设置成capacity。如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”
compact(): compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
hasRemaining():缓冲区中是否还有剩余数据
直接缓冲区(DirectBuffer)
将缓冲区建立在物理内存中,可以跳过用户内存和内核内存之间的复制操作,提高io效率,可以调用allocateDirect()方法来创建,但是此方法分配和取消分配所需成本通常高于非直接缓冲区,而且无法通过JVM进行实时管理,内存无法及时回收(clear),只能等待垃圾回收该无用缓存,严重时会导致操作系统内存紧张。也可以通过FileChannel的map()方法创建MappedByteBuffer,可以直接通过这个MappedByteBuffer进行数据传输而非Channel。建议经过测试后能带来明显的效率提升和持久缓存的情况下才使用。
11.2.3 多路复用(Selector和Channel)
一个Selector不断轮询多个Channel,只有当莫个Channel有对应的请求的时候才会创建线程,由于没有多个线程阻塞等待某个资源,服务端也就不存在阻塞了
11.3 AIO
异步非阻塞IO,基于Proactor模型实现。 每个连接发送过来的请求,都会绑定一个Buffer,数据都是通过buffer来完成读写。
读:首先通知操作系统去完成异步的读,等到操作系统完成读之后,就会调用你的接口给你读完的数据,这个时候你就可以拿到数据进行处理
写:在往回写的过程,同样是给操作系统一个Buffer,让操作系统去完成写,写完了来通知你。
将数据写入的缓冲区后,就不去管它,剩下的去交给操作系统去完成。操作系统写回数据也是一样,写到Buffer里面,写完后通知客户端来进行读取数据。
11.4 NIO 与IO的主要区别
IO面向流(Stream),NIO面向缓冲区(Buffer)
Stream就像水流一样,本身具有数据和流动方向,本身是不断流动的;Buffer就像火车,可以用来运输数据,但是没有方向行,来回都可以,连接两端的铁轨就是Channel,它记录了连接信息。简而言之,Channel负责传输,Buffer负责存储。
阻塞与非阻塞的网络通信
首先,这个概念是在存在网络通信的情况下才出现的,也就是存在客户端可服务端的情况下才有的,本地IO不在这个概念的讨论范围之内。然后阻塞是线程的一个概念,是多个线程对同一资源发生竞争时导致的。
BIO即是说服务端为客户端的每一个请求都生成一个线程阻塞式的等待资源可用,这在高并发环境下显然会导致创建大量线程。而NIO会有一个selector的组件,Channel会首先在该组件上完成注册,selector就会不断循环所有Channel上是否有感兴趣的事件发生,如果有,就会创建线程(一般是从线程池里取)去处理。非阻塞也就是说服务端线程不存在资源竞争问题,但是和BIO一样,客户端线程还是要阻塞等待结果的,同步异步指的就是这个了。