异常
异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行.
异常体系 异常机制其实是帮助我们找到 程序中的问题,异常的根类是java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
,平常所说的异常指java.lang.Exception
。
Throwable体系:
Error :严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
Exception :表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。
Throwable中的常用方法:
public void printStackTrace()
:打印异常的详细信息。
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
public String getMessage()
:获取发生异常的原因。
提示给用户的时候,就提示错误原因。
public String toString()
:获取异常的类型和异常描述信息(不用)。
出现异常,不要紧张,把异常的简单类名,拷贝到API中去查。
异常分类 我们平常说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。
异常(Exception)的分类 :根据在编译时期还是运行时期去检查异常?
编译时期异常 :checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
运行时期异常 :runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)
异常的产生过程解析 先运行下面的程序,程序会产生一个数组索引越界异常ArrayIndexOfBoundsException。我们通过图解来解析下异常产生的过程。
工具类
1 2 3 4 5 6 7 public class ArrayTools { public static int getElement (int [] arr, int index) { int element = arr[index]; return element; } }
测试类
1 2 3 4 5 6 7 8 public class ExceptionDemo { public static void main (String[] args) { int [] arr = { 34 , 12 , 67 }; intnum = ArrayTools.getElement(arr, 4 ) System.out.println("num=" + num); System.out.println("over" ); } }
抛出异常throw 在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的方式来告诉调用者。
在java中,提供了一个throw 关键字,它用来抛出一个指定的异常对象。那么,抛出一个异常具体如何操作呢?
创建一个异常对象。封装一些提示信息(信息可以自己编写)。
需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。
throw用在方法内 ,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
使用格式:
例如:
1 2 3 throw new NullPointerException ("要访问的arr数组不存在" );throw new ArrayIndexOutOfBoundsException ("该索引在数组中不存在,已超出范围" );
学习完抛出异常的格式后,我们通过下面程序演示下throw的使用。
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 public class ThrowDemo { public static void main (String[] args) { int [] arr = {2 ,4 ,52 ,2 }; int index = 4 ; int element = getElement(arr, index); System.out.println(element); System.out.println("over" ); } public static int getElement (int [] arr,int index) { if (index<0 || index>arr.length-1 ){ throw new ArrayIndexOutOfBoundsException ("哥们,角标越界了```" ); } int element = arr[index]; return element; } }
注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。
那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续讲问题声明出去,使用throws声明处理。
声明异常throws 声明异常 :将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws进行声明,让调用者去处理。
关键字throws 运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
声明异常格式:
1 修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
声明异常的代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ThrowsDemo { public static void main (String[] args) throws FileNotFoundException { read("a.txt" ); } public static void read (String path) throws FileNotFoundException { if (!path.equals("a.txt" )) { throw new FileNotFoundException ("文件不存在" ); } } }
throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ThrowsDemo2 { public static void main (String[] args) throws IOException { read("a.txt" ); } public static void read (String path) throws FileNotFoundException, IOException { if (!path.equals("a.txt" )) { throw new FileNotFoundException ("文件不存在" ); } if (!path.equals("b.txt" )) { throw new IOException (); } } }
捕获异常try…catch 如果异常出现的话,会立刻终止程序,所以我们得处理异常:
该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
在方法中使用try-catch的语句块来处理异常。
try-catch 的方式就是捕获异常。
捕获异常 :Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
捕获异常语法如下:
1 2 3 4 5 6 try { 编写可能会出现异常的代码 }catch (异常类型 e){ 处理异常的代码 }
try: 该代码块中编写可能产生异常的代码。
catch: 用来进行某种异常的捕获,实现对捕获到的异常进行处理。
注意:try和catch都不能单独使用,必须连用。
演示如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class TryCatchDemo { public static void main (String[] args) { try { read("b.txt" ); } catch (FileNotFoundException e) { System.out.println(e); } System.out.println("over" ); } public static void read (String path) throws FileNotFoundException { if (!path.equals("a.txt" )) { throw new FileNotFoundException ("文件不存在" ); } } }
如何获取异常信息:
Throwable类中定义了一些查看方法:
public String getMessage()
:获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。
public String toString()
:获取异常的类型和异常描述信息(不用)。
public void printStackTrace()
:打印异常的跟踪栈信息并输出到控制台。
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
在开发中呢也可以在catch将编译期异常转换成运行期异常处理。
多个异常使用捕获又该如何处理呢?
多个异常分别处理。
多个异常一次捕获,多次处理。
多个异常一次捕获一次处理。
一般我们是使用一次捕获多次处理方式,格式如下:
1 2 3 4 5 6 7 8 9 try { 编写可能会出现异常的代码 }catch (异常类型A e){ 当try 中出现A类型异常,就用该catch 来捕获. 处理异常的代码 }catch (异常类型B e){ 当try 中出现B类型异常,就用该catch 来捕获. 处理异常的代码 }
注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
finally 代码块 finally :有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
什么时候的代码必须最终执行?
当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。
finally的语法:
try…catch….finally:自身需要处理异常,最终还得关闭资源。
注意:finally不能单独使用。
比如在我们之后学习的IO流中,当打开了一个关联文件的资源,最后程序不管结果如何,都需要把这个资源关闭掉。
finally代码参考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class TryCatchDemo4 { public static void main (String[] args) { try { read("a.txt" ); } catch (FileNotFoundException e) { throw new RuntimeException (e); } finally { System.out.println("不管程序怎样,这里都将会被执行。" ); } System.out.println("over" ); } public static void read (String path) throws FileNotFoundException { if (!path.equals("a.txt" )) { throw new FileNotFoundException ("文件不存在" ); } } }
当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。
异常注意事项
运行时异常被抛出可以不处理。即不捕获也不声明抛出。
如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是他的子集。
父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
当多异常处理时,捕获处理,前边的类不能是后边类的父类
在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。
概述 为什么需要自定义异常类:
我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN没有定义好的,此时我们根据自己业务的异常情况来定义异常类。,例如年龄负数问题,考试成绩负数问题。
在上述代码中,发现这些异常都是JDK内部定义好的,但是实际开发中也会出现很多异常,这些异常很可能在JDK中没有定义过,例如年龄负数问题,考试成绩负数问题.那么能不能自己定义异常呢?
什么是自定义异常类:
在开发中根据自己业务的异常情况来定义异常类.
自定义一个业务逻辑异常: LoginException 。一个登陆异常类。
异常类如何定义:
自定义一个编译期异常: 自定义类 并继承于java.lang.Exception
。
自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException
。
自定义异常的练习 要求:我们模拟登陆操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
首先定义一个登陆异常类LoginException:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class LoginException extends Exception { public LoginException () { } public LoginException (String message) { super (message); } }
模拟登陆操作,使用数组模拟数据库中存储的数据,并提供当前注册账号是否存在方法用于判断。
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 public class Demo { private static String[] names = {"bill" ,"hill" ,"jill" }; public static void main (String[] args) { try { checkUsername("nill" ); System.out.println("注册成功" ); } catch (LoginException e) { e.printStackTrace(); } } public static boolean checkUsername (String uname) throws LoginException { for (String name : names) { if (name.equals(uname)){ throw new LoginException ("亲" +name+"已经被注册了!" ); } } return true ; } }
File类 概述 java.io.File
类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
构造方法
public File(String pathname)
:通过将给定的路径名字符串 转换为抽象路径名来创建新的 File实例。
public File(String parent, String child)
:从父路径名字符串和子路径名字符串 创建新的 File实例。
public File(File parent, String child)
:从父抽象路径名和子路径名字符串 创建新的 File实例。
构造举例,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 String pathname = "D:\\aaa.txt" ;File file1 = new File (pathname); String pathname2 = "D:\\aaa\\bbb.txt" ;File file2 = new File (pathname2); String parent = "d:\\aaa" ; String child = "bbb.txt" ; File file3 = new File (parent, child); File parentDir = new File ("d:\\aaa" );String child = "bbb.txt" ;File file4 = new File (parentDir, child);
小贴士:
一个File对象代表硬盘中实际存在的一个文件或者目录。
无论该路径下是否存在文件或者目录,都不影响File对象的创建。
常用方法 获取功能的方法
public String getAbsolutePath()
:返回此File的绝对路径名字符串。
public String getPath()
:将此File转换为路径名字符串。
public String getName()
:返回由此File表示的文件或目录的名称。
public long length()
:返回由此File表示的文件的长度。
方法演示,代码如下:
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 public class FileGet { public static void main (String[] args) { File f = new File ("d:/aaa/bbb.java" ); System.out.println("文件绝对路径:" +f.getAbsolutePath()); System.out.println("文件构造路径:" +f.getPath()); System.out.println("文件名称:" +f.getName()); System.out.println("文件长度:" +f.length()+"字节" ); File f2 = new File ("d:/aaa" ); System.out.println("目录绝对路径:" +f2.getAbsolutePath()); System.out.println("目录构造路径:" +f2.getPath()); System.out.println("目录名称:" +f2.getName()); System.out.println("目录长度:" +f2.length()); } } 输出结果: 文件绝对路径:d:\aaa\bbb.java 文件构造路径:d:\aaa\bbb.java 文件名称:bbb.java 文件长度:636 字节 目录绝对路径:d:\aaa 目录构造路径:d:\aaa 目录名称:aaa 目录长度:4096
API中说明:length(),表示文件的长度。但是File对象表示目录,则返回值未指定。
绝对路径和相对路径
绝对路径 :从盘符开始的路径,这是一个完整的路径。
相对路径 :相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class FilePath { public static void main (String[] args) { File f = new File ("D:\\bbb.java" ); System.out.println(f.getAbsolutePath()); File f2 = new File ("bbb.java" ); System.out.println(f2.getAbsolutePath()); } } 输出结果: D:\bbb.java D:\idea_project_test4\bbb.java
判断功能的方法
public boolean exists()
:此File表示的文件或目录是否实际存在。
public boolean isDirectory()
:此File表示的是否为目录。
public boolean isFile()
:此File表示的是否为文件。
方法演示,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class FileIs { public static void main (String[] args) { File f = new File ("d:\\aaa\\bbb.java" ); File f2 = new File ("d:\\aaa" ); System.out.println("d:\\aaa\\bbb.java 是否存在:" +f.exists()); System.out.println("d:\\aaa 是否存在:" +f2.exists()); System.out.println("d:\\aaa 文件?:" +f2.isFile()); System.out.println("d:\\aaa 目录?:" +f2.isDirectory()); } } 输出结果: d:\aaa\bbb.java 是否存在:true d:\aaa 是否存在:true d:\aaa 文件?:false d:\aaa 目录?:true
创建删除功能的方法
public boolean createNewFile()
:当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete()
:删除由此File表示的文件或目录。
public boolean mkdir()
:创建由此File表示的目录。
public boolean mkdirs()
:创建由此File表示的目录,包括任何必需但不存在的父目录。
方法演示,代码如下:
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 public class FileCreateDelete { public static void main (String[] args) throws IOException { File f = new File ("aaa.txt" ); System.out.println("是否存在:" +f.exists()); System.out.println("是否创建:" +f.createNewFile()); System.out.println("是否存在:" +f.exists()); File f2= new File ("newDir" ); System.out.println("是否存在:" +f2.exists()); System.out.println("是否创建:" +f2.mkdir()); System.out.println("是否存在:" +f2.exists()); File f3= new File ("newDira\\newDirb" ); System.out.println(f3.mkdir()); File f4= new File ("newDira\\newDirb" ); System.out.println(f4.mkdirs()); System.out.println(f.delete()); System.out.println(f2.delete()); System.out.println(f4.delete()); } }
API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除。
目录的遍历
public String[] list()
:返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles()
:返回一个File数组,表示该File目录中的所有的子文件或目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class FileFor { public static void main (String[] args) { File dir = new File ("d:\\java_code" ); String[] names = dir.list(); for (String name : names){ System.out.println(name); } File[] files = dir.listFiles(); for (File file : files) { System.out.println(file); } } }
小贴士:
调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。
IO概述 什么是IO 生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s
,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。
我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input
和输出output
,即流向内存是输入流,流出内存的输出流。
Java中I/O操作主要是指使用java.io
包下的内容,进行输入、输出操作。输入 也叫做读取 数据,输出 也叫做作写出 数据。
IO的分类 根据数据的流向分为:输入流 和输出流 。
输入流 :把数据从其他设备
上读取到内存
中的流。
输出流 :把数据从内存
中写出到其他设备
上的流。
格局数据的类型分为:字节流 和字符流 。
字节流 :以字节为单位,读写数据的流。
字符流 :以字符为单位,读写数据的流。
IO的流向说明图解 顶级父类们
输入流
输出流
字节流
字节输入流InputStream
字节输出流OutputStream
字符流
字符输入流Reader
字符输出流Writer
字节流 一切皆为字节 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
字节输出流【OutputStream】 java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。
public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b)
:将指定的字节输出流。
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileOutputStream类 OutputStream
有很多子类,我们从最简单的一个子类开始。
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
1 2 3 4 5 6 7 8 9 10 public class FileOutputStreamConstructor throws IOException { public static void main (String[] args) { File file = new File ("a.txt" ); FileOutputStream fos = new FileOutputStream (file); FileOutputStream fos = new FileOutputStream ("b.txt" ); } }
写出字节数据
写出字节 :write(int b)
方法,每次可以写出一个字节数据,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class FOSWrite { public static void main (String[] args) throws IOException { FileOutputStream fos = new FileOutputStream ("fos.txt" ); fos.write(97 ); fos.write(98 ); fos.write(99 ); fos.close(); } } 输出结果: abc
小贴士:
虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
流操作完毕后,必须释放系统资源,调用close方法,千万记得。
写出字节数组 :write(byte[] b)
,每次可以写出数组中的数据,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class FOSWrite { public static void main (String[] args) throws IOException { FileOutputStream fos = new FileOutputStream ("fos.txt" ); byte [] b = "黑马程序员" .getBytes(); fos.write(b); fos.close(); } } 输出结果: 黑马程序员
写出指定长度字节数组 :write(byte[] b, int off, int len)
,每次写出从off索引开始,len个字节,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class FOSWrite { public static void main (String[] args) throws IOException { FileOutputStream fos = new FileOutputStream ("fos.txt" ); byte [] b = "abcde" .getBytes(); fos.write(b,2 ,2 ); fos.close(); } } 输出结果: cd
数据追加续写 经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?
public FileOutputStream(File file, boolean append)
: 创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name, boolean append)
: 创建文件输出流以指定的名称写入文件。
这两个构造方法,参数中都需要传入一个boolean类型的值,true
表示追加数据,false
表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class FOSWrite { public static void main (String[] args) throws IOException { FileOutputStream fos = new FileOutputStream ("fos.txt" ,true ); byte [] b = "abcde" .getBytes(); fos.write(b); fos.close(); } } 文件操作前:cd 文件操作后:cdabcde
写出换行 Windows系统里,换行符号是\r\n
。把
以指定是否追加续写了,代码使用演示:
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 FOSWrite { public static void main (String[] args) throws IOException { FileOutputStream fos = new FileOutputStream ("fos.txt" ); byte [] words = {97 ,98 ,99 ,100 ,101 }; for (int i = 0 ; i < words.length; i++) { fos.write(words[i]); fos.write("\r\n" .getBytes()); } fos.close(); } } 输出结果: a b c d e
回车符\r
和换行符\n
:
回车符:回到一行的开头(return)。
换行符:下一行(newline)。
系统中的换行:
Windows系统里,每行结尾是 回车+换行
,即\r\n
;
Unix系统里,每行结尾只有 换行
,即\n
;
Mac系统里,每行结尾是 回车
,即\r
。从 Mac OS X开始与Linux统一。
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public void close()
:关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read()
: 从输入流读取数据的下一个字节。
public int read(byte[] b)
: 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
构造方法
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
。
1 2 3 4 5 6 7 8 9 10 public class FileInputStreamConstructor throws IOException{ public static void main (String[] args) { File file = new File ("a.txt" ); FileInputStream fos = new FileInputStream (file); FileInputStream fos = new FileInputStream ("b.txt" ); } }
读取字节数据
读取字节 :read
方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-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 public class FISRead { public static void main (String[] args) 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(); } } 输出结果: a b c d e -1
循环改进读取方式,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class FISRead { public static void main (String[] args) throws IOException{ FileInputStream fis = new FileInputStream ("read.txt" ); int b ; while ((b = fis.read())!=-1 ) { System.out.println((char )b); } fis.close(); } } 输出结果: a b c d e
小贴士:
虽然读取了一个字节,但是会自动提升为int类型。
流操作完毕后,必须释放系统资源,调用close方法,千万记得。
使用字节数组读取 :read(byte[] b)
,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class FISRead { public static void main (String[] args) 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(); } } 输出结果: ab cd ed
错误数据d
,是由于最后一次读取时,只读取一个字节e
,数组中,上次读取的数据没有被完全替换,所以要通过len
,获取有效的字节,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class FISRead { public static void main (String[] args) 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(); } } 输出结果: ab cd e
小贴士:
使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。
字节流练习:图片复制 案例实现 复制图片文件,代码使用演示:
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 Copy { public static void main (String[] args) throws IOException { FileInputStream fis = new FileInputStream ("D:\\test.jpg" ); FileOutputStream fos = new FileOutputStream ("test_copy.jpg" ); byte [] b = new byte [1024 ]; int len; while ((len = fis.read(b))!=-1 ) { fos.write(b, 0 , len); } fos.close(); fis.close(); } }
小贴士:
流的关闭原则:先开后关,后开先关。
字符流 当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
字符输入流【Reader】 java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。
public int read()
: 从输入流读取一个字符。
public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
FileReader类 java.io.FileReader
类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
小贴士:
字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
idea中UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据。
构造方法
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。
FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
1 2 3 4 5 6 7 8 9 10 public class FileReaderConstructor throws IOException{ public static void main (String[] args) { File file = new File ("a.txt" ); FileReader fr = new FileReader (file); FileReader fr = new FileReader ("b.txt" ); } }
读取字符数据
读取字符 :read
方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1
,循环读取,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class FRRead { public static void main (String[] args) throws IOException { FileReader fr = new FileReader ("read.txt" ); int b ; while ((b = fr.read())!=-1 ) { System.out.println((char )b); } fr.close(); } } 输出结果: 黑 马 程 序 员
小贴士:虽然读取了一个字符,但是会自动提升为int类型。
使用字符数组读取 :read(char[] cbuf)
,每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1
,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class FRRead { public static void main (String[] args) throws IOException { FileReader fr = new FileReader ("read.txt" ); int len ; char [] cbuf = new char [2 ]; while ((len = fr.read(cbuf))!=-1 ) { System.out.println(new String (cbuf)); } fr.close(); } } 输出结果: 黑马 程序 员序
获取有效的字符改进,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class FISRead { public static void main (String[] args) throws IOException { FileReader fr = new FileReader ("read.txt" ); int len ; char [] cbuf = new char [2 ]; while ((len = fr.read(cbuf))!=-1 ) { System.out.println(new String (cbuf,0 ,len)); } fr.close(); } } 输出结果: 黑马 程序 员
字符输出流【Writer】 java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
void write(int c)
写入单个字符。
void write(char[] cbuf)
写入字符数组。
abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
void write(String str)
写入字符串。
void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
void flush()
刷新该流的缓冲。
void close()
关闭此流,但要先刷新它。
FileWriter类 java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
构造方法
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。
FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
1 2 3 4 5 6 7 8 9 10 public class FileWriterConstructor { public static void main (String[] args) throws IOException { File file = new File ("a.txt" ); FileWriter fw = new FileWriter (file); FileWriter fw = new FileWriter ("b.txt" ); } }
基本写出数据 写出字符 :write(int b)
方法,每次可以写出一个字符数据,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class FWWrite { public static void main (String[] args) throws IOException { FileWriter fw = new FileWriter ("fw.txt" ); fw.write(97 ); fw.write('b' ); fw.write('C' ); fw.write(30000 ); } } 输出结果: abC田
小贴士:
虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。
关闭和刷新 因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush
方法了。
flush
:刷新缓冲区,流对象可以继续使用。
close
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class FWWrite { public static void main (String[] args) throws IOException { FileWriter fw = new FileWriter ("fw.txt" ); fw.write('刷' ); fw.flush(); fw.write('新' ); fw.flush(); fw.write('关' ); fw.close(); fw.write('闭' ); fw.close(); } }
小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。
写出其他数据
写出字符数组 :write(char[] cbuf)
和 write(char[] cbuf, int off, int len)
,每次可以写出字符数组中的数据,用法类似FileOutputStream,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class FWWrite { public static void main (String[] args) throws IOException { FileWriter fw = new FileWriter ("fw.txt" ); char [] chars = "黑马程序员" .toCharArray(); fw.write(chars); fw.write(b,2 ,2 ); fos.close(); } }
写出字符串 :write(String str)
和 write(String str, int off, int len)
,每次可以写出字符串中的数据,更为方便,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class FWWrite { public static void main (String[] args) throws IOException { FileWriter fw = new FileWriter ("fw.txt" ); String msg = "黑马程序员" ; fw.write(msg); fw.write(msg,2 ,2 ); fos.close(); } }
续写和换行 :操作类似于FileOutputStream。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class FWWrite { public static void main (String[] args) throws IOException { FileWriter fw = new FileWriter ("fw.txt" ,true ); fw.write("黑马" ); fw.write("\r\n" ); fw.write("程序员" ); fw.close(); } } 输出结果: 黑马 程序员
小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。
当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
IO异常的处理 JDK7前处理 之前的入门练习,我们一直把异常抛出,而实际开发中并不能这样处理,建议使用try...catch...finally
代码块,处理异常部分,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class HandleException1 { public static void main (String[] args) { FileWriter fw = null ; try { fw = new FileWriter ("fw.txt" ); fw.write("黑马程序员" ); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fw != null ) { fw.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
JDK7的处理 还可以使用JDK7优化后的try-with-resource
语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
格式:
1 2 3 4 5 try (创建流对象语句,如果多个,使用';' 隔开) { } catch (IOException e) { e.printStackTrace(); }
代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 public class HandleException2 { public static void main (String[] args) { try ( FileWriter fw = new FileWriter ("fw.txt" ); ) { fw.write("黑马程序员" ); } catch (IOException e) { e.printStackTrace(); } } }
JDK9的改进 JDK9中try-with-resource
的改进,对于引入对象 的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close,我们来了解一下格式。
改进前格式:
1 2 3 4 5 6 7 8 9 final Resource resource1 = new Resource ("resource1" );Resource resource2 = new Resource ("resource2" );try (Resource r1 = resource1; Resource r2 = resource2) { }
改进后格式:
1 2 3 4 5 6 7 8 9 final Resource resource1 = new Resource ("resource1" );Resource resource2 = new Resource ("resource2" );try (resource1; resource2) { }
改进后,代码使用演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class TryDemo { public static void main (String[] args) throws IOException { final FileReader fr = new FileReader ("in.txt" ); FileWriter fw = new FileWriter ("out.txt" ); try (fr; fw) { int b; while ((b = fr.read())!=-1 ) { fw.write(b); } } catch (IOException e) { e.printStackTrace(); } } }
缓冲流 能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等。这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。
概述 缓冲流,也叫高效流,是对4个基本的FileXxx
流的增强,所以也是4个流,按照数据类型分类:
字节缓冲流 :BufferedInputStream
,BufferedOutputStream
字符缓冲流 :BufferedReader
,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
字节缓冲流 构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。
public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
1 2 3 4 BufferedInputStream bis = new BufferedInputStream (new FileInputStream ("bis.txt" ));BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream ("bos.txt" ));
效率测试 查询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 public class BufferedDemo { public static void main (String[] args) throws FileNotFoundException { long start = System.currentTimeMillis(); try ( FileInputStream fis = new FileInputStream ("jdk9.exe" ); FileOutputStream fos = new FileOutputStream ("copy.exe" ) ){ int b; while ((b = fis.read()) != -1 ) { fos.write(b); } } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("普通流复制时间:" +(end - start)+" 毫秒" ); } } 十几分钟过去了...
缓冲流,代码如下:
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 BufferedDemo { public static void main (String[] args) throws FileNotFoundException { long start = System.currentTimeMillis(); try ( BufferedInputStream bis = new BufferedInputStream (new FileInputStream ("jdk9.exe" )); BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream ("copy.exe" )); ){ int b; while ((b = bis.read()) != -1 ) { bos.write(b); } } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("缓冲流复制时间:" +(end - start)+" 毫秒" ); } } 缓冲流复制时间:8016 毫秒
如何更快呢?
使用数组的方式,代码如下:
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 BufferedDemo { public static void main (String[] args) throws FileNotFoundException { long start = System.currentTimeMillis(); try ( BufferedInputStream bis = new BufferedInputStream (new FileInputStream ("jdk9.exe" )); BufferedOutputStream bos = new BufferedOutputStream (new FileOutputStream ("copy.exe" )); ){ int len; byte [] bytes = new byte [8 *1024 ]; while ((len = bis.read(bytes)) != -1 ) { bos.write(bytes, 0 , len); } } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("缓冲流使用数组复制时间:" +(end - start)+" 毫秒" ); } } 缓冲流使用数组复制时间:666 毫秒
字符缓冲流 构造方法
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" ));
特有方法 字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
BufferedReader:public String readLine()
: 读一行文字。
BufferedWriter:public void newLine()
: 写一行行分隔符,由系统属性定义符号。
readLine
方法演示,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BufferedReaderDemo { public static void main (String[] args) throws IOException { BufferedReader br = new BufferedReader (new FileReader ("in.txt" )); String line = null ; while ((line = br.readLine())!=null ) { System.out.print(line); System.out.println("------" ); } br.close(); } }
newLine
方法演示,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class BufferedWriterDemo throws IOException { public static void main (String[] args) throws IOException { BufferedWriter bw = new BufferedWriter (new FileWriter ("out.txt" )); bw.write("黑马" ); bw.newLine(); bw.write("程序" ); bw.newLine(); bw.write("员" ); bw.newLine(); bw.close(); } } 输出效果: 黑马 程序 员
转换流 字符编码和字符集 字符编码 计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
编码:字符(能看懂的)–字节(看不懂的)
解码:字节(看不懂的)–>字符(能看懂的)
字符集
**字符集 Charset
**:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码 ,它所对应的字符集 自然就指定了,所以编码 才是我们最终要关心的。
ASCII字符集 :
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
ISO-8859-1字符集 :
拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
ISO-8859-1使用单字节编码,兼容ASCII编码。
GBxxx字符集 :
GB就是国标的意思,是为了显示中文而设计的一套字符集。
GB2312 :简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。
GBK :最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
GB18030 :最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
Unicode字符集 :
Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
128个US-ASCII字符,只需一个字节编码。
拉丁文等字符,需要二个字节编码。
大部分常用字(含中文),使用三个字节编码。
其他极少使用的Unicode辅助字符,使用四字节编码。
编码引出的问题 在IDEA中,使用FileReader
读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8
编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
1 2 3 4 5 6 7 8 9 10 11 12 public class ReaderDemo { public static void main (String[] args) throws IOException { FileReader fileReader = new FileReader ("E:\\File_GBK.txt" ); int read; while ((read = fileReader.read()) != -1 ) { System.out.print((char )read); } fileReader.close(); } } 输出结果: ���
那么如何读取GBK编码的文件呢?
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
构造举例,代码如下:
1 2 InputStreamReader isr = new InputStreamReader (new FileInputStream ("in.txt" ));InputStreamReader isr2 = new InputStreamReader (new FileInputStream ("in.txt" ) , "GBK" );
指定编码读取 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ReaderDemo2 { public static void main (String[] args) throws IOException { String FileName = "E:\\file_gbk.txt" ; InputStreamReader isr = new InputStreamReader (new FileInputStream (FileName)); InputStreamReader isr2 = new InputStreamReader (new FileInputStream (FileName) , "GBK" ); int read; while ((read = isr.read()) != -1 ) { System.out.print((char )read); } isr.close(); while ((read = isr2.read()) != -1 ) { System.out.print((char )read); } isr2.close(); } }
OutputStreamWriter类 转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。
构造举例,代码如下:
1 2 OutputStreamWriter isr = new OutputStreamWriter (new FileOutputStream ("out.txt" ));OutputStreamWriter isr2 = new OutputStreamWriter (new FileOutputStream ("out.txt" ) , "GBK" );
指定编码写出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class OutputDemo { public static void main (String[] args) throws IOException { String FileName = "E:\\out.txt" ; OutputStreamWriter osw = new OutputStreamWriter (new FileOutputStream (FileName)); osw.write("你好" ); osw.close(); String FileName2 = "E:\\out2.txt" ; OutputStreamWriter osw2 = new OutputStreamWriter (new FileOutputStream (FileName2),"GBK" ); osw2.write("你好" ); osw2.close(); } }
转换流是字节与字符间的桥梁!
练习:转换文件编码 将GBK编码的文本文件,转换为UTF-8编码的文本文件。
案例分析
指定GBK编码的转换流,读取文本文件。
使用UTF-8编码的转换流,写出文本文件。
案例实现 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 public class TransDemo { public static void main (String[] args) { String srcFile = "file_gbk.txt" ; String destFile = "file_utf8.txt" ; InputStreamReader isr = new InputStreamReader (new FileInputStream (srcFile) , "GBK" ); OutputStreamWriter osw = new OutputStreamWriter (new FileOutputStream (destFile)); char [] cbuf = new char [1024 ]; int len; while ((len = isr.read(cbuf))!=-1 ) { osw.write(cbuf,0 ,len); } osw.close(); isr.close(); } }
序列化 概述 Java 提供了一种对象序列化 的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存 了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化 。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
ObjectOutputStream类 java.io.ObjectOutputStream
类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out)
: 创建一个指定OutputStream的ObjectOutputStream。
构造举例,代码如下:
1 2 FileOutputStream fileOut = new FileOutputStream ("employee.txt" );ObjectOutputStream out = new ObjectOutputStream (fileOut);
序列化操作
一个对象要想序列化,必须满足两个条件:
该类必须实现java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient
关键字修饰。
1 2 3 4 5 6 7 8 public class Employee implements java .io.Serializable { public String name; public String address; public transient int age; public void addressCheck () { System.out.println("Address check : " + name + " -- " + address); } }
2.写出对象方法
public final void writeObject (Object obj)
: 将指定的对象写出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class SerializeDemo { public static void main (String [] args) { Employee e = new Employee (); e.name = "zhangsan" ; e.address = "beiqinglu" ; e.age = 20 ; try { ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream ("employee.txt" )); out.writeObject(e); out.close(); fileOut.close(); System.out.println("Serialized data is saved" ); } catch (IOException i) { i.printStackTrace(); } } } 输出结果: Serialized data is saved
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定InputStream的ObjectInputStream。
反序列化操作1 如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream
读取对象的方法:
public final Object readObject ()
: 读取一个对象。
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 public class DeserializeDemo { public static void main (String [] args) { Employee e = null ; try { FileInputStream fileIn = new FileInputStream ("employee.txt" ); ObjectInputStream in = new ObjectInputStream (fileIn); e = (Employee) in.readObject(); in.close(); fileIn.close(); }catch (IOException i) { i.printStackTrace(); return ; }catch (ClassNotFoundException c) { System.out.println("Employee class not found" ); c.printStackTrace(); return ; } System.out.println("Name: " + e.name); System.out.println("Address: " + e.address); System.out.println("age: " + e.age); } }
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
反序列化操作2 另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。 发生这个异常的原因如下:
该类的序列版本号与从流中读取的类描述符的版本号不匹配
该类包含未知数据类型
该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
1 2 3 4 5 6 7 8 9 10 11 12 public class Employee implements java .io.Serializable { private static final long serialVersionUID = 1L ; public String name; public String address; public int eid; public void addressCheck () { System.out.println("Address check : " + name + " -- " + address); } }
练习:序列化集合
将存有多个自定义对象的集合序列化操作,保存到list.txt
文件中。
反序列化list.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 public class SerTest { public static void main (String[] args) throws Exception { Student student = new Student ("老王" , "laow" ); Student student2 = new Student ("老张" , "laoz" ); Student student3 = new Student ("老李" , "laol" ); ArrayList<Student> arrayList = new ArrayList <>(); arrayList.add(student); arrayList.add(student2); arrayList.add(student3); ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("list.txt" )); ArrayList<Student> list = (ArrayList<Student>)ois.readObject(); for (int i = 0 ; i < list.size(); i++ ){ Student s = list.get(i); System.out.println(s.getName()+"--" + s.getPwd()); } } private static void serializ (ArrayList<Student> arrayList) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("list.txt" )); oos.writeObject(arrayList); oos.close(); } }
打印流 概述 平时我们在控制台打印输出,是调用print
方法和println
方法完成的,这两个方法都来自于java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
PrintStream类 构造方法
public PrintStream(String fileName)
: 使用指定的文件名创建一个新的打印流。
构造举例,代码如下:
1 PrintStream ps = new PrintStream ("ps.txt" );
改变打印流向 System.out
就是PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个”小把戏”,改变它的流向。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class PrintDemo { public static void main (String[] args) throws IOException { System.out.println(97 ); PrintStream ps = new PrintStream ("ps.txt" ); System.setOut(ps); System.out.println(97 ); } }
压缩流和解压缩流 压缩流:
负责压缩文件或者文件夹
解压缩流:
负责把压缩包中的文件和文件夹解压出来
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 public class ZipStreamDemo1 { public static void main (String[] args) throws IOException { File src = new File ("D:\\aaa.zip" ); File dest = new File ("D:\\" ); unzip(src,dest); } public static void unzip (File src,File dest) throws IOException { ZipInputStream zip = new ZipInputStream (new FileInputStream (src)); ZipEntry entry; while ((entry = zip.getNextEntry()) != null ){ System.out.println(entry); if (entry.isDirectory()){ File file = new File (dest,entry.toString()); file.mkdirs(); }else { FileOutputStream fos = new FileOutputStream (new File (dest,entry.toString())); int b; while ((b = zip.read()) != -1 ){ fos.write(b); } fos.close(); zip.closeEntry(); } } zip.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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class ZipStreamDemo2 { public static void main (String[] args) throws IOException { File src = new File ("D:\\a.txt" ); File dest = new File ("D:\\" ); toZip(src,dest); } public static void toZip (File src,File dest) throws IOException { ZipOutputStream zos = new ZipOutputStream (new FileOutputStream (new File (dest,"a.zip" ))); ZipEntry entry = new ZipEntry ("aaa\\bbb\\a.txt" ); zos.putNextEntry(entry); FileInputStream fis = new FileInputStream (src); int b; while ((b = fis.read()) != -1 ){ zos.write(b); } zos.closeEntry(); zos.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 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 public class ZipStreamDemo3 { public static void main (String[] args) throws IOException { File src = new File ("D:\\aaa" ); File destParent = src.getParentFile(); File dest = new File (destParent,src.getName() + ".zip" ); ZipOutputStream zos = new ZipOutputStream (new FileOutputStream (dest)); toZip(src,zos,src.getName()); zos.close(); } public static void toZip (File src,ZipOutputStream zos,String name) throws IOException { File[] files = src.listFiles(); for (File file : files) { if (file.isFile()){ ZipEntry entry = new ZipEntry (name + "\\" + file.getName()); zos.putNextEntry(entry); FileInputStream fis = new FileInputStream (file); int b; while ((b = fis.read()) != -1 ){ zos.write(b); } fis.close(); zos.closeEntry(); }else { toZip(file,zos,name + "\\" + file.getName()); } } } }
工具包(Commons-io) 介绍:
Commons是apache开源基金组织提供的工具包,里面有很多帮助我们提高开发效率的API
比如:
StringUtils 字符串工具类
NumberUtils 数字工具类
ArrayUtils 数组工具类
RandomUtils 随机数工具类
DateUtils 日期工具类
StopWatch 秒表工具类
ClassUtils 反射工具类
SystemUtils 系统工具类
MapUtils 集合工具类
Beanutils bean工具类
Commons-io io的工具类
等等…..
其中:Commons-io是apache开源基金组织提供的一组有关IO操作的开源工具包。
作用:提高IO流的开发效率。
使用方式:
1,新建lib文件夹
2,把第三方jar包粘贴到文件夹中
3,右键点击add as a library
代码示例:
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 CommonsIODemo1 { public static void main (String[] args) throws IOException { } }
介绍:
Commons是国人开发的开源工具包,里面有很多帮助我们提高开发效率的API
比如:
DateUtil 日期时间工具类
TimeInterval 计时器工具类
StrUtil 字符串工具类
HexUtil 16进制工具类
HashUtil Hash算法类
ObjectUtil 对象工具类
ReflectUtil 反射工具类
TypeUtil 泛型类型工具类
PageUtil 分页工具类
NumberUtil 数字工具类
使用方式:
1,新建lib文件夹
2,把第三方jar包粘贴到文件夹中
3,右键点击add as a library
代码示例:
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 public class Test1 { public static void main (String[] args) { List<String> list = FileUtil.readLines("D:\\a.txt" , "UTF-8" ); System.out.println(list); } }