深入了解Java I/O的工作机制

本文深入探讨Java I/O操作,涵盖字节与字符流、磁盘与网络I/O、序列化机制及线程间通信。解析标准与直接I/O工作原理,介绍缓冲流提升性能技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值