Java I/O操作类
在i/o类中有将近80个类,大致分为四组
1.基于字节操作的I/O接口:InputStream和OutStream
2.基于字符操作的I/O接口:Writer和Reader
3.基于磁盘操作的I/O接口:File
4.基于网络操作的I/O接口:Socket
其实不管是磁盘操作还是网络传输,最小的存储单元还是字节,而不是字符。所以I/O操作的还是字节。那为什么还设有I/O的字符接口?因为我们在程序日常操作中基本都是字符,如果直接用字节操作的I/O接口的话,还需要将字符转换成字节,从字符转成字节要经过编码转换,是一个比较复杂耗时的过程,还容易出现乱码。所以为了方便直接提供一个操作字符的I/O接口。
所以会有字节和字符的转换接口
字节和字符的转换接口
InputStreamReader和OutputStreamWriter
InputStreamReader:将字节的输入流变成字符的输入流
OutputStreamWriter:将 字符的的输出流变成字节的输出流
磁盘的I/O工作机制
我们知道硬盘是由操作系统来管理的,所以我们读取和写入文件I/O操作都需要调用系统提供的接口。而系统为了运行安全,将磁盘分为内核空间地址和用户空间地址。所以再运行的时候这两部分内存是隔离的,保证了操作系统的运行安全,但是总会有需要数据从内核空间传到用户空间的,这就出现了数据在内核空间向用户空间复制的问题。
如果我们用到了再去复制,会非常影响性能。操作系为了加速I/O访问,在内核空间使用了缓存机制,也就是说将从磁盘读取的文件按照一定的组织顺序进行缓存,如果用户程序访问事的是同一段磁盘地址的数据空间,那么操作系统姜葱内核缓存中直接取出返回给用户程序。
标准访问文件方式
标准的访问文件,当程序调用read()接口时,操作系统检查在内核的高速缓存中有没有需要的数据,如果已经有缓存了,那么直接从缓存中返回,如果没有,从磁盘读取,缓存在电脑的缓存了。
写入就是程序调用write()接口将数据从用户空间复制到内核的缓存中,这个时候我们的任务已经完成,从内核缓存写到磁盘中,是操作系统完成的。
直接I/O的方式
也就是我们不经过内核的缓存,直接从磁盘中读取和写入。我们知道程序调用read()接口时,回去程序本身的缓存中找有没有数据,如果没有直接去磁盘中读取。这有这有自己的好处,程序知道哪些是热点数据,也就是常用的数据,能够提高性能,但是如果这个数据不再程序缓存中的话,去磁盘读写会非常的浪费时间,因为I/O操作占用的时间比较长。
同步I/O方式
也就是数据的读取和写入都是同步操作的,只有等到数据成功被写到磁盘时才返回给程序成功的标志。这与标准的访问文件方式不同,标准的文件访问方式把数据复制高速缓存中就可以了,不需要等待从缓存写入磁泡。
异步I/O方式
异步,也就是说调用read()接口访问数据的时候,当线程发送请求后,线程不是等待数据访问成功才去干别的事,线程发送请求后,直接去处理其他事情了。这种方式的好处是,提高程序的效率,但是不影响程序的正常运行。
内存映射方式
内存映射,就是将操作系统中的内存的某一区域和磁盘中的某个文件关联起来,当要访问内存中的某一个数据时,转换成访问文件的某一段数据。
Java序列化
Java序列化就是把对象转换成一串二进制表达的字节数组,通过保存或者转移这些字节数据来达到持久化的目的。反序列化就是将这个字节数组重新构造成对象。序列化的数据并不是想class文件保存了真个类的信息,所以反序列化还是需要原始类作为模板。序列化的接口为Serializable接口。
序列化注意情况
1.当父类实现了Serializable接口时,所有的子类都可以被序列化
2.子类实现了Serializable接口时,父类没有实现,父类的属性不能被序列化,但是子类的属性可以被序列化
3.如果序列化的属性是一个对象,则这个对象也必须是实现Serializable接口,否则报错
4.在反序列化是,如果对象有属性被修改或者删除,则修改的部分会丢失,不出错
具体使用输入和输出流
输入流
public class MyTest {
public static void main(String[] args) throws IOException{
File file= new File("e:\\a.txt");
if(file.exists()){
InputStream in =new FileInputStream(file);
int a=0;
while((a=in.read())!=-1){
System.out.print((char)a);
}
in.close();
}
else{
System.out.println("文件不存在");
}
}
}
如上面代码所示,我们能够用inputStream读取文件里的内容
但是我们发现从文件读取的内容英文是没问题,但是中文部分是乱码。由于我们用的是字节流,读取的。没有指定编码,中文字符会乱码。
public class MyTest {
public static void main(String[] args) throws IOException{
File file= new File("e:\\a.txt");
if(file.exists()){
InputStream in =new FileInputStream(file);
InputStreamReader inputStreamReader=new InputStreamReader(in,"UTF-8");
int a=0;
while((a=inputStreamReader.read())!=-1){
System.out.print((char)a);
}
in.close();
}
else{
System.out.println("文件不存在");
}
}
}
如上面代码,我们将把输入流作为参数传给InputStreamReader,并指定了编码,从字节转成了指定字符。解决了中文乱码问题。
输出流
public class MyTest {
public static void main(String[] args) throws IOException{
File file = new File("e:\\b.txt");
OutputStream out=new FileOutputStream(file);
String str="zxcv";
for(int i=0;i<str.length();i++){
char a=str.charAt(i);
out.write(a);
}
out.close();
}
}
上面的代码就能够往文件里面写内容了,但是每次都是覆盖的,在FileOutputStream的构造方法中有一个在尾部追加的。来看看源码
public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
this.fd = new FileDescriptor();
fd.attach(this);
this.append = append;
this.path = name;
open(name, append);
}
所以再实例化的时候把参数改为true就好了
OutputStream out=new FileOutputStream(file,true);
但是这个还是没有解决中文乱码的问题
public class MyTest {
public static void main(String[] args) throws IOException{
File file = new File("e:\\b.txt");
OutputStream out=new FileOutputStream(file,true);
OutputStreamWriter outputStreamWriter=new OutputStreamWriter(out,"UTF-8");
String str="zxcv 陈";
outputStreamWriter.write(str);
outputStreamWriter.flush();
out.close();
}
}
使用OutputStreamWriter就可以解决,从字符到字节流中的问题了。
这里介绍了从硬盘中读入和写入的过程,接下来看看,线程与线程之间是如何通信的。
I/O管道
两个线程是独立的,想要进行通信可以一方采用PipedInputStream另一方采用PipedOutputSream,这样就进行通信了。
带缓冲的字符流
带缓冲区的字符输入,可以提高熟读IO处理的速度,可以一次读取一大块数据,而不需要每次从硬盘中一次读取一个字节。
BufferedReader和BufferedInputStream
BufferedWriter
也可以指定缓冲区的大小,
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
不指定大小的话
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
使用的是默认的大小,8kb
private static int DEFAULT_BUFFER_SIZE = 8192;
本文属于自己参考文章和结合自己的总结来学习和巩固Java基础
参考文章:https://github.com/h2pl/Java-Tutorial/blob/master/docs/java/basic/1%E3%80%81%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E5%9F%BA%E7%A1%80.md