第15章_File类与IO流 本章专题与脉络
1. java.io.File类的使用 1.1 概述
File类及本章下的各种流,都定义在java.io 包下。
一个File对象 —-代表—-> 硬盘或网络中可能存在的一个文件/文件目录(俗称文件夹),与平台无关。(体会万事万物皆对象)
File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
1.2 构造器
public File(String pathname)
:以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
public File(String parent, String child)
:以parent为父路径,child为子路径创建File对象。
public File(File parent, String child)
:根据一个父File对象和子文件路径创建File对象
关于路径:
绝对路径: 从盘符开始的路径,这是一个完整的路径。
相对路径: 相对于项目目录
的路径,这是一个便捷的路径,开发中经常使用。
IDEA中,main中的文件的相对路径,是相对于”当前工程project
“
IDEA中,单元测试方法中的文件的相对路径,是相对于”当前模块module
“
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import java.io.File;public class FileObjectTest { @Test public void test () { File file1 = new File("D:\\javacode\\test\\abc" ); System.out.println(file1.getAbsoluteFile()); String parent = "D:\\aaa" ; String child = "bbb.txt" ; File file2 = new File(parent, child); System.out.println(file2.getAbsoluteFile()); File parentDir = new File("D:\\aaa" ); String childFile = "bbb.txt" ; File file3 = new File(parentDir, childFile); System.out.println(file3.getAbsoluteFile()); } @Test public void test01 () throws IOException { File f1 = new File("d:\\atguigu\\javase\\HelloIO.java" ); System.out.println("文件/目录的名称:" + f1.getName()); System.out.println("文件/目录的构造路径名:" + f1.getPath()); System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath()); System.out.println("文件/目录的父目录名:" + f1.getParent()); } @Test public void test02 () throws IOException { File f2 = new File("/HelloIO.java" ); System.out.println("文件/目录的名称:" + f2.getName()); System.out.println("文件/目录的构造路径名:" + f2.getPath()); System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath()); System.out.println("文件/目录的父目录名:" + f2.getParent()); } @Test public void test03 () throws IOException { File f3 = new File("HelloIO.java" ); System.out.println("user.dir =" + System.getProperty("user.dir" )); System.out.println("文件/目录的名称:" + f3.getName()); System.out.println("文件/目录的构造路径名:" + f3.getPath()); System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath()); System.out.println("文件/目录的父目录名:" + f3.getParent()); } @Test public void test04 () throws IOException { File f5 = new File("HelloIO.java" ); System.out.println("user.dir =" + System.getProperty("user.dir" )); System.out.println("文件/目录的名称:" + f5.getName()); System.out.println("文件/目录的构造路径名:" + f5.getPath()); System.out.println("文件/目录的绝对路径名:" + f5.getAbsolutePath()); System.out.println("文件/目录的父目录名:" + f5.getParent()); } }
注意:
无论该路径下是否存在文件或者目录,都不影响File对象的创建。
window的路径分隔符使用“\”,而Java程序中的“\”表示转义字符,所以在Windows中表示路径,需要用“\”。或者直接使用“/”也可以,Java程序支持将“/”当成平台无关的路径分隔符
。或者直接使用File.separator常量值表示。比如:
File file2 = new File(“d:” + File.separator + “atguigu” + File.separator + “info.txt”);
当构造路径是绝对路径时,那么getPath和getAbsolutePath结果一样
当构造路径是相对路径时,那么getAbsolutePath的路径 = user.dir的路径 + 构造路径
1.3 常用方法 1、获取文件和目录基本信息
public String getName() :获取名称
public String getPath() :获取路径
public String getAbsolutePath()
:获取绝对路径
public File getAbsoluteFile():获取绝对路径表示的文件
public String getParent()
:获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
如果File对象代表的文件或目录存在,则File对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(例如,时间、类型等)为File对象的属性赋值,否则除了路径和名称,File对象的其他属性将会保留默认值。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void test2 () { File file=new File("E:\\宋亚翔-学业奖材料\\宋亚翔.docx" ); System.out.println("文件名称:" +file.getName()); System.out.println("文件路径:" +file.getPath()); System.out.println("文件绝对路径:" +file.getAbsoluteFile()); System.out.println("绝对路径的文件:" +file.getAbsoluteFile()); System.out.println("上层文件目录路径:" +file.getParent()); System.out.println("文件大小(字节数):" +file.length()); System.out.println("足迹后一次修改时间(毫秒值):" +file.lastModified()); System.out.println("-----------------------------------------------------------------" ); File file1=new File("E:\\宋亚翔-学业奖材料\\李四.docx" ); System.out.println("文件名称:" +file1.getName()); System.out.println("文件路径:" +file1.getPath()); System.out.println("文件绝对路径:" +file1.getAbsoluteFile()); System.out.println("绝对路径的文件:" +file1.getAbsoluteFile()); System.out.println("上层文件目录路径:" +file1.getParent()); System.out.println("文件大小(字节数):" +file1.length()); System.out.println("足迹后一次修改时间(毫秒值):" +file1.lastModified()); }
2、列出目录的下一级
public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void test3 () { File file=new File("E:\\宋亚翔-学业奖材料" ); String[] strs=file.list(); for (String temp:strs){ System.out.println(temp); } System.out.println("--------------------------------------" ); File[] files = file.listFiles(); for (File temp:files){ System.out.println(temp); } }
3、File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径。
4、判断功能的方法
public boolean exists()
:此File表示的文件或目录是否实际存在。
public boolean isDirectory()
:此File表示的是否为目录。
public boolean isFile()
:此File表示的是否为文件。
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
举例:
1 2 3 4 5 6 7 8 9 10 11 @Test public void test4 () { File file=new File("E:\\宋亚翔-学业奖材料\\宋亚翔.docx" ); System.out.println("文件是否存在:" +file.exists()); System.out.println("file是否为目录:" +file.isDirectory()); System.out.println("file是否为文件:" +file.isFile()); System.out.println("file是否可读:" +file.canRead()); System.out.println("file是否可写:" +file.canWrite()); System.out.println("file是否隐藏:" +file.isHidden()); }
如果文件或目录不存在,那么exists()、isFile()和isDirectory()都是返回true
5、创建、删除功能
public boolean createNewFile()
:创建文件。若文件存在,则不创建,返回false。
public boolean mkdir()
:创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs()
:创建文件目录。如果上层文件目录不存在,一并创建。
public boolean delete()
:删除文件或者文件夹 删除注意事项:① Java中的删除不走回收站。② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.io.File;import java.io.IOException;public class FileCreateDelete { public static void main (String[] args) throws IOException { File f = new File("aaa.txt" ); System.out.println("aaa.txt是否存在:" +f.exists()); System.out.println("aaa.txt是否创建:" +f.createNewFile()); System.out.println("aaa.txt是否存在:" +f.exists()); File f2= new File("newDir" ); System.out.println("newDir是否存在:" +f2.exists()); System.out.println("newDir是否创建:" +f2.mkdir()); System.out.println("newDir是否存在:" +f2.exists()); File f3= new File("newDira\\newDirb" ); System.out.println("newDira\\newDirb创建:" + f3.mkdir()); File f4= new File("newDir\\newDirb" ); System.out.println("newDir\\newDirb创建:" + f4.mkdir()); File f5= new File("newDira\\newDirb" ); System.out.println("newDira\\newDirb创建:" + f5.mkdirs()); System.out.println("aaa.txt删除:" + f.delete()); System.out.println("newDir删除:" + f2.delete()); System.out.println("newDir\\newDirb删除:" + f4.delete()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 运行结果: aaa.txt是否存在:false aaa.txt是否创建:true aaa.txt是否存在:true newDir是否存在:false newDir是否创建:true newDir是否存在:true newDira\newDirb创建:false newDir\newDirb创建:true newDira\newDirb创建:true aaa.txt删除:true newDir删除:false newDir\newDirb删除:true
API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。
1.4 练习 练习1:利用File构造器,new 一个文件目录file
1) 在其中创建多个文件和目录
2) 编写方法,实现删除file中指定文件的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void test6() throws IOException { File file=new File("F:\\test1"); File file1=new File("F:\\test1\\abc.txt"); File file2=new File("F:\\test1\\aaa.txt"); File file3=new File("F:\\test1\\test1mulu"); //创建一个文件目录 System.out.println(file.mkdir()); //创建一个test1目录 //创建多个文件和目录 System.out.println(file1.createNewFile()); //新增abc.txt文件 System.out.println(file2.createNewFile()); //新增aaa.txt文件 System.out.println(file3.mkdir()); //新增test1mulu文件 //删除file中指定文件 System.out.println(file1.delete()); //删除abc.txt文件 }
练习2:判断指定目录下是否有后缀名为.jpg的文件。如果有,就输出该文件名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class FindJPGFileTest { @Test public void test1 () { File srcFile = new File("d:\\code" ); String[] fileNames = srcFile.list(); for (String fileName : fileNames){ if (fileName.endsWith(".jpg" )){ System.out.println(fileName); } } } @Test public void test2 () { File srcFile = new File("d:\\code" ); File[] listFiles = srcFile.listFiles(); for (File file : listFiles){ if (file.getName().endsWith(".jpg" )){ System.out.println(file.getAbsolutePath()); } } } @Test public void test3 () { File srcFile = new File("d:\\code" ); File[] subFiles = srcFile.listFiles(new FilenameFilter() { @Override public boolean accept (File dir, String name) { return name.endsWith(".jpg" ); } }); for (File file : subFiles){ System.out.println(file.getAbsolutePath()); } } }
练习3:遍历指定目录所有文件名称,包括子文件目录中的文件。
拓展1:并计算指定目录占用空间的大小
拓展2:删除指定文件目录及其下的所有文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class ListFilesTest { public static void printSubFile (File dir) { File[] subfiles = dir.listFiles(); for (File f : subfiles) { if (f.isDirectory()) { printSubFile(f); } else { System.out.println(f.getAbsolutePath()); } } } public void listAllSubFiles (File file) { if (file.isFile()) { System.out.println(file); } else { File[] all = file.listFiles(); for (File f : all) { listAllSubFiles(f); } } } @Test public void testListAllFiles () { File dir = new File("E:\\teach\\01_javaSE\\_尚硅谷Java编程语言\\3_软件" ); printSubFile(dir); } public long getDirectorySize (File file) { long size = 0 ; if (file.isFile()) { size = file.length(); } else { File[] all = file.listFiles(); for (File f : all) { size += getDirectorySize(f); } } return size; } public void deleteDirectory (File file) { if (file.isDirectory()) { File[] all = file.listFiles(); for (File f : all) { deleteDirectory(f); } } file.delete(); } }
2. IO流原理及流的分类
2.1 Java IO原理
2.2 流的分类 java.io
包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法
输入或输出数据。
小结:图解
2.3 流的API
Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
(抽象基类)
输入流
输出流
字节流(以字节为单位,读写数据)
InputStream
OutputStream
字符流(以字符为单位,读写数据)
Reader
Writer
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
常用节点流:
文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter
对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
常用处理流:
缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率。
转换流:InputStreamReader、OutputStreamReader
对象流:ObjectInputStream、ObjectOutputStream
3. 节点流之一:FileReader\FileWriter 3.1 Reader与Writer Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。不能操作图片,视频等非文本文件。
常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py等
注意:.doc、.xls、.ppt这些都不是文本文件。
3.1.1 字符输入流:Reader java.io.Reader
抽象类是表示用于读取字符流的所有类的父类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。
public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。
public int read(char[] cbuf,int off,int len)
:从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。
注意:当完成流的操作时,必须调用close()方法,释放系统资源,否则会造成内存泄漏。
3.1.2 字符输出流:Writer java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int c)
:写出单个字符。
public void write(char[] cbuf)
:写出字符数组。
public void write(char[] cbuf, int off, int len)
:写出字符数组的某一部分。off:数组的开始索引;len:写出的字符个数。
public void write(String str)
:写出字符串。
public void write(String str, int off, int len)
:写出字符串的某一部分。off:字符串的开始索引;len:写出的字符个数。
public void flush()
:刷新该流的缓冲。
public void close()
:关闭此流。
注意:当完成流的操作时,必须调用close()方法,释放系统资源,否则会造成内存泄漏。
3.2 FileReader 与 FileWriter 3.2.1 FileReader java.io.FileReader
类用于读取字符文件,构造时使用系统默认的字符编码和默认字节缓冲区。
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。
FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
举例: 读取hello.txt文件中的字符数据,并显示在控制台上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 public class FileReaderWriterTest { @Test public void test1 () throws IOException { File file = new File("hello.txt" ); FileReader fr = new FileReader(file); int data; while ((data = fr.read()) != -1 ) { System.out.print((char ) data); } fr.close(); } @Test public void test2 () { FileReader fr = null ; try { File file = new File("hello.txt" ); fr = new FileReader(file); int data; while ((data = fr.read()) != -1 ) { System.out.println((char ) data); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fr != null ) fr.close(); } catch (IOException e) { e.printStackTrace(); } } } @Test public void test3 () { FileReader fr = null ; try { File file = new File("hello.txt" ); fr = new FileReader(file); char [] cbuf = new char [5 ]; int len; while ((len = fr.read(cbuf)) != -1 ) { String str = new String(cbuf, 0 , len); System.out.print(str); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fr != null ) fr.close(); } catch (IOException e) { e.printStackTrace(); } } } }
不同实现方式的类比:
3.2.2 FileWriter java.io.FileWriter
类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区。
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。
FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
FileWriter(File file,boolean append)
: 创建一个新的 FileWriter,指明是否在现有文件末尾追加内容。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 public class FWWrite { @Test public void test01 () throws IOException { FileWriter fw = new FileWriter(new File("fw.txt" )); fw.write(97 ); fw.write('b' ); fw.write('C' ); fw.write(30000 ); fw.close(); } @Test public void test02 () throws IOException { FileWriter fw = new FileWriter(new File("fw.txt" )); char [] chars = "尚硅谷" .toCharArray(); fw.write(chars); fw.write(chars,1 ,2 ); fw.close(); } @Test public void test03 () throws IOException { FileWriter fw = new FileWriter("fw.txt" ); String msg = "尚硅谷" ; fw.write(msg); fw.write(msg,1 ,2 ); fw.close(); } @Test public void test04 () { FileWriter fw = null ; try { File file = new File("personinfo.txt" ); fw = new FileWriter(file); fw.write("I love you," ); fw.write("you love him." ); fw.write("so sad" .toCharArray()); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fw != null ) fw.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }
3.2.3 小结 1 2 3 4 5 6 7 ① 因为出现流资源的调用,为了避免内存泄漏,需要使用try-catch-finally处理异常 ② 对于输入流来说,File类的对象必须在物理磁盘上存在,否则执行就会报FileNotFoundException。如果传入的是一个目录,则会报IOException异常。 对于输出流来说,File类的对象是可以不存在的。 > 如果File类的对象不存在,则可以在输出的过程中,自动创建File类的对象 > 如果File类的对象存在, > 如果调用FileWriter(File file)或FileWriter(File file,false),输出时会新建File文件覆盖已有的文件 > 如果调用FileWriter(File file,true)构造器,则在现有的文件末尾追加写出内容。
3.3 关于flush(刷新) 因为内置缓冲区的原因,如果FileWriter不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush()
方法了。
flush()
:刷新缓冲区,流对象可以继续使用。
close()
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
注意:即便是flush()方法写出了数据,操作的最后还是要调用close方法,释放系统资源。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class FWWriteFlush { @Test public void test () throws IOException { FileWriter fw = new FileWriter("fw.txt" ); fw.write('刷' ); fw.flush(); fw.write('新' ); fw.flush(); fw.write('关' ); fw.close(); fw.write('闭' ); fw.close(); } }
如果我们读取或写出的数据是非文本文件,则Reader、Writer就无能为力了,必须使用字节流。
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。
public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
public int read(byte[] b,int off,int len)
:从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。
说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。
4.1.2 字节输出流:OutputStream java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int b)
:将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。
说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
举例:
读取操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public class FISRead { @Test public void test () throws IOException { FileInputStream fis = new FileInputStream("read.txt" ); int read = fis.read(); System.out.println((char ) read); read = fis.read(); System.out.println((char ) read); read = fis.read(); System.out.println((char ) read); read = fis.read(); System.out.println((char ) read); read = fis.read(); System.out.println((char ) read); read = fis.read(); System.out.println(read); fis.close(); } @Test public void test02 () throws IOException { FileInputStream fis = new FileInputStream("read.txt" ); int b; while ((b = fis.read())!=-1 ) { System.out.println((char )b); } fis.close(); } @Test public void test03 () throws IOException { FileInputStream fis = new FileInputStream("read.txt" ); int len; byte [] b = new byte [2 ]; while (( len= fis.read(b))!=-1 ) { System.out.println(new String(b)); } fis.close(); } @Test public void test04 () throws IOException { FileInputStream fis = new FileInputStream("read.txt" ); int len; byte [] b = new byte [2 ]; while (( len= fis.read(b))!=-1 ) { System.out.println(new String(b,0 ,len)); } fis.close(); } }
4.2.2 FileOutputStream java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
public FileOutputStream(File file)
:创建文件输出流,写出由指定的 File对象表示的文件。
public FileOutputStream(String name)
: 创建文件输出流,指定的名称为写出文件。
public FileOutputStream(File file, boolean append)
: 创建文件输出流,指明是否在现有文件末尾追加内容。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import org.junit.Test;import java.io.FileOutputStream;import java.io.IOException;public class FOSWrite { @Test public void test01 () throws IOException { FileOutputStream fos = new FileOutputStream("fos.txt" ); fos.write(97 ); fos.write(98 ); fos.write(99 ); fos.close(); } @Test public void test02 () throws IOException { FileOutputStream fos = new FileOutputStream("fos.txt" ); byte [] b = "abcde" .getBytes(); fos.write(b,2 ,2 ); fos.close(); } @Test public void test03 () throws IOException { FileOutputStream fos = new FileOutputStream("fos.txt" ,true ); byte [] b = "abcde" .getBytes(); fos.write(b); fos.close(); } @Test public void test05 () { FileInputStream fis = null ; FileOutputStream fos = null ; try { fis = new FileInputStream(new File("hello.txt" )); fos = new FileOutputStream(new File("hello1.txt" )); byte [] buffer = new byte [1024 ]; int len; while ((len = fis.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } System.out.println("复制成功" ); } catch (IOException e) { throw new RuntimeException(e); } finally { try { if (fos != null ) fos.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if (fis != null ) fis.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }
4.3 练习 练习: 实现图片加密操作。
提示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class FileSecretTest { @Test public void test1 () { FileInputStream fis = null ; FileOutputStream fos = null ; try { File file1 = new File("pony.jpg" ); File file2 = new File("pony_secret.jpg" ); fis = new FileInputStream(file1); fos = new FileOutputStream(file2); int len; byte [] buffer = new byte [1024 ]; while ((len = fis.read(buffer)) != -1 ){ for (int i = 0 ;i < len;i++){ buffer[i] = (byte ) (buffer[i] ^ 5 ); } fos.write(buffer,0 ,len); } System.out.println("加密成功" ); } catch (IOException e) { e.printStackTrace(); } finally { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } @Test public void test2 () { FileInputStream fis = null ; FileOutputStream fos = null ; try { File file1 = new File("pony_secret.jpg" ); File file2 = new File("pony_unsecret.jpg" ); fis = new FileInputStream(file1); fos = new FileOutputStream(file2); int len; byte [] buffer = new byte [1024 ]; while ((len = fis.read(buffer)) != -1 ){ for (int i = 0 ;i < len;i++){ buffer[i] = (byte ) (buffer[i] ^ 5 ); } fos.write(buffer,0 ,len); } System.out.println("解密成功" ); } catch (IOException e) { e.printStackTrace(); } finally { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
5. 处理流之一:缓冲流
为了提高数据读写的速度
,Java API提供了带缓冲功能的流类:缓冲流。
缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
字节缓冲流 :BufferedInputStream
,BufferedOutputStream
字符缓冲流 :BufferedReader
,BufferedWriter
缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用8192个字节(8Kb)
的缓冲区),通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
5.1 构造器
public BufferedInputStream(InputStream in)
:创建一个 新的字节型的缓冲输入流。
public BufferedOutputStream(OutputStream out)
: 创建一个新的字节型的缓冲输出流。
代码举例:
1 2 3 4 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("abc.jpg" )); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("abc_copy.jpg" ));
public BufferedReader(Reader in)
:创建一个 新的字符型的缓冲输入流。
public BufferedWriter(Writer out)
: 创建一个新的字符型的缓冲输出流。
代码举例:
1 2 3 4 BufferedReader br = new BufferedReader(new FileReader("br.txt" )); BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt" ));
5.2 效率测试 查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(375MB),测试它的效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public void copyFileWithFileStream (String srcPath,String destPath) { FileInputStream fis = null ; FileOutputStream fos = null ; try { fis = new FileInputStream(new File(srcPath)); fos = new FileOutputStream(new File(destPath)); byte [] buffer = new byte [100 ]; int len; while ((len = fis.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } System.out.println("复制成功" ); } catch (IOException e) { throw new RuntimeException(e); } finally { try { if (fos != null ) fos.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if (fis != null ) fis.close(); } catch (IOException e) { throw new RuntimeException(e); } } } @Test public void test1 () { String srcPath = "C:\\Users\\shkstart\\Desktop\\01-复习.mp4" ; String destPath = "C:\\Users\\shkstart\\Desktop\\01-复习2.mp4" ; long start = System.currentTimeMillis(); copyFileWithFileStream(srcPath,destPath); long end = System.currentTimeMillis(); System.out.println("花费的时间为:" + (end - start)); } public void copyFileWithBufferedStream (String srcPath,String destPath) { FileInputStream fis = null ; FileOutputStream fos = null ; BufferedInputStream bis = null ; BufferedOutputStream bos = null ; try { File srcFile = new File(srcPath); File destFile = new File(destPath); fis = new FileInputStream(srcFile); fos = new FileOutputStream(destFile); bis = new BufferedInputStream(fis); bos = new BufferedOutputStream(fos); int len; byte [] buffer = new byte [100 ]; while ((len = bis.read(buffer)) != -1 ) { bos.write(buffer, 0 , len); } System.out.println("复制成功" ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (bos != null ) bos.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if (bis != null ) bis.close(); } catch (IOException e) { throw new RuntimeException(e); } } } @Test public void test2 () { String srcPath = "C:\\Users\\shkstart\\Desktop\\01-复习.mp4" ; String destPath = "C:\\Users\\shkstart\\Desktop\\01-复习2.mp4" ; long start = System.currentTimeMillis(); copyFileWithBufferedStream(srcPath,destPath); long end = System.currentTimeMillis(); System.out.println("花费的时间为:" + (end - start)); }
5.3 字符缓冲流特有方法 字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
BufferedReader:public String readLine()
: 读一行文字。
BufferedWriter:public void newLine()
: 写一行行分隔符,由系统属性定义符号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class BufferedIOLine { @Test public void testReadLine () throws IOException { BufferedReader br = new BufferedReader(new FileReader("in.txt" )); String line; while ((line = br.readLine())!=null ) { System.out.println(line); } br.close(); } @Test public void testNewLine () throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt" )); bw.write("尚" ); bw.newLine(); bw.write("硅" ); bw.newLine(); bw.write("谷" ); bw.newLine(); bw.close(); } }
说明:
涉及到嵌套的多个流时,如果都显式关闭的话,需要先关闭外层的流,再关闭内层的流
其实在开发中,只需要关闭最外层的流即可,因为在关闭外层流时,内层的流也会被关闭。
5.4 练习 练习1: 分别使用节点流:FileInputStream、FileOutputStream和缓冲流:BufferedInputStream、BufferedOutputStream实现文本文件/图片/视频文件的复制。并比较二者在数据复制方面的效率。
练习2:
姓氏统计:一个文本文件中存储着北京所有高校在校生的姓名,格式如下:
1 2 3 4 每行一个名字,姓与名以空格分隔: 张 三 李 四 王 小五
现在想统计所有的姓氏在文件中出现的次数,请描述一下你的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public static void main (String[] args) { HashMap<String, Integer> map = new HashMap<>(); BufferedReader br = null ; try { br = new BufferedReader(new FileReader(new File("e:/name.txt" ))); String value = null ; StringBuffer buffer = new StringBuffer(); flag: while ((value = br.readLine()) != null ) { char [] c = value.toCharArray(); for (int i = 0 ; i < c.length; i++) { if (c[i] != ' ' ) { buffer.append(String.valueOf(c[i])); } else { if (map.containsKey(buffer.toString())) { int count = map.get(buffer.toString()); map.put(buffer.toString(), count + 1 ); } else { map.put(buffer.toString(), 1 ); } buffer.delete(0 , buffer.length()); continue flag; } } } } catch (Exception e) { e.printStackTrace(); } finally { if (br != null ) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } Set<Map.Entry<String, Integer>> set = map.entrySet(); Iterator<Map.Entry<String, Integer>> it = set.iterator(); while (it.hasNext()) { Map.Entry<String, Integer> end = (Map.Entry<String, Integer>) it.next(); System.out.println(end); } }
6. 处理流之二:转换流 6.1 问题引入 引入情况1:
使用FileReader
读取项目中的文本文件。由于IDEA设置中针对项目设置了UTF-8编码,当读取Windows系统中创建的文本文件时,如果Windows系统默认的是GBK编码,则读入内存中会出现乱码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import java.io.FileReader;import java.io.IOException;public class Problem { public static void main (String[] args) throws IOException { FileReader fileReader = new FileReader("E:\\File_GBK.txt" ); int data; while ((data = fileReader.read()) != -1 ) { System.out.print((char )data); } fileReader.close(); } } 输出结果: ���
那么如何读取GBK编码的文件呢?
引入情况2:
针对文本文件,现在使用一个字节流进行数据的读入,希望将数据显示在控制台上。此时针对包含中文的文本数据,可能会出现乱码。
6.2 转换流的理解 作用:转换流是字节与字符间的桥梁!
具体来说:
InputStreamReader
OutputStreamWriter
6.4 字符编码和字符集 6.4.1 编码与解码 计算机中储存的信息都是用二进制数
表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。
字符编码(Character Encoding) : 就是一套自然语言的字符与二进制数之间的对应规则。
编码表 :生活中文字和计算机中二进制的对应规则
乱码的情况 :按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
1 2 3 编码:字符(人能看懂的)--字节(人看不懂的) 解码:字节(人看不懂的)-->字符(人能看懂的)
6.4.2 字符集
字符集Charset :也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码 ,它所对应的字符集 自然就指定了,所以编码 才是我们最终要关心的。
ASCII字符集 :
ASCII码(American Standard Code for Information Interchange,美国信息交换标准代码):上个世纪60年代,美国制定了一套字符编码,对英语字符
与二进制位之间的关系,做了统一规定。这被称为ASCII码。
ASCII码用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
基本的ASCII字符集,使用7位(bits)表示一个字符(最前面的1位统一规定为0),共128个
字符。比如:空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。
缺点:不能表示所有字符。
ISO-8859-1字符集 :
拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰语、德语、意大利语、葡萄牙语等
ISO-8859-1使用单字节编码,兼容ASCII编码。
GBxxx字符集 :
GB就是国标的意思,是为了显示中文
而设计的一套字符集。
GB2312 :简体中文码表。一个小于127的字符的意义与原来相同,即向下兼容ASCII码。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字
,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,这就是常说的”全角”字符,而原来在127号以下的那些符号就叫”半角”字符了。
GBK :最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节
编码方案,共收录了21003个
汉字,完全兼容GB2312标准,同时支持繁体汉字
以及日韩汉字等。
GB18030 :最新的中文码表。收录汉字70244个
,采用多字节
编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
Unicode字符集 :
Unicode编码为表达任意语言的任意字符
而设计,也称为统一码、标准万国码。Unicode 将世界上所有的文字用2个字节
统一进行编码,为每个字符设定唯一的二进制编码,以满足跨语言、跨平台进行文本处理的要求。
Unicode 的缺点:这里有三个问题:
第一,英文字母只用一个字节表示就够了,如果用更多的字节存储是极大的浪费
。
第二,如何才能区别Unicode和ASCII
?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?
第三,如果和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符
。
Unicode在很长一段时间内无法推广,直到互联网的出现,为解决Unicode如何在网络上传输的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现。具体来说,有三种编码方案,UTF-8、UTF-16和UTF-32。
UTF-8字符集 :
Unicode是字符集,UTF-8、UTF-16、UTF-32是三种将数字转换到程序数据
的编码方案。顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。其中,UTF-8 是在互联网上使用最广
的一种 Unicode 的实现方式。
互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。UTF-8 是一种变长的编码方式
。它使用1-4个字节为每个字符编码,编码规则:
128个US-ASCII字符,只需一个字节编码。
拉丁文等字符,需要二个字节编码。
大部分常用字(含中文),使用三个字节编码。
其他极少使用的Unicode辅助字符,使用四字节编码。
Unicode符号范围 | UTF-8编码方式
1 2 3 4 5 6 7 8 9 10 11 (十六进制) | (二进制) ————————————————————|—–—–—–—–—–—–—–—–—–—–—–—–—–—– 0000 0000-0000 007F | 0xxxxxxx(兼容原来的ASCII) 0000 0080-0000 07FF | 110xxxxx 10xxxxxx 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
注意:在中文操作系统上,ANSI(美国国家标准学会、AMERICAN NATIONAL STANDARDS INSTITUTE: ANSI)编码即为GBK;在英文操作系统上,ANSI编码即为ISO-8859-1。
6.5 练习 把当前module下的《康师傅的话.txt》字符编码为GBK,复制到电脑桌面目录下的《寄语.txt》, 字符编码为UTF-8。
在当前module下的文本内容:
1 2 3 4 5 6 7 六项精进: (一)付出不亚于任何人的努力 (二)要谦虚,不要骄傲 (三)要每天反省 (四)活着,就要感谢 (五)积善行、思利他 (六)不要有感性的烦恼
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class InputStreamReaderDemo { @Test public void test () { InputStreamReader isr = null ; OutputStreamWriter osw = null ; try { isr = new InputStreamReader(new FileInputStream("康师傅的话.txt" ),"gbk" ); osw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\shkstart\\Desktop\\寄语.txt" ),"utf-8" ); char [] cbuf = new char [1024 ]; int len; while ((len = isr.read(cbuf)) != -1 ) { osw.write(cbuf, 0 , len); osw.flush(); } System.out.println("文件复制完成" ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (isr != null ) isr.close(); } catch (IOException e) { e.printStackTrace(); } try { if (osw != null ) osw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
7. 处理流之三/四:数据流、对象流 7.1 数据流与对象流说明 如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文件中,那怎么办呢?
1 2 3 4 5 6 7 8 int age = 300 ;char gender = '男' ;int energy = 5000 ;double price = 75.5 ;boolean relive = true ;String name = "巫师" ; Student stu = new Student("张三" ,23 ,89 );
Java提供了数据流和对象流来处理这些类型的数据:
1 2 3 4 5 byte readByte () short readShort () int readInt () long readLong () float readFloat () double readDouble () char readChar () boolean readBoolean () String readUTF () void readFully (byte [] b)
对象流DataOutputStream中的方法:将上述的方法的read改为相应的write即可。
数据流的弊端:只支持Java基本数据类型和字符串的读写,而不支持其它Java对象的类型。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以重点介绍对象流ObjectOutputStream和ObjectInputStream。
对象流:ObjectOutputStream、ObjectInputStream
ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储。
ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写出的基本数据类型的数据和对象进行读入操作,保存在内存中。
说明:对象流的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
7.2 对象流API ObjectOutputStream中的构造器:
public ObjectOutputStream(OutputStream out)
: 创建一个指定的ObjectOutputStream。
1 2 FileOutputStream fos = new FileOutputStream("game.dat" ); ObjectOutputStream oos = new ObjectOutputStream(fos);
ObjectOutputStream中的方法:
public void writeBoolean(boolean val):写出一个 boolean 值。
public void writeByte(int val):写出一个8位字节
public void writeShort(int val):写出一个16位的 short 值
public void writeChar(int val):写出一个16位的 char 值
public void writeInt(int val):写出一个32位的 int 值
public void writeLong(long val):写出一个64位的 long 值
public void writeFloat(float val):写出一个32位的 float 值。
public void writeDouble(double val):写出一个64位的 double 值
public void writeUTF(String str):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。根据字符的值,将字符串 s 中每个字符转换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入流中与将它作为 Object 写入流中明显不同。 如果 s 为 null,则抛出 NullPointerException。
public void writeObject(Object obj)
:写出一个obj对象
public void close() :关闭此输出流并释放与此流相关联的任何系统资源
ObjectInputStream中的构造器:
public ObjectInputStream(InputStream in)
: 创建一个指定的ObjectInputStream。
1 2 FileInputStream fis = new FileInputStream("game.dat" ); ObjectInputStream ois = new ObjectInputStream(fis);
ObjectInputStream中的方法:
public boolean readBoolean():读取一个 boolean 值
public byte readByte():读取一个 8 位的字节
public short readShort():读取一个 16 位的 short 值
public char readChar():读取一个 16 位的 char 值
public int readInt():读取一个 32 位的 int 值
public long readLong():读取一个 64 位的 long 值
public float readFloat():读取一个 32 位的 float 值
public double readDouble():读取一个 64 位的 double 值
public String readUTF():读取 UTF-8 修改版格式的 String
public void readObject(Object obj)
:读入一个obj对象
public void close() :关闭此输入流并释放与此流相关联的任何系统资源
7.3 认识对象序列化机制 1、何为对象序列化机制?
对象序列化机制
允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存
了一个对象的信息。
反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
2、序列化机制的重要性
序列化是 RMI(Remote Method Invoke、远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。
序列化的好处,在于可将任何实现了Serializable接口的对象转化为字节数据 ,使其在保存和传输时可被还原。
3、实现原理
7.4 如何实现序列化机制 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现java.io.Serializable
接口。Serializable
是一个标记接口
,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable
接口
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient
关键字修饰。
静态(static)变量
的值不会序列化。因为静态变量的值不属于某个对象。
举例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import org.junit.Test;import java.io.*;public class ReadWriteDataOfAnyType { @Test public void save () throws IOException { String name = "巫师" ; int age = 300 ; char gender = '男' ; int energy = 5000 ; double price = 75.5 ; boolean relive = true ; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat" )); oos.writeUTF(name); oos.writeInt(age); oos.writeChar(gender); oos.writeInt(energy); oos.writeDouble(price); oos.writeBoolean(relive); oos.close(); } @Test public void reload () throws IOException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat" )); String name = ois.readUTF(); int age = ois.readInt(); char gender = ois.readChar(); int energy = ois.readInt(); double price = ois.readDouble(); boolean relive = ois.readBoolean(); System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive); ois.close(); } }
举例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import java.io.Serializable;public class Employee implements Serializable { public static String company; public String name; public String address; public transient int age; public Employee (String name, String address, int age) { this .name = name; this .address = address; this .age = age; } public static String getCompany () { return company; } public static void setCompany (String company) { Employee.company = company; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getAddress () { return address; } public void setAddress (String address) { this .address = address; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } @Override public String toString () { return "Employee{" + "name='" + name + '\'' + ", address='" + address + '\'' + ", age=" + age + ", company=" + company + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import org.junit.Test;import java.io.*;public class ReadWriteObject { @Test public void save () throws IOException { Employee.setCompany("尚硅谷" ); Employee e = new Employee("小谷姐姐" , "宏福苑" , 23 ); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat" )); oos.writeObject(e); oos.close(); System.out.println("Serialized data is saved" ); } @Test public void reload () throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream("employee.dat" ); ObjectInputStream ois = new ObjectInputStream(fis); Employee e = (Employee) ois.readObject(); ois.close(); fis.close(); System.out.println(e); } }
举例3:如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import org.junit.Test;import java.io.*;import java.util.ArrayList;public class ReadWriteCollection { @Test public void save () throws IOException { ArrayList<Employee> list = new ArrayList<>(); list.add(new Employee("张三" , "宏福苑" , 23 )); list.add(new Employee("李四" , "白庙" , 24 )); list.add(new Employee("王五" , "平西府" , 25 )); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat" )); oos.writeObject(list); oos.close(); } @Test public void reload () throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream("employees.dat" ); ObjectInputStream ois = new ObjectInputStream(fis); ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject(); ois.close(); fis.close(); System.out.println(list); } }
7.5 反序列化失败问题 问题1:
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
问题2:
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。发生这个异常的原因如下:
该类的序列版本号与从流中读取的类描述符的版本号不匹配
该类包含未知数据类型
解决办法:
Serializable
接口给需要序列化的类,提供了一个序列版本号:serialVersionUID
。凡是实现 Serializable接口的类都应该有一个表示序列化版本标识符的静态变量:
1 static final long serialVersionUID = 234242343243L ;
serialVersionUID用来表明类的不同版本间的兼容性。简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。
如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成
的。若类的实例变量做了修改,serialVersionUID 可能发生变化
。因此,建议显式声明。
如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。
1 2 3 4 5 6 import java.io.Serializable;public class Employee implements Serializable { private static final long serialVersionUID = 1324234L ; }
7.6 面试题&练习 面试题:谈谈你对java.io.Serializable接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?
1 2 3 实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。 由于大部分作为参数的类如String、Integer等都实现了java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更灵活。
练习:
需求说明:
网上购物时某用户填写订单,订单内容为产品列表,保存在“save.bin”中。
运行时,如果不存在“save.bin”,则进行新订单录入,如果存在,则显示并计算客户所需付款。
分析:
编写Save()方法保存对象到“save.bin”
编写Load()方法获得对象,计算客户所需付款
8. 其他流的使用 8.1 标准输入、输出流
System.in和System.out分别代表了系统标准的输入和输出设备
默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
重定向:通过System类的setIn,setOut方法对默认设备进行改变。
public static void setIn(InputStream in)
public static void setOut(PrintStream out)
举例:
从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 System.out.println("请输入信息(退出输入e或exit):" ); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String s = null ; try { while ((s = br.readLine()) != null ) { if ("e" .equalsIgnoreCase(s) || "exit" .equalsIgnoreCase(s)) { System.out.println("安全退出!!" ); break ; } System.out.println("-->:" + s.toUpperCase()); System.out.println("继续输入信息" ); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (br != null ) { br.close(); } } catch (IOException e) { e.printStackTrace(); } }
拓展:
System类中有三个常量对象:System.out、System.in、System.err
查看System类中这三个常量对象的声明:
1 2 3 public final static InputStream in = null ;public final static PrintStream out = null ;public final static PrintStream err = null ;
奇怪的是,
这三个常量对象有final声明,但是却初始化为null。final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗?
这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗?
这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?
1 final 声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void setOut (PrintStream out) { checkIO(); setOut0(out); } public static void setErr (PrintStream err) { checkIO(); setErr0(err); } public static void setIn (InputStream in) { checkIO(); setIn0(in); } private static void checkIO () { SecurityManager sm = getSecurityManager(); if (sm != null ) { sm.checkPermission(new RuntimePermission("setIO" )); } } private static native void setIn0 (InputStream in) ;private static native void setOut0 (PrintStream out) ;private static native void setErr0 (PrintStream err) ;
练习:
Create a program named MyInput.java: Contain the methods for reading int, double, float, boolean, short, byte and String values from the keyboard.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.atguigu.java;import java.io.*;public class MyInput { public static String readString () { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String string = "" ; try { string = br.readLine(); } catch (IOException ex) { System.out.println(ex); } return string; } public static int readInt () { return Integer.parseInt(readString()); } public static double readDouble () { return Double.parseDouble(readString()); } public static double readByte () { return Byte.parseByte(readString()); } public static double readShort () { return Short.parseShort(readString()); } public static double readLong () { return Long.parseLong(readString()); } public static double readFloat () { return Float.parseFloat(readString()); } }
8.2 打印流
1 2 3 4 5 6 7 8 9 10 11 import java.io.FileNotFoundException;import java.io.PrintStream;public class TestPrintStream { public static void main (String[] args) throws FileNotFoundException { PrintStream ps = new PrintStream("io.txt" ); ps.println("hello" ); ps.println(1 ); ps.println(1.5 ); ps.close(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 PrintStream ps = null ; try { FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt" )); ps = new PrintStream(fos, true ); if (ps != null ) { System.setOut(ps); } for (int i = 0 ; i <= 255 ; i++) { System.out.print((char ) i); if (i % 50 == 0 ) { System.out.println(); } } } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (ps != null ) { ps.close(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Logger { public static void log (String msg) { try { PrintStream out = new PrintStream(new FileOutputStream("log.txt" , true )); System.setOut(out); Date nowTime = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS" ); String strTime = sdf.format(nowTime); System.out.println(strTime + ": " + msg); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
1 2 3 4 5 6 7 8 public class LogTest { public static void main (String[] args) { Logger.log("调用了System类的gc()方法,建议启动垃圾回收" ); Logger.log("调用了TeamView的addMember()方法" ); Logger.log("用户尝试进行登录,验证失败" ); } }
8.3 Scanner类 构造方法
Scanner(File source) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
Scanner(File source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
Scanner(InputStream source) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
Scanner(InputStream source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
常用方法:
boolean hasNextXxx(): 通过nextXxx(),此扫描器输入信息中的下一个标记可以解释为默认基数中的一个 Xxx 值,则返回 true。
Xxx nextXxx(): 将输入信息的下一个标记扫描为一个Xxx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import org.junit.Test;import java.io.*;import java.util.Scanner;public class TestScanner { @Test public void test01 () throws IOException { Scanner input = new Scanner(System.in); PrintStream ps = new PrintStream("1.txt" ); while (true ){ System.out.print("请输入一个单词:" ); String str = input.nextLine(); if ("stop" .equals(str)){ break ; } ps.println(str); } input.close(); ps.close(); } @Test public void test2 () throws IOException { Scanner input = new Scanner(new FileInputStream("1.txt" )); while (input.hasNextLine()){ String str = input.nextLine(); System.out.println(str); } input.close(); } }
9. apache-common包的使用 9.1 介绍 IO技术开发中,代码量很大,而且代码的重复率较高,为此Apache软件基金会,开发了IO技术的工具类commonsIO
,大大简化了IO开发。
Apahce软件基金会属于第三方,(Oracle公司第一方,我们自己第二方,其他都是第三方)我们要使用第三方开发好的工具,需要添加jar包。
9.2 导包及举例
1 2 - 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。 - 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test01 { public static void main (String[] args) throws Exception { IOUtils.copy(new FileInputStream("E:\\Idea\\io\\1.jpg" ),new FileOutputStream("E:\\Idea\\io\\file\\柳岩.jpg" )); } }
1 2 3 4 5 6 7 8 - 静态方法:void copyDirectoryToDirectory (File src,File dest) :整个目录的复制,自动进行递归遍历 参数: src:要复制的文件夹路径 dest:要将文件夹粘贴到哪里去 - 静态方法:void writeStringToFile (File file,String content) :将内容content写入到file中 - 静态方法:String readFileToString (File file) :读取文件内容,并返回一个String - 静态方法:void copyFile (File srcFile,File destFile) :文件复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Test02 { public static void main (String[] args) { try { FileUtils.copyDirectoryToDirectory(new File("E:\\Idea\\io\\aa" ),new File("E:\\Idea\\io\\file" )); FileUtils.writeStringToFile(new File("day21\\io\\commons.txt" ),"柳岩你好" ); String s = FileUtils.readFileToString(new File("day21\\io\\commons.txt" )); System.out.println(s); FileUtils.copyFile(new File("io\\yangm.png" ),new File("io\\yangm2.png" )); System.out.println("复制成功" ); } catch (IOException e) { e.printStackTrace(); } } }