【Java】15 输入输出

一、File类

1.访问文件和目录
/** 
 * @ClassName: FileTest
 * @description: 本节代码主要讨论FIle类的相关访问文件和目录
 * @author: FFIDEAL
 * @Date: 2020年4月10日 上午10:17:48
 */  

package M15;

import java.io.File;
import java.io.IOException;

public class FileTest {
	public static void main(String[] args) throws IOException{
		//以当前===路径===来创建一个File对象
		File file = new File(".");
		//获取文件名
		System.out.println(file.getName()); 	//输出:newFile.txt
		//获取相对路径的父路径(可能会出错,输出null)
		System.out.println(file.getParent());
		//获取绝对路径		
		System.out.println(file.getAbsolutePath());		//输出:E:\JavaCode\Code\newFile.txt
		//获取上一级路径
		System.out.println(file.getAbsoluteFile().getParent());		//输出:E:\JavaCode\Code
		//在当前路径下创建一个临时文件
		File tmpFile = File.createTempFile("tempFile", ".txt");
		//输出当前临时文件的绝对路径
		System.out.println(tmpFile.getParent());	
		//指定JVM退出时删除该文件
		tmpFile.deleteOnExit();
		//以系统当前时间来命令新文件
		File newFile = new File(System.currentTimeMillis()+"");
		System.out.println("newFile对象是否存在:" + newFile.exists());
		newFile.createNewFile();
		//以newFile创建一个目录,因为newFile已经存在
		//所以下面方法返回false,即无法创建该目录
		newFile.mkdir();
		//使用list()方法列出当前路径下的所有文件
	    String[] fileList = file.list(); 
	    System.out.println("===当前路径下的所有文件和路径==="); 
	    for(String fileName : fileList){
		    System.out.println(fileName);
	    }
		//listRoots()静态方法列出所有磁盘根路径
		File[] roots = File.listRoots();
		System.out.println("===系统过有根目录===");
		for(File root : roots) {
			System.out.println(root);
		}
		//listFiles是获取该目录下所有文件和目录的绝对路径  
		File[] fs = file.listFiles();
		for(File f:fs) {
			System.out.println(f);
		}
	}
}

2.文件过滤器
/** 
 * @ClassName: FileNameFilterTest
 * @description: 文件过滤器
 * @author: FFIDEAL
 * @Date: 2020年4月14日 上午10:07:43
 */  

package M15;

import java.io.File;

public class FileNameFilterTest {
	public static void main(String[] args) {
		File file = new File(".");
		//文件以“.java”结尾或者文件对应一个路径,则返回true
		String[] nameList = file.list((dir , name) -> name.endsWith(".java") || new File(name).isDirectory());
		for(int i = 0; i < nameList.length; i++) {
			System.out.println(i);
			System.out.println(nameList[i]);
		}
		for(String name : nameList) {
			System.out.println(name);
		}
	}
}

二、理解Java的IO流

1.流的分类
①按数据流向分类

​ 从程序所在内存角度划分

​ 输出流:只能向其写入数据,而不能从中读取数据(内存 → 硬盘)------- 基类为:OutputStream类和Writer类

​ 输入流:只能从中读取数据,而不能向其写入数据(cache → 内存)--------基类为:InputStream类和Reader类

②按数据单元的长度划分

​ 字节流:数据单元为8位的字节 ------- 基类为:InputStream类和OutputStream类

​ 字符流:数据单元为16位的字节------- 基类为:Writer类和Reader类

③按流的角色来分类

​ 节点流:从特定的IO设备(如磁盘、网络等)读/写的数据的流

​ 处理流:用一个已存在的流进行连接或封装,通过封装后的流来实现读/写的数据的功能

​ 处理流的好处:不能直接连接到实际的数据源,可以采用完全相同的输入/输出代码来访问不同的数据源,消除不同节点流带来的差异(只要使用相同的处理流)————变压器?

2.流的概念模型

​ Reader/Writer:是对人来说,Reader读者,获取知识,输入;Writer写着,输出知识,输出

​ InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流

​ OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流

三、字节流和字符流

1.输入流中的字节流和字符流

read():读取一个“水滴(字节或字符)”

read(byte[] b)/read(char[] ch):读取多个"水滴"

read(char[] ch,int off,int len)/read(byte[] ch,int off,int len):同上,规定了长度

字节流

/** 
 * @ClassName: FileInputStreamTest
 * @description: 本节代码讨论了使用FileInputStream (字节流而非字符流)输入流读取数据的一种实现方法
 * 注意:在本节代码中,如果设置的“水管”不长,也就是说,不能一次性取完文件中所有的数据,要分多批次
 * 		此时,若文件中时有中文的话,意味着可能会发生乱码 ———— 原因是,中文占2个字符,而读取时,只能制度去一个字符
 * @author: FFIDEAL
 * @Date: 2020年4月14日 下午2:06:07
 */  

package M15;

import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamTest {
	public static void main(String[] args) throws IOException{
		//创建一个字节输入流
		FileInputStream fis = new FileInputStream("E:\\JavaCode\\Code\\src\\M15\\FileNameFilterTest.java");
		//创建一个字节流水管,用来取水
		byte[] bbuf = new byte[1024];
		//用于保存实际读取的字节数
		int hasRead = 0;
		//当水管里还有水的时候
		while((hasRead = fis.read(bbuf)) > 0) {
			System.out.println(new String(bbuf , 0 , hasRead));
		}
		//关闭水管
		fis.close();
	}
}

字符流

/** 
 * @ClassName: FileInputStreamTryCatchTest
 * @description: 本节代码主要讨论使用try-catch和FileReader(字符输入流)   字符流  来实现输入流的实现过程
 * 另外,也是使用了自动关闭资源的try语句来关闭文件输入流
 * @author: FFIDEAL
 * @Date: 2020年4月14日 下午2:18:59
 */  

package M15;

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

public class FileInputStreamTryCatchTest {
	public static void main(String[] args) throws IOException{
		try(FileReader fis = 
				new FileReader("E:\\JavaCode\\Code\\src\\M15\\FileNameFilterTest.java")){
			//创建一个字符流水管
			char[] chbuf = new char[32];
			//取水珠计数器
			int hasRead = 0;
			while((hasRead = fis.read(chbuf)) > 0) {
				System.out.println(new String(chbuf, 0 , hasRead));
			}
			
		}
		catch(IOException ex) {
			ex.printStackTrace();
		}
	}
}

2.输入流中的字节流和字符流

write(int a):写入一个字节/字符

write(byte[] b/char[] ch):写入一组数据

write(byte[] b/char[] ch,int off,int len):写入一组由off开始,长度为lend数据

字节流

/** 
 * @ClassName: FileOutputStreamTest
 * @description: 本节代码主要讨论==字节输出流==的实现类,配合字节输入流,将一个文档里面的数据转移到另一个文档中去
 * @author: FFIDEAL
 * @Date: 2020年4月14日 下午2:32:35
 */  

package M15;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamTest {
	public static void main(String[] args) throws IOException{
		 try(
				 //创建一个字节输入流
				 FileInputStream fis = 
				 		new FileInputStream("E:\\JavaCode\\Code\\src\\M15\\FileNameFilterTest.java");
				 //创建一个字节输出流
				 FileOutputStream fos =
						 new FileOutputStream("E:\\JavaCode\\Code\\src\\M15\\FileNameFilterTest.txt")
				 ){
			 //创建一根字节流水管
			 byte[] bbuf = new byte[1024];
			 //取水计数器
			 int hasRead = 0;
			 while((hasRead = fis.read(bbuf)) > 0) {
				 //每读一次,即写入文件输出流,读了多少就写多少
				 fos.write(bbuf, 0, hasRead);
			 }
		 }
		 
		 catch(IOException ex) {
			 ex.printStackTrace();
		 }
	}
}

字符流

/** 
 * @ClassName: FileWriterTest
 * @description: 本节讨论了使用==字符输出流==来实现将一首诗写入到文件中,同时也将一个文件里的数据写入到另一个文件里
 * @author: FFIDEAL
 * @Date: 2020年4月14日 下午2:41:50
 */  
public class FileWriterTest {
	public static void main(String[] args) throws IOException {
		try(//创建一个字符输出流
				FileWriter fw = new FileWriter("E:\\JavaCode\\Code\\src\\M15\\pom.txt");
				){
			fw.write("床前明月光,\t\n");
			fw.write("疑是地上霜。\t\n");
			fw.write("举头望明月。\t\n");
			fw.write("低头思故乡。\t\n");
		}
		catch(IOException ex) {
			ex.printStackTrace();
		}
		//创建一个字符输入流
		FileReader fr = new FileReader("E:\\JavaCode\\Code\\src\\M15\\pom.txt");
		//创建一个字符输出流
		FileWriter fw = new FileWriter("E:\\JavaCode\\Code\\src\\M15\\pomWriter.txt");
		//创建一根水管
		char[] chbuf = new char[1024];
		//取水计数器
		int hasRead = 0;
		while((hasRead = fr.read(chbuf)) > 0) {
			fw.write(chbuf, 0, hasRead);
		}
		//关闭输入流和输出流
		fr.close();
		fw.close();
	}
}

四、输出/输出流体系

1.处理流用法

处理流的典型思路是:使用处理流来包装节点流,程序通过处理流来执行输入、输出功能,让节点流和底层IO设备以及文件交互

识别处理流很简单:只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就是处理流

/** 
 * @ClassName: PrintStreamTest
 * @description: 本节代码讨论的是PrintStream处理流,
 * @author: FFIDEAL
 * @Date: 2020年4月15日 下午1:46:30
 */  
public class PrintStreamTest {
	public static void main(String[] args) throws IOException{
		try(
				//创建一个字节流
				FileOutputStream fos = new FileOutputStream("E:\\JavaCode\\Code\\src\\M15\\print.txt");
				//创建一个PrintStream流,把FileOutputStream字节流包装起来
				PrintStream ps = new PrintStream(fos)){
			//使用PrintStream执行输出
			ps.println("PrintStream执行输出");
			//直接使用PrintStream对象输出对象
			ps.println(new PrintStreamTest());
		}
		catch(IOException e) {
			e.printStackTrace();
		}
	}
}

2.输入/输出流体系

字节流以字节数粗为节点,字符流以字符数组为节点;

字符流可以使用字符串作为物理节点,用于实现从字符串读取内容,或将内容写入字符串

/** 
 * @ClassName: StringNodeTest
 * @description: 本节代码主要讨论输入输出的体系问题
 * @author: FFIDEAL
 * @Date: 2020年4月15日 下午2:09:39
 */  

public class StringNodeTest {
	public static void main(String[] args) throws IOException{
		String src ="一去二三里,烟村四五家。 \r\n" + 
				"亭台六七座,八九十枝花。";
		char[] chbuf = new char[1024];
		int hasRead = 0;
		try(
				StringReader sr = new StringReader(src)){
			//以循环的方式读取字符串
			while((hasRead = sr.read(chbuf)) > 0) {
				System.out.println(new String(chbuf,0,hasRead));
			}
		}
		catch(IOException ioe) {
			ioe.printStackTrace();
		}
		try(
				//创建StringWriter时,是加上以一个StringBuffer作为输出节点
				//一面指定的20就是StringBuffer的初始长度
				StringWriter sw = new StringWriter()
				){
			sw.write(src);
			System.out.println("===下面是sw字符串节点里面的内容===");
			System.out.println(sw.toString());
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

3.转换流
/** 
 * @ClassName: KeyinTest
 * @description: 本节代码主要讨论System.in对象转换成Reader对象(利于包装为BufferReader),
 * 将普通的Reader包装为BufferReader(使用BufferReader对象的方法利于读取)
 * @author: FFIDEAL
 * @Date: 2020年4月15日 下午4:29:34
 */  

package M15;

public class KeyinTest {
	public static void main(String[] args) throws IOException{
		try(
				//将System.in对象转换成Reader对象
				InputStreamReader isr = new InputStreamReader(System.in);
				//将普通的Reader包装为BufferReader
				BufferedReader br = new BufferedReader(isr)
				){
			String line =null;
			//采用循环的方式来读取
			while((line = br.readLine()) != null) {
				//如果读取到字符串“exit”,程序就退出
				if(line.equals("exit")) {
					System.exit(1);
				}
				System.out.println("输出内容为:" + line);
			}
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

4.推回输入流

就是输出到—传入的参数----输出传参以前的字符

五、重定向标准输入输出

将System.out的输出重定向到指定文件输出,而非在屏幕上输出

/** 
 * @ClassName: RedirectOut
 * @description: 本节代码主要讨论将系统的标准输出重定向到该printStream输出流
 * 运行上面程序时将看不到任何输出 ———— 这意味着标准输出不再输出到屏幕,而是输出到out.txt
 * 将System.in重定义到指定文件里的内容,正好与程序中的输出一致
 * @author: FFIDEAL
 * @Date: 2020年4月15日 下午6:05:58
 */  

public class RedirectOut {
	public static void main(String[] args) throws IOException{
		try(
				//一次性创建PrintStream输出流
				PrintStream ps = new PrintStream(
                    new FileOutputStream("E:\\JavaCode\\Code\\src\\M15\\out.txt"))
				){
			//将标准输出重定向到ps输出流
			System.setOut(ps);
			//向标准输出输出一个字符串
			System.out.println("这是一个字符串");
			//向标准输出输出一个对象
			System.out.println(new RedirectOut());
		}
		catch(IOException ioe) {
			ioe.printStackTrace();
		}
	}
}

将System.in的重定向到指定文件,而不是键盘输入

/** 
 * @ClassName: RedirectIn
 * @description: 创建一个FileInputStream输入流,并使用System的setIn()方法将系统标准输入重定向到该文件输入流
 * 运行本程序,程序不会等用户输入,而是直接输出RedirectOut.java文件的内容,这表明程序不再使用键盘作为标准输入
 * @author: FFIDEAL
 * @Date: 2020年4月15日 下午9:42:45
 */  

public class RedirectIn {
	public static void main(String[] args) throws IOException{
		try(
				//创建一个FileInputStream输入流
				FileInputStream fis = new FileInputStream("E:\\JavaCode\\Code\\src\\M15\\RedirectOut.java")
				){
			//将标准输入重定向到fis输出流
			System.setIn(fis);
			//使用System.in创建Scanner对象,用于获取标准输入
			Scanner sc = new Scanner(System.in);
			//增加下面一行只把回车作为分隔符
			sc.useDelimiter("\n");
			//判断是否还有下一个输入项
			while(sc.hasNext()) {
				System.out.println("键盘输入的内容是:" + sc.next());
			}
		}
		catch(IOException ioe) {
			ioe.printStackTrace();
		}
	}
}

六、Java虚拟机读写其他进程的数据

/** 
 * @ClassName: ReadFromProcess
 * @description: 本节代码主要讨论使用Runtime对象的exec()方法可以运行平台上的其他程序,
 * 该方法产生一个Process对象,
 * Process对象代表由该Java程序启动的子进程
 * @author: FFIDEAL
 * @Date: 2020年4月16日 上午10:50:18
 */  

public class ReadFromProcess {
	public static void main(String[] args) throws IOException{
		//运行javac命令,返回运行该命令的子进程
		Process p = Runtime.getRuntime().exec("javac");
		try(
				//以p进程的错误流创建BufferReader对象,这个错误流对本程序是输入流,对p进程是输出流
				BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()))
				){
			String buff = null;
			while((buff = br.readLine()) != null) {
				System.out.println(buff);
			}
		} 
	}
}

/** 
 * @ClassName: WriteToProcess
 * @description: 本节代码讨论通过Process的getOutStream()方法获得向进程输入数据的流,
 * 如下程序实现了在java程序中启动Java虚拟机运行另一个java程序,并向另一个程序输入数据
 * @author: FFIDEAL
 * @Date: 2020年4月16日 下午12:50:20
 */  

public class WriteToProcess {
	public static void main(String[] args) throws IOException{
		//运行Java ReadStandard命令,返回该命令的子进程
		Process p = Runtime.getRuntime().exec("Java ReadStandard");
		try(
				//以p进程的输出流创建PrintStream对象
				//对于程序来说这是输出流,对于子进程来说,这是输入流
				PrintStream ps = new PrintStream(p.getOutputStream());
				){
			//向ReadStandard程序写入内容,这些内容被ReadStandard读取
			ps.println("这是ReadStandard读取的内容");
			ps.println(new WriteToProcess());  
		}
	}
}

//定义一个ReadStandard类,该类可以接收标准输入
//并将标准输入写入ReadStandard.txt文件中d
class ReadStandard{
	public static void main(String[] args) throws IOException{
		try(
				//使用System.in创建Scanner对象用于标准输入
				Scanner sc = new Scanner(System.in);
				PrintStream ps = new PrintStream("E:\\JavaCode\\Code\\src\\M15\\ReadStandard.txt")
				){
			//增加下面一行只把回车作为分隔符
			sc.useDelimiter("\n");
			while(sc.hasNext()) {
				//输出键盘输入的内容
				System.out.println("键盘输入的内容是:" + sc.next());
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
}

七、RandomAccessFile

RandomAccessFile(任意访问文件):是功能最丰富的文件访问类,它既==可以读文件,也可以写文件,同时还有**“任意访问”==**文件。若只需要访问文件的部分内容,而非把文件从头到尾读取,采用RandomAccessFile类将是一个很好的选择。

但是她有一个缺陷,也就是说他只能读写文件但**不能读写其他IO节点**。

以下使用RandomAccessFile来访问指定的中间部分数据

public class RandomAccessFiletest {
	public static void main(String[] args) throws IOException{
		try(
				//创建一个RandomAccessFile对象
				RandomAccessFile raf = 
					new RandomAccessFile("E:\\JavaCode\\Code\\src\\M15\\pomWriter.txt", "r");
				){
			//获取RandomAccessFile对象指针位置,初始位置为0
			System.out.println("RandomAccessFile的文件指针的初始地址" + raf.getFilePointer());
			//移动文件记录指针位置
			raf.seek(6);
			byte[] bbuf = new byte[1024];
			//记录实际读取的字节数
			int hasRead = 0;
			while((hasRead = raf.read(bbuf)) > 0) {
				//取出竹筒中的水滴(“字节”),将字节数组转换成字符串输入
				System.out.println(new String(bbuf,0,hasRead));
			}
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

下面代码是写入文件

public class AppendContent {
	public static void main(String[] args) throws IOException{
		try(
				//以“读写”的方式打开一个RandomAccessFile对象
				RandomAccessFile raf = new RandomAccessFile("E:\\JavaCode\\Code\\src\\M15\\pomWriter.txt", "rw");
				){
			//将指针移动到最后
			raf.seek(raf.length());
			raf.write("\n这是使用RandomAccessFile对象追加的内容".getBytes());
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		
	}
}

下面程序实现了向指定文件、指定位置插入内容的功能

/** 
 * @ClassName: InsertContent
 * @description: 本节代码向指定位置插入数据,在此位置之后的数据放入缓存区,
 * 当全部写完输入后,缓存区里的数据接在插入数据之后
 * @author: FFIDEAL
 * @Date: 2020年4月16日 下午5:42:01
 */
public class InsertContent {
	public static void insert(String fileName, long position , String insertContent) 
			throws IOException{
		//创建一个临时文件,用作缓存区
		File tmp = File.createTempFile("tmp", null);
		tmp.deleteOnExit();
		try(
				RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
				//使用临时文件来保存插入点数据
				FileOutputStream tmpOut = new FileOutputStream(tmp);
				FileInputStream tmpIn = new FileInputStream(tmp)){
			raf.seek(position);
			//下面代码将插入点后的内容读入临时文件中保存
			byte[] bbuf = new byte[20];
			//用于保存实际读取的字节
			int hasRead = 0;
			while((hasRead = raf.read(bbuf)) > 0) {
				//将读取的数据写入 临时文件
				tmpOut.write(bbuf, 0, hasRead);
			}
			//下面代码用于插入内容
			//把文件记录指针重新定位到pos位置
			raf.seek(position);
			//追加需要插入的内容
			raf.write(insertContent.getBytes());
			//追加临时文件中的内容
			while((hasRead = tmpIn.read(bbuf)) > 0) {
				raf.write(bbuf,0,hasRead);
			}
		}
	}

	public static void main(String[] args) throws IOException {
		insert("E:\\JavaCode\\Code\\src\\M15\\print.txt", 45, "=========================");
	}
}

八、对象序列化

1.序列化的含义和意义

序列化机制:将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象 —— 序列化机制使得对象可以脱离程序存在

对象的序列化:讲一个Java对象希尔IO流中,与此对应的四,对象的反序列化则指从IO流中恢复该Java对象

实现对象的序列化的前提就是对象的类要序列化,类的序列化就是必须实现Serialiable或Externalizable接口

2.使用对象流实现序列化

使用Serializable来实现序列化非常简单,主要让目标实现Serializable标记接口即可,无需事先任何方法

​ 1.创建ObjectOutputStream,这是一个输出流

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));

​ 2.调用ObjectOutputStream对象的writeObject()方法输出可序列化对象

oos.writeObject(per);
序列化

实例代码

//创建一个普通类,继承Serializable接口。当然Externalizable接口也行
public class Person implements Serializable{
	private String name;
	private int age;
	public Person(String name, int age) {
		System.out.println("有参数构造器");
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}

使用objectOutputStream(对象输出流)讲一个Person类的对象写入文件中

public class WriteObject {
	public static void main(String[] args) throws IOException{
		try(
				//创建一个ObjectoutputStream输出流
				ObjectOutputStream oos = new ObjectOutputStream(
						new FileOutputStream("E:\\JavaCode\\Code\\src\\M15\\Seriliable.txt"));
				){
			Person per = new Person("张三", 18);
			//将这个对象写入输出流
			oos.writeObject(per);
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

反序列化

1.创建一个ObjectInputStream输入流,这个输入流是一个处理流,必须建立在其他节点流基之上

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.txt"));

2.调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的java对象

Person p = (Person)ois.readObject();

以下代码示范了刚刚生成的文件中读取Person对象的步骤

public class ReadObjectTest {
	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
		try(
				ObjectInputStream ois = new ObjectInputStream(
						new FileInputStream("E:\\JavaCode\\Code\\src\\M15\\Seriliable.txt"))
				){
					Person p = (Person)ois.readObject();
					System.out.println("p的姓名:" + p.getName() + ",年龄:" + p.getAge());
				}
		catch(Exception e) {
			e.printStackTrace();
		}
		
	}
}

注意:反序列化读取的仅仅使Java对象的数据,而非Java类

若使用序列化机制向文件中写入多个Java对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取

3.对象引用的序列化

若一个类的成员变量不是基本类型或者String型,而是另一种引用类型,那么这个引用类必须是可序列化的

此外,序列化机制会采用一种特殊的序列化算法

1.所有保存到磁盘中的对象都有一个序列化编号

2.在程序试图序列化一个对象的时候,程序会优先检查该对象是否被序列化过,若没有,系统才会将该对象转换成字节序列并输出

3.程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象

public class Teacher implements Serializable{
	private String name;
	private Person student;
	
	public Teacher(String name,Person student) {
		this.name = name;
		this.student = student;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Person getStudent() {
		return student;
	}

	public void setStudent(Person student) {
		this.student = student;
	}
}
public class WriteTeacher {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		try(
				ObjectOutputStream oos = new ObjectOutputStream(
						new FileOutputStream("E:\\JavaCode\\Code\\src\\M15\\teacher.txt"))){
			Person per = new Person("小明", 13);
			Teacher t1 = new Teacher("王老师", per);
			Teacher t2 = new Teacher("张老师", per);
			//依次将4个对象写入输出流
			oos.writeObject(t1);
			oos.writeObject(t2);
			oos.writeObject(per);
			oos.writeObject(t2);
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

上面writeObject()方法仿佛写入了四个对象,但实际上只写入三个对象,而且序列的两个Teacher对象的student引用实际是同一个Person对象。下面程序读取序列化文件中的对象即可证明

public class ReadTeacher {
	public static void main(String[] args) 
			throws FileNotFoundException,IOException, ClassNotFoundException{
		try(
				ObjectInputStream ois = new ObjectInputStream(
						new FileInputStream("E:\\JavaCode\\Code\\src\\M15\\teacher.txt"))){
			//依次读取ObjectInputStream输入流中的4个对象
			Teacher t1 = (Teacher)ois.readObject();
			Teacher t2 = (Teacher)ois.readObject();
			Person per = (Person)ois.readObject();
			Teacher t3 = (Teacher)ois.readObject();
			//输出true
			System.out.println("t1的student引用和p是否相同:" + (t1.getStudent()==per));
			//输出true
			System.out.println("t2的student引用和p是否相同:" + (t2.getStudent()==per));
			//输出true
			System.out.println("t2和t3是否相同:" + (t2==t3));
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

注意:序列化机制当程序序列化一个可变对象时,只有第一次使用writeObject()方法输出时才会将该对象转换成字节序列并输出,再次调用只会输出前面序列化的编号

4.自定义序列化

在一些特殊的场景下不希望系统将实例变量值进行序列化

通过在实例变量前用transient关键词修饰,可以指定Java序列化无需理会该实例变量。

private transient int age;

下面程序先序列化一个Person对象,然后再反序列化该Person对象,得到反序列化兑现的Person对象之后程序输出该对象的age实例变量值

public class TransientTest {
	public static void main(String[] args) 
			throws FileNotFoundException, IOException, ClassNotFoundException {
		try(
				ObjectOutputStream oos = new ObjectOutputStream(
						new FileOutputStream("E:\\JavaCode\\Code\\src\\M15\\transient.txt"));
				ObjectInputStream ois = new ObjectInputStream(
						new FileInputStream("E:\\JavaCode\\Code\\src\\M15\\transient.txt"))){
			Person per = new Person("哪吒", 7);
			oos.writeObject(per);
			Person p = (Person)ois.readObject();
			System.out.println(p.getAge());
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}

九、NIO

新IO采用内存映射文件的方式来处理输入/输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件。Channel(通道)和Buffer(缓冲)是新IO的两个核心对象。

Channel比InputStream、OutputStream最大的区别在于他提供了一个map方法,通过这个方法可以直接将“一块数据”映射到内存中。Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先放到Buffer中去。

打个比方:Buffer既可以像“竹筒”一样一次次从Channel中取水,也允许使用Channel直接将文件袋某块数据映射成Buffer

1.Buffer

Buffer有三个重要的概念:容量(capacity)、界限(limit)和位置(position)
在这里插入图片描述

/** 
 * @ClassName: BufferTest
 * @description: 
 * @author: FFIDEAL
 * @Date: 2020年4月17日 下午9:44:47
 */  

package M15;

import java.nio.CharBuffer;

public class BufferTest {
	public static void main(String[] args) {
		//创建Buffer,通过CharBuffer的一个静态方法allocate创建一个capacity为8的CharBuffer
		CharBuffer buff = CharBuffer.allocate(8);
		System.out.println("capacity:" + buff.capacity());	//输出:capacity:8
		System.out.println("limit:" + buff.limit());	//输出:limit:8
		System.out.println("position:" + buff.position());	//输出:position:0
		//放入元素
		buff.put('a');
		buff.put('d');
		buff.put('e');
		System.out.println("加入三个元素之后:position:" 
							+ buff.position());	//输出:加入三个元素之后:position:3
		//调用flip(),把limit设为position处,把position设为0
		buff.flip();
		System.out.println("执行flip()方法之后,limit = "
							+ buff.limit());	//输出:执行flip()方法之后,limit = 3
		System.out.println("执行flip()方法之后,position = "
							+ buff.position());	//输出:执行flip()方法之后,limit = 3
		//取出第一个元素
		System.out.println("第一个元素position = 0:" + buff.get());	//输出:第一个元素position = 0:a
		System.out.println("取出一个元素后的position = " 
							+ buff.position());	//输出:取出一个元素后的position = 1
		//调用clear方法,将position归0,limit = capacity
		System.out.println("执行clear()方法之后,limit = " 
							+ buff.limit());	//输出:执行clear()方法之后,limit = 3
		System.out.println("执行clear()方法之后,position = " 
							+ buff.position());	//输出:执行clear()方法之后,position = 1
		System.out.println("执行clear()后,缓冲区内容并没有被清除,第三个元素为"
							+ buff.get(2));	//输出:执行clear()后,缓冲区内容并没有被清除,第三个元素为e
		System.out.println("执行绝对读取后,position = " + buff.position());						        //输出:执行绝对读取后,position = 1
		
	}
}

2.Channel

Channel有以下两个特点

  1. Channel可以直接将指定的文件部分或全部映射成Buffer
  2. 程序不能直接访问Channel,读写都不行,Channel只能与Buffer交互

Channel有三个主要的方法:map() read() write()

public class FileChannelTest {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		File f = new File("E:\\JavaCode\\Code\\src\\M15\\print.txt");
		try(
				//创建FileInputStream,以文件输入流创建FileChannel
				FileChannel inChannel = new FileInputStream(f).getChannel();
				//文件输出流创建FileChannel,用以控制输出
				FileChannel	outChannel = 
						new FileOutputStream("E:\\JavaCode\\Code\\src\\M15\\ChannelOut.txt").getChannel();
				){
			//将FileChannel里的全部数据映射成ByteBuffer,将FileChannel里面的输出全部改为ByteChannel
			MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
			//使用GBK的字符集来创建解码器
			Charset charset = Charset.forName("GBK");
			//直接将buffer里的数据全部输出,将整个ByteBuffer的全部数据写入一个输出FileChannel
			outChannel.write(buffer);
			//在调用buffer的clear()方法,复原limit和position的位置
			buffer.clear();
			//创建解码器(CharsetDecoder)对象
			CharsetDecoder cd = charset.newDecoder();
			//使用解码器将ByteBuffer转换成charBuffer
			CharBuffer cb = cd.decode(buffer);
			//CharBuffer的toString方法可以获取对应的字符串
			System.out.println(cb);
		}
		catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_之桐_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值