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接口系统自动存储必要信息
Java内建支持,易于实现只需实现该接口即可
无须任何代码支持
性能略差
实现Externalizable接口
程序员决定需要存储哪些信息
仅仅提供两个空方法实现该接口必须为两个空方法提供实现
类中必须存在一个无参构造方法
性能略高
15、Charset对象
Java提供了Charset来处理字节序列和字符序列之间的转换关系,该类包含了用于创建解码器和编码器的方法
例程:CharsetTransformTest.java
import java.nio.*; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; public class CharsetTransformTest { public static void main(String[] args) throws CharacterCodingException { // 创建简体中文对应的Charset Charset cn = Charset.forName("GBK"); CharsetEncoder cnEncoder = cn.newEncoder(); CharsetDecoder cnDecoder = cn.newDecoder(); CharBuffer chbuf = CharBuffer.allocate(10); chbuf.put(‘周‘); chbuf.put(‘总‘); chbuf.put(‘裁‘); chbuf.flip(); // 将CharBuffer中的字符序列转换成字节序列 ByteBuffer bbuf = cnEncoder.encode(chbuf); System.out.println("bbuf.capacity():" + bbuf.capacity()); System.out.println("chbuf.capacity():" + chbuf.capacity()); // 循环访问每个字节 for (int i = 0; i < bbuf.capacity(); i++) { System.out.println(bbuf.get(i) + ""); } //将字节序列解码成字符序列 System.out.println("\n " + cnDecoder.decode(bbuf)); } }
16、Buffer(缓冲)抽象基类
常用子类ByteBuffer,这些Buffer的子类不是通过构造器创建而是通过static xxxBuffer allocate(int capacity)来创建一个容量为capacity的xxxBuffer对象。Buffer中包含两个重要的方法flip(该方法将limit设置为position所在位置,position设置 为0,)和clear(将position设为0,limit设为 capacity)。flip为从Buffer中读取数据做好准备,而clear是为向Buffer中装入数据做好准备
例程:BufferTest.java
import java.nio.*; public class BufferTest { public static void main(String[] args) { //创建Buffer CharBuffer cbuf=CharBuffer.allocate(8); System.out.println("cbuf.position():"+cbuf.position()); System.out.println("cbuf.limit():"+cbuf.limit()); System.out.println("cbuf.capacity():"+cbuf.capacity()); //放入元素 cbuf.put(‘a‘); cbuf.put(‘走‘); cbuf.put(‘好‘); System.out.println("加入三个元素后position:"+cbuf.position()); System.out.println("加入三个元素后limit():"+cbuf.limit()); cbuf.flip(); System.out.println("调用flip后position:"+cbuf.position()); System.out.println("调用flip后limit():"+cbuf.limit()); System.out.println("第一个元素(position=0):"+cbuf.get()); System.out.println("取出第一个元素后position:"+cbuf.position()); cbuf.clear(); System.out.println("调用clear后position:"+cbuf.position()); System.out.println("调用clear后limit():"+cbuf.limit()); System.out.println("调用clear后缓冲区没有被清除:"+cbuf.get(2)); System.out.println("执行绝对读取(不影响position)后position:"+cbuf.position()); } }
17、Channel(通道)
程序不能直接访问Channel中放入数据,Channel只能和Buffer进行交互,发送到Channel中的数据必须先放到Buffer对象中,程序再将Buffer的输入写入Channel。而从Channel中读取数据也必须先读到Buffer中,程序再从Buffer中取出这些数据。
例程:FileChannelTest.java
/* * 把F:\\out.txt中的内容通过Channel的方式写到F:\\niao.txt中 */ import java.nio.CharBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.io.*; public class FileChannelTest { public static void main(String[] args) { FileChannel inChannel=null; FileChannel outChannel=null; try{ File f=new File("F:\\niao.txt"); //创建FileInputStream,以该文件输入流创建FileChannel inChannel=new FileInputStream(f).getChannel(); //将inChannel里的数据映射成byteBuffer MappedByteBuffer buff=inChannel.map(FileChannel.MapMode.READ_ONLY,0, f.length()); //直接把buff中的内容输出到另一个channel buff.limit(); outChannel =new FileOutputStream("F:\\out.txt").getChannel(); //buff是输入型的不能put //buff.put("ABC".getBytes()); System.out.println("buff.get():"+buff.get()); outChannel.write(buff); buff.clear();//复原limit,position的位置 Charset c=Charset.forName("GBK"); //使用解码器将ByteBuffer转换成CharBuffer CharsetDecoder d=c.newDecoder(); CharBuffer chbuf=d.decode(buff); System.out.println("chbuf:\n"+chbuf); }catch (IOException e) { e.printStackTrace(); } } } /* * 将一个文本文件的内容复制到它自身 */ import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class RandomAccessFileTest { public static void main(String[] args) { FileChannel randomChannel = null; try { File f = new File("F:\\niao.txt"); RandomAccessFile raf = new RandomAccessFile(f, "rw"); randomChannel = raf.getChannel(); // 把Channel中的数据映射到buffer(map方法一次将所有文件内容映射到内存) ByteBuffer buff = randomChannel.map(FileChannel.MapMode.READ_WRITE, 0, f.length()); // 把channel的指针后移到最后 randomChannel.position(f.length()); // 将buffer中的数据输出 randomChannel.write(buff); } catch (IOException e) { e.printStackTrace(); } finally { try { if (randomChannel != null) { randomChannel.close(); } } catch (Exception e) { e.printStackTrace(); } } } }