java IO操作
1. Java中String与BufferedReader、InputStream转换
2. 字符流
字符串转字节流
byte[] bytes="hello world".getBytes("UTF-8");
for(byte b :bytes){
System.out.print(b+" ");
}
/**
*hello world是字符
*104 101 108 108 111 32 119 111 114 108 100是字节
*/
打印结果如下:(a~z的ASCII码是97 ~ 122,空格是32,故hello对应104 101 108 108 111)
104 101 108 108 111 32 119 111 114 108 100
- ASCII码:一个英文字母(不分大小写)占一个字节的空间(1字节=8位),一个中文汉字占两个字节的空间。
- UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节
- Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节。中文标点占两个字节,英文标点占两个字节
byte[] bytes="你好 世界".getBytes("UTF-8");
for(byte b :bytes){
System.out.print(b+" ");
}
-28 -67 -96 -27 -91 -67 32 -28 -72 -106 -25 -107 -116
ps:操作系统中没有-1的编码
3. 为什么“-1”能作为判断文本文件结束的标志然而为什么不能作为二进制文件是否结束的标志?
在java中,由于没有编码为-1的字符,所以操作系统就使用-1作为硬盘上的每个文件的结束标志。
这种使用“-1”作为判断文本文件的结束标志而不能作为判断二进制文件是否结束。尽管二进制文件的结尾标记也是-1,当程序读取到一个正好为-1的字节时,就难以偶按段是文件结尾还是文件中的有效数据。
对于标准的二进制文件,在文件开始的部分,都有一个文件头指定文件的大小,程序就是凭借文件头中的这个大小来读取文件的所有内容。
Reader
用于读取字符流的抽象类。子类必须实现的唯一方法是
read(char[],int,int)【尝试将字符读入指定的字符缓冲区。缓冲区按原样用作字符存储库:所做的唯一更改是put操作的结果。不执行缓冲区的翻转或倒带。】和
close()
【关闭流并释放与其关联的任何系统资源。流关闭后,进一步的read()、ready()、mark()、reset()或skip()调用将引发IOException。关闭以前关闭的流无效。】。
然而,大多数子类将重写这里定义的一些方法,以提供更高的效率、附加功能,或者两者兼而有之
BufferedReader
从字符输入流中读取文本,缓冲字符,以便有效地读取字符、数组和行。
可以指定缓冲区大小,也可以使用默认大小。默认值对于大多数目的来说都足够大。
一般来说,读卡器发出的每个读请求都会导致对底层字符或字节流发出相应的读请求。因此,建议将BufferedReader包装在read()操作可能代价高昂的任何读卡器(如FileReaders和InputStreamReaders)周围。例如,
BufferedReader in = new BufferedReader(new FileReader("foo.in"));
将缓冲来自指定文件的输入。如果不进行缓冲,每次调用read()或readLine()都可能导致从文件中读取字节,转换为字符,然后返回,这可能非常低效。(不使用缓冲,则就是每次都访问外存,从外存中读取字节转字符这样就增加了IO操作,我们知道io操作是很耗时的。使用缓冲就可以将外存中的字节成批的读入内存的缓冲区中,从而在内存中将字节转字符。这样极大的减少了io操作…也极大地提高了读写的性能FileReader是从外存中去读,而BufferReader是在内存中进行操作。从内存中转100个字符需1秒,可从外存中读可能需要10秒)
使用DataInputStreams进行文本输入的程序可以通过使用适当的BufferedReader替换每个DataInputStream进行本地化。
read方法如下:
将字符读入数组的一部分。
该方法实现了Reader类的相应read方法的一般约定。作为一个额外的便利,它试图通过重复调用底层流的read方法来读取尽可能多的字符。此迭代读取将继续,直到下列条件之一变为真:
-
已读取指定数量的字符,
-
底层流的read方法返回-1,表示文件结束,或者
-
底层流的ready方法返回false,指示将阻止进一步的输入请求。
如果对基础流的第一次读取返回-1以指示文件结束,则此方法返回-1。否则,此方法返回实际读取的字符数。
该类的子类被鼓励(但不是必需的)尝试以相同的方式读取尽可能多的字符。
通常,此方法从该流的字符缓冲区中提取字符,必要时从底层流中填充。但是,如果缓冲区为空,标记无效,并且请求的长度至少与缓冲区一样大,则此方法将直接从基础流中将字符读取到给定数组中。因此,冗余的BufferedReaders不会不必要地复制数据。
ready()方法
指示此流是否准备好读取。如果缓冲区不是空的,或者如果基础字符流已准备就绪,则缓冲字符流已准备就绪。
readLine()
阅读一行文字。换行符(’\n’)、回车符(’\r’)或紧跟换行符的回车符中的任何一个都会将行终止。
InputStreamReader
InputStreamReader是从字节流到字符流的桥梁:它读取字节并使用指定的字符集将其解码为字符。它使用的字符集可以按名称指定或显式给定,也可以接受平台的默认字符集。
每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流读取一个或多个字节。为了实现字节到字符的有效转换,可以从底层流中提前读取比满足当前读取操作所需的字节更多的字节。
为了获得最高效率,请考虑将InputStreamReader包装在BufferedReader中。例如:
BufferedReader in= new BufferedReader(new InputStreamReader(System.in));
public InputStreamReader(InputStream in, CharsetDecoder dec) {
super(in);
if (dec == null)
throw new NullPointerException("charset decoder");
sd = StreamDecoder.forInputStreamReader(in, this, dec);
}
4. 字节流
InputStream
这个抽象类是表示字节输入流的所有类的超类。
需要定义InputStream子类的应用程序必须始终提供返回下一个输入字节的方法。
read(byte[] b,int off, int len)
从输入流中读取最多len字节的数据到字节数组中。尝试读取多达len字节的数据,但可能会读取较小的数据。实际读取的字节数作为整数返回。
此方法将阻塞,直到输入数据可用、检测到文件结尾或引发异常。
如果len为0,则不读取字节并返回0;否则,将尝试至少读取一个字节。如果由于流位于文件末尾而没有字节可用,则返回值-1;否则,将至少读取一个字节并将其存储到b中。
读取的第一个字节存储在元素b[off]中,下一个字节存储在元素b[off+1]中,以此类推。读取的字节数最多等于len。设k为实际读取的字节数;这些字节将存储在元素b[off]到b[off+k-1]中,而元素b[off+k]到b[off+len-1]不受影响。
在任何情况下,元素b[0]到b[off]和元素b[off+len]到b[b.length-1]都不受影响。
类InputStream的read(b,off,len)方法只是重复调用read()方法。如果第一次这样的调用导致IOException,则从对read(b,off,len)方法的调用中返回该异常。如果对read()的任何后续调用导致IOException,则捕获异常并将其视为文件结尾;读取到该点的字节存储在b中,并返回发生异常之前读取的字节数。此方法的默认实现将阻塞,直到已读取请求的输入数据量len、检测到文件结尾或引发异常为止。鼓励子类提供此方法的更有效实现
参数:
b—数据被读入的缓冲区。
off—数组b中写入数据的起始偏移量。
LEN——读取的最大字节数。
返回:
读取到缓冲区中的字节总数,如果由于已到达流的结尾而没有更多数据,则为-1。
Throws:
IOException—如果第一个字节由于文件结尾以外的任何原因无法读取,或者如果输入流已关闭,或者如果发生其他I/O错误。
NullPointerException-如果b为空。
indexOutboundsException-如果off为负,len为负,或者len大于b。leng-off
Write类
用于写入字符流的抽象类。子类必须实现的唯一方法是write(char[]、int、int)、flush()和close()。然而,大多数子类将重写这里定义的一些方法,以提供更高的效率、附加功能,或者两者兼而有之。
write()
写一个字符。要写入的字符包含在给定整数值的16个低阶位中;忽略16个高阶位。
打算支持有效的单字符输出的子类应该重写此方法。
append()
将指定字符序列的子序列追加到此编写器。可追加。
当csq不为空时,调用out.append(csq,start,end)形式的方法的行为与调用完全相同
out.write(csq.subSequence(start,end.toString())
Specified by:
可追加接口
Parameters:
csq-附加子序列的字符序列。如果csq为空,那么字符将被追加,就像csq包含四个字符“null”。
start-子序列中第一个字符的索引
end-子序列中最后一个字符后面的字符的索引
Returns:
This writer
Throws:
indexOutboundsException-如果start或end为负,则start大于end,或end大于csq.length()
IOException-如果发生I/O错误
flush()
字节流是直接与数据产生交互,而字符流在与数据交互之前要经过一个缓冲区
使用字符流对资源进行操作的时候,如果不使用close()方法,则读取的数据将保存在缓冲区中,要清空缓冲区中的数据有两种办法:
public abstract void close() throws IOException
关闭流的同时将清空缓冲区中的数据,该抽象方法由具体的子类实现
public abstract void flush() throws IOException
不关闭流的话,使用此方法可以清空缓冲区中的数据,但要注意的是,此方法只有Writer类或其子类拥有,而在Reader类中并没有提供。此方法同样是在具体的子类中进行实现 。
当清空缓冲区,再写入之后,如果再执行close()关闭流的方法,数据将正常写入
刷新流。
如果流已将各种write()方法中的任何字符保存在缓冲区中,请立即将它们写入其预期目标。然后,如果目标是另一个字符或字节流,则刷新它。因此,一次flush()调用将刷新写入程序和输出流链中的所有缓冲区。
如果此流的预期目的地是底层操作系统(例如文件)提供的抽象,则刷新该流仅保证先前写入该流的字节被传递给操作系统进行写入;它不能保证它们实际上被写入物理设备,如磁盘驱动器。
InputStream&&OutputStream字节IO
InputStream
-- FileInputStream(给出文件路径,再通过构造方法即可将文件转inputstream)
-- FilterInputStream
-- ByteBufferInputStream
-- ByteStream
-- PipedInputStream
-- ByteArrayInputStream(可通过构造方法将byte[]转inputstream)
-- InputStreamAdapter
-- ObjectInputStream
OutputStream
-- ObjectOutputStream(对象转输出流)
-- WriteStream
-- FileOutputStream(文件转输出流)
-- FilterOutputStream
-- PipedOutputStream
Read&&Writer字符IO
InputStreamReader(将输入流InputStream转字符输入流)
BufferedReader从缓冲区读入字符
5.如何解决bio编程中的read()和readLine()阻塞问题
- read的阻塞问题
/**
* 解决方案
* Socket任意一端在调用完write()方法时调用shutdownOutput()方法关闭输出流,
* 这样对端的inputStream上的read操作就会返回-1, 这里我们要注意下不能调用socket.getInputStream().close()。
* 因为它会导致socket直接被关闭。 当然如果不需要继续在socket上进行读操作,也可以直接关闭socket。
* 但是这个方法不能用于通信双方需要多次交互的情况。
* out.write("sender say hello socket".getBytes());
* out.flush();
* client.shutdownOutput(); //调用shutdown 通知对端请求完毕
*/
- readLine的阻塞问题
一个客户端,一个服务端。当服务端使用readLine()读消息时,需要读到一个换行符(‘\n’,或’\r’)才会结束。
否则回一直阻塞,主线程就挂在这儿了
并且,当realLine()读取到的内容为空时,并不会返回 null,而是会一直阻塞,
只有当读取的输入流发生错误或者被关闭时,readLine()方法才会返回null。
解决方法:在客户端每次输入回车后,手动给输入内容加入"\n"或"\r",再写入服务器;
或者在服务器端使用read()方法进行读取。