zoukankan      html  css  js  c++  java
  • 五、Java SE核心II

    五、Java SE核心II

    5.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)(父类,顶极异常)捕获未知的错误(或不需要针对处理的错误)。

    3catch的捕获是由上至下的,所以不要把父类异常写在子类异常的上面,否则子类异常永远没有机会处理!在catch块中可以使用方法获取异常信息:

    ①getMessage()方法:用来得到有关异常事件的信息。

    ②printStackTrace()方法:用来跟踪异常事件发生时执行堆栈的内容。

    4throw关键字:用于主动抛出一个异常

    当我们的方法出现错误时(不一定是真实异常),这个错误我们不应该去解决,

    而是通知调用方法去解决时,会将这个错误告知外界,而告知外界的方式就是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;//出了错不解决,抛给调用者解决

      }

    }

    5throws关键字:不希望直接在某个方法中处理异常,而是希望调用者统一处理该异常。

    声明方法的时候,我们可以同时声明可能抛出的异常种类,通知调用者强制捕获。就是所谓的“丑话说前面”。

    原则上throws声明的异常,一定要在该方法中抛出。否则没有意义。

    相反的,若方法中我们主动通过throw抛出一个异常,应该在throws中声明该种类异常,通知外界捕获。

     注意事项:

    ①注意throwthrows关键字的区别:抛出异常和声明抛出异常。

    ②不能在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)捕获异常两种方式:上例SimpleDataFormatparse方法在声明的时候就是用了throws,强制我们调用parse方法时必须捕获ParseException

      我们的做法有两种: 一是添加try-catch捕获该异常,

                二是在我们的方法中声明出也追加这种异常的抛出(继续往外抛)。

    7java中抛出异常过程:

      java虚拟机在运行程序时,一但在某行代码运行时出现了错误,JVM会创建这个错误的实例,并抛出。

      这时JVM会检查出错代码所在的方法是否有try捕获,若有,则检查catch块是否有可以处理该异常的能力

      (看能否把异常实例作为参数传进去,看有没有匹配的异常类型)。

      若没有,则将该异常抛给该方法的调用者(向上抛)。

      以此类推,直到抛至main方法外仍没有解决(即抛给了JVM处理)。那么JVM会终止该程序。

    8java中的异常Exception分为:

    ①非检测异常(RuntimeException子类):编译时不检查异常。若方法中抛出该类异常或其子类,那么声明方法时可以不在throws中列举该类抛出的异常。

    常见的运行时异常有:

    NullPointerException

    IllegalArgumentException

    ClassCastException

    NumberFormatException

    ArrayIndexOutOfBoundsException

    ArithmeticException

    ②可检测异常(非RuntimeException子类):

    编译时检查,除了运行时异常之外的异常,都是可检查异常,则必须在声明方法时用throws声明出可能抛出的异常种类!

    9finally块:

      finally块定义在catch块的最后(所有catch最后),且只能出现一次(01次), 无论程序是否出错都会执行的块! 无条件执行!

      通常在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被重新定义的方法上的异常之父类别(抛出了异常的父类)。

    5.2 File文件类

    java使用File类(java.io.File)表示操作系统上文件系统中的文件或目录。

    换句话说,我们可以使用File操作硬盘上的文件或目录进行创建或删除。

    File可以描述文件或目录的名字,大小等信息,但不能对文件的内容操作!File类的构造器都是有参的。

    1)关于路径的描述:不同的文件系统差异较大,LinuxWindows就不同!最好使用相对路径,不要用绝对路径。

    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");不建议使用

    4createNewFile()中有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():返回文件或目录名。

    boolean exists():是否存在。        

    boolean isDirectory():是否是目录。 

    boolean canWrite():是否可以写入、修改。 

    ⑦File[] listFiles():获取当亲目录的子项(文件或目录)

    String getPath():返回路径字符串。

    boolean isFile():是否是标准文件。

    boolean canRead():是否可以读取。

    eg1File类相关操作

      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);//有路径显示,输出FiletoString()

            //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+"已被删除!");

    }    

     

    9FileFilter:文件过滤器。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)回调模式:我们定义一段逻辑,在调用其他方法时,将该逻辑通过参数传入。

      这个方法在执行过程中会调用我们传入的逻辑来达成目的。这种现象就是回调模式。

      最常见的应用环境:按钮监听器,过滤器的应用。

    5.3 RandomAccessFile

    可以方便的读写文件内容,但只能一个字节一个字节(byte)的读写8位。

    1)计算机的硬盘在保存数据时都是byte by byte的,字节埃着字节。

    2RandomAccessFile打开文件模式:rw:打开文件后可进行读写操作;r:打开文件后只读。

    3RandomAccessFile是基于指针进行读写操作的,指针在哪里就从哪里读写。

    void seek(long pos)方法:从文件开头到设置位置的指针偏移量,在该位置发生下一次读写操作。

    getFilePointer()方法:获取指针当前位置,而seek(0)则将指针移动到文件开始的位置。

    int skipBytes(int n)方法:尝试跳过输入的n个字节。

    4RandomAccessFile类的构造器都是有参的。

    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传入,RandomAccessFileFile表示的文件进行读写操作。

    /**116进制代表42进制;216进制代表一个字节 82进制;

     * 4字节代表322进制;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); // {23456}

    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):int111转换为字节并写入磁盘;持久化:将数据写入磁盘的过程。

    5.4基本流:FISFOS

    Java I/O 输入/输出   

    流:根据方向分为:输入流和输出流。方向的定了是基于我们的程序的。

    流向我们程序的流叫做:输入流;从程序向外流的叫做:输出流

    我们可以把流想象为管道,管道里流动的水,而java中的流,流动的是字节。

    1)输入流是用于获取(读取)数据的,输出流是用于向外输出(写出)数据的。

    InputStream:该接口定义了输入流的特征

    OutputStream:该接口定义了输出流的特征

    2)流根据源头分为:

    基本流(节点流):从特定的地方读写的流类,如磁盘或一块内存区域。即有来源。

    处理流(高级流、过滤流):没有数据来源,不能独立存在,它的存在是用于处理基本流的。是使用一个已经存在的输入流或输出流连接创建的。

    3)流根据处理的数据单位不同划分为:

    字节流:以一个“字节”为单位,以Stream结尾

    字符流:以一个“字符”为单位,以Reader/Writer结尾

    4close()方法:流用完一定要关闭!流关闭后,不能再通过其读、写数据

    5)用于读写文件的字节流FIS/FOS(基本流)

    ①FileInputStream:文件字节输入流。  

    ②FileOutputStream:文件字节输出流。

    6FileInputStream 常用构造方法:

    FileInputStream(File file):通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定。

                 即向file文件中写入数据。

    FileInputStream(String filePath):通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的文件路径名指定。

                   也可直接写当前项目下文件名。

    常用方法:

    int read(int d):读取int值的低8位。

    int read(byte[] b):b数组中所有字节读出,返回读取的字节个数。

    int read(byte[] b,int offset,int length):b数组中offset位置开始读出length个字节。

    available()方法:返回当前字节输入流 可读取的总字节数。

    7FileOutputStream常用构造方法:

    FileOutputStream(File File):创建一个向指定File对象表示的文件中写入数据的文件输出流。

                 会重写以前的内容,向file文件中写入数据时,若该文件不存在,则会自动创建该文件。

    FileOubputStream(File file,boolean append)appendtrue则对当前文件末尾进行写操作(追加,但不重写以前的)。

    FileOubputStream(String filePath):创建一个向具有指定名称的文件中写入数据的文件输出流。

                        前提路径存在,写当前目录下的文件名或者全路径。

    FileOubputStream(String filePath,boolean append)appendtrue则对当前文件末尾进行写操作(追加,但不重写以前的)。

    常用方法:

    void write(int d):写入int值的低8位。

    void write(byte[] d):d数组中所有字节写入。

    void write(byte[] d,int offset,int length):d数组中offset位置开始写入length个字节。

    5.5缓冲字节高级流:BISBOS

    对传入的流进行处理加工,可以嵌套使用。

    1BufferedInputStream:缓冲字节输入流 

    A.构造方法:BufferedInputStream(InputStream in)

        BufferedInputStream(InputStream in, int size)

    B.常用方法:

    int read():从输入流中读取一个字节。

    int read(byte[] b,int offset,int length):从此字节输入流中给定偏移量offset处开始将各字节读取到指定的 byte 数组中。 

    2BufferedOutputStream:缓冲字节输出流

    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的话,通常情况下,我们只需要关闭最外层的流。第三方流可能需要一层一层关。

    5.6基本数据类型高级流:DISDOS

    是对“流”功能的扩展,简化了对基本类型数据的读写操作。

    1DataInputStream(InputStream in):可以直接读取基本数据类型的流

    常用方法:

    int readInt():连续读取4个字节(一个int值),返回该int

    double readDouble():连续读取8个字节(一个double值),返回double

    String readUTF():连续读取字符串

      ……

    2DataOutputStream(OutputStream out):可以直接写基本数据类型的流

    常用方法:

    void writeInt(int i):连续写入4个字节(一个int值)

    void writeLong(long l):连续写入8个字节(一个long值)

    void writeUTF(String s):连续写入字符串

    void flush():将缓冲区中的数据一次性写出,“清空”缓冲区。

      ……

    5.7字符高级流:ISROSW

    以“单个”“字符”为单位读写数据,一次处理一个字符(unicode)

    字符流底层还是基于字节形式读写的。

    在字符输入输出流阶段,进行编码修改与设置。

    所有字符流都是高级流。

    1) OutputStreamWriter:字符输出流。

    A.常用构造方法:

    OutputStreamWriter(OutputStream out):创建一个字符集的输出流。

    OutputStreamWriter(OutputStream out, String charsetName):创建一个使用指定字符集的输出流。

    B.常用方法:

    void write(int c):写入单个字符。

    void write(char c[], int off, int len):写入从字符数组off开头到len长度的部分

    void write(String str, int off, int len):写入从字符串off开头到len长度的部分。

    void flush():将缓冲区中的数据一次性写出,“清空”缓冲区。

    void close():关闭流。

    eg:向文件中写入字符:

      ①创建文件输出流(字节流)。

      ②创建字符输出流(高级流),处理文件输出流,目的是我们可以以字节为单位写数据。

      ③写入字符。

      ④写完后关闭流。

    OutputStreamWriter writer=null;//不写try-catch外的话finally找不到流,就无法关闭

    try{ FileOutputStream fos=new FileOutputStream("writer.txt");

      // writer=new OutputStreamWriter(fos);//默认构造方法使用系统默认的编码集

      writer=new OutputStreamWriter(fos,"UTF-8");//最好指定字符集输出

      writer.write("你好!"); writer.flush();//将缓冲区数据一次性写出

    }catch(IOException e){

      throw e;

    }finally{

      if(writer!=null){

         writer.close();

      }

    }

    2InputStreamReader:字符输入流。

    A.常用构造方法:

    InputStreamReader(InputStream in):创建一个字符集的输入流。

    InputStreamReader(InputStream in, String charsetName):创建一个使用指定字符集的输入流。

    B.常用方法:

    int read():读取单个字符。

    int read(char cbuf[], int offset, int length):读入字符数组中从offset开始的length长度的字符。

    void close():关闭流。

    eg:读取文件中的字符

    InputStreamReader reader=null;

      try{

        FileInputStream fis=new FileInputStream("writer.txt"); //创建用于读取文件的字节出入流

        reader=new InputStreamReader(fis,"UTF-8");            //创建用于以字符为单位读取数据的高级流

        int c=-1;//读取数据

        while((c=reader.read())!=-1){ //InputStreamReader只能一个字符一个字符的读

          System.out.println((char)c);

        }

    }catch(IOException e){

      throw e;

    } finally{

      if(reader!=null){

        reader.close();

      }

    }

    5.8缓冲字符高级流:BRBW

    可以以“行”为单位读写“字符”,高级流。

    在字符输入输出流修改编码。

    1BufferedWriter:缓冲字符输出流,以行为单位写字符

    A.常用构造方法:

    BufferedWriter(Writer out):创建一个使用默认大小的缓冲字符输出流。 

    BufferedWriter(Writer out,int size):创建一个使用给定大小的缓冲字符输出流。 

    B.常用方法:

    void write(int c):写入单个字符。 

    void write(char[] c,int off,int len):写入字符数组从off开始的len长度的字符。

    void write(String s,int off,int len):写入字符串中从off开始的len长度的字符。 

    void newLine():写入一个行分隔符。

    flush():将缓冲区中的数据一次性写出,“清空”缓冲区。

    close():关闭流。

    注意事项:BufferedWriter的构造方法中不支持给定一个字节输出流,只能给定一个字符输出流Writer的子类,Writer是字符输出流的父类。

    //创建用于写文件的输出流

    FileOutputStream fos=new FileOutputStream("buffered.txt");

    //创建一个字符输出流,在字符输入输出流修改编码

    OutputStreamWriter osw=new OutputStreamWriter(fos,"UTF-8");

    BufferedWriter writer=new BufferedWriter(osw);

    writer.write("你好啊!!");

    writer.newLine();//输出一个换行

    writer.write("我是第二行!!");

    writer.newLine();//输出一个换行

    writer.write("我是第三行!!");

    writer.close();//输出流关闭后,不能再通过其写数据

    2BufferedReader:缓冲字符输入流,以行为单位读字符

    A.常用构造方法:

    BufferedReader(Reader in):创建一个使用默认大小的缓冲字符输入流。 

    BufferedReader(Reader in,int size):创建一个使用指定大小的缓冲字符输入流。 

    B.常用方法:

    int read():读取单个字符。如果已到达流末尾,则返回-1

    int read(char cbuf[], int off, int len):从字符数组中读取从off开始的len长度的字符。

                    返回读取的字符数,如果已到达流末尾,则返回-1

    String readLine():读取一个文本行。通过下列字符之一即可认为某行已终止:换行 (' ')、回车 (' ') 或回车后直接跟着换行。

              如果已到达流末尾,则返回 nullEOFend of file文件末尾。

    void close():关闭流。

    eg:读取指定文件中的数据,并显示在控制台

    FileInputStream fis=new FileInputStream("src"+File.separator+"day08"+File.separator+"DemoBufferedReader.java");

    InputStreamReader isr=new InputStreamReader(fis);

    BufferedReader reader=new BufferedReader(isr);  

    String str=null;

    if((str=reader.readLine())!=null){//readLine()读取一行字符并以字符串形式返回

      System.out.println(str);

    }

    reader.close();

    eg:读取控制台输入的每以行信息,直到在控制台输入exit退出程序

      //1 将键盘的字节输入流转换为字符输入流

      InputStreamReader isr=new InputStreamReader(System.in);

      //2 将字符输入流转换为缓冲字符输入流,按行读取信息

      BufferedReader reader=new BufferedReader(isr);

      // 循环获取用户输入的信息并输出到控制台

      String info=null; while(true){

         info=reader.readLine();                                        

        if("exit".equals(info.trim())){

          break;

        }

        System.out.println(info);//输出到控制台       

      }

      reader.close();

    5.9文件字符高级流:FRFW

    用于读写“文本文件”的“字符”输入流和输出流。

    1FileWriter写入:继承OutputStreamWriter

    A.常用构造方法

    FileWriter(File file) 、FileWriter(File file, boolean append)

    FileWriter(String filePath)FileWriter(String fileName, boolean append)

    意思和FileOutputStream的四个同类型参数的构造方法一致。

    u 注意事项:FileWriter的效果等同于:FileOutputStream + OutputStreamWriter

    B.常用方法:

    void write(int c):写入单个字符。

    void write(char c[], int off, int len):写入字符数组从offlen长度的部分

    void write(String str, int off, int len):写入字符串从offlen长度的部分。

    void flush():将缓冲区中的数据一次性写出,“清空”缓冲区。

    void close():关闭流。

    FileWriter writer=new FileWriter("filewriter.txt");

    //File file=new File("filewriter.txt");

    //FileWriter writer=new FileWriter(file);

    writer.write("hello!FileWriter!"); writer.close();

    2 FileReader读取:继承InputStreamReader 

       A.“只能”以“字符”为单位读取文件,所以效率低

    B.常用构造方法

    FileReader(File file)FileReader(String filePath)

    意思和FileInputStream的两个同类型参数的构造方法一致。

    C.常用方法:

    int read():读取单个字符。

    int read(char cbuf[], int offset, int length):读入字符数组中从offset开始的length长度的字符。

    void close():关闭流。

    FileReader reader=new FileReader("filewriter.txt");

    //int c=-1; //只能以字符为单位读取文件

    //while((c=reader.read())!=-1){  

      System.out.println((char)c);

    }

    //将文件字符输入流转换为缓冲字符输入流便可以行为单位读取

    BufferedReader br=new BufferedReader(reader);

    String info=null; while((info=br.readLine())!=null){   

      System.out.println(info);

    }

    br.close();

    5.10 PrintWriter

    另一种缓冲“字符”输出流,以“行”为单位,常用它作输出,BufferedWriter用的少。

    1Servlet:运行在服务器端的小程序,给客户端发送相应使用的输出流就是PrintWriter

    2)写方法:println(String data):带换行符输出一个字符串,不用手动换行了。

       println……

    3)构造方式:

    PrintWriter(File file):以行为单位向文件写数据

    PrintWriter(OutputStream out):以行为单位向字节输出流写数据

    PrintWriter(Writer writer):以行为单位向字符输出流写数据

    PrintWriter(String fileName):以行为单位向指定路径的文件写数据

    PrintWriter writer=new PrintWriter("printwriter.txt"); //向文件写入一个字符串

    writer.println("你好!PrintWriter");//自动加换行符

    /**我们要在确定做写操作的时候调用flush()方法,否则数据可能还在输出流的缓冲区中,没有作真实的写操作!*/

    writer.flush(); writer.close();

    eg:将输出流写入文件

    System.out.println("你好!!");

    PrintStream out=System.out;

    PrintStream fileOut=new PrintStream(  new FileOutputStream("SystemOut.txt")  );

    System.setOut(fileOut);//将我们给定的输出流赋值到System.out

    System.out.println("你好!我是输出到控制台的!");

    System.setOut(out);

    System.out.println("我是输出到控制台的!");     

    fileOut.close();

    5.11对象序列化

    将一个对象转换为字节形式的过程就是对象序列化。

    序列化还有个名称为串行化,序列化后的对象再被反序列化后得到的对象,与之前的对象不再是同一个对象。

    1)对象序列化必须实现Serializable接口,但该接口无任何抽象方法,不需要重写方法,只为了标注该类可序列化。

    2)且同时建议最好添加版本号(编号随便写):serialVersionUID。版本号,用于匹配当前类与其被反序列化的对象是否处于同样的特征(属性列表一致等)。

      反序列化时,ObjectInputStream会根据被反序列化对象的版本与当前版本进行匹配,来决定是否反序列化。 

      不加版本号可以,但是可能存在反序列化失败的风险。

    3JDK提供的大多数java bean都实现了该接口

    4transient关键字:序列化时忽略被它修饰的属性。

    5)对象的序列化使用的类:ObjectOutputStream

    writeObject(Object obj):①将给定对象序列化。②然后写出。

    6)对象的反序列化使用的类:ObjectInputStream

    Object readObject():将读取的字节序列还原为对象

    7)对于HTTP协议:通信一次后,必须断开连接,想再次通信要再次连接。

    8想要实现断点续传,我们必须告诉服务器我们当前读取文件的开始位置。

      相当于我们本地调用的seek(),因为我们不可能直接调用服务器的对象的方法,所以我们只能通过某种方式告诉服务器我们要干什么。

      让它自行调用自己流对象的seek()到我们想读取的位置。bytes=0- 的意思是告诉服务器从第一个字节开始读,即seek(0)从头到尾

      bytes=128- 的意思是告诉服务器从地129个字节开始读,即seek(128)

      String prop="bytes="+info.getPos()+"-";

    eg:序列化和反序列化

      try{

        DownloadInfo info=new DownloadInfo("http://www.baidu.com/download/xxx.zip", "xxx.zip"  );

        info.setPos(12587);

        info.setFileSize(5566987);

        File file=new File("obj.tmp");//将对象序列化以后写到文件中

        FileOutputStream fos=new FileOutputStream(file);

        //通过oos可以将对象序列化后写入obj.tmp文件中

        ObjectOutputStream oos=new ObjectOutputStream(fos);

        oos.writeObject(info);//info序列化后写出

        oos.close();

        //反序列化操作

        FileInputStream fis=new FileInputStream(file);

        ObjectInputStream ois=new ObjectInputStream(fis);

        DownloadInfo obj=(DownloadInfo)ois.readObject();//反序列化

        System.out.println(obj.getUrl());

        System.out.println(obj.getFileName());

        System.out.println(obj.getFileSize());

        System.out.println(obj.getPos());

        System.out.println(info==obj);

        ois.close();

      }catch(Exception e){  

        e.printStackTrace();  

        System.out.println("非常sorry");

      }

    5.12 Thread线程类及多线程

    进程:一个操作系统中可以同时运行多个任务(程序),每个运行的任务(程序)被称为一个进程。

         即系统级别上的多线程(多个任务)。

    线程:一个程序同时可能运行多个任务(顺序执行流),那么每个任务(顺序执行流)就叫做一个线程。

            即在进程内部。

    并发:线程是并发运行的。操作系统将时间化分为若干个片段(时间片),尽可能的均匀分配给每一个任务,被分配时间片后,任务就有机会被cpu所执行。

       微观上看,每个任务都是走走停停的。但随着cpu高效的运行,宏观上看所有任务都在运行。

       这种都运行的现象称之为并发,但不是绝对意义上的“同时发生”。

    1Thread类的实例代表一个并发任务。任何线程对象都是Thread类的(子类)实例。

       Thread类是线程的模版,它封装了复杂的线程开启等操作,封装了操作系统的差异性。

       因此并发的任务逻辑实现只要重写Threadrun方法即可。

    2)线程调度:线程调度机制会将所有并发任务做统一的调度工作,划分时间片(可以被cup执行的时间)给每一个任务,时间片尽可能的均匀,但做不到绝对均匀。

       同样,被分配时间片后,该任务被cpu执行,但调度的过程中不能保证所有任务都是平均的获取时间片的次数。

       只能做到尽可能平均。这两个都是程序不可控的。

    3)线程的启动和停止:void start():想并发操作不要直接调用run方法!而是调用线程的start()方法启动线程!

          void stop():不要使用stop()方法来停止线程的运行,这是不安全的操作,想让线程停止,应该通过run方法的执行完毕来进行自然的结束。

    4线程的创建方式一:

      1:继承自Thread

      2:重写run方法:run方法中应该定义我们需要并发执行的任务逻辑代码。

    5线程的创建方式二:

      将线程与执行的逻辑分离开,即实现Runnalbe接口。因为有了这样的设计,才有了线程池。关注点在于要执行的逻辑。

    6Runnable接口:用于定义线程要执行的任务逻辑。我们定一个类实现Runnable接口,这时我们必须重写run方法,在其中定义我们要执行的逻辑。

             之后将Runnable交给线程去执行。从而实现了线程与其执行的任务分离开。

            将任务分别交给不同的线程并发处理,可以使用线程的重载构造方法:Thread(Runnable runnable)

            解藕:线程与线程体解藕,即打断依赖关系。Springioc就是干这个的。

    /**创建两个需要并发的任务,MyFirstRunnableMySecRunnable都继承了Runnable接口并重写了run()方法 */

    Runnable r1=new MyFirstRunnable();

    Runnable r2=new MySecRunnable();

    Thread t1=new Thread(r1);

    Thread t2=new Thread(r2);

    t1.start();

    t2.start();

    7线程的创建方式三:使用匿名内部类方式创建线程

     

     /** * 匿名类实现继承Thread形式*/

    new Thread(){

      public void run(){

        

      }

    }.start(); 

     

    eg:

    Thread t1=new Thread(){

      public void run(){

        for(int i=0;i<1000;i++){

          System.out.println(i);

        }

      }

    };

    t1.start();

     

    /**匿名类实现Runnable接口的形式 */

    new Thread(

      new Runnable(){

        public void run(){

          

        }

      }

    ).start();

     

    eg:

     

    Thread t2=new Thread(new Runnable(){

      public void run(){

        for(int i=0;i<1000;i++){

          System.out.println("你好"+i+"");

        }

      }

    } );

    t2.start();

     

    8)线程生命周期:

     

     

    9)线程睡眠阻塞:使当前线程放弃cpu时间,进入阻塞状态。在阻塞状态的线程不会分配时间片。

      直到该线程结束阻塞状态回到Runnable状态,方可再次获得时间片来让cpu运行(进入Running状态)。

    ①static void sleep(times)方法:让当前线程主动进入Block阻塞状态,并在time毫秒后回到Runnalbe状态。

     注意事项:使用Thread.sleep()方法阻塞线程时,强制让我们必须捕获“中断异常”。 

     引发情况:当前线程处于Sleep阻塞期间,被另一个线程中断阻塞状态时,当前线程会抛出该异常。

    int i=0;

    while(true){

      System.out.println(i+"");

      i++;

      try {

        Thread.sleep(1000);

      } catch (InterruptedException e) {

        e.printStackTrace();

      }

    }

     

    10void interrupt()方法:打断/唤醒线程。一个线程可以提前唤醒另外一个sleep Block的线程。

     注意事项:方法中定义的类叫局部内部类:局部内部类中,若想引用当前方法的其他局部变量,那么该变量必须是final的。

    final Thread lin=new Thread(){

      public void run(){

        System.out.println("林:睡觉了……");

        try {  

          Thread.sleep(1000000);  

        } catch (InterruptedException e) {

          System.out.println("林:干嘛呢!干嘛呢!干嘛呢!");

          System.out.println("林:都破了相了!");

        }

      }

    };

    lin.start();//启动第一个线程

     

    Thread huang=new Thread(){

      public void run(){

        System.out.println("80一锤子,您说咂哪儿?");

        for(int i=0;i<5;i++){

          System.out.println("80!");

          try {  

            Thread.sleep(1000);

          } catch (InterruptedException e) {

            e.printStackTrace();

          }

        }

        System.out.println("咣当!");

        System.out.println("黄:搞定!");

        lin.interrupt();//中断第一个线程的阻塞状态

      }

    };

    huang.start();//启动第二个线程

     

    11)线程的其他方法:

    ①static void yield():当前线程让出处理器(离开Running状态)即放弃当前时间片,主动进入Runnable状态等待。

      ②final void setPriority(int):设置线程优先级;优先级越高的线程,理论上获取cpu的次数就越多。

                   但理想与现实是有差距的……设置线程优先级一定要在线程启动前设置!

    ③final void join():等待该线程终止。

    Thread t1=new Thread(){

      public void run(){

        for(int i=0;i<100;i++){

          System.out.println("我是谁啊?");

          Thread.yield();

        }

      }

    };

    Thread t2=new Thread(){

      public void run(){

        for(int i=0;i<100;i++){

          System.out.println("我是修水管的");

          Thread.yield();

        }

      }

    };

    Thread t3=new Thread(){

      public void run(){

        for(int i=0;i<100;i++){

          System.out.println("我是打酱油的");

          Thread.yield();    

        }

      }

    };

    t1.setPriority(Thread.MAX_PRIORITY);   

    t2.setPriority(Thread.MIN_PRIORITY);

    t1.start(); 

    t2.start();

    t3.start();

    12)线程并发安全问题:synchronized关键字,线程安全锁、同步监视器。

    多线程在访问同一个数据时(写操作),可能会引发不安全操作。

    ①哪个线程报错不捕获,则线程死,不影响主程序。

    ②同步:同一时刻只能有一个执行,AB配合工作,步调一致的处理(B得到A的执行结果才能继续)。如一群人上公交车。

      异步:同一时刻能有多个执行,并发,各自干各自的。如一群人上卡车。

    synchronized可以修饰方法也可以单独作为语句块存在(同步块)。作用是限制多线程并发时同时访问该作用域。

    synchronized修饰方法后,会为方法上锁。方法就不是异步的了,而是同步的。锁的是当前对象。

    synchronized同步块:分析出只有一段代码需要上锁,则使用。效率比直接修饰方法要高。

    ⑥线程安全的效率低,如VectorHashtable。线程不安全的效率高,如ArrayListHashMap

    synchronized void getMoney(int money){

      if(count==0){

        throw new RuntimeException("余额为0");

      }

      Thread.yield();

      count-=money;

    }

    void getMoney(int money){

      synchronized(this){ //synchronized(Object){需要同步的代码片段}

        if(count==0){ 

          throw new RuntimeException("余额为0");

        }

      Thread.yield();

      count-=money;   

    }

    13Daemon后台线程也称为守护线程:当当前进程中“所有”“前台”线程死亡后,后台线程将被强制死亡(非自然死亡),无论是否还在运行。

    ①守护线程,必须在启动线程前调用。

    main方法也是靠线程运行的,且是一个前台线程。

    ③正在运行的线程都是守护线程时,JVM退出。

    14wait/notify方法

    这两个方法不是在线程Thread中定义的方法,这两个方法定义在Object中。两个方法的作用是用于协调线程工作的。

    ①等待机制与锁机制密切关联:wait/notify方法必须与synchronized同时使用,谁调用waitotify方法,就锁谁!

    wait()方法:当条将不满足时,则等待。当条件满足时,等待该条件的线程将被唤醒。

      如:浏览器显示一个图片,displayThread要想显示图片,则必须等代下载线程downloadThread将该图片下载完毕。

        如果图片没有下杂完成,则dialpayThread可以暂停。当downloadThread下载完成后,再通知displayThread可以显示了,此时displayThread继续执行。

    notify()方法:随机通知、唤醒一个在当前对象身上等待的线程。

    ④notifyAll方法:通知、唤醒所有在当前对象身上等待的线程。

     

     

    5.13 Socket网络编程

    Socket套接字。在java.net.Socket包下。

    1)网络通信模型:

    C/Sclient/server,客户端/服务器端;

    B/Sbrowser/server,浏览器端/服务器端;

    C/S结构的优点:应用的针对性强,画面绚丽,应用功能复杂。缺点:不易维护。

    B/S结构的优点:易于维护。缺点:效果差,交互性不强。

    2Socket:封装着本地的地址,服务端口等信息。ServerSocket:服务端的套接字。

    服务器:使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,

        所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。

    客户端:使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket

        客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

    3)永远都是Socket去主动连接ServerSocket。一个ServerSocket可以接收若干个Socket的连接。网络通信的前提:一定要捕获异常。

    4Socket连接基于TCP/IP协议,是一种长连接(长时间连着)。

    5)读取服务器信息会阻塞,写操作不会。

    6)建立连接并向服务器发送信息步骤:

      ①通过服务器的地址及端口与服务器连接,而创建Socket时需要以上两个数据

      ②连接成功后可以通过Socket获取输入流和输出流,使用输入流接收服务端发送过来的信息。

      ③关闭连接。

    7)连接服务器:一旦Socket被实例化,那么它就开始通过给定的地址和端口号去尝试与服务器进行连接(自动的)。

            这里的地址"localhost"是服务器的地址,8088端口是服务器对外的端口。我们自身的端口是系统分配的,我们无需知道。

    8)和服务器通信(读写数据):使用Socket中的getInputStream()获取输入流,使用getOutputStream()获取输出流。

    9)ServerSocket构造方法要求我们传入打开的端口号,ServerSocket对象在创建的时候就向操作系统申请打开这个端口

    10通过调用ServerSocketaccept方法,使服务器端开始等待接收客户端的连接。

      该方法是一个阻塞方法,监听指定的端口是否有客户端连接。直到有客户端与其连接并接收客户端套接字,否则该方法不会结束。

    eg1.1:客户端ClientDemo类

    private Socket socket;

    public void send(){

      try{

        System.out.println("开始连接服务器");

        socket=new Socket("localhost",8088);

        InputStream in=socket.getInputStream();//获取输入流

        OutputStream out=socket.getOutputStream();//获取输出流

        /**将输出流变成处理字符的缓冲字符输出流*/

        PrintWriter writer=new PrintWriter(out);

        writer.println("你好!服务器!");

        /**注意,写到输出流的缓冲区里了,并没有真的发给服务器。想真的发送就要作真实的写操作,清空缓冲区*/

        writer.flush();

        /**将输入流转换为缓冲字符输入流*/

        BufferedReader reader=new BufferedReader(new InputStreamReader(in));

        /**读取服务器发送过来的信息*/

        String info=reader.readLine();//读取服务器信息会阻塞

        System.out.println(info);

        writer.println("再见!服务器!");

        writer.flush();

        info=reader.readLine();

        System.out.println(info);

      }catch(Exception e){

        e.printStackTrace();

      }

    }

    public static void main(String[] args){

      ClientDemo demo=new ClientDemo();

      demo.send();//连接服务器并通信

    }

     

     

    eg1.2:服务器端ServerDemo类(不使用线程)

    private ServerSocket socket=null;

    private int port=8088;

    /**构建ServerDemo对象时就打开服务端口*/

    public ServerDemo(){

      try{

        socket=new ServerSocket(port); 

      }catch(Exception e){

        e.printStackTrace();

      }

    }

    /**开始服务,等待收受客户端的请求并与其通信*/

    public void start(){

      try{

        System.out.println("等待客户端连接……");

        Socket s=socket.accept();

        //获取与客户端通信的输入输出流

        InputStream in=s.getInputStream();

        OutputStream out=s.getOutputStream();

        //包装为缓冲字符流

        PrintWriter writer=new PrintWriter(out);

        BufferedReader reader=new BufferedReader(new InputStreamReader(in));

        //先听客户端发送的信息

        String info=reader.readLine();//这里同样会阻塞

        System.out.println(info);

        //发送信息给客户端

        writer.println("你好!客户端");

        writer.flush();

        info=reader.readLine();

        System.out.println(info);

        writer.println("再见!客户端");

        writer.flush();

        socket.close();//关闭与客户端的连接

      }catch(Exception e){

        e.printStackTrace();

      }

    }

     

    public static void main(String[] args){

      System.out.println("服务器启动中……");

      ServerDemo demo=new ServerDemo();

      demo.start();

    }

     

     

    eg2:服务器端ServerDemo类(使用线程),start()方法的修改以及Handler

    public void start(){

      try{

        while(true){ 

          System.out.println("等待客户端连接……"); 

          Socket s=socket.accept();

          /** 当一个客户端连接了,就启动一个线程去接待它 */

          Thread clientThread=new Thread(new Handler(s));

          clientThread.start();    

        }

      }catch(Exception e){

        e.printStackTrace();

      } 

    }

    /** 定义线程体,该线程的作用是与连接到服务器端的客户端进行交互操作 */

    class Handler implements Runnable{

      private Socket socket;//当前线程要进行通信的客户端Socket

      public Handler(Socket socket){//通过构造方法将客户端的Socket传入

        this.socket=socket;

      }

      public void run(){

        try{ //获取与客户端通信的输入输出流

            InputStream in=socket.getInputStream();

            OutputStream out=socket.getOutputStream();

            PrintWriter writer=new PrintWriter(out);//包装为缓冲字符流

            BufferedReader reader=new BufferedReader(new InputStreamReader(in));

                String info=reader.readLine();//先听客户端发送的信息,这里同样会阻塞

            System.out.println(info);

                  //发送信息给客户端

            writer.println("你好!客户端");    

            writer.flush();

            info=reader.readLine();

            System.out.println(info);

            writer.println("再见!客户端");   

            writer.flush();

            socket.close();//关闭与客户端的连接

        }catch(Exception e){

          e.printStackTrace();

        }

      }

    }

     

    public static void main(String[] args){

      System.out.println("服务器启动中……");

      ServerDemo demo=new ServerDemo();

      demo.start();

    }

    5.14线程池

    线程若想启动需要调用start()方法。这个方法要做很多操作。要和操作系统打交道。注册线程等工作,等待线程调度。

    ExecutorService提供了管理终止线程池的方法。

    1线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,

             服务完后不关闭该线程,而是将该线程还回到线程池中。在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,

             线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,一个线程同时只能执行一个任务,

             但可以同时向一个线程池提交多个任务。

    2)线程池的创建都是工厂方法。我们不要直接去new线程池,因为线程池的创建还要作很多的准备工作。

    3)常见构造方法:

    Executors.newCachedThreadPool():可根据任务需要动态创建线程,来执行任务。

                   若线程池中有空闲的线程将重用该线程来执行任务。

                   没有空闲的则创建新线程来完成任务。理论上池子里可以放int最大值个线程。

                    缓存线程生命周期1分钟,得不到任务直解kill

    Executors.newFixedThreadPool(int threads):创建固定大小的线程池。池中的线程数是固定的。若所有线程处于饱和状态,新任务将排队等待。

    Executors.newScheduledThreadPool():创建具有延迟效果的线程池。可将带运行的任务延迟指定时长后再运行。

    Executors.newSingleThreadExecutor():创建单线程的线程池。池中仅有一个线程。所有未运行的任务排队等待。

    5.15双缓冲队列

    BlockingQueue:解决了读写数据阻塞问题,但是同时写或读还是同步的。

    1)双缓冲队列加快了读写数据操作,双缓冲对列可以规定队列存储元素的大小,一旦队列中的元素达到最大值,待插入的元素将等。

      等待时间是给定的,当给定时间到了元素还没有机会被放入队列那么会抛出超时异常。

    2LinkedBlockingQueue是一个可以不指定队列大小的双缓冲队列。若指定大小,当达到峰值后,待入队的将等待。理论上最大值为int最大值。

    eg1.1log服务器写日志文件,客户端ClientDemo类,try语句块中修改如下

    try{

      System.out.println("开始连接服务器");

      socket=new Socket("localhost",8088);

      OutputStream out=socket.getOutputStream();

      PrintWriter writer=new PrintWriter(out);

      while(true){

        writer.println("你好!服务器!");

        writer.flush();

        Thread.sleep(500);

      }

    }

     

    eg1.2log服务器写日志文件,服务器端ServerDemo类,增加线程池和双缓冲队列两个属性,删掉与原客户端的输出流

    private ExecutorService threadPool;//线程池

    private BlockingQueue<String> msgQueue; //双缓冲队列

    public ServerDemo(){

      try{

        socket=new ServerSocket(port);

        //创建50个线程的固定大小的线程池

        threadPool=Executors.newFixedThreadPool(50);

        msgQueue=new LinkedBlockingQueue<String>(10000);

        /**创建定时器,周期性的将队列中的数据写入文件*/

        Timer timer=new Timer();

        timer.schedule(new TimerTask(){

          public void run(){

            try{   //创建用于向文件写信息的输出流

                PrintWriter writer=new PrintWriter(new FileWriter("log.txt",true));

                //从队列中获取所有元素,作写出操作

                String msg=null;

                for(int i=0;i<msgQueue.size();i++){

                  /**参数 0:时间量TimeUnit.MILLISECONDS:时间单位*/

                  msg=msgQueue.poll(0,TimeUnit.MILLISECONDS);

                  if(msg==null){  

                    break;

                  }

                  writer.println(msg);//通过输出流写出数据

                }

                writer.close();

              }catch(Exception e){

                  e.printStackTrace();

              }

            }

          }, 0,500);

      }catch(Exception e){

        e.printStackTrace();

      }

    }

     

    public void start(){

      try{

        while(true){

          System.out.println("等待客户端连接……");

          Socket s=socket.accept();

          /**将线程体(并发的任务)交给线程池,线程池会自动将该任务分配给一个空闲线程去执行。*/

          threadPool.execute(new Handler(s));

          System.out.println("一个客户端连接了,分配线程");

        }

      }catch(Exception e){

        e.printStackTrace();

      }

    }

    /**定义线程体,该线程的作用是与连接到服务器端的客户端进行交互操作*/

    class Handler implements Runnable{

      private Socket socket;//当前线程要进行通信的客户端Socket

      public Handler(Socket socket){//通过构造方法将客户端的Socket传入

        this.socket=socket;

      }

      public void run(){

        try{ //获取与客户端通信的输入输出流

          InputStream in=socket.getInputStream();

          //包装为缓冲字符流

          BufferedReader reader=new BufferedReader(new InputStreamReader(in));

          String info=null;

                  while(true){//循环读取客户端发送过来的信息

            info=reader.readLine();

            if(info!=null){ //插入对列成功返回true,失败返回false

              //该方法会阻塞线程,若中断会报错!

              boolean b=msgQueue.offer(info, 5, TimeUnit.SECONDS);

            }

          }

        }catch(Exception e){ e.printStackTrace(); }

      }

     

  • 相关阅读:
    iframeUpload
    获取当前文件路径。
    ie6 overflow 失效
    ie8 vml不显示
    json转换
    nodejs for windows
    模块化管理组件(2012/05/09)
    模块化管理组件v0.1
    Firefox和IE之间7个JavaScript的差异
    c输入函数细节
  • 原文地址:https://www.cnblogs.com/Leemi/p/3654879.html
Copyright © 2011-2022 走看看