五、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(); }

  }

 

原文地址:https://www.cnblogs.com/Leemi/p/3654879.html