事先可以阅读下吃透 Java IO:字节流、字符流、缓冲流

📁Netty入门

常见的网络协议 HTTP、TCP_IP、UDP 等【知识点整理】

File类

一:输入输出流

●输入流:

常见的输入设备有键盘、扫描仪、文件等;

●输出流:

常见的输出设备有屏幕、打印机、文件等;

●文件,既可以作为输入设备,又可以作为输出设备:

文件作为输入设备时:是读取文件中的内容;即输入流是读取数据;

文件作为输出设备时:是写文件;即输出流是写数据,将一些数据写到文件中去;


二:File类

●什么是文件:文件可认为是相关记录或放在一起的数据的集合;

●Java中,使用java.io.File类对文件进行一系列的操作;Java中的File对象,不区分文件和目录,二者都是“文件”。


File类简介

File类的构造方法:

File类的常用方法:主要目的是 知道有这个方法 ,具体用的时候,如果不清楚再去查API:

●canRead():文件是否可以读;

●canWrite():文件是否可以写;

●exists():当前的文件或目录是否存在;

●getName():获取文件或路径的名称;

●isDirectory():是否是目录;(如果一个文件不存在,那么这个方法返回false)

●isFile():是否是文件;(File对象是不区分文件和目录的;)(如果一个文件不存在,那么这个方法返回false)

●lastModified():文件的最后一次修改时间;

●isHidden():是否是一个隐藏文件;

●midir():创建目录;

●midirs():创建多级目录;

等等,主要留个印象,知道File类可以干什么,然后具体应用时,不清楚可以去查API。


File示例: File类主要用于文件、目录的创建,并不涉及具体文件内容的读写

(1)三种创建File对象的方式:(File类的三种构造方法的演示)

public class FileDemo {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// (一)创建文件的第一种方式;(2)"\"本身是一个转义字符,两个反斜杠才代表一个反斜杠;(3)再Linux系统的写法:"c:/";
		//File file1 = new File("c:\\imooc\\io\\score.txt");
		// (二)创建文件的第二种方式;
		//File file1 = new File("c:\\imooc","io\\score.txt");
		// (三)创建文件的第三种方式;有些业务场景需要File的这种构造方法
		File file = new File("c:\\imooc");
		File file1 = new File(file,"io\\score.txt");
		System.out.println(file1.exists());
		System.out.println(file1.isFile());
		System.out.println(file1.isDirectory());
	}
}

(2)创建目录:mkdir()和mkdirs()

public class FileDemo {
 
	public static void main(String[] args) {
		// 创建一个目录
		File file2 = new File("c:\\imooc\\set","HashSet");
		// 如果文件不存在,再去创建;经过实测,如果去创建一个存在的目录,并不会引起什么问题,文件中的内容并没有消失;换句话说,创建一个已经存在的目录,并是把原文件夹删除然后再原样新建一个;
		if(!file2.exists()){
			file2.mkdir();// file2.mkdirs() 实测发现,创建单级目录时,也可以使用mkdirs()方法;
		}
		// 创建一个多级目录
		File file3 = new File("c:\\imooc\\set\\LinkSet\\mm");
		if(!file3.exists()){
			file3.mkdirs();// 实测发现,如果是创建多级目录,调用mkdir()方法不会报错,也不会创建目录
		}
	}
}

(3)创建文件:createNewFile();(I/O异常是检查异常,需要捕获)

public class FileDemo {
 
	public static void main(String[] args) {
		// 创建文件
		File file1 = new File("c:\\imooc\\io\\score.txt");   // 如果是"c:\\imooc\\io\\score",其还是会创建文件,只是一个没有扩展名的score文件
		if(!file1.exists()){
			// I/O异常是检查异常,需要捕获
			try {
				file1.createNewFile();  // 实测发现,如果一个文件已经存在了,还去调用createNewFile方法,其并不会损伤原文件,即这个创建文件的操作没有执行
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
 
	}
}

三:绝对路径和相对路径

●绝对路径:“c:\\imooc\\io\\score.txt”,从盘符(这儿是c盘)开始的;

●相对路径:不从盘符开始,是一个相对的位置;从当前路径开始的路径, 当前路径大概率是当前Java文件所在的路径

相对路径三个例子:

(1)Java文件和要操作的文件处于同一目录:不涉及目录的切换,直接文件名即可;

public class FileDemo {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File f = new File("thread.txt");
		System.out.println(f.exists());
 
	}
 
}

(2)Java文件在待处理文件的上级目录中时;

public class FileDemo {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File f = new File("thread\\thread.txt");
		System.out.println(f.exists());
 
	}
}

(3)Java文件和待处理文件处在不同的文件夹时;涉及文件夹的上跳和下转;

其中的“..”代表上跳一级目录

public class FileDemo {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File f = new File("..\\thread\\thread.txt");
		System.out.println(f.exists());
 
	}
 
}

IDE中(这儿是eclipse为例)中当前路径是工程所在目录的根目录:

工程根目录:

直接创建创建文件:

public class FileDemo {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File f1 = new File("thread.txt");
		try {
			f1.createNewFile();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
	}
 
}

发现, thread.txt文件的创建位置是工程根目录


获取绝对路径和相对路径的示例:这个例子很简单,稍微一看就行

public class FileDemo {
 
	public static void main(String[] args) {
		File f = new File("thread\\thread.txt");
		try {
			f.createNewFile();
			// 判断是否是绝对路径
			System.out.println(f.isAbsolute());
			// 获取相对路径
			System.out.println(f.getPath());
			// 获取绝对路径
			System.out.println(f.getAbsolutePath());
			// 获取文件名
			System.out.println(f.getName());
 
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
 
}

输出结果:

false
thread\thread.txt
E:\java_tools\InputAndOutput\thread\thread.txt
thread.txt

字节流分类

File类主要用来,创建文件和目录;具体读取文件,复制文件等,就需要用到字节流和字符流了


●字节流包括:

字节输入流:InputStream;

字节输出流:OutputStream;

●字节输入流的父类是:ImputStream类,其包括多个子类。

其中需要着重了解的有: FileInputStream(文件输入流)BufferedInputStream(缓冲输入流);后面对象序列化时,需要了解ObjectInputStream(对象输入流);

●字节输出流的父类是:OutputStream类,其包括多个子类。

需要着重了解的有, FileOutputStream(文件输出流)BufferedOutputStream(缓冲输出流) ;后面对象序列化时,需要了解ObjectOutputStream(对象输出流);

字节流之FileInputStream(文件输入流)

一:前置介绍

●从文件系统中的某个文件中获得输入字节,例如文档复制时候,文件输入流主要负责“读取”文件,获得文件输入流;

●用于读取诸如图像、图片数据之类的原始字节流(如视频图像和图片数据是二进制的数据,字节流一般用于读取这类的二进制数据;而一般读文档等内容是字符串的数据时,一般不使用字节流,而是使用字符流去读取文档)


二:FileInputStream类常用方法简介

FileInputStream类简介:

常用方法:

public int read():从输入流中读取一个字节数据,也就是读取一个整数;该返回值int,就是读到什么返回什么,即把读到的数据返回;返回值-1:已经读到了文件末尾;

public int read(byte[] b):用这个方法之前,需要事先定义一个byte类型的数组,好把读到的数据存到这个数组中;返回值为-1:已经读到了文件末尾;

public int read(byte[] b,int off,int len):off:从什么位置处开始存放,又称偏移量?

public void close():关闭输入流,释放资源;


三:FileInputStream类常用方法范例:

(1)public int read() :读取文件的一个字节,返回该字节对应的整数值:

public class FileInputDemo1 {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// (1)创建文件输入流对象,(建立访问文件的一个"通道");
		// (2)因为imooc.txt在工程根目录,所以这儿直接用相对路径访问了;其中,imooc.txt的内容为:Hello,imooc!
		// (3)访问文件时文件可能不存在,所以必须处理FileNotFoundException异常;
		try {
			FileInputStream fis = new FileInputStream("imooc.txt");
			int n = fis.read();
			System.out.println(n);
			fis.close();
		} catch (FileNotFoundException e) {   // FileNotFoundException是IOException的子类;对于catch来说,子类必须写在上面;
			// TODO Auto-generated catch block
			e.printStackTrace();
		}catch(IOException e){   // int n = fis.read();这条语句会抛出I/O异常,这儿处理了一下
			e.printStackTrace();
		}
	}
}

输出:

72

public int read()方法返回的是读取到的字节的整数;上述程序,将方法返回的int值装换成char后输出即:System.out.println((char)n);

输出结果:即为,imooc.txt的第一个字母H

H

public int read()循环读取文件内容

public class FileInputDemo1 {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// (1)创建文件输入流对象,(建立访问文件的一个"通道");
		// (2)因为imooc.txt在工程根目录,所以这儿直接用相对路径访问了;其中,imooc.txt的内容为:Hello,imooc!
		// (3)访问文件时文件可能不存在,所以必须处理FileNotFoundException异常;
		try {
			FileInputStream fis = new FileInputStream("imooc.txt");
			int n = 0;
			while((n = fis.read())!= -1){
				System.out.println((char)n);
			}
			fis.close();
		} catch (FileNotFoundException e) {   // FileNotFoundException是IOException的子类;对于catch来说,子类必须写在上面;
			// TODO Auto-generated catch block
			e.printStackTrace();
		}catch(IOException e){   // int n = fis.read();这条语句会抛出I/O异常,这儿处理了一下
			e.printStackTrace();
		}
	}
}

输出结果:可以发现public int read()方法是,每次读一个字节;一个英文字母占一个字节,很ok,不会出现乱码,但如果imooc.txt中的内容为中文是怎样的?见下面

H
e
l
l
o
,
i
m
o
o
c
!

如果imooc.txt中的内容为中文时,其输出结果是:很显然出现了,乱码,英文一个中文字至少占两个字节;

?
?
?
?
?
?
?
?
?
?
°
?
?
?
?
?
?
?
?
?
?

(2)public int read(byte[] b) :用这个方法之前,需要事先定义一个byte类型的数组,好把读到的数据存到这个数组中;返回值为-1:已经读到了文件末尾;

实际使用的时候,如何满足业务需求,可能需要编写一下辅助的逻辑代码,以更好地应用这个read()方法。

public class FileImputStreamDemo2 {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			FileInputStream fis = new FileInputStream("imooc.txt");
			byte[] b = new byte[100];
			fis.read(b);
			// new String(b):将字节数组转成字符串;
			System.out.println(new String(b));
			fis.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
 
	}
}

输出结果:

Console显示:

实际将输出结果copy后,后面的正方形没了:这是因为上面的程序中,byte数组定义长度为100,而imooc.txt中只有12个字符,所有byte数组中有88位的值是null的缘故吧。

Hello,imooc!

注:当imooc.txt内容改成中文后,如果项目编码方式为GBK的时会出现乱码;项目编码为utf-8时正常输出中文;


(3)public int read(byte[] b,int off,int len) :off:从什么位置处开始存放,又称偏移量;

public class FileImputStreamDemo2 {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			FileInputStream fis = new FileInputStream("imooc.txt");
			byte[] b = new byte[100];
			// 第二个参数:偏移量,代表从byte数组的第几位开始存放数据;
			// 第三个参数:存放几个数据;
			fis.read(b,10,8);
			// new String(b):将字节数组转成字符串
			System.out.println(new String(b));
			fis.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

输出结果:可以,因为偏移量设成了10,所以,字节数组的第0到第9位的值位null,第三个参数设成了8,即本次只读8位;(如果想继续读的化,就继续调用read方法了)

字节流之FileOutputStream(文件输出流)

文件输入流将文件内容读取(程序读取文件内容,暂存在内存中);

文件输出流,将程序读取到的内容写入到一个文件中;


一:FileOutputStream类常用方法介绍

构造方法:

常用方法:

●public void write(byte[] b,int off,int len):

off偏移量:从数组的第几位算起;

一般,再读写文件时:令 len = fis.read(b);

len:如果没有读到文件末尾:len的值就是实际读取的字节数;假如byte数组的长度为1024,那么开始的时候,len应该是1024;最后一次读的时候,文件快被读完了,可能byte数组离只有800个字节了,所以这时len为800;如果再继续读,因为文件已经被读完,此时len就会是-1了;


二:FileOutputStream类方法示例:

(1)write()方法演示

public class FileOutputDemo1 {
 
	public static void main(String[] args) {
 
		FileOutputStream fos;  // 编程习惯:将字节流对象的定义放在外面。
		try {
			fos = new FileOutputStream("imooc.txt",true);
			fos.write(50);
			fos.write('c');
			fos.close();
 
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

结果: 经过多次测试,可以发现,字节流对于处理文档类内容显得有点拖沓和笨重,这中间涉及到各种编码方式的切换;所以,字节流更善于处理视频、图像等字节型文件,而不善于处理文档等字符型文件


三:文件拷贝

(1)文件的拷贝流程示例:

public class FileOutputDemo2 {
 
	public static void main(String[] args) {
		// 文件拷贝:图片文件的复制粘贴,读取和写入
		// (1)创建输入流对象,输出对象
		FileInputStream fis;
		FileOutputStream fos;
		try {
			// (2)实例化输入流对象指向待复制文件;实例化输出对象,参数为复制后文件的全路径;
			fis = new FileInputStream("happy.jpg");
			fos = new FileOutputStream("happyCopy.jpg");
			// (3)n存放读方法的返回值,返回值为实际读到的数据的长度;如果为-1代表已经读到文件末尾了;
			int n = 0;
			// (4)定义一个字节数组,存放读取到的数据;这个数组得大一点;
			byte[] b = new byte[1024];
			// (5)读取内容,并同时将读取到的内容写出来;这儿是每读1024个字节就写一下,即读一点写一点,直到全部复制完;
			while((n = fis.read(b))!= -1){
				fos.write(b); // 将读取到的内容写到fos这个文件流对象对应的文件中去;
			}
			// (6)关闭输入流、输出流;
			fis.close();
			fos.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){  // 注:这几文件的读写,IOException是必须抛出的,当成常识记住就行…
			e.printStackTrace();
		}
	}
 
}

结果:

注:

(1)发现,复制后的happyCopy.jpg比happy.jpg要大:因为快读完的时候(即最后一次读的时候),文件剩余的东西,装不满1024个字节了;

解决办法:更改write方法。以保证俩文件大小是一致的;

n = fis.read(b):n的值为实际读的字节数,如果读到文件末尾,n为-1;

fos.write(b,0,n):n从偏移位置(这个是从数组的第0位开始算),写n位数据,如果n=-1,不写;

(如果没有读到文件末尾:len的值就是实际读取的字节数;假如byte数组的长度为1024,那么开始的时候,len应该是1024;最后一次读的时候,文件快被读完了,可能byte数组离只有800个字节了,所以这时len为800;如果再继续读,因为文件已经被读完,此时len就会是-1了;)

(2)上面文件复制时,如果已经有一个happyCopy.jpg文件了,其会把已有的文件干掉,然后新增本次复制的文件;

字节流之缓冲流(BufferedInputStream&BufferedOutputStream)

一:缓冲流简介:为什么能提升效率

● 原先没有缓冲流的情况:通过FileInputStream(文件输入流)和FileOutputStream(文件输出流)可知,在文件拷贝的时候,其文件数据的流转顺序为:

文件→FileInputStream→字节数组→FileOutputStream→文件Copy;

●使用缓冲流的情况:

文件→FileInputStream→字节数组→缓冲区→FileOutputStream→文件Copy;

●缓冲流为什么能够提高读写速度:(有几种说法如下,有点笼统)

(1)文件的读写包括:文件的I/O和其他操作,没有缓冲区时,I/O和其他操作都是CPU的工作(CPU采用DMA的方式去执行I/O操作);设了缓冲区后,CPU 将I/O操作交给专门的DMA控制器去做,当缓冲区填满后,DMA控制器会通知CPU,以让其完成其他操作;DMA控制器比CPU更善于做I/O与内存数据交换,从而提升效率。

(2)文件读写是需要和硬件打交道的,每次读写就是一次I/O操作,而io操作是很费时的。而其中,又包括读和写;没有缓冲区时,是读一点(byte字节数组)写一点;有缓冲区后,是读一点,往缓冲区里存一点,如此多次读,直到将缓冲区存满,缓冲区满了后,再将这些数据一次性地写入到文件中。所以,可以这样认为,缓冲区减少了写的次数,从而提升效率。

(2.1) 通过本篇博客的例子中,缓冲流的使用。可以看出,读的时候,还是一个字节数组一个字节数组的读,把读到的数据存到缓冲区中,然后写的时候,看起来也是一个字节数组一个字节数组的写,好像并没有看到缓冲区在哪儿,,,,,好吧,为了说服自己,暂时认为缓冲区对用户(在程序代码中)不可见吧


二:缓冲流属性和方法简介

BufferedInputStream:缓冲输入流

属性:

构造方法:

缓冲输入流的构建需要传入一个文件输入流作为参数

常用方法:

BufferedOutputStream:缓冲输出流

属性:

构造方法:

缓冲输出流的构建需要传入一个文件输出流作为参数

常用方法:

注:close()方法会强制清空缓冲区内容,这也会把缓冲区中的内容写入到文件中。


三:缓冲流在文件复制中的示例

(1)使用缓冲流复制文件:使用“#####”标识的语句为缓冲流新增内容;

(2)为了使代码简洁,也可写成这样:

public class FileOutputDemo2 {
 
	public static void main(String[] args) {
		// 文件拷贝:图片文件的复制粘贴,读取和写入
		// (1)创建输入流对象,输出对象
		FileInputStream fis;
		FileOutputStream fos;
		BufferedInputStream bis;
		BufferedOutputStream bos;
		try {
			// (2)实例化输入流对象指向待复制文件;实例化输出对象,参数为复制后文件的全路径;
			fis = new FileInputStream("video.mp4");
			// (2.1)构造缓冲输入流
			bis = new BufferedInputStream(fis);     // 缓冲输入流#####
			fos = new FileOutputStream("videoCopy.mp4");
			// (2.2)构造缓冲输出流
			bos = new BufferedOutputStream(fos);    // 缓冲输出流#####
			// (3)n存放读方法的返回值,如果为-1代表已经读到文件末尾了;
			int n = 0;
			// (4)定义一个字节数组,存放读取到的数据;这个数组得大一点;
			byte[] b = new byte[1024];
 
			long startTime = System.currentTimeMillis();   // 对比运行时间用  #####
 
			// (5)读取内容,并同时将读取到的内容写出来;这儿是每读1024个字节就写一下,即读一点写一点,直到全部复制完;
			// (5.1)调用缓冲输入流的read()方法
			while((n = bis.read(b))!= -1){  // 缓冲流的read方法和write方法和文件流的并无二致#####
			// (5.2)调用缓冲输出流的write()方法
				bos.write(b,0,n); // 将读取到的内容写到fos这个文件流对象对应的文件中去;
			}
			// (6)关闭输入流、输出流;
			bos.flush();
			long endTime = System.currentTimeMillis();// 对比运行时间用  #####
			System.out.println(endTime - startTime);
			fis.close();
			fos.close();
			bis.close();
			bos.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){  // 注:这几文件的读写,IOException是必须抛出的,当成常识记住就行…
			e.printStackTrace();
		}
	}
 
}

其运行时间为:

上篇博客中未使用缓冲流复制文件时:

(下面代码可以不用看和上篇博客内容一样)

public class BufferedDemo1 {
 
	public static void main(String[] args) {
		// 文件拷贝:图片文件的复制粘贴,读取和写入
		// (1)创建输入流对象,输出对象
		FileInputStream fis;
		FileOutputStream fos;
		BufferedInputStream bis;
		BufferedOutputStream bos;
		try {
			// (2)实例化输入流对象指向待复制文件;实例化输出对象,参数为复制后文件的全路径;
			fis = new FileInputStream("video.mp4");
			//bis = new BufferedInputStream(fis);     // 缓冲输入流
			fos = new FileOutputStream("videoCopy.mp4");
			//bos = new BufferedOutputStream(fos);    // 缓冲输出流
			// (3)n存放读方法的返回值,如果为-1代表已经读到文件末尾了;
			int n = 0;
			// (4)定义一个字节数组,存放读取到的数据;这个数组得大一点;
			byte[] b = new byte[1024];
 
			long startTime = System.currentTimeMillis();
 
			// (5)读取内容,并同时将读取到的内容写出来;这儿是每读1024个字节就写一下,即读一点写一点,直到全部复制完;
			while((n = fis.read(b))!= -1){
				fos.write(b,0,n); // 将读取到的内容写到fos这个文件流对象对应的文件中去;
			}
			// (6)关闭输入流、输出流;
			//bos.flush();
			long endTime = System.currentTimeMillis();
			System.out.println(endTime - startTime);
			fis.close();
			fos.close();
			//bis.close();
			//bos.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){  // 注:这几文件的读写,IOException是必须抛出的,当成常识记住就行…
			e.printStackTrace();
		}
	}
}

运行时间:可以发现,缓冲流对效率的提升还是挺明显的。

字符流分类

字节输入输出流:读取和写入数据都是二进制格式,善于处理如视频图片等二进制文件;( 其输入的时候是二进制文件,输出来的也是二进制内容,可能在具体落实到实际载体上的时候,会编码成合适的内容

字符输入输出流:读取和输出的是字符内容,注入微信聊天时发送的文字;( 输入输出都是字符 ?)

●字符流包括:

字符输入流:Reader

字符输出流:Writer

●字符输入流的父类是:Reader类,其包括多个子类。

其中需要着重了解的有: BufferedReader(缓冲字符输入流)InputStreamReader(字节流和字符流的转换流)FileReader(文件输入流)

●字符输出流的父类是:Writer类,其包括多个子类。

其中需要着重了解的有: BufferedWriter(缓冲字符输出流)OutputStreamWriter(字节流和字符流的转换流)FileWriter(文件输出流)

字符流之InputStreamReader和OutputStreamWriter

●字符流中常用的两个是:InputStreamReader和OutputStreamWriter;这两个是不带缓冲区的;

●InputStreamReader和OutputStreamWriter是字节字符 转换 流,这两个类的作用就是把字节流转换成字符流;

●InputStreamReader和OutputStreamWriter,在涉及到网络传输时较为常用,因为网络上传输的数据都是字节流,本地接受后,如有需要可以通过InputStreamReader和OutputStreamWriter将接受过来的字节流转成字符流。

除了InputStreamReader和OutputStreamWriter,当不涉及到网路传入或其他场景时,可以通过字符流中的另外两个子类,FileReader和FileWriter,直接把数据读成字符流


一:InputStreamReader类和OutputStreamWriter类,构造方法,常用方法简介

InputStreamReader类:

构造方法:参数是字节输入流,因为InputStreamReader的作用就是把字节输入流转换成字符

常用方法:


OutputStreamWriter类:

构造方法:

常用方法:


二:InputStreamReader示例

InputStreamReader类,调用read()方法读取数据并打印;

(1)构造InputStreamReader对象时,需要FileInputStream对象;即构造字符输入流对象,需要用到字节输入流对象;

public class ReaderDemo {
 
	public static void main(String[] args) {
 
		try {
			// (1)先用字节输入流读取数据,得到文件的字节流(后续再转成字符流)
			FileInputStream fis = new FileInputStream("imooc.txt");
			// (2)将字节流转成字符流,这一步是流的连接
			InputStreamReader isr = new InputStreamReader(fis);
			int n = 0;
			// (3)定义字符数组,存放读取到的字符
			char[] cbuf = new char[10];
			while((n=isr.read())!=-1){     // 这个read()方法的返回值是实际读取到的值
				System.out.print((char)n);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

InputStreamReader类,调用read()方法读取数据并打印;使用read(char b)方法读取:和上例一样,只是使用了read方法的另一种重载形式。

public class ReaderDemo {
 
	public static void main(String[] args) {
 
		try {
			// (1)先用字节输入流读取数据,得到文件的字节流(后续再转成字符流)
			FileInputStream fis = new FileInputStream("imooc.txt");
			// (2)将字节流转成字符流,这一步是流的连接
			InputStreamReader isr = new InputStreamReader(fis);
			int n = 0;
			// (3)定义字符数组,存放读取到的字符
			char[] cbuf = new char[10];
			// 调用另一种read(char b)方法
			while((n = isr.read(cbuf))!= -1){    // 这个read(char b)方法的返回值,是读取字符的数量
				String s = new String(cbuf,0,n);
				System.out.print(s);
			}
 
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

三:InputStreamReader和OutputStreamWriter示例,文件复制

(1)InputStreamReader和OutputStreamWriter对象的构建,都需要相应的字节流对象;

(2)InputStreamReader类的read方法和OutputStreamWriter类的write方法相互对应,根据业务需求选用;

(3) 这儿自所以写成字节流转成字符流的样子,只是为了模拟网络传输的情况

public class ReaderDemo1 {
	public static void main(String[] args) {
 
		try {
			// (1)先用字节输入流读取数据,得到文件的字节流(后续再转成字符流)
			FileInputStream fis = new FileInputStream("imooc.txt");
			FileOutputStream fos = new FileOutputStream("imooc1.txt");
			// (2)将字节流转成字符流,这一步是流的连接
			InputStreamReader isr = new InputStreamReader(fis);
			OutputStreamWriter osw = new OutputStreamWriter(fos);
 
			int n = 0;
			// (3)定义字符数组,存放读取到的字符
			char[] cbuf = new char[10];
			// (4)读写文件
			while((n = isr.read(cbuf))!= -1){
				osw.write(cbuf, 0, n);   // OutputStreamWriter类的write()方法有很多重载形式,可按需求选用
			}
			// (5)flush并关闭流
			osw.flush();
			fis.close();
			fos.close();
			isr.close();
			osw.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

上面也可指定字符编码:

字符流之BufferedReader(缓冲输入流)和BufferedWriter(缓冲输出流)

一:BufferedReader(缓冲输入流)和BufferedWriter(缓冲输出流)构造方法和常用方法简介

BufferedReader (缓冲输入流)

常用方法:注意readLine()在某些文档处理中常用,以前在实际开发中也曾重点用过这个方法


BufferedWriter(缓冲输出流):

构造方法

常用方法:


二:文件复制示例

(1)下面的例子,是现用字节流读数据,然后把字节流转成字符流(这一步是为了模拟网络传输的场景),然后把字符流转成字符缓冲流。

(2)疑问:为什么要用字节流去读文件,这儿是为了模拟数据的网络传输(因为,数据在网络上传输时,都是以字节流的形式传输的)。这儿模拟了一下,本地接受网络上过来的数据时,需要先进行字节流和字符流的转换。

(3) 而实际上,字符流也提供了文件读写类:FileReader和FileWriter(下篇博客介绍这两个类)

public class ReaderDemo1 {
	public static void main(String[] args) {
 
		try {
			// (1)先用字节输入流读取数据,得到文件的字节流(后续再转成字符流)
			FileInputStream fis = new FileInputStream("imooc.txt");
			FileOutputStream fos = new FileOutputStream("imooc1.txt");
			// (2)将字节流转成字符流,这一步是流的连接
			InputStreamReader isr = new InputStreamReader(fis);
			OutputStreamWriter osw = new OutputStreamWriter(fos);
			// (3)将字符流转成  带缓冲的字符流,这又是一次流的连接
			BufferedReader br = new BufferedReader(isr);
			BufferedWriter bw = new BufferedWriter(osw);
 
			int n = 0;
			// (4)定义字符数组,存放读取到的字符
			char[] cbuf = new char[10];
			// (5)读写文件
			while((n = br.read(cbuf))!= -1){
				bw.write(cbuf, 0, n);   // OutputStreamWriter类的write()方法有很多重载形式,可按需求选用
			}
			// (6)flush并关闭流
			bw.flush();
			fis.close();
			fos.close();
			isr.close();
			osw.close();
			br.close();
			bw.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

复制文件

一:FileReader和FileWriter文件复制范例:

(1)可见,FileReader和FileWriter的使用,和前面的字节流,其他字符流类的使用情况类似;

(2)这儿没有使用缓冲流;而以前在处理文档时, 常用的readLine()方法是BufferedReader类的

public class ReaderDemo2 {
public static void main(String[] args) {
 
		try {
			FileReader fr = new FileReader("imooc.txt");
			FileWriter fw = new FileWriter("imooc2.txt");
			int n = 0;
			char[] cbuf = new char[10];
			while((n = fr.read(cbuf))!= -1){
				fw.write(cbuf, 0, n);
			}
			fw.flush();
			fr.close();
			fw.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
 
}

二:带缓冲的FileReader和FileWriter复制文件

(1)其中流的连接,其操作模式和其他情况类似

public static void main(String[] args) {
 
		try {
			FileReader fr = new FileReader("imooc.txt");
			FileWriter fw = new FileWriter("imooc2.txt");
			BufferedReader br = new BufferedReader(fr);
			BufferedWriter bw = new BufferedWriter(fw);
			int n = 0;
			char[] cbuf = new char[10];
			while((n = br.read(cbuf))!= -1){
				bw.write(cbuf, 0, n);
			}
			fw.flush();
			fr.close();
			fw.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

三:ReadLine()方法的应用

readLine():该方法时BufferedReader类中的

public class ReaderDemo2 {
public static void main(String[] args) {
 
		try {
			FileReader fr = new FileReader("imooc.txt");
			FileWriter fw = new FileWriter("imooc2.txt");
			BufferedReader br = new BufferedReader(fr);
			BufferedWriter bw = new BufferedWriter(fw);
 
			String s;
			while((s = br.readLine())!= null){
				// 这儿可以添加各种对本行文档的处理程序
				s = s+"hahaha";
				bw.write(s);
				bw.newLine();   // 这种换行方式,在不同的系统中不一样,会存在问题
			}
			bw.flush();
			fr.close();
			fw.close();
			br.close();
			bw.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

四:换行符

不同的系统中,换行符是不同的:

●Windows:\r\n;

●Linux:\r;

●mac:\n

一般开发是在Windows环境下开发,而项目部署到Linux系统上;

而上面用到的newLine(),在在不同的系统上都会产生换行的效果,但实际对应的字符是不同的。由此会引发一些问题,在实际开发中,如若涉及可能由此引发的问题,需要解决一下。

对象序列化

对象序列化:把Java对象转换为字节序列的过程;

对象反序列化:把字节序列恢复为Java对象的过程;

●对象序列化的来源:

实际网络上发送信息时,往往会把一组信息封装成一个对象;即网络传输时经常会遇到如何发送对象的内容和如何解析对象的内容的问题。就是对象序列化要解决的问题。

以前做项目的时候,也遇到对象序列化~~~

●对象序列化的步骤:

(1)创建一个类,这个类需要继承Serializable接口;一个类只有继承了Serializable接口,这个类的对象才能在网络上进行读写传输;

(2)创建对象;

(3)将对象写入文件;(实际上是写入到网络中的,这儿为了方便演示,写入到文件中了,以后了解到网络传输时,再回头看看)

(4)从文件中读取对象信息;(实际上是从网络中读取对象的,这儿为了方便演示,改成文件了,以后了解到网络传输时,再回头看看)

●对象的读写需要用到:

ObjectInputStream:对象输入流

ObjectOutputStream:对象输出流 (这两个类是字节流的子类)

Java serialVersionUID 有什么作用? - 简书

面试官: 为什么不能轻易修改 serialVersionUID 字段?


一:ObjectOutputStream和ObjectInputStream简述

需要注意,在构建ObjectOutputStrea和ObjectInputStream对象流时,需要借助一个OutputStream和InputStream。一般使用FileOutputStream类和FileInputStream类对象

ObjectOutputStream

构造方法:

其常用方法有:close(),flush(),writeObject(Object obj)等,可查API。


ObjectInputStream

构造方法:

其常用方法有:close(), readLine()( 这个的读取一行常用不? ,readObject()。其中,readObject()负责去读取writeObject(Object obj)写的数据,这俩成对使用。


二:ObjectOutputStream和ObjectInputStream示例

Goods类时待序列化和反序列化的类,其需要实现Serializable接口

// Goods实现Serializable接口
public class Goods implements Serializable{
	private String goodsId;
	private String goodsName;
	private double price;
 
	public Goods(){}
	public Goods(String goodsId,String goodsName,double price){
		this.goodsId = goodsId;
		this.goodsName = goodsName;
		this.price = price;
	}
	public String getGoodsId() {
		return goodsId;
	}
	public void setGoodsId(String goodsId) {
		this.goodsId = goodsId;
	}
	public String getGoodsName() {
		return goodsName;
	}
	public void setGoodsName(String goodsName) {
		this.goodsName = goodsName;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
 
	@Override
	public String toString(){
		return goodsId+" "+goodsName+" "+price;
	}
}

示例一:ObjectOutputStream输出范例:(对于对象序列化和反序列化,一般是先输出才有输入哦;就像在网络传输中,只有一方发出了,另一方才能接收一样。)

public class SerialTest {
 
	public static void main(String[] args) {
		Goods good1 = new Goods("gd01","电脑",3000);
		try {
			// (1)构建OutputStream对象,因为ObjectOutputStream的构造方法需要用到
			FileOutputStream fos = new FileOutputStream("imooc.txt");
			// (2)构建ObjectOutputStream对象
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			// (3)开始写
			oos.writeObject(good1);  // 将对象写入到文件中,也实现了一个持久化的存储
			// (4)flush(),和close()
			oos.flush();
			fos.close();oos.close();
 
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		}
	}
}

输出结果是乱码,很正常,这其中可能像http报文格式那样,不仅有对象信息,还有各种辅助标识信息等。本来就不是给人直接用眼看的。


示例二:ObjectOutputStream和ObjectInputStream范例:写读一个Goods对象

public class SerialTest {
 
	public static void main(String[] args) {
		Goods good1 = new Goods("gd01","电脑",3000);
		try {
			// (1)构建OutputStream对象,因为ObjectOutputStream的构造方法需要用到
			FileOutputStream fos = new FileOutputStream("imooc.txt");
			// (2)连接文件输出流,构建ObjectOutputStream对象
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			// (3)构建InputStream对象,因为ObjectInputStream的构造方法需要用到
			FileInputStream fis = new FileInputStream("imooc.txt");
			// (4)连接文件输出流,构建ObjectInputStream对象
			ObjectInputStream ois = new ObjectInputStream(fis);
			// (5)将对象信息写入文件
			oos.writeObject(good1);  // 将对象写入到文件中,也实现了一个持久化的存储
			// (6)flush()
			oos.flush();
			// (7)从文件中读取对象数据
			Goods good = (Goods)ois.readObject();  //这条语句需要捕获 ClassNotFoundException异常
			// (8)打印对象,验证是否正确读取对象
			System.out.println(good);// 输出对象(调用toString方法),验证对象是否被正确读出来
			// (9)调用close方法
			fos.close();oos.close();fis.close();ois.close();
 
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		} catch( ClassNotFoundException e){
			e.printStackTrace();
		}
	}
}

输出结果:发现,可以正常识别Goods对象


示例三:ObjectOutputStream和ObjectInputStream范例:写读Goods对象和Boolean对象

(1)注意有 //*******的是新增内容

(2)可以发现ObjectOutputStream和ObjectInputStream类中,有多种类型的write方法和read方法,以方便写读各种类型的对象;(大概率是不同类型的对象的诸如结尾符号,分隔符等等的标识信息不一样,所以需要不同的写读方法)

public class SerialTest {
 
	public static void main(String[] args) {
		Goods good1 = new Goods("gd01","电脑",3000);
		try {
			// (1)构建OutputStream对象,因为ObjectOutputStream的构造方法需要用到
			FileOutputStream fos = new FileOutputStream("imooc.txt");
			// (2)连接文件输出流,构建ObjectOutputStream对象
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			// (3)构建InputStream对象,因为ObjectInputStream的构造方法需要用到
			FileInputStream fis = new FileInputStream("imooc.txt");
			// (4)连接文件输出流,构建ObjectInputStream对象
			ObjectInputStream ois = new ObjectInputStream(fis);
			// (5)将对象信息写入文件
			oos.writeObject(good1);  // 将对象写入到文件中,也实现了一个持久化的存储
			oos.writeBoolean(true); //**********写入Boolean的对象,包装类吧应该
			// (6)flush()
			oos.flush();
			// (7)从文件中读取对象数据
			Goods good = (Goods)ois.readObject();  //这条语句需要捕获 ClassNotFoundException异常
			// (8)打印对象,验证是否正确读取对象
			System.out.println(good);// 输出对象(调用toString方法),验证对象是否被正确读出来
			boolean bool = ois.readBoolean(); //**********读取Boolean对象并输出
			System.out.println(bool);
			// (9)调用close方法
			fos.close();oos.close();fis.close();ois.close();
 
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e){
			e.printStackTrace();
		} catch( ClassNotFoundException e){
			e.printStackTrace();
		}
	}
}

输出结果:可以正常读出Goods对象和Boolean对象

注:(1)

(2)可知我们存的时候,是先存的Goods对象,后存的Boolean对象,自然读的时候,先读出来的是Goods对象,如果读的是Goods对象,而用Boolean类型对象接收会引发EOFException结尾异常。


示例四:ObjectOutputStream和ObjectInputStream范例:写读多个Goods对象


示例五:ObjectOutputStream和ObjectInputStream范例:写读Goods对象和Goodss对象

(注:其中Goodss类内容和Goods类内容类似,这儿仅仅是展示多种类对象的存储示例)


由上面的例子,可以看到,由几个问题需要验证下:

(1)有没有一个统一的read()方法,该方法可以返回一个对象;有没有判断该次read方法读取的对象是什么类型的方法;有没有判断还有没有没读完的对象的类似迭代器那样的方法?这些问题,

三:常见问题

问题一 :有什么方法可以判断文件中的对象是否读完并循环输出

可以手动在最后写入一个结束标识null,oss.writeObject(null)

读的判定条件是 ois.readObject != null;

使用try-catch语句块,捕获EOFException异常,并提示“已经温泉读入即可”;

经过实测,上面的策略是可以的;上面的异常,应该是为了防止已经读完了,但任然继续读了最后的一个null对象而准备的。

问题二 :流的关闭顺序

建议,先打开的先关闭,后打开的后关闭。

问题三 :对象序列化的文件乱码

对象输出流写到文件中的东西,其本身不是文本数据,是对象序列化后的数据,所以乱码是正常的;

问题四 :为什么需要flush方法

Java在使用流时,会有一个小的缓冲,就像一个管道一样,输出的时候通过管道存到介质上(硬盘或其他),当输出完成后管道里面可能还有残余,使用flush方法可以清空管道将残余内容全部存到介质上。