Thinking in java 繁琐知识点之 I/O流 、对象序列化

Thinking in java 琐碎知识点之 I/O流 、对象序列化
Java I/O流 、对象序列化
1、File类
   此类的实例可能表示(也可能不表示)实际文件系统对象,如文件或目录。
   File类可以新建、删除和重命名文件和目录,但是File不能访问文件本身的内容,这要使用IO流。
   File对象的createNewFile()方法在磁盘上创建真实的文件
例程:FileTest.java
import java.io.*;
public class FileTest {
	public static void main(String[] args) throws IOException {
		File file=new File("test1");//此相对路径即表示相对JVM的路径
		File file2=new File(".");//以当前路径来创建一个File对象
		File file3=new File("F:\\workplace\\IO\\jaa.txt");//系统不一定就存在jaa.txt这个文件
		System.out.println("file.getName()\t"+file.getName());
		System.out.println("file2.getName()\t"+file2.getName());
		System.out.println("file3.getName()\t"+file3.getName());
		System.out.println("file.getParent()\t"+file.getParent());
		System.out.println("file2.getParent()\t"+file2.getParent());
		System.out.println("file3.getParent()\t"+file3.getParent());
		System.out.println("file.getAbsolutePath()\t"+file.getAbsolutePath());
		System.out.println("file.getAbsoluteFile()\t"+file.getAbsoluteFile());
		System.out.println("file.getAbsoluteFile().getParent()\t"+file.getAbsoluteFile().getParent());
		System.out.println("file2.getAbsolutePath()\t"+file2.getAbsolutePath());
		System.out.println("file3.getAbsolutePath()\t"+file3.getAbsolutePath());
		File file4=new File("F://FileTest//test1.doc");
		System.out.println("file4.exists()\t"+file4.exists());
		//在系统中创建一个文件,注意test1.doc前面的目录一定要是真实存在的,否则执行createNewFile方法会报错
		file4.createNewFile();
		System.out.println("file4.exists()\t"+file4.exists());
		File file5=new File("F:\\zpc");
		System.out.println("file5.mkdir()"+file5.mkdir());//在系统中创建一个File对象所对应的目录
		File file6=new File("F:\\workplace");
		String fileList[]=file6.list();
		System.out.println("=======F:\\workplace目录下的所有文件和路径如下=======");
		for(String s:fileList){
			System.out.println(s);
		}
		//File的静态方法listRoots列出所有的磁盘根路径
		File[] roots=File.listRoots();
		System.out.println("======系统所有根路径=======");
		for(File f:roots){
			System.out.println(f);
		}
		
		//在F:\\zpc目录下创建一个临时文件,并指定当JVM退出时删除该文件
		File temFile=File.createTempFile("zpca", ".txt",file5);
		temFile.deleteOnExit();
		//通过挂起当前线程5秒,会看到临时文件被创建5秒后由于程序执行完毕,JVM退出,该文件又自动删除
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


文件过滤器:
import java.io.*;
public class FileNameFilterTest {
	public static void main(String[] args) {
		File file=new File("F:\\workplace\\collection\\src");
		String fileList[]=file.list(new MyFilenameFilter());
		for(String s:fileList){
			System.out.println(s);
		}
	}
}


class MyFilenameFilter implements FilenameFilter{
	public boolean accept(File dir, String name) {
		return name.endsWith(".java")||new File(name).isDirectory();
	}
}


2. io流分类

  读取一个文件,输入流  
  写出一个文件,输出流

  字符流:专门用于读写文本文件的(记事本可以正常打开),字符流操作的最小数据单元是16位的字符 查询本机上的编码表(GBK)
  
  问题?word excel是不是文本文件
  常见的文本文件: .txt  .java  html  xml  sql

  字节流:操作的最小单元是8位的字节,最小的存储单位1个字节
          1个字节8个二进制位
 任意文件
   问题?能操作文件夹吗 (不能)

3、Java中的io继承体系

   继承体系,类与类之间继承关系,子类中的共性提取成的父类
   父类中定义的功能,是这个体系中的最共性的内容
   学习一个继承体系的时候,找父类去看,建立子类对象

   字符流:
     输出流,写入文件的抽象基类(体系中的最高层的父类) Writer
     输入流,读取文件的抽象基类 Reader

   字节流:
     输出流,写入文件的抽象基类 OutputStream
     输入流,读取文件的抽象基类 InputStream

4. 字符流的输入流(读文件)使用
   字符流读取文件
     查阅API文档找到了Reader
       read()方法读取单个字符,返回int值?
       返回的int值,是读取到的字符的ASCII码值
       read()方法,每执行一次,自动的向后读取一个字符
       读取到文件末尾的时候,得到-1的值

     找到Reader类的子类 FileReader
      FileReader(String fileName) 传递字符串文件名

      read(字符数组)
      返回int值
      数组中存储的就是文件中的字符
      int返回值,读到末尾就是-1
      int返回数组中,读取到的字符的有效个数
      好处:可以提高读取的效率

      注意问题:
        出现异常:XXXX拒绝访问
 A.操作,确认是不是操作的文件
 B.登录windows的账户是不是管理员,不是管理员登录的,不能操作c盘下的文件
    其他盘符是可以的
例程:字符流读取文件
import java.io.*;
//Reader是字符流读的抽象基类(输入流,读取文件的抽象基类)
public class FileReaderDemo {
	public static void main(String[] args) {
		FileReader fr1 = null;
		FileReader fr2 = null;
		try {
			fr1 = new FileReader("F:\\f.txt");
			int len = 0;// read方法返回读取的单个字符的ASCII码(可以转换成字符输出)
			while ((len = fr1.read()) != -1) {
				System.out.print((char) len);
			}
			System.out.println("\n******将字符读入缓冲数组再输出*****");
			// 定义字符数组
			char[] buf = new char[512];// 将512*2字节字符读入缓冲数组
			fr2 = new FileReader("F:\\Thinking.txt");
			while ((len = fr2.read(buf)) != -1) {//返回值len表示读取的有效字符的长度
				System.out.print(new String(buf,0,len));//将字符数组包装成String
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fr1 != null) {
					fr1.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				throw new RuntimeException("文件关闭失败");
			}
			try {
				if (fr2 != null) {
					fr2.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

例程:自己实现的一行一行读取的方法
import java.io.*;
class MyReadLine {
	// 自己实现一行一行读取
	private Reader r;
	public MyReadLine(Reader r) {
		this.r = r;
	}


	public String myReaderLine() throws IOException {
		// 定义一个字符缓冲区,读一个字符就存储到这里
		StringBuilder sb = new StringBuilder();
		int len = 0;
		while ((len = r.read()) != -1) {
			if (len == '\r') {
				continue;
			}
			if (len == '\n') {
				return sb.toString();
			} else {
				sb.append((char) len);// 读到的是有效字符,存到缓冲区
			}
		}
		//看看缓冲区是否还有内容,有可能内容不是以回车符结尾的
		if (sb.length() != 0) {
			return sb.toString();
		} else
			return null;
	}


	public void MyClose() {
		try {
			if (r != null)
				r.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


public class TestMyReadLine {
	public static void main(String[] args) throws IOException {
		MyReadLine my = null;
		try {
			my = new MyReadLine(new FileReader("F:\\Thinking.txt"));
			String line = null;
			while ((line = my.myReaderLine()) != null) {
				System.out.println(line);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (my != null) {
					my.MyClose();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}


5.字符流的输出流(写文件)使用
    字符流写文件
    查阅API文档,找到了使用的子类,FileWriter
FileWriter(String fileName) 
          根据给定的文件名构造一个 FileWriter 对象。

void write(String str) 是FileWriter类的父类的方法

   记住:Java中字符流写数据,不会直接写到目的文件中,写到内存中
         想将数据写到目的文件中,刷新,刷到目的文本中
flush()刷新

   记住:流对象中的功能,调用了Windows系统中的功能来完成
         释放掉操作系统中的资源,简称:关闭流
close方法,关闭流之前,关闭的时候,先要刷新流中的数据
但是,如果写文件的数据量很大,写一句刷一句才好

    看到了父类中的write方法
    记住:IO操作,需要关闭资源,关闭资源的时候,开了几个流,就要关闭几个流
    单独的进行关闭资源,单独写try catch,保证每一个流都会被关闭

例程:Writer类的使用
import java.io.*;
//Writer是字符流写的抽象基类(输出流,写入文件的抽象基类)
public class FileWriterDemo {
	public static void main(String[] args) {
		FileWriter fw1 = null;
		FileWriter fw2 = null;// 关流的时候要分别处理异常
		try {
			fw1 = new FileWriter("F:\\filewriter.txt");
			fw1.write("作品z");
			char[] ch={'q','w','e','你'};
			fw2 = new FileWriter("E:\\fileWriter.txt");
			fw2.write(ch,1,ch.length-1);
			//第二个参数表示开始写入字符处的偏移量(从第几个字符开始写),第三个参数表示写多少长度的数据
			fw2.write(ch,0,ch.length-3);
			fw2.write(ch,0,ch.length);
			fw2.write(ch);//Writer能一次写入一个字符数组大小的内容;Reader能一次读出一个字符数组(相当于缓冲)大小的内容
			fw1.flush();// 字符流必须手动刷新才能真的写入文件
			fw2.flush();
		} catch (IOException e) {
			throw new RuntimeException("文件写入失败");
		} finally {
			try {
				if (fw1 != null) // 健壮性判断,流有可能为空(流对象没有建立成功)
				{
					fw1.close();// 调用close方法时会自动刷新,但是不要都等到这一步才刷
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//开了几个流关闭几个,单独关闭(否则第一个关的时候抛了异常,第二个就没有机会关了)
			try {
				if (fw2 != null)
					fw2.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

6. 复制文本文件
读取源文件 FileRreader
写入到目的文件 FileWriter
   
   两种文件复制方式,分别计算时间
读一个字符,写一个字符
第一个数组,写一个数组

   利用缓冲区复制文件
        第一行,写一行的操作
例程:复制文本文件
import java.io.*;
public class CopyText {
	// 读取一个数组,写一个数组
	public static void copyText1() {
		FileReader fr = null;
		FileWriter fw = null;
		try {
			fr = new FileReader("F:\\Thinking.txt");
			fw = new FileWriter("F:\\Thinking2.txt");
			char[] buff = new char[1024];
			int len = 0;
			while ((len = fr.read(buff)) != -1) {
				fw.write(buff, 0, len);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fr != null)
					fr.close();
			} catch (IOException e) {
				throw new RuntimeException("文件读取关闭失败");
			}
			try {
				if (fw != null)
					fw.close();
			} catch (IOException e) {
				throw new RuntimeException("文件写入关闭失败");
			}


		}
	}

	// 读取一个字符,写一个字符
	public static void copyText2() {
		FileReader fr = null;
		FileWriter fw = null;
		try {
			fw = new FileWriter("F:\\Thinking2.txt");
			fr = new FileReader("F:\\Thinking.txt");
			int len = 0;
			while ((len = fr.read()) != -1) {
				fw.write((char) len);
			}
		} catch (Exception e) {
			throw new RuntimeException("文件复制失败");
		} finally {
			try {
				if (fr != null)
					fr.close();
			} catch (IOException e) {
				throw new RuntimeException("文件读取关闭失败");
			}
			try {
				if (fw != null){
					fw.close();
				}
			} catch (IOException e) {
				throw new RuntimeException("文件写入关闭失败");
			}
		}
	}

	//读取一行写一行的方式,利用的缓冲区对象
	public static void copyText3(){
		FileReader fr=null;
		FileWriter fw=null;
		BufferedReader bfr=null;
		BufferedWriter bfw=null;
		try {
			fr=new FileReader("F:\\Thinking.txt");
			fw=new FileWriter("F:\\Thinking3.txt");
			bfr=new BufferedReader(fr);
			bfw=new BufferedWriter(fw);
			String sLine=null; 
			while((sLine=bfr.readLine())!=null){
				bfw.write(sLine);
				//bfw.write("\r\n");
				bfw.newLine();
			}
			
		} catch (IOException e) {
			throw new RuntimeException("文件复制失败");
		}finally{
			try {
				if(bfr!=null);
				bfr.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				if(bfw!=null)
				bfw.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	} 
		
	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		 copyText1();
		//copyText2();
		//copyText3();
		long cost = System.currentTimeMillis() - start;
		System.out.println("耗时:" + cost+"ms");
	}
}

7. 字符流的缓冲区对象(也叫处理流、包装流)
   利用数组提升了文件的读写速度,运行效率提升了

   我们想到了效率问题,Java工程师(Oracle),也想到了提升效率
   写好了字符流的缓冲区对象,目的提供流的写,读的效率

   写入流的缓冲区对象BufferedWriter
BufferedWriter(Writer out)
参数Writer类型的参数,传递的参数是Writer类的子类对象
缓冲区,提供流的写的效率,哪个流的效率 FileWriter

         void newLine() 写一个换行,具有跨平台
不用newLine()方法,也可以实现换行 \r\n

\r\n Windows下的换行符号
\n Linux下的换行符号

字符缓冲流示例
BufferedWriter将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
import java.io.*;
public class BufferedWriterDemo {
	public static void main(String[] args)throws IOException {
		//字符输出流对象
		FileWriter fw=new FileWriter("F:\\f.txt");
		//建立缓冲区对象,提高输出流的效率
		BufferedWriter bfw=new BufferedWriter(fw);
		bfw.write("哈w哈");
		bfw.write("呵w呵");
		bfw.newLine();//写一行,BufferedReader可以读一行
		bfw.write("嘻w嘻");
		bfw.append("append");//追加写
		bfw.flush();
		bfw.close();
	}
}

   读取流的缓冲区对象BufferedReader
      BufferedReader(Reader in) 
      参数Reader类型参数,传递的是Reader类的子类对象
      缓冲区,提供流的读的效率,哪个流的效率FileRreader


      读取一行的方法 readLine(),文件末尾返回null
      不是末尾返回字符串 String 
字符缓冲流示例
BufferedReader 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
import java.io.*;
public class BufferedReaderDemo {
	public static void main(String[] args)throws IOException {
		//字符输出流对象
		FileReader fr=new FileReader("F:\\Thinking.txt");
		//建立缓冲区对象,提高输出流的效率
		BufferedReader bfw=new BufferedReader(fr);
		//读一行,返回字符串,末位返回null
		String line=null;
		while((line=bfw.readLine())!=null){
			System.out.println(line);
		}
		bfw.close();
	}
}
8、字节流
例程1:
import java.io.*;
public class InputStreamDemo {
	public static void main(String[] args)throws IOException {
		FileInputStream fis=new FileInputStream("F:\\Thinking.txt");
		byte[] bytes=new byte[1024];//1kb
		int len=0;
		len=fis.read(bytes);//len表示有效个数
		System.out.println(new String(bytes));
		System.out.println(len);
	} 
}

例程2:
import java.io.*;
public class OutPutStreamDemo {
	public static void main(String[] args) throws IOException{
		FileOutputStream fos=new FileOutputStream("F:\\f.txt");
		//写字节数组
		byte[] bytes={97,98,99};
		fos.write(bytes,0,bytes.length);//第二个参数是下标,第三个是写入的个数
		fos.write("\r\n".getBytes());//换行
		//写字符数组,只能将字符串转成字节数组,用String类方法getBytes
		fos.write(97);//写入了a字符
		fos.write("zpc".getBytes());
		fos.close();
	}
}
例程3:
import java.io.*;
public class CopyFile {
	public static void main(String[] args) throws IOException {
		FileInputStream fis=new FileInputStream("E:\\KuGou\\屠洪刚-孔雀东南飞.mp3");
		FileOutputStream fos=new FileOutputStream("I:\\孔雀东南飞.mp3");
		int len=0;
		byte[] bytes=new byte[1024];
		while((len=fis.read(bytes))!=-1){
			fos.write(bytes,0,len);
		}
		fos.close();
		fis.close();
	}
}


例程4:
import java.io.*;
/*
 * 字节流读取键盘的输入
 * 用户一行一行的写,但是read方法每次只能读取一个字节,不方便
 * 能否一次读取一个行呢?可以使用转换流
 * 字节流转换成字符流后可以使用字符缓冲流按行读取
 */
public class KeyInDemo1 {
	public static void main(String[] args) throws IOException {
		InputStream in = System.in;// 返回字节输入流对象
		// int len=0;
		// len=in.read();//read方法每次只能读取一个字节
		// System.out.println(len);
		// 使用转换流将字节输入流转成字符输入流
		InputStreamReader isr = new InputStreamReader(in);
		//为了达到最高效率,可要考虑在 BufferedReader内包装 InputStreamReader
		BufferedReader bfr = new BufferedReader(isr);
		String line = null;
		while ((line = bfr.readLine()) != null) {
			// 使用了while循环,则要自定义结束循环的语句
			if ("exit".equals(line)) {
				System.out.println("程序终止!");
				break;
			}
			System.out.println(line);
		}
	}
}


例程5:
import java.io.*;
public class KeyInDemo2 {
	public static void main(String[] args) throws IOException {
		InputStream in = System.in;// 返回字节输入流对象
		//将字节输入流转换成字符输入流
		InputStreamReader isr = new InputStreamReader(in);
		// 为了达到最高效率,可要考虑在 BufferedReader内包装 InputStreamReader
		BufferedReader bfr = new BufferedReader(isr);
		OutputStream out=new FileOutputStream("F:\\test.txt");
		//将字节输出流转化为字符输出流
		OutputStreamWriter osw=new OutputStreamWriter(out);
		BufferedWriter bfw=new BufferedWriter(osw);
		String line = null;
		while ((line = bfr.readLine()) != null) {
			// 使用了while循环,则要自定义结束循环的语句
			if ("exit".equals(line)) {
				System.out.println("程序终止!");
				break;
			}
			//一行一行的写
			bfw.write(line);
			bfw.newLine();
			bfw.flush();
		}
	}
}

9. 处理流PrintStream流的用法
import java.io.*;
/*
 * PrintStream类的输出功能非常强大,通常需要输出文本内容时,
 * 应该将输出流包装成PrintStream后再输出
 * 对应的由于BufferedReader具有readLine方法,可以方便地一次读取一行内容,
 * 所以经常把读取文本的输入流包装成BufferedReader,用以方便读取输入流的文本内容
 */
public class PrintStreamTest {
	public static void main(String[] args) {
		PrintStream pst=null;//称作处理流或包装流
		try {
			FileOutputStream fos=new FileOutputStream("F:\\Thinking.txt");//直接和物理文件打交道的流也称节点流
			//以PrintStream来包装FileOutputStream输出流
			pst=new PrintStream(fos);
			//使用printStream来实现输出
			pst.println("普通字\r\n符串");
			pst.println(new PrintStreamTest());
			
		} catch (FileNotFoundException e) {
			e.printStackTrace(pst);
		}
		finally{
			if(pst!=null){
			pst.close();}
		}
	}
}

10、重定向标准输入输出流
   Java的标准输入输出分别通过System.in和System.out来代表,默认情况下它们分别代表键盘和显示器
   System类提供了重定向标准输入输出的方法
例程:RedirectIn.java
import java.io.*;
import java.util.Scanner;
public class RedirectIn {
	public static void main(String[] args) {
		FileInputStream fis = null;
		try {
			// 将标准输入重定向到fis输入流
			fis = new FileInputStream("F:\\Thinking.txt");
			System.setIn(fis);
			//使用流接受标准输入
//			BufferedReader bf = new BufferedReader(new InputStreamReader(
//					System.in));
//			String len = "";
//			while ((len = bf.readLine()) != null) {
//				System.out.println(len);
//			}
			
			//使用Scanner
			Scanner sn=new Scanner(System.in);
			System.out.println("=====使用Scanner=====");
			while(sn.hasNext()){
				System.out.println(sn.next());
			}
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				if (fis != null)
					fis.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}


}


例程:RedirectOut.java
import java.io.*;
public class RedirectOut {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		PrintStream p=null;
		try {
			p=new PrintStream(new FileOutputStream("F:\\out.txt"));
			p.println("p.println()");
			System.out.println("重定向输出流到F:\\out.txt");
			System.setOut(p);
			System.out.println("此行不显示在屏幕上(不在控制台输出),直接写入了文件");
			
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}finally{
			if(p!=null){
				p.close();
			}
		}
	}

}

Scanner和IO流接(BufferedReader)受键盘输入的比较(BufferedReader输入都被当成String对象,BufferedReader不能读取基本类型输入项)
例程:InputTest.java
public class InputTest {
	public static void main(String[] args) throws IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
//		int len=br.read();
//		System.out.println((char)len);
//		String len2=br.readLine();
//		System.out.println(len2);
		
//		Scanner sn=new Scanner(System.in);
//		System.out.println(sn.nextLine());
//		System.out.println(sn.nextInt());
		String input = "1 fish 3 fish red fish blue fish";
	     Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");//自定义分隔符为fish
	     System.out.println(s.nextInt());
	     System.out.println(s.nextInt());
	     System.out.println(s.next());
	     System.out.println(s.next());
	     
	     input="324 42314 fish zpc";
	     s=new Scanner(input);
	     System.out.println(s.nextInt());
	     System.out.println(s.nextInt());
	     System.out.println(s.next());
	    // System.out.println(s.nextByte(0));
	     s.close(); 
	}
}
//Scanner也可以指定读取某个文件
public class TestScanner {
	public  static void main(String[] args){
		Scanner sn=null;
		//sn.useDelimiter("\n");//如果增加这一行将只把回车符作为分隔符
		try {
			sn=new Scanner(new File("F:\\Thinking.txt"));
			while(sn.hasNext()){
				System.out.println(sn.nextLine());
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

11、RandomAccessFile对象
package Serialize;
/*
 * 功能:读一个文件,复制自身追加到此文件,操作完成后文件应该变成双倍大小
 * RandomAccessFile对象不能向文件的指定位置插入内容,这会覆盖原来的内容
 * 如果要在指定位置插入内容,程序需要先把插入点后面的内容都入缓冲区,
 * 等把需要插入的数据写入文件后,在将缓冲区的内容追加到文件后面,详见InsertContent.java
 */
import java.io.*;


public class RandomAccessFileTest {
	public static void main(String[] args) {
		RandomAccessFile raf = null;
		// 以只读方式打开一个RandomAccessFile对象
		try {
			raf = new RandomAccessFile("F:\\掀起你的盖头来.mp3", "rw");
			System.out.println("RandomAccessFile对象的文件指针初始位置:"
					+ raf.getFilePointer());
			System.out.println("raf.length():"+raf.length());
			//读文件
			byte[] b = new byte[1024];
			int hasRead = 0;
			int readTotal=0;
			long FileTotalLen= raf.length();
			while (((hasRead = raf.read(b)) != -1)&&readTotal<=FileTotalLen) {
				readTotal+=hasRead;
				//System.out.println(new String(b, 0, hasRead));
				raf.seek(raf.length());
				raf.write(b);
				raf.seek(readTotal);
			}
			
			System.out.println("raf.length():"+raf.length());
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (raf != null) {
					raf.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}


//InsertContent.java
import java.io.*;
/*
 * 功能:向一个文件的指定位置插入内容,而不覆盖原有的内容
 */
public class InsertContent {
	public static void main(String[] args) {
		insert("F:\\thinking.txt",200,"===========我是鸟鹏!==========");
	}
	public static void insert(String fileName, long pos, String content) {
		InputStream is = null;
		OutputStream os = null;
		RandomAccessFile raf = null;
		// 创建一个临时文件来保存插入点后面的数据
		try {
			File temp = File.createTempFile("tem", null);
			temp.deleteOnExit();
			raf = new RandomAccessFile(fileName, "rw");
			os=new FileOutputStream(temp);
			byte[] buf=new byte[1024];
			int hasRead=0;
			raf.seek(pos);
			while((hasRead=raf.read(buf))!=-1){
				os.write(buf,0,hasRead);
			}
			raf.seek(pos);
			raf.write(content.getBytes());
			
			//下面的代码实现插入数据的功能
			is=new FileInputStream(temp);
			while((hasRead=is.read(buf))!=-1){
				raf.write(buf,0,hasRead);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if(raf!=null)
				raf.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

12、对象序列化
在Java中如果需要将某个对象保存到磁盘或者通过网络传输,那么这个类应该实现Serializable标记接口或者Externalizable接口之一
序列化的步骤:
a、创建一个ObjectOutputstream处理流(必须建立在其它结点的基础上)对象
b、调用ObjectOutputstream对象的writeObject方法输出可序列化对象
从二进制流中恢复Java对象的反序列化步骤(按照实际写入的顺序读取):
a、创建一个ObjectInputStream处理流(必须建立在其它结点的基础上)对象
b、调用ObjectInputstream对象的readObject方法输出可序列化对象(该方法返回的是Object类型的Java对象)
(反序列化恢复Java对象时必须要提供java对象所属类的class文件)
注:如果一个可序列化对象有多个父类,则该父类要么是可序列化的,要么有无参的构造器,因为反序列化机制要恢复其关联的父类实例
而恢复这些父类实例有两种方式:使用序列化机制、使用父类无参的构造器
采用Java序列化机制时,只有当第一次调用writeObject输出某个对象时才会将该对象转换成字节序列写到ObjectOutputStream
在后面程序中如果该对象的属性发生了改变,即再次调用writeObject方法输出该对象时,改变后的属性不会被输出
如果父类没有实现Serializable接口,则其必须有默认的构造函数(即没有参数的构造函数)
但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。
这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。

例程:
import java.io.Serializable;
public class Person implements Serializable {
	private String name ;
	private int age;
	public Person(){
		System.out.println("Person的无参构造器");
	}
	public Person(String name ,int age){
		System.out.println("Person的有参构造器");
		this.age=age;
		this.name=name;
	}
	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.Serializable;
public class Teacher implements Serializable {
	private String name;
	private Person student;//Teacher类持有的Person类应该是可序列化的
	public Teacher(String name,Person student){
		System.out.println("Teacher的有参构造器");
		this.student=student;
		this.name=name;
	}
	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;
	}
}


import java.io.*;
public class SerializeMutable {
	public static void main(String[] args) {
		ObjectOutputStream oos=null;
		ObjectInputStream ois=null;
		try {
			oos=new ObjectOutputStream(new FileOutputStream("F:\\objecttest.txt"));
			Person p=new Person("zpc", 24);
			oos.writeObject(p);
			p.setName("niao鹏");
			//第二次写同样的对象时只是输出序列化编号
			oos.writeObject(p);
			ois=new ObjectInputStream(new FileInputStream("F:\\objecttest.txt"));
			Person p1=(Person) ois.readObject();
			//实际得到的是和p1同样的对象
			Person p2=(Person) ois.readObject();
			System.out.println("p1=p2? "+(p1==p2));
			System.out.println("p2.getName():"+p2.getName());
			System.out.println("===================================");
			Person perstu=new Person("云翔", 15);
			Teacher t1=new Teacher("马老师",perstu);
			Teacher t2=new Teacher("周老师",perstu);
			oos.writeObject(perstu);
			oos.writeObject(t1);
			oos.writeObject(t2);
			
			Person pperstu1=(Person)ois.readObject();
			Teacher tt1=(Teacher)ois.readObject();
			Teacher tt2=(Teacher)ois.readObject();
			System.out.println("tt1.getStudent()==p? "+(tt1.getStudent()==pperstu1));
			System.out.println("tt2.getStudent()==p? "+(tt2.getStudent()==pperstu1));
			System.out.println(tt2.getStudent().getName());
			oos.writeObject(perstu);
			Person pperstu2=(Person)ois.readObject();
			System.out.println("pperstu2==pperstu1? "+(pperstu2==pperstu1));
			System.out.println(pperstu1.getName());
			System.out.println(pperstu2.getName());
			//下面的语句报错,再次证明了写入、读出对象要一致
//			Person pperstu3=(Person)ois.readObject();
//			System.out.println(pperstu3.getName());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}catch(ClassNotFoundException e){
			e.printStackTrace();
		}
	}
}

13、自定义序列化
 在属性前面加上transient关键字,可以指定Java序列化时无需理会该属性值,由于transient修饰的属性将被完全隔离在序列化机制外,
 这会导致在反序列化恢复Java对象时无法取得该属性值。
 实现自定义序列化要重写类的如下方法:
 private void writeObject(java.io.ObjectOutputStream out) throws IOException;
 private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException;


 还有种更彻底的序列化机制可以在序列化某对象时替换该对象,此种情况下应为序列化类提供如下特殊方法:
 ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
 Java的序列化机制保证在序列化某个对象之前,先调用对象的writeReplace()方法,如果该方法返回另一个Java对象,
 则系统转为序列化另一个对象。
 小结:系统在序列化某个对象之前,先会调用该对象的如下两个方法:
 writeReplace和writeObject,系统先调用被序列化对象的writeReplace方法,如果返回另一个Java对象,则再次调用该java对象的writeReplace方法....
 直到该方法不在返回一个对象为止,程序最后调用该对象的writeObject方法来保存该对象的状态


 与writeReplace方法相对的有一个方法可以替代原来反序列化的对象
 即:private Object readResolve() throws ObjectStreamException;
 这个方法会紧接着readObject之后被立即调用,该方法的返回值会代替原来反序列化的对象,而原来readObject反序列化的对象会被立即丢弃
 (此方法在序列化单例类、早期枚举类时很有用)


14、另一种序列化机制:Java类实现Externalizable接口

    两种序列化机制的区别:


      实现Serializable接口                                                                       实现Externalizable接口

      系统自动存储必要信息    程序员决定需要存储哪些信息
      Java内建支持,易于实现只需实现该接口即可             仅仅提供两个空方法实现该接口必须为两个空方法提供实现
      无须任何代码支持     类中必须存在一个无参构造方法
      性能略差   性能略高