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

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中声明该种类异常,通知外界捕获。

注意事项:

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

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

5.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():返回文件或目录名。

④boolean exists():是否存在。

⑤boolean isDirectory():是否是目录。

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

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

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

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

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

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

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

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

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

5.4基本流:FIS和FOS

Java I/O 输入/输出

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

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

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

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

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

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

2)流根据源头分为:

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

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

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

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

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

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

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

①FileInputStream:文件字节输入流。

②FileOutputStream:文件字节输出流。

6)FileInputStream

常用构造方法:

①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()方法:返回当前字节输入流 可读取的总字节数。

7)FileOutputStream常用构造方法:

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

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

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

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

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

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

常用方法:

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

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

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

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

5.6基本数据类型高级流:DIS和DOS

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

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

常用方法:

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

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

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

……

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

常用方法:

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

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

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

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

……

5.7字符高级流:ISR和OSW

以“单个”“字符”为单位读写数据,一次处理一个字符(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();

}

}

2)InputStreamReader:字符输入流。

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缓冲字符高级流:BR和BW

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

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

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

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();//输出流关闭后,不能再通过其写数据

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

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():读取一个文本行。通过下列字符之一即可认为某行已终止:换行(‘\n‘)、回车(‘\r‘)或回车后直接跟着换行。

如果已到达流末尾,则返回null。EOF:end 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文件字符高级流:FR和FW

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

1)FileWriter写入:继承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):写入字符数组从off到len长度的部分

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

④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用的少。

1)Servlet:运行在服务器端的小程序,给客户端发送相应使用的输出流就是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会根据被反序列化对象的版本与当前版本进行匹配,来决定是否反序列化。

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

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

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

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高效的运行,宏观上看所有任务都在运行。

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

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

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

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

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

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

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

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

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

4)线程的创建方式一:

1:继承自Thread。

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

5)线程的创建方式二:

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

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

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

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

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

/**创建两个需要并发的任务,MyFirstRunnable和MySecRunnable都继承了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)线程生命周期:

20180921191455878572.png

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();

}

}

10)void 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关键字,线程安全锁、同步监视器。

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

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

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

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

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

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

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

⑥线程安全的效率低,如Vector、Hashtable。线程不安全的效率高,如ArrayList、HashMap

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;

}

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

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

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

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

14)wait/notify方法

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

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

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

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

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

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

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

5.13 Socket网络编程

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

1)网络通信模型:

C/S:client/server,客户端/服务器端;

B/S:browser/server,浏览器端/服务器端;

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

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

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

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

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

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

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

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

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

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

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

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

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

③关闭连接。

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

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

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

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

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

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

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

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

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

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

private ExecutorService threadPool;//线程池

private BlockingQueue msgQueue; //双缓冲队列

public ServerDemo(){

try{

socket=new ServerSocket(port);

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

threadPool=Executors.newFixedThreadPool(50);

msgQueue=new LinkedBlockingQueue(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

/**参数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(); }

}

}

原文:http://www.cnblogs.com/Leemi/p/3654879.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值