JAVA(输入输出)+序列化

站在内存的角度:

  • 输入:程序从外部(存储设备)中读取数据
  • 输出:程序将数据输出到存储设备中
  • 输入输出包括两种IO流:字节流、字符流
  • 字节流以字节为单位处理输入、输出操作
  • 字符流以字符来处理输入、输出操作

一、File类

File能新建、删除、重命名文件和目录。

//当前路径下的所有文件和路径
String [] fileList = file.list();
for(String fileName:fileList){
System.out.println(filaName);
}

1.访问文件和目录

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

public class FileTest {
	public static void main(String[] args) throws IOException {
		File file = new File(".");
		System.out.println(file.getName());
		System.out.println(file.getParent());//获取父目录名
		System.out.println(file.getAbsoluteFile());//获取绝对路径
		System.out.println(file.getAbsoluteFile().getParent());//获取父目录名
		File tempFile = File.createTempFile("aaa", ".txt");
		tempFile.deleteOnExit();//当Java虚拟机退出时,删除该文件
		File newFile = new File(System.currentTimeMillis()+"");
		System.out.println("newFile对象是否存在"+newFile.exists());
		newFile.createNewFile();//如果newFile对象对应的文件不存在时,该方法将新建一个该File对象所指定的新文件
		newFile.mkdir();//创建目录
		String[] fileList = file.list();
		System.out.println("当前路径下所有文件和路径如下:");
		for(String fileName:fileList) {
			System.out.println(fileName);
		}
		File[] roots = File.listRoots();
		System.out.println("系统所有根路径如下:");
		for(File root:roots) {
			System.out.println(root);
		}
	}
}

运行结果:
.
null
C:\Users\ycy\eclipse-workspace1\Leetcode.
C:\Users\ycy\eclipse-workspace1\Leetcode
newFile对象是否存在false
当前路径下所有文件和路径如下:
.classpath
.project
.settings
1554045581331
bin
Java_Final_Part
Java_Final_project5.0
src
系统所有根路径如下:
C:\

2.文件过滤器
File类的list()方法中可以接收一个FilenameFilter参数,通过参数可以只列出符合条件的文件。FilenameFilter接口中包含了一个accept(File dir, String name)方法,该方法依次 对指定的File的所有子目录或者文件进行迭代。

import java.io.File;

public class FilenameFilterTest {
	public static void main(String[] args) {
		File file = new File(".");
		//如果文件以.txt结尾,或者文件对应要给文件目录,就返回true
		String[] nameList = file.list((dir,name) -> name.endsWith(".txt")
				||new File(name).isDirectory());
		for(String name : nameList) {
			System.out.println(name);
		}
	}

}

二、理解Java的IO流

1 流的分类

(1)输入流输出流

  • 输入流主要由InputStream和Reader作为基类
  • 输出流主要由OutputStream 和Writer作为基类

(2)字节流和字符流

  • 它们操作的数据单元不同:字节流操作的数据单元是8位的字节,字符流操作的数据单元是16位的字符
  • 字节流由InputStream和OutputStream作为基类,字符流由Reader和Writer作为基类。
  • 字节流比字符流的使用范围广,但字符流比字节流操作方便。

(3)节点流和处理流

  • 字节流:可以从/向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流。当使用节点流输入/输出时,程序连接到实际的数据源,和实际的输入/输出结点。
  • 处理流用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。程序不会直接连接到实际的数据流,没有和实际的输入/输出结点连接装饰器设计模式
  • 使用处理流的优点:只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源,随着处理流所包装的节点流的变化,程序实际访问的数据源也放生变化。
    在这里插入图片描述

2.流的概念模型

  • 所有输入流的基类:InputStream(字节输入流)/Reader(字符输入流)
  • 所有输出流的基类:OutputStream, Writer
  • 对于InputStream和Reader来说,它们把输入设备抽象成了一个“水管”,这个水管中的每个“水滴”依次排列,字符流和字节流处理方式类似,只是处理的单位不同,输入流使用隐式的记录指针表示当前正准备从哪个“水滴”还是读取,每当程序从InputStream和Reader里读取一个或多个“水滴”后,记录指针自动向后移动。同时提供一些方法来控制记录指针的移动。
  • 对于OutputStream和Writer而言,它们把输出设备抽象成了一个“水管”,刚开始位空,程序依次把“水滴”放入输出流的水管中,输出流同样采用隐式的记录指针来标识当前水滴即将放入的位置,指针自动向后移动。
  • Java的处理流显示了Java输入/输出设计的灵活性。处理流的功能:
    (1)性能的提高:通过缓冲的方式提高输入/输出的效率
    (2)操作的便捷:处理流可以提供了一系列便捷的方法来一次处理输入/输出大批量的内容,而不是输入/输出一个或多个“水滴”。
    处理流可以“嫁接”在任何已存在的流的基础上,允许Java应用程序采用相同的代码、透明的方式来访问不同输入/输出设备的数据流。
    在这里插入图片描述
    在这里插入图片描述

三、字节流和字符流

1.InputStream 和Reader

InputStream和Reader都是抽象类,不能创建实例,它们分别有一个用于读取文件的输入流:FileInputStream和FileReader.它们都是节点流,直接和指定文件关联。

如下程序:创建一个长度为1024的字节数组来读取该文件,然后输出结果就是程序本身。

package TreeCreate;

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

public class FileInputStreamTest {
	public static void main(String[] args) throws IOException {
		//创建字节输入流  ./当前目录   ../父级目录     /根目录
		FileInputStream fis = new FileInputStream("./src/TreeCreate/FileInputStreamTest.java");
		//创建一个长度位1024的“管子”
		byte[] bbuf = new byte[1024];
		int hasRead = 0;//用来保存实际读取的字节数
		//read(bbuf),从输入流中最多读取bbuf.length个字节的数据,将其存储在字节数组bbuf中,返回实际读取的字节数
		while((hasRead = fis.read(bbuf)) > 0) {
			//取出“水滴”(字节),将字节数组转换成字符串
			System.out.println(new String(bbuf, 0, hasRead));
		}
		fis.close();
	}
}

下面的代码用FileRead来读取文件本身
try(){}catch(){}:数据流会在 try 执行完毕后自动被关闭

package TreeCreate;

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

public class FileReaderTest {
	public static void main(String[] args) throws IOException {
		try (
			FileReader fr = new FileReader("./src/TreeCreate/FileReaderTest.java"))
		{
			char[] cbuf = new char[32];
			int hasRead = 0;
			while((hasRead = fr.read(cbuf)) > 0) {
				System.out.println(new String(cbuf,0,hasRead));
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

2.OutputStream 和 Writer

  • 因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即String对象作为参数。如果希望直接输入字符串内容,则使用Writer会有更好的效果。
  • 使用try(){ }catch( ){ } 语句关闭文件输入流,保证输入流一定会被关闭。
    以下程序: 使用FileInputStream来执行输入,使用FileOutputStream执行输出,实现复制MapTest.java的功能。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamTest {
	public static void main(String[] args) throws IOException {
		try (
			FileInputStream fis = new FileInputStream("./src/TreeCreate/MapTest.java");
			FileOutputStream fos = new FileOutputStream("newFile1.txt"))
		{
			byte[] bbuf = new byte[1024];
			int hasRead = 0;
			while((hasRead = fis.read(bbuf))> 0){
				fos.write(bbuf,0,hasRead);
			}
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

如果希望直接输出字符串的内容,则使用Writer效果更好。

import java.io.FileWriter;
import java.io.IOException;

public class FileWriteTest {
	public static void main(String[] args) {
		try (FileWriter fw = new FileWriter("pom1.txt"))
			{
			fw.write("Hello\r\n");//换行
			fw.write("Bonjour");
			}
		 catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

四、输入/输出流体系

借助处理流简化编程。

1.处理流的用法

  • 处理流:其构造器参数是已经存在的流。

  • 节点流:构造器参数是物理IO节点。

  • 处理流优势:处理流的输入/输出操作更简单;使用处理流的执行效率更高。

  • System.out的类型是PrintStream.

  • 如果需要输出文本内容,都应该将输出流包装成PrintStream后再输出。

  • 创建处理流时传入一个节点流作为构造器参数即可。关闭时,只用关闭最上层的处理流即可,系统会自动关闭该处理流包装的节点流。

以下程序用PrintStream处理流来包装OutputStream,输出更加方便。首先定义了一个节点输出流fos,然后用PrintStream包装了节点输出流,使用PrintStream输出字符串、输出对象。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

public class PrintStreamTest {
	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("test.txt");
			PrintStream ps = new PrintStream(fos);
			ps.println("普通字符串");
			ps.println(new PrintStreamTest());
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

2.输入/输出流体系

  • 通常来说,字节流的功能比字符流强大,因为计算机中的数据都是二进制的,而字节流可以处理二进制文件。问题是:如果用字节流来处理文本文件,则需要合适的方法把这些字节转换成字符,这就增加了编程的复杂度。
  • 如果进行输入/输出的内容是文本内容,则应该考虑使用字符流。
  • 如果进行输入/输出的内容是二进制内容,则使用字节流。
  • Window下简体中文默认使用GBK字符集
  • Linux下简体中文默认使用UTF-8字符集

以数组为物理节点的节点流,字节流以字节数组为节点,字符流以字符数组为节点。
字符流还可以使用字符串作为物理节点,用于实现从字符串读取内容,或将内容写入字符串(用StringBuffer充当字符串)的功能。
下面程序使用字符串作为物理节点的字符输入/输出流的用法。

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

public class StringNodeTest {
	public static void main(String[] args) {
		String src = "Hello\n"+"Bonjour\n"+"Salut";
		char[] buffer = new char[20];
		int hasRead = 0;
		try(StringReader sr = new StringReader(src)) {
			while((hasRead = sr.read(buffer)) > 0) {
				System.out.println(new String(buffer,0,hasRead));
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//创建StringWriter时,实际上以一个StringBuffer作为输出节点。
		try(StringWriter sw = new StringWriter(20))
		{
			sw.write("有一个美丽的世界\n");
			sw.write("她在远方等我\n");
			sw.write("天真的孩子\n");
			System.out.println("下面是sw字符串的内容:");
			System.out.println(sw.toString());
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
}

以上程序与前面使用FileReader和FileWriter的程序基本相似,只是创建StringReader和StringWriter对象时传入的是字符串节点,而不是文件节点。
创建StringWriter时,实际上以一个StringBuffer作为输出节点。因为String是不可变得字符串对象,所以StringWriter使用StringBuffer作为输出节点。
缓冲流增加了缓冲功能,可以提高输入、输出的效率,同时需要使用flush()才可以将缓冲区的内容写到实际的物理节点。
为什么String是不可变字符对象呢?有什么好处呢?
不可变对象,顾名思义就是创建后不可以改变的对象。
String s = “ABC”;
s.toLowerCase();
如上s.toLowerCase()并没有改变“ABC“的值,而是创建了一个新的String类“abc”,然后将新的实例的指向变量s。
相对于可变对象,不可变对象有很多优势:
1).不可变对象可以提高String Pool的效率和安全性。如果你知道一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址复制地址(通常一个指针的大小)需要很小的内存效率也很高。对于同时引用这个“ABC”的其他变量也不会造成影响。

2).不可变对象对于多线程是安全的,因为在多线程同时进行的情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况。
String是所有语言中最常用的一个类。我们知道在Java中,String是不可变的、final的。Java在运行时也保存了一个字符串池(String pool),这使得String成为了一个特别的类。

String类不可变性的好处

1.只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(译者注:String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

2.如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。

3.因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

4.类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。

5.因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

3.转换流

  • 两个转换流:InputStreamReader, OutputStreamWriter。 用于将字节流转换成字符流。
  • Java使用System.in代表标准输入,即键盘输入,但这个标准输入流是InputStream类的实例,而且键盘输入内容都是文本内容,所以可以使用InputStreamReader将其转换成字符输入流,普通的Reader读取内容时不方便,可以将普通的Reader再次包装成BufferedReader。使用BufferedReader的readLine()方法一次读取一行内容。
  • BufferedReader
    由Reader类扩展而来,提供通用的缓冲方式文本读取,readLine读取一个文本行,从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。
  • 经常将文本内容的输入流包装成BufferedReader,调用readLine()方法一次读入一行内容。

如下代码:首先将System.in转换成Read对象,再把Reader包装成BufferedReader,BufferedReader流具有缓冲功能,它可以一次读取一行内容。

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class KeyinTest {
	public static void main(String[] args) {
		try(
				//将System.in对象转换成Reader对象
				InputStreamReader reader = new InputStreamReader(System.in);
				//将普通的Reader包装成BufferedReader
				BufferedReader br = new BufferedReader(reader))
		
		{
			String line = null;
			//循环逐行读取
			while((line = br.readLine())!= null) {
				if(line.equals("exit"))
					System.exit(1);//非正常退出,无论程序正在执行与否,都退出
				System.out.println("输入内容:"+line);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

4.推回输入流
PushbackInputStream和PushbackReader
两个推回输入流都带有一个推回缓冲区,当程序调用unread()方法时,系统会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,如果不够才去原输入流中读取。
在这里插入图片描述
如下程序:打印目标字符串“new PushbackTest”之前的内容。

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;

public class PushbackTest {
	public static void main(String[] args) throws IOException {
		//指定推回缓冲区的长度使64
		try(PushbackReader pr = new PushbackReader(new FileReader("PushbackTest.java"),64)) 
		{
			char[] buf = new char[32];
			String lastContent = "";
			int hasRead = 0;
			while((hasRead = pr.read(buf))>0) {
				//将读到的字符数组转成字符串
				String content = new String(buf,0,hasRead);
				int targetIndex = 0;
				//目标字符串“new PushbackReader”
				if((targetIndex = (lastContent+content).indexOf("new PushbackReader"))>0) {
					//将本次内容和上次内容一起推回缓冲区
					pr.unread((lastContent+content).toCharArray());
					if(targetIndex>32) {
						buf = new char[targetIndex];
					}
					//每次读取指定长度的内容(目标字符串之前的内容)
					pr.read(buf,0,targetIndex);
					//打印读取的内容
					System.out.println(new String(buf,0,targetIndex));
					System.exit(0);
				}
				else {
					System.out.println(lastContent);
					lastContent = content;
				}
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

5.重定向标准输入/输出
Java的标准输入:System.in,键盘
标准输出:System.out,显示器

下面的程序创建了一个PrintStream输出流,并将系统的标准输出重定向到该PrintStream输出流。这意味着标准输出不再输出到屏幕,而是输出到out.txt文件。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

public class RedirectOut {
	public static void main(String[] args) {
		//创建输出流
		//PrintStream处理流,对FileOutputStream包装
		try(PrintStream ps = new PrintStream(new FileOutputStream("out.txt")))
		{
			//将标准输出量定向到ps输出流
			System.setOut(ps);
			System.out.println("普通字符串");
			System.out.println(new RedirectOut());
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

下面的程序:首先创建了一个FileInputStream输入流,使用System.setIn()方法将系统标准输入重定向到该文件输入流。运行程序后,程序不会等待用户输入,而是直接输出了RedirectIn.java文件的内容,这表明程序不是使用键盘作为标准输入,而是使用RedirectIn.java文件作为标准输入源。

package TreeCreate;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;

public class RedirectIn {

	public static void main(String[] args) throws IOException {
		try(FileInputStream fis = new FileInputStream("./src/TreeCreate/RedirectIn.java"))
		{
			//重定向标准输入流
			System.setIn(fis);
			//使用System.in创建Scanner对象,用于获取标准输入
			Scanner sc = new Scanner(System.in);
			//只把回车当作分隔符
			sc.useDelimiter("\n");
			while(sc.hasNext()) {
				System.out.println("键盘中输入的内容:"+sc.next());
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

6.Java虚拟机读写其他进程的数据
Runtime对象的exec()方法可以运行平台上的其他程序,该方法产生一个Process对象,Process对象代表由该Java程序启动的子进程。
子进程读取程序中的数据(使用输入流,对于Java程序而言)

下面的程序:使用Runtime 启动javac进程,获得子进程p,以p进程的错误输入流创建BufferedReader br,错误流对本程序来说是输入流。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ReadFromProcess {
	public static void main(String[] args) throws IOException {
		Process p = Runtime.getRuntime().exec("javac");
		try(
		//BufferedReader是Reader的报装流,可以调用readLine()方法一次读取一行内容。
			BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())))
		{
			String buff = null;
			while(((buff=br.readLine())!=null))
				System.out.println(buff);
		}	
	}
}

还可以通过Process的getOutputStream()方法获取向子进程输入数据的流(对本程序来说是输出流)如下程序实现了在Java程序中启动Java虚拟机运行另一个Java程序,并向领域给Java程序中输入数据。

import java.io.IOException;
import java.io.PrintStream;

public class WriteToProcess {
	public static void main(String[] args) throws IOException {
	//运行java ReadStandard命令,返回运行该命令的子进程
		Process p = Runtime.getRuntime().exec("java ReadStandard");
		try(PrintStream ps = new PrintStream(p.getOutputStream()))
		{
			ps.println("普通字符串");
			ps.println(new WriteToProcess());
		}
		}
}
public class ReadStandard {
	private void main() {
		// TODO Auto-generated method stub
		
		try(	//使用System.in创建Scanner对象,用于获取标准输入
				Scanner sc = new Scanner(System.in);
				PrintStream ps = new PrintStream(new FileOutputStream("out.txt")))
		{
			//只把回车作为分隔符
			sc.useDelimiter("\n");
			while(sc.hasNext()) {
				ps.println("键盘输入的内容是:"+sc.next());
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

在程序ReadStandard中:使用Scanner获取标准输入的类,该类提供了main()方法,可以被运行,但是此处不打算直接运行此类,而是由WriteToProcess类来运行ReadStandard类。程序首先使用Runtime的exec()方法运行Java ReadStandard命令,该命令将运行ReadStandard类,并返回运行该程序的子进程,程序通过输出流向进程p输出数据,这些数据被ReadStandard类读到。
运行WriteToProcess类,程序运行结束将产生一个out.txt文件,该文件由ReadStandard类产生,文件内容由WriteToProcess类写入ReadStandard进程里,并由ReadStandard读取这些数据,将这些数据保存在out.txt文件里。

7.RandomAccessFile

  • 它既可以读取文件,也可以向文件输出数据,支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读取数据。
  • 如果只需要读取文件部分内容,需要改方法最好。
  • 如果需要在已存在的文件后追加内容,使用改方法最好
  • 局限性:只能读取文件,不能去读取其他IO节点
  • RandomAccessFile对象包含一个记录指针,用以标识当前读写处的位置。文件记录指针刚开始指向文件头,可以自由移动记录指针。
  • 包含了InputStream中的三个read()方法,OutputStream中的三个write()方法。
  • RandomAccessFile类有两个构造器,一个是使用String参数指定文件名,一个是使用File参数指定文件本身。同时,创建RandomAccessFile对象时还需要一个mode参数,指定其访问模式。

以下程序的功能:从300处开始访问文件数据

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileTest {
	public static void main(String[] args) throws IOException {
		try(RandomAccessFile raf = new RandomAccessFile("RandomAccessFileTest.java", "r"))
		{
			System.out.println("文件指针的初始位置:"+raf.getFilePointer());
			raf.seek(300);
			byte[] bbuf = new byte[1024];
			int hasRead = 0;
			while((hasRead = raf.read(bbuf))>0) {
				//将字节转成字符串输出
				System.out.println(new String(bbuf,0,hasRead));
			}
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

一下程序的功能:在文件尾部追加内容,先将记录指针移动到文件最后,然后开始向文件中输入内容。

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class AppendContent {
	public static void main(String[] args) throws IOException {
		try(RandomAccessFile raf = new RandomAccessFile("out.txt","rw"))
		{
			raf.seek(raf.length());
			raf.write("追加的内容:\r\n".getBytes());
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
  • RandomAccessFile 不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某个位置开始输出,则新输出的内容会覆盖文件中原有的内容。
  • 如果需要向指定位置插入内容,程序需要先把插入点后面的内容读入缓冲区,等把需要插入的数据写入文件后,再将缓冲区的内容追加到文件后面。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

public class InsertContetn {
	
	public static void main(String[] args) throws IOException {
		Insert("InsertContent.java",45,"插入的内容\r\n");
	}

	private static void Insert(String fileName, long pos, String insertContent) throws IOException {
		// TODO Auto-generated method stub
		//创建临时文件,JVM退出时被删除。
		File tmp = File.createTempFile("temp", null);
		tmp.deleteOnExit();
		try (RandomAccessFile raf = new RandomAccessFile(fileName,"rw");
			FileOutputStream tmpOut = new FileOutputStream(tmp);
			FileInputStream tmpIn = new FileInputStream(tmp))
		{
			raf.seek(pos);
			//下面代码将插入后的内容读入临时文件中保存
			byte[] bbuf = new byte[64];
			int hasRead = 0;
			while((hasRead = raf.read(bbuf))>0) {
				tmpOut.write(bbuf,0,hasRead);
			}
			//将指针重新定位到Pos位置
			raf.seek(pos);
			raf.write(insertContent.getBytes());
			//追加临时文件中的内容
			while((hasRead = tmpIn.read(bbuf))>0) {
				raf.write(bbuf, 0, hasRead);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

8.对象序列化

  • 对象序列化的目标:将对象保存在磁盘中,或允许在网络中直接传输对象。
  • 对象序列化机制允许把内存中的Java对象转化成二进制流,从而允许二进制流持久的保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。
  • 其他程序一旦获得了这种二进制流,都可以将二进制流恢复成原来的Java对象。

(1)序列化的含义和意义

  • 序列化机制允许将序列化的Java对象转换成字节序列,这些字节序列保存在磁盘中或通过网络传输,以备以后重新恢复成原来的对象。序列化机制使对象可以脱离程序的运行而独立存在。
  • 对象的序列化(Serialize)指将一个Java对象写入IO流中,对象的反序列化是指从IO流中恢复该Java对象
  • 为了让某个类是可序列化的,该类必须实现两个接口之一:Serializable:该接口是标记接口,无须实现任何方法,它只是表明该类是可序列化的。
    Externalizable。

(2)使用对象流实现序列化

a.程序通过以下两个步骤来序列化对象

  • 创建一个ObjectOutputStream 输出流,这个输出流是一个处理流,所以必须建立在其他节点流的基础之上。
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“object.txt”));
  • 调用ObjectOutputStream对象的writeObject()方法输出可序列化对象。
    将一个Person对象输出到输出流中
    oos.writeObject(per);

以下程序:定义一个Person类,这个类是普通的Java类,实现了Serializable接口,该接口标识该类的对象是可序列化的

public class Person implements java.io.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;
	}
	
}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class WriteObject {
	public static void main(String[] args) {
		try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt")))
		{
			Person per = new Person("sun",500);
			//将Person对象写入输出流
			oos.writeObject(per);
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

运行上面程序会看到生成了一个object.txt文件,该文件的内容是Perosn对象。

b.反序列化对象步骤

  • 创建一个ObjectInputStream 输入流,它是一个处理流,必须建立在其他节点流的基础上。
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“Object.txt”))
    调用ObjectInputStream 对象的readObject()方法读取流中的内容,该方法返回一个Object类型的Java对象,然后将其对象强制类型转换成其真是的类型。
    Person p = (Person)ois.readObject();
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ReadObject {
	public static void main(String[] args) throws ClassNotFoundException {
		try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/TreeCreate/object.txt")))
		{
			Person p = (Person)ois.readObject();
			System.out.println("名字:"+p.getName()+"\n年龄:"+p.getAge());
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

当一个可序列化类有多个父类时,这些父类要么有无参数构造器,要么是可序列化的。

(3)对象引用的序列化
如果Teacher类持有一个Person类的引用,只有Person类是可序列化的,Teacher 类才是可序列化的。
以下程序对per有三次引用,序列化算法:

  • 所有保存到磁盘中的对象都有一个序列化编号
  • 当程序试图序列化一个对象时,程序先检查该对象是否被序列化过,只有从未被序列化过,才会序列化
  • 如果某对象已经被序列化过了,程序直接输出一个序列化编号。

以下程序:序列化顺序:t1, t2, pre.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class WriteTeacher {
	public static void main(String[] args) {
		try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt")))
		{
			Person per = new Person("孙悟空",500);
			Teacher t1 = new Teacher("唐僧",per);
			Teacher t2 = new Teacher("菩提老祖",per);
			oos.writeObject(t1);
			oos.writeObject(t2);
			oos.writeObject(per);
			oos.writeObject(t2);
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

在这里插入图片描述


版权声明:本文为weixin_43321444原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。