码农的世界

Java SE核心II

2018-11-23  本文已影响0人  海若Hero

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
上一篇下一篇

猜你喜欢

热点阅读