Java学习手册:Java IO面试问题

1、Java学习手册:Java基础知识点
2、Java学习手册:Java面向对象面试问题
3、Java学习手册:Java集合、泛型面试问题
4、Java学习手册:Java并发与多线程面试问题
5、Java学习手册:Java虚拟机面试问题
6、Java学习手册:Java IO面试问题
7、Java学习手册:Java反射机制面试问题
8、Java学习手册:Java网络编程面试问题
9、Java学习手册:Java异常面试问题
10、Java学习手册:Java设计模式面试问题
11、Java学习手册:Java数据库面试问题


零、Java序列化

Java学习手册:Java序列化
将Java写人流的过程称为序列化,将对象从流中读取出来称为反序列化
参与(反)序列化的类必须要实现java.io.Seralizable接口,并生成一个序列化ID。静态属性和使用transient修饰符修饰的属性不参与序列化。如果父类是可以序列化的,那么子类必然是可序列化的。类序列化时,属性依赖的对象也必须是一个可以序列化的对象。


一、Java IO流的实现机制

在Java语言中,输入和输出都被称为抽象的流,流可以看作一组有序的字节集合,即数据在两设备之间的传输。

流的本质是数据传输,根据处理数据类型的不同,流可以分为两大类:字节流和字符流。

字节流以字节(8bit)为单位,包含两个抽象类:InputStream(输入流)和OutputStream(输出流)

字符流以字符(16bit)为单位,根据码表映射字符,一次可以读多个字节,包含两个抽象类:Reader(输入流)和Writer(输出流)

字节流和字符流最主要的区别为:字节流在在处理输入输出时不会用到缓存,而字符流用到了缓存


二、Java中有几种类型的流?

答:常见的流有两种,分别为字节流字符流。其中,字节流继承于InputStream与OutputStream字符流继承于Reader与Writer。InputStream是所有字节输入流的父类,OutputStream是所有字节输出流的父类。Reader是字符输入流的父类,Writer是字符输出流的父类。在java.io包中还有许多其他的流,流的作用主要是为了改善程序性能并且使用方便。

注:InputStream和OutPutStream这两个类虽然提供了一系列和读写数据有关的方法,但是这两个类是抽象的,不能被实例化


三、字节流与字符流

字节流可以处理所有类型的数据,如MP3、图片、文字、视频等。在读取时,读到一个字节就返回一个字节。在Java中对应的类以"stream"结尾。

字符流仅能够处理纯文本数据,如txt文本等。在读取时,读到一个或者多个字节,先查找指定的编码表,然后将查到的字符返回。在Java中对应的类都以"Reader"或"Writer"结尾。所有的字符流都是高级流。字符流是以字符(char)为单位读写数据的,一次处理一个unicode。字符流的底层仍然是基本的字节流。

实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时,使用了缓冲区在操作文件。字节流在在处理输入输出时不会用到缓存,而字符流用到了缓存

字节输出流:程序→字节流(直接操作文件)→文件

字符输出流:程序→字符流→缓存→文件


四、文件流(字节流)

(1)FOS(FileOutputStream)
FileOutputStream是文件的字节输出流,使用该流以字节为单位将数据写入文件。
java.io.FileOutputStream向文件中写出字节的输出流,这是一个低级流,数据的去向明确(文件中)。

FileOutputStream(File file)//创建一个指向File对象表示的文件中写出数据的文件输出流
FileOutputStream(String filename)//创建一个向具有指定名称文件中写出数据的文件输出流
//注:若指定的文件已经包含内容,那么当使用FOS对其写入数据时,会将该文件中原有数据全部清除。
FileOutputStream(File file, boolean append)//创建一个向指定File对象表示的文件中写出数据的文件输出流
FileOutputStream(String filename, boolean append)//创建一个向具有指定名称的文件中写出数据的文件输出流
//注:以上两个构造方法中,第二个参数若为true,那么通过该FOS写出的数据都是在文件末尾追加的。

(2)FIS(FileInputStream)
FileInputStream是文件的字节输入流,使用该流可以以字节为单位,从文件中读取数据。
java.io.FileInoutStream从文件中读取数据的流,是一个低级流。

FileInputStream(File file)//创建一个从指定File对象表示的文件中读取数据的文件输人流
FileInputStream(String name)//创建用于读取给定的文件系统中的路径名name所指定的文件的文件输入流
int read()//从此输入流中读取一个数据字节,若返回-1则表示EOF(End od File)
void write(int d)//将指定字节写入此文件输出流,这里只写给定的int值的低八位

(3)文件复制
使用流的形式完成文件的复制操作。
①创建一个FileInputStream读取源文件
②创建一个FileOutputStream向目标文件写
③循环读取每一个字节并写入文件
④关闭两个流完成复制


五、缓冲流(字节流)

(1)BOS(BufferedOutputStream)
BufferedOutputStream缓冲输出流,内部维护着一个缓冲区,每当我们向该流写数据时,都会先将数据存入缓冲区,当缓冲区已满时,缓冲流会将数据一次性全部写出。

①BOS实现写出缓冲

public void testBos() throws Exception{
	FileOutputStream fos = new FileOutputStream("demo.txt");
	//创建缓存字节输出流
	BufferedOutputStream bos = new BufferedOutputStream(fos);
	//所有字节被存入缓冲区,等待一次性写出
	bos.write("helloworld".getBytes());
	//关闭流之前,缓冲输出流会将缓冲区内容一次性写出
	//数据在缓冲区中保存直到缓冲区满后才写出
	bos.close();
}

②BOS的flush方法

void flush()//清空缓冲区,将缓冲区中的数据强制写出(一次性写出)。
//注:频繁使用该方法会提高写出次数从而降低写出效率

(2)BIS(BufferedInputStream)
BufferedInputStream是缓冲输入流。其内部维护着一个缓冲区(字节数组),使用该流在读写一个字节时,该流会尽可能多的一次性读取若干字节并存入缓冲区,然后逐一的将字节返回,直到缓冲区中的数据被全部读取完毕,会再次读取若干字节从而反复,这样就减少了读取的次数,从而提高了读取效率。

BIS是一个处理流,该流为我们提供了缓冲功能。

①BIS实现输入缓冲

public void testBis() throws Exception{
	FileInputStream fis = new FileInputStream("demo.txt");
	//创建缓存字节输入流
	BufferedInputStream bis = new BufferedInputStream(fis);
	int d = -1;
	//缓冲读入,实际上并非是一个字节一个字节从文件读取的
	while((d=bis.read()) != -1) {
		System.out.println(d+" ");
	}
	bis.close();//关闭流时只需关闭最外层的高级流即可
}

②缓冲流的方法 int read()
虽然也是返回一个字节,但实际上缓冲流会让其处理的流一次性读一组字节回来,并存入自身提供的字节数组(缓冲区),然后将第一个字节返回。这样的好处在于当我们再次调用该方法读取第二个字节时,缓冲流会直接将字节数组中的第二个字节返回,而不是再去读取。(也是通过提高了读取量,减少读取次数来提高读取效率)

(3)实现基于缓存区的文件复制

package com.haobi;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
/*
 * 实现基于缓存区的文件复制
 */
public class CppyFile {
	public static void main(String[] args) throws Exception {
		//读取源文件
		FileInputStream fis = new FileInputStream("E:\\File_java\\IODemo\\demo.txt");
		//创建缓存字节输入流
		BufferedInputStream bis = new BufferedInputStream(fis);
		//向目标文件写
		FileOutputStream fos = new FileOutputStream("demo_copy.txt");
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		int d = -1;
		//缓冲读入,实际上并非是一个字节一个字节从文件读取的
		while((d=bis.read()) != -1) {
			bos.write(d);
		}
		bis.close();
		System.out.println("复制完毕!");
		bis.close();
		bos.close();
	}
}
//程序输出结果如下:
复制完毕!

六、对象流(字节流)

序列化:从数据结构转化为字节的过程
对象序列化:将Java中对象转换为字节的过程(对象→字节)
持久化:把内存上的数据写入磁盘中做长久保存的过程
(1)ObjectOutputStream
ObjectOutputStream是用来对对象进行序列化的输出流,可以将Java中的任何对象转化为一组字节后写出。

void writeObject(Object o)//将指定的对象转换为一组字节后写出

(2)ObjectInputStream
ObjectInputStream是用来对对象进行反序列化的输入流。

Object readObject()//该方法可以从流中读取字节(必须是oos将对象转换的一组字节)并转换为对应的对象

1、在反序列化时,会检测待反序列化的对象与该类定义的现有版本是否一致,若不一致,则会抛出异常,反序列化失败
2、若版本号没变,但是类定义发生了改变,那么采用兼容模式:
原来对象有的属性,现在还有的就还原;
原来对象有的属性,现在没的就忽略;
原来对象没有的属性,现在还有的则使用默认值;
例:

private static final long serialVersionUID = 1l;
//ObjectOutputStream在对对象进行序列化时有1个要求,就是需要序列化的的对象所属的类必须实现Serializable接口。

transient关键字,该关键字修饰的属性在序列化时其值将被忽略。


七、转换流(字符流)

(1)OutputStreamWriter(字符输出流)

//构造方法
OutputStreamWriter(OutputStream out)
//是将给定的输出流转换为字符输出流,通过该流写出的字符串是按照系统默认的字符集写出的。

//重载构造方法
OUtputStreamWriter(OutputStream out, String charsetName)
//该构造方法的第二个参数用来指定字符集,这样就可以按照当前指定的字符集将字符串转换为字节后写出。

(2)InputStreamReader(字符输入流)
转换过程:
在这里插入图片描述
(3)将osw.txt文件编码转换为GBK,并存入另一个文件中

package com.haobi;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
/*
 * 将osw.txt文件编码转换为GBK,并存入另一个文件中
 * 思路:
 * 由于osw.txt文件中的字符数据都是以UTF-8编码转换的字节保存的,所以我们要先用UTF-8编码
 * 将每一个字符读取出来,然后再将该字符按照GBK编码写入另一个文件即可
 */
public class ChangeCharsetDemo {
	public static void main(String[] args) throws IOException{
		FileInputStream fis = new FileInputStream("osw.txt");
		InputStreamReader isr = new InputStreamReader(fis, "UTF-8");//若不写,则文件默认为GBK
		FileOutputStream fos = new FileOutputStream("osw_copy.txt");
		OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");
		char[] buf = new char[100];
		int len = -1;
		while((len = isr.read(buf)) != -1) {
			osw.write(buf, 0, len);
		}
		System.out.println("转码完成!");
		osw.close();
		isr.close();
	}
}
//程序输出结果如下:
复制完毕!

八、缓冲字符流(字符流)

(1)PrintWriter
优势:1、读写字符快;2、可以以行为单位读写字符串;
缓冲字符输入输出流:BufferedReader、BufferedWriter
PrintWriter内部嵌套了BufferedWriter,所以其也具有缓冲功能,PrintWriter还提供了自动的行刷新功能。所以通常我们使用它来当作缓冲字符输出流。

//PrintWriter提供了很多构造方法,方便我们创建实例
//PrintWriter提供了用于流的嵌套(流的连接)使用的构造方法
PrintWriter(OutputStream out)
PrintWriter(Writer writer)
//若使用以流的方式创建PrintWriter,那么可以在构造方法中传入第二个参数,是一个Boolean值,该值若为true,
//则PrintWriter具有自动行刷新,意思是每当我们调用println方法写出内容后就会自动调用flush。如下:
PrintWriter(OutputStream out, boolean autoFlush)
PrintWriter(Writer writer,boolean autoFlush)
//示例如下:
FileOutputStream fos = new FileOutputStream("pw.txt");
PrintWriter pw = new PrintWriter(fos, true);

(2)BufferedReader
BufferedReader是缓冲字符输入流,其内部提供了缓冲区,可以提高读取效率。

//常用构造方法
BufferedReader(Reader reader)
//可以按行读取字符串
String readLine()
//缓冲字符输出流的该方法可以按行为单位读取字符串,它会读取若干字符,直到读取了换行符为止,然后将换行符之前的所
//有字符组成一个字符串后返回,但是返回的字符串中不包含换行符。若返回null,则说明读取到末尾了,再没有数据可读。

(3)将当前程序的源文件读取出来并输出到控制台1

package com.haobi;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/*
 * 将当前程序的源文件读取出来并输出到控制台
 */
public class BufferedReaderDemo {
	public static void main(String[] args) throws IOException{
		//字节流
		FileInputStream fis = new FileInputStream("demo.txt");
		//字节流→字符流
		InputStreamReader isr = new InputStreamReader(fis);
		//包装字符流(缓冲字符流只能包装字符流)
		BufferedReader br = new BufferedReader(isr);
		String line = null;
		while((line = br.readLine())!= null) {//按行读取字符串
			System.out.println(line);
		}
		br.close();
	}
}
//程序输出结果如下:
demo
second
third

(4)将当前程序的源文件读取出来并输出到控制台2

package com.haobi;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderDemo1 {
	public static void main(String[] args) throws IOException {
		FileReader fileReader = new FileReader("demo.txt");
		BufferedReader bufferedReader = new BufferedReader(fileReader);
		String line = bufferedReader.readLine();
		while(line != null) {
			System.out.println(line);
			line = bufferedReader.readLine();
		}
		bufferedReader.close();
		fileReader.close();
	}
}

九、读取一个文件,把文件中的大写字母转换成小写字母,把小写字母转换成大写字母。

package com.haobi;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/*
 * 把文件中的大写字母转换成小写字母,把小写字母转换成大写字母
 */
class MyOwnInputStream extends FilterInputStream{
	public MyOwnInputStream(InputStream in) {
		super(in);
	}
	public int read() throws IOException{
		int c = 0;
		if((c = super.read()) != -1) {
			if(Character.isLowerCase((char)c)){//把小写转换为大写
				return Character.toUpperCase((char)c);
			}else if(Character.isUpperCase((char)c)){//把大写转换为小写
				return Character.toLowerCase(c);	
			}else {//如果不是字母,保持不变
				return c;
			}
		}else {
			return -1;
		}
	}
}

public class InputStreamDemo {
	public static void main(String[] args) {
		int c;
		try {
			InputStream is = new MyOwnInputStream(new BufferedInputStream(new FileInputStream("iotest.txt")));
			while((c = is.read()) >= 0) {
				System.out.print((char)c);
			}
			is.close();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}

十、RandomAccessFile

Java学习手册:RandomAccessFile


十一、File类

Java学习手册:File类


十二、NIO有什么作用?

在Java程序中会用到大量的I/O操作,由于传统的I/O操作是非阻塞式,无法有效地利用CPU。大大影响了程序性能,为此,我们通常会使用NIO来改写以前的I/O部分,NIO是非阻塞式的,可以提高程序的性能,提升CPU的利用率。

注:谈谈NIO的理解
NIO( New Input/ Output) 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。 NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。


十三、NIO与IO有哪些区别?

1、面向流与面向缓冲区

NIO是面向缓冲区的,数据读取到一个缓冲区,需要时可以在缓冲区中前后移动。这就增加了处理过程的灵活性。但是,还需检查该缓冲区中是否包含需要处理的数据。
IO是面向流的,直至读取所有字节,他们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。

2、阻塞与非阻塞IO

NIO是非阻塞模式的,当一个线程从某通道发送请求读取数据时,它仅能得到目前可用的数据,如果目前没有数据可用,就什么都不做。而不是保持线程阻塞,所以直至数据可以读取之前,该线程可以继续做其他的事情。
IO的各种流是阻塞的,这意味着,当一个线程调用read()或write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情。

3、选择器(selector)

NIO的选择器允许一个单独的线程来监视多个输入通道,可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道。
I/O无选择器。


十四、磁盘I/O的工作机制

当传入一个文件路径时,会根据这个路径创建一个File对象来标识这个文件,然后会根据这个File对象创建真正读取文件的操作对象,这时将会真正创建一个关联真实存在的磁盘文件的文件描述符FileDescriptor,通过这个对象可以直接控制这个磁盘文件。由于需要读取的是字符格式,所以需要StreamDecoder类将byte编码为char格式。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值