Java SE核心II
1.1.1 Java异常处理机制
异常结构中的父类Throwable类,其下子类Exceptionlei类和Error类。我们在程序中可以捕获的是Exception的子类异常。
Error系统级别的错误:Java运行时环境出现的错误,我们不可控。
Exception是程序级别的错误:我们可控。
1)异常处理语句:try-catch,如果try块捕获到异常,则到catch块中处理,否则跳过忽略catch块(开发中,一定有解决的办法才写,无法解决就向上抛throws)。
try{//关键字,只能有一个try语句
可能发生异常的代码片段
}catch(Exception e){//列举代码中可能出现的异常类型,可有多个catch语句
当出现了列举的异常类型后,在这里处理,并有针对性的处理
}
2)良好的编程习惯,在异常捕获机制的最后书写catch(Exception e)(父类,顶极异常)捕获未知的错误(或不需要针对处理的错误)。
3)catch的捕获是由上至下的,所以不要把父类异常写在子类异常的上面,否则子类异常永远没有机会处理!在catch块中可以使用方法获取异常信息:
①getMessage()方法:用来得到有关异常事件的信息。
②printStackTrace()方法:用来跟踪异常事件发生时执行堆栈的内容。
4)throw关键字:用于主动抛出一个异常
当我们的方法出现错误时(不一定是真实异常),这个错误我们不应该去解决,而是通知调用方法去解决时,会将这个错误告知外界,而告知外界的方式就是throw异常(抛出异常)catch语句中也可抛出异常。虽然不解决,但要捕获,然后抛出去。
使用环境:
我们常在方法中主动抛出异常,但不是什么情况下我们都应该抛出异常。原则上,自身决定不了的应该抛出。那么方法中什么时候该自己处理异常什么时候抛出?
方法通常有参数,调用者在调用我们的方法帮助解决问题时,通常会传入参数,若我们方法的逻辑是因为参数的错误而引发的异常,应该抛出,若是我们自身的原因应该自己处理。
public static void main(String[] args) {
try{/**通常我们调用方法时需要传入参数的话,那么这些方法,JVM都不会自动处理异常,而是将错误抛给我们解决*/
String result=getGirlFirend("女神"); System.out.println("追到女神了么?"+result);
}catch(Exception e){
System.out.println("没追到");//我们应该在这里捕获异常并处理。
}
}
public static String getGirlFirend(String name){
try{ if("春哥".equals(name)){ return "行";
}else if("曾哥".equals(name)){ return "行";
}else if("我女朋友".equals(name)){ return "不行";
}else{/**当出现了错误(不一定是真实异常)可以主动向外界抛出一个异常!*/
throw new RuntimeException("人家不干!");
}
}catch(NullPointerException e){
throw e;//出了错不解决,抛给调用者解决
}
}
5)throws关键字:不希望直接在某个方法中处理异常,而是希望调用者统一处理该异常。声明方法的时候,我们可以同时声明可能抛出的异常种类,通知调用者强制捕获。就是所谓的“丑话说前面”。原则上throws声明的异常,一定要在该方法中抛出。否则没有意义。相反的,若方法中我们主动通过throw抛出一个异常,应该在throws中声明该种类异常,通知外界捕获。
u 注意事项:
v 注意throw和throws关键字的区别:抛出异常和声明抛出异常。
v 不能在main方法上throws,因为调用者JVM直接关闭程序。
public static void main(String[] args) {
try{ Date today=stringToDate("2013-05-20");
} catch (ParseException e){
//catch中必须含有有效的捕获stringToDate方法throws的异常
// 输出这次错误的栈信息可以直观的查看方法调用过程和出错的根源
e.printStackTrace();
}
}
eg:将一个字符串转换为一个Date对象,抛出的异常是字符格式错误java.text.ParseException
public static Date stringToDate(String str) throws ParseException{
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-DD");
Date date=format.parse(str);
return date;
}
6)捕获异常两种方式:上例SimpleDataFormat的parse方法在声明的时候就是用了throws,强制我们调用parse方法时必须捕获ParseException,我们的做法有两种:一是添加try-catch捕获该异常,二是在我们的方法中声明出也追加这种异常的抛出(继续往外抛)。
7)java中抛出异常过程:java虚拟机在运行程序时,一但在某行代码运行时出现了错误,JVM会创建这个错误的实例,并抛出。这时JVM会检查出错代码所在的方法是否有try捕获,若有,则检查catch块是否有可以处理该异常的能力(看能否把异常实例作为参数传进去,看有没有匹配的异常类型)。若没有,则将该异常抛给该方法的调用者(向上抛)。以此类推,直到抛至main方法外仍没有解决(即抛给了JVM处理)。那么JVM会终止该程序。
8)java中的异常Exception分为:
①非检测异常(RuntimeException子类):编译时不检查异常。若方法中抛出该类异常或其子类,那么声明方法时可以不在throws中列举该类抛出的异常。常见的运行时异常有: NullPointerException、 IllegalArgumentException、
ClassCastException、NumberFormatException、
ArrayIndexOutOfBoundsException、ArithmeticException
②可检测异常(非RuntimeException子类):编译时检查,除了运行时异常之外的异常,都是可检查异常,则必须在声明方法时用throws声明出可能抛出的异常种类!
9)finally块:finally块定义在catch块的最后(所有catch最后),且只能出现一次(0-1次), 无论程序是否出错都会执行的块! 无条件执行!通常在finally语句中进行资源的消除工作,如关闭打开的文件,删除临时文件等。
public static void main(String[] args) {
System.out.println( test(null)+","+test("0")+","+test("") ); }
/**输出结果?1,0,2 ? 4,4,4为正确结果 */
public static int test(String str){
try{ return str.charAt(0)-'0';
}catch(NullPointerException e){ return 1;
}catch(RuntimeException e){ return 2;
}catch(Exception e){ return 3;
}finally{
//无条件执行
return 4;
}
}
10)重写方法时的异常处理
如果使用继承时,在父类别的某个地方上宣告了throws某些异常,而在子类别中重新定义该方法时,可以:①不处理异常(重新定义时不设定throws)。②可仅throws父类别中被重新定义的方法上的某些异常(抛出一个或几个)。③可throws被重新定义的方法上的异常之子类别(抛出异常的子类)。
但不可以:①throws出额外的异常。 ②throws被重新定义的方法上的异常之父类别(抛出了异常的父类)。
1.1.2 File文件类
java使用File类(java.io.File)表示操作系统上文件系统中的文件或目录。换句话说,我们可以使用File操作硬盘上的文件或目录进行创建或删除。
File可以描述文件或目录的名字,大小等信息,但不能对文件的内容操作!File类的构造器都是有参的。
1)关于路径的描述:不同的文件系统差异较大,Linux和Windows就不同!最好使用相对路径,不要用绝对路径。
2)“.”代表的路径:当前目录(项目所处的目录),在eclipse_workspace/project_name下,File.separator:常量,目录分隔符,推荐使用!根据系统自动识别用哪种分割符,windows中为/,Linux中为\。
3)创建该对象并不意味着硬盘上对应路径上就有该文件了,只是在内存中创建了该对象去代表路径指定的文件。当然这个路径对应的文件可能根本不存在!
File file=new File("."+File.separator+"data.dat");// 效果为./data.dat
File file=new File("e:/XX/XXX.txt");//不建议使用
4)createNewFile()中有throws声明,要求强制捕获异常!
5)新建文件或目录:
①boolean mkdir():只能在已有的目录基础上创建目录。
②boolean mkdirs():会创建所有必要的父目录(不存在的自动创建)并创建该目录。
③boolean createNewFile():创建一个空的新文件。
6)创建目录中文件的两种方式:
①直接指定data.dat需要创建的位置,并调用createNewFile(),前提是目录都要存在!
②先创建一个File实例指定data.dat即将存放的目录,若该目录不存在,则创建所有不存在的目录,再创建一个File实例,代表data.dat文件,创建是基于上一个代表目录的File实例的。使用File(File dir,String fileName)构造方法创建File实例,然后再调用createNewFile():在dir所代表的目录中表示fileName指定的文件
File dir=new File("."+File.separator+"demo"+File.separator+"A");
if(!dir.exists()){ dir.mkdirs();//不存在则创建所有必须的父目录和当亲目录 }
File file=new File(dir,"data.dat");
if(!file.exists()){file.createNewFile();System.out.println("文件创建完毕!"); }
7)查看文件或目录属性常用方法
①long length():返回文件的长度。
②long lastModified():返回文件最后一次被修改的时间。
③String getName():返回文件或目录名。 ⑧String getPath():返回路径字符串。
④boolean exists():是否存在。 ⑨boolean isFile():是否是标准文件。
⑤boolean isDirectory():是否是目录。 ⑩boolean canRead():是否可以读取。
⑥boolean canWrite():是否可以写入、修改。
⑦File[] listFiles():获取当亲目录的子项(文件或目录)
eg1:File类相关操作
File dir=new File("."); if(dir.exists()&&dir.isDirectory()){//是否为一个目录
File[] files=dir.listFiles();//获取当前目录的子项(文件或目录)
for(File file:files){//循环子项
if(file.isFile()){//若这个子项是一个文件
System.out.println("文件:"+file.getName());
}else{ System.out.println("目录:"+file.getName()); } } }
eg2:递归遍历出所有子项
File dir=new File("."); File[] files=dir.listFiles(); if(files!=null&&files.length>0){//判断子项数组有项
for(File file:files){//遍历该目录下的所有子项
if(file.isDirectory()){//若子项是目录
listDirectory(file);//不到万不得已,不要使用递归,非常消耗资源
}else{System.out.println("文件:"+file);//有路径显示,输出File的toString()
//file.getName()无路径显示,只获取文件名
}
}
}
8)删除一个文件:boolean delete():①直接写文件名作为路径和"./data.dat"代表相同文件,也可直接写目录名,但要注意第2条。②删除目录时:要确保该目录下没有任何子项后才可以将该目录删除,否则删除失败!
File dir=new File("."); File[] files=dir.listFiles();
if(files!=null&&files.length>0){ for(File file:files){ if(file.isDirectory()){
deleteDirectory(file);//递归删除子目录下的所有子项 }else{
if(!file.delete()){ throw new IOException("无法删除文件:"+file); }
System.out.println("文件:"+file+"已被删除!"); } }
9)FileFilter:文件过滤器。FileFilter是一个接口,不可实例化,可以规定过滤条件,在获取某个目录时可以通过给定的删选条件来获取满足要求的子项。accept()方法是用来定义过滤条件的参数pathname是将被过滤的目录中的每个子项一次传入进行匹配,若我们认为该子项满足条件则返回true。如下重写accept方法。
FileFilter filter=new FileFilter(){
public boolean accept(File pathname){
return pathname.getName().endsWith(".java");//保留文件名以.java结尾的
//return pathname.length()>1700;按大小过滤 } };
File dir=new File(".");//创建一个目录
File[] sub=dir.listFiles(filter);//获取过滤器中满足条件的子项,回调模式
for(File file:sub){ System.out.println(file); }
10)回调模式:我们定义一段逻辑,在调用其他方法时,将该逻辑通过参数传入。这个方法在执行过程中会调用我们传入的逻辑来达成目的。这种现象就是回调模式。最常见的应用环境:按钮监听器,过滤器的应用。
1.1.3 RandomAccessFile类
可以方便的读写文件内容,但只能一个字节一个字节(byte)的读写8位。
1)计算机的硬盘在保存数据时都是byte by byte的,字节埃着字节。
2)RandomAccessFile打开文件模式:rw:打开文件后可进行读写操作;r:打开文件后只读。
3)RandomAccessFile是基于指针进行读写操作的,指针在哪里就从哪里读写。
①void seek(long pos)方法:从文件开头到设置位置的指针偏移量,在该位置发生下一次读写操作。
②getFilePointer()方法:获取指针当前位置,而seek(0)则将指针移动到文件开始的位置。
③int skipBytes(int n)方法:尝试跳过输入的n个字节。
4)RandomAccessFile类的构造器都是有参的。
①RandomAccessFile构造方法1:
RandomAccessFile raf=new RandomAccessFile(file,"rw");
②RandomAccessFile构造方法2:
RandomAccessFile raf=new RandomAccessFile("data.dat","rw");
直接根据文件路径指定,前提是确保其存在!
5)读写操作完了,不再写了就关闭:close();
6)读写操作:
File file=new File("data.dat");//创建一个File对象用于描述该文件
if(!file.exists()){//不存在则创建该文件
file.createNewFile();//创建该文件,应捕获异常,仅为演示所以抛给main了 }
RandomAccessFile raf=new RandomAccessFile(file,"rw");//创建RandomAccessFile,并将File传入,RandomAccessFile对File表示的文件进行读写操作。
/**1位16进制代表4位2进制;2位16进制代表一个字节 8位2进制;
* 4字节代表32位2进制;write(int) 写一个字节,且是从低8位写*/
int i=0x7fffffff;//写int值最高的8位 raf.write(i>>>24);//00 00 00 7f
raf.write(i>>>16);//00 00 7f ff raf.write(i>>>8);// 00 7f ff ff
raf.write(i);// 7f ff ff ff
byte[] data=new byte[]{0,1,2,3,4,5,6,7,8,9};//定义一个10字节的数组并全部写入文件
raf.write(data);//写到这里,当前文件应该有14个字节了
/**写字节数组的重载方法:write(byte[] data.int offset,int length),从data数组的offset位置开始写,连续写length个字节到文件中 */
raf.write(data, 2, 5);// {2,3,4,5,6}
System.out.println("当前指针的位置:"+raf.getFilePointer());
raf.seek(0);//将指针移动到文件开始的位置
int num=0;//准备读取的int值
int b=raf.read();//读取第一个字节 7f 也从低8位开始
num=num | (b<<24);//01111111 00000000 00000000 00000000
b=raf.read();//读取第二个字节 ff
num=num| (b<<16);//01111111 11111111 00000000 00000000
b=raf.read();//读取第三个字节 ff
num=num| (b<<8);//01111111 11111111 11111111 00000000
b=raf.read();//读取第四个字节 ff
num=num| b;//01111111 11111111 11111111 11111111
System.out.println("int最大值:"+num); raf.close();//写完了不再写了就关了
7)常用方法:
①write(int data):写入第一个字节,且是从低8位写。
②write(byte[] data):将一组字节写入。
③write(byte[] data.int offset,int length):从data数组的offset位置开始写,连续写length个字节到文件中。
④writeInt(int):一次写4个字节,写int值。
⑤writeLong(long):一次写8个字节,写long值。
⑥writeUTF(String):以UTF-8编码将字符串连续写入文件。
write……
①int read():读一个字节,若已经读取到文件末尾,则返回-1。
②int read(byte[] buf):尝试读取buf.length个字节。并将读取的字节存入buf数组。返回值为实际读取的字节数。
③int readInt():连续读取4字节,返回该int值
④long readLong():连续读取8字节,返回该long值
⑤String readUTF():以UTF-8编码将字符串连续读出文件,返回该字符串值
read……
byte[] buf=new byte[1024];//1k容量 int sum=raf.read(buf);//尝试读取1k的数据
System.out.println("总共读取了:"+sum+"个字节");
System.out.println(Arrays.toString(buf)); raf.close();//写完了不再写了就关了
8)复制操作:读取一个文件,将这个文件中的每一个字节写到另一个文件中就完成了复制功能。
try { File srcFile=new File("chang.txt");
RandomAccessFile src=new RandomAccessFile(srcFile,"r");//创建一个用于读取文件的RandomAccessFile用于读取被拷贝的文件
File desFile=new File("chang_copy.txt"); desFile.createNewFile();//创建复制文件
RandomAccessFile des=new RandomAccessFile(desFile,"rw");//创建一个用于写入文件的RandomAccessFile用于写入拷贝的文件
//使用字节数组作为缓冲,批量读写进行复制操作比一个字节一个字节读写效率高的多!
byte[] buff=new byte[1024*100];//100k 创建一个字节数组,读取被拷贝文件的所有字节并写道拷贝文件中
int sum=0;//每次读取的字节数
while((sum=src.read(buff))>0){ des.write(buff,0,sum);//注意!读到多少写多少!}
src.close(); des.close(); System.out.println("复制完毕!");
} catch (FileNotFoundException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace(); }
//int data=0;//用于保存每一个读取的字节
//读取一个字节,只要不是-1(文件末尾),就进行复制工作
//while((data=src.read())!=-1){ des.write(data);//将读取的字符写入 }
9)基本类型序列化:将基本类型数据转换为字节数组的过程。writeInt(111):将int值111转换为字节并写入磁盘;持久化:将数据写入磁盘的过程。
1.1.4 基本流:FIS和FOS
Java I/O 输入/输出
流:根据方向分为:输入流和输出流。方向的定了是基于我们的程序的。流向我们程序的流叫做:输入流;从程序向外流的叫做:输出流
我们可以把流想象为管道,管道里流动的水,而java中的流,流动的是字节。
1)输入流是用于获取(读取)数据的,输出流是用于向外输出(写出)数据的。
InputStream:该接口定义了输入流的特征
OutputStream:该接口定义了输出流的特征
2)流根据源头分为:
基本流(节点流):从特定的地方读写的流类,如磁盘或一块内存区域。即有来源。
处理流(高级流、过滤流):没有数据来源,不能独立存在,它的存在是用于处理基本流的。是使用一个已经存在的输入流或输出流连接创建的。
3)流根据处理的数据单位不同划分为:
字节流:以一个“字节”为单位,以Stream结尾
字符流:以一个“字符”为单位,以Reader/Writer结尾
4)close()方法:流用完一定要关闭!流关闭后,不能再通过其读、写数据
5)用于读写文件的字节流FIS/FOS(基本流)
①FileInputStream:文件字节输入流。 ②FileOutputStream:文件字节输出流。
6)FileInputStream 常用<u>构造方法</u>:
①FileInputStream(File file):通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定。即向file文件中写入数据。
②FileInputStream(String filePath):通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的文件路径名指定。也可直接写当前项目下文件名。
常用<u>方法</u>:
①int read(int d):读取int值的低8位。
②int read(byte[] b):将b数组中所有字节读出,返回读取的字节个数。
③int read(byte[] b,int offset,int length):将b数组中offset位置开始读出length个字节。
④available()方法:返回当前字节输入流 可读取的总字节数。
7)FileOutputStream常用<u>构造方法</u>:
①FileOutputStream(File File):创建一个向指定File对象表示的文件中写入数据的文件输出流。会重写以前的内容,向file文件中写入数据时,<u>若该文件不存在,则会自动创建该文件。</u>
②FileOubputStream(File file,boolean append):append为true则对当前文件末尾进行写操作(追加,但不重写以前的)。
③FileOubputStream(String filePath):创建一个向具有指定名称的文件中写入数据的文件输出流。前提路径存在,写当前目录下的文件名或者全路径。
④FileOubputStream(String filePath,boolean append):append为true则对当前文件末尾进行写操作(追加,但不重写以前的)。
常用<u>方法</u>:
①void write(int d):写入int值的低8位。
②void write(byte[] d):将d数组中所有字节写入。
③void write(byte[] d,int offset,int length):将d数组中offset位置开始写入length个字节。
1.1.5 缓冲字节高级流:BIS和BOS
对传入的流进行处理加工,可以嵌套使用。
1)BufferedInputStream:缓冲字节输入流
A.构造方法:BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)
B.常用方法:
①int read():从输入流中读取一个字节。
②int read(byte[] b,int offset,int length):从此字节输入流中给定偏移量offset处开始将各字节读取到指定的 byte 数组中。
2)BufferedOutputStream:缓冲字节输出流
A.构造方法:BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size)
B.常用方法:
①void write(int d):将指定的字节写入此缓冲的输出流。
②void write(byte[] d,int offset,int length):将指定 byte数组中从偏移量 offset开始的 length个字节写入此缓冲的输出流。
③void flush():将缓冲区中的数据一次性写出,“清空”缓冲区。
C.内部维护着一个缓冲区,每次都尽可能的读取更多的字节放入到缓冲区,再将缓冲区中的内容部分或全部返回给用户,因此可以提高读写效率。
3)辨别高级流的简单方法:看构造方法,若构造方法要求传入另一个流,那么这个流就是高级流。所以高级流是没有空参数的构造器的,都需要传入一个流。
4)有缓冲效果的流,一般为写入操作的流,在数据都写完后一定要flush,flush的作用是将缓冲区中未写出的数据一次性写出:bos.flush();即不论缓存区有多少数据,先写过去,缓冲区再下班~确保所有字符都写出
5)使用JDK的话,通常情况下,我们只需要关闭最外层的流。第三方流可能需要一层一层关。
tobehero666.png