站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2SE 专题j2SE学习总结

j2SE学习总结

摘要: j2SE学习总结
J2se学习总结 —Allan 2005-12 Java的一些规定 1、若在源文件中定义了声明为public的类,需要将类所在的源文件的文件名取名为类名2、在同一个源文件中有且只能有一个定义为public的类3、编译时文件名大小写是不敏感的,执行的时候加载的类名是大小写敏感的 Java的语法 1、byte类型是一个有符号的8位的整数(-128~127)。其他语言的字节类型通常是无符号的整数。2、为了保持精度,byte型与byte型或整数运算时,将转换为整型后运算。将结果赋值给byte变量需要类型转换。3、short类型(-32768~32767)4、java中的char类型可以表示0~65535个字符,利用unicode编码格式5、可以使用单引号字符或者整数对char型赋值6、java中小数常量被认为double型,若要表明为float型,在其后加f7、float是4个字节,double是8个字节8、java中的boolean只有两个取值true和false9、java中条件判断只能使用true或者false java中的数组 1、java中一维数组在定义的时候是不能够分配空间的,例: int num[];//中括号中不能写大小 只有在定义完成之后为数组分配大小 num = new int[3]; java中数组定义建议采取下面的形式: int[] num; 数组可以在定义时进行初始化: int[] num = {1, 2, 3}; 或者 int[] num = new int[]{1, 2, 3}; 注意不要写为: int[] num = new int[3]{1, 2, 3};2、java中的二维数组 定义: int[][] num; 分配空间: num = new int[1][2];3、java中二维数组每行的列数可以不相同: int[][] num; num = new int[3][]; num[0] = new int[1]; num[1] = new int[2]; num[2] = new int[3]; 有些像c/c++中的指针数组4、java中定义一个数值型数组的时候,会自动将数组的元素全部赋值为05、当用初始值填充数组时,不要在中括号中填写大小,例: int[][] num = new int[2][]{{1, 2, 3}, {4, 5, 6}}; //error6、java支持不规则数组元素 int[][] num = {{1, 2, 3}, {4, 5}, {6}}; java中的自增操作 1、表现形式与c/c++中一样 java的移位运算符 1、左移<< 带符号右移>> 无符号右移>>> java的包 1、package语句必须是java源文件中的第一条语句。如果不加package语句,则指定为缺省包或者无名包。2、包对应着文件系统的目录层次结构。在package语句中,用“.”来指明包(目录)的层次。 如果在源文件中定义了package(给类取了包名),则类的完整的名字为:包名.类名 。3、java中提供的包(package)和文件系统的目录层次结构是相对应的。当为类定义了包名时, 要求在硬盘上有相应包名的目录,该目录下有类文件。4、可以使用“/”分隔包名和类名,通常使用“.”。5、包名可以使用多重限定名,例:package p1.p2.p3;//即顶层包p1有子包p2,p2有子包p3,包 p3中有当前类。注意:文件系统中应该有相应的目录层次结构。6、可以使用javac的-d参数指定在什么位置生成类的文件,并且会根据源文件中定义的包名生成相应的目录层次结构。未指定-d则在当前目录生成类文件,并且不会生成package指定的目录层次。 import语句 1、引入包中的类:import java.io.File;2、引入整个包: import java.io.*;3、在同一包中的类可以相互引用,无需import语句。4、java.lang包是自动导入的,不需要显式的加上import语句。5、除非有必要尽量避免导入一个包中的所有类。 类的说明符 1、类的访问说明符 (1)public (2)default(不加访问说明符时)2、类的其它修饰符 (1)final 表明类为最终类,不能派生其它子类 (2)abstract 抽象类3、将目录下的所有源文件都编译生成:javac -d . *.java 好处是不需要考虑哪个类先生成了。4、class关键字前没有加任何访问说明符时,类为缺省类。不同包中的类无法访问。5、在不同的包之间访问类时,只能访问到包中声明为public的类。6、缺省的类访问说明符,表明类只能被同一包中的类访问。 方法的访问说明符 1、方法的访问说明符 (1)public (2)protected (3)default(不加访问说明符时) (4)private public protected default private 同类 v v v v 同包 v v v 子类 v v 不同包 v 2、方法的其它修饰符 (1)static (2)final (3)abstract (4)native (5)synchronized3、final方法:为了确保某个函数的行为在继承过程中保持不变,并且不能被覆盖 (overridden),可以使用final方法。4、抽象的方法和抽象类: *在类中没有方法体的方法就是抽象方法。 *含有抽象方法的类,即为抽象类。 *如果一个子类没有实现抽象基类中所有的抽象方法,则子类也成为一个抽象类。 *我们可以将一个没有任何抽象方法的类声明为abstract,避免由这个类产生任何的对象。 *抽象类需要声明abstract 垃圾回收 1、java虚拟机退出之前,会去调用函数finalize()2、java虚拟机中,垃圾回收是作为一个低优先级的线程在运行。在内存不够的情况下,才会运行垃圾收集器。3、使用System的静态方法gc()运行垃圾回收器。 接口 1、接口中所有的方法都是抽象的,定义了一类行为的集合,行为的实现由其实现类来完成。2、使用interface定义接口,而不是class。3、类使用implements关键字实现接口。4、接口中所有的方法都是public abstract。5、实现一个接口时,如果类需要实例话,则要实现接口中所有的方法。6、接口不能实例化为一个对象,但实现了接口的类可以作为接口的实例。7、在接口中声明方法时,不能使用native、static、final、synchronized、private、 protected等修饰符。8、和public类一样,public接口也必须定义在与接口同名的文件中。9、接口中可以有数据成员,这些成员默认都是public static final。10、访问接口的数据成员的几种形式: 接口.接口数据成员名称 实现了接口的类的名称.接口数据成员名称 实现了接口的类的实例.接口数据成员名称11、一个接口可以继承自另一个接口。12、java中不允许类的多继承,但允许接口的多继承。13、在java中,一个类可以实现多个接口。14、一个类在继承另外一个类的同时,可以实现多个接口。 内部类 1、内部类:在一个类中定义另外一个类,这个类就叫做内部类或内置类(inner class)。2、内部类可以让我们将逻辑上相关的一组类组织起来,并由外部类(outer class)来控制内部类的可见性。3、当我们建立一个inner class时,其对象就拥有了与外部类对象之间的一种关系,这就是通过一个特殊的this reference形成的,使得内部类对象可以随意的访问外部类中的所有的成员。4、内部类可以随意的访问外部类中所有的成员方法和成员变量(包括私有的成员方法和成员变 量)。5、java中凡是用new产生的对象,都是在堆内存中分配的。6、在内部类中引用外部成员变量可以使用以下形式: 外部类名.this.成员变量 例:outer.this.varname7、若main函数不在外部类中,引用外部类中的内部类,如声明一个内部类,形式如下: Outer.Inner inner;8、如果想要直接实例化一个内部类对象,必须要有一个引用指向外部类对象 如何在main方法中直接产生内部类对象: Outer.Inner inner = outer.new Inner();9、内部类定义可以放在函数、条件语句、语句块中,并且不管内部类嵌套的层次有多深,都可以访外部类的成员。10、若方法的内部有内部类,方法定义的局部变量需要被内部类所访问,则需要将变量声明为final 。11、对于一个类,访问权限可以是缺省的或者是public的。而对于一个内部类,还也可以声明为 protected 或者 private、abstract、final、static 。如果内部类声明为 abstract,则内部类就不能实例化了。需要在外部类中再定义一个内部类,从声明为 abstract的类派生出来,然后再实例化。一个静态的内部类,只能访问外部类中静态 的成员变量或者一个静态的成员方法。12、静态的内部类可以有静态的成员函数或变量,非静态内部类中不能有静态的声明。13、在方法中定义的内部类,如果要访问方法中定义的本地变量或者方法的参数,则变量必须被声明为final。14、内部类可以声明为private或protected;还可以声明为abstract或final。15、内部类可以声明为static的,但此时就不能再使用外部类的非static的成员变量和非static的成员方法;16、非static的内部类中的成员不能声明为static的,只有在顶层类或static的内部类中才可以声明static成员。17、产生一个派生类对象的时候,会调用基类的构造函数,产生一个基类的对象。18、要产生一个内部类对象,首先需要产生一个外部类的对象,然后才能产生内部类对象。从而建立起内部类对象到外部类对象的一个引用关系。19、为什么使用内部类? *在内部类(inner class)中,可以随意的访问外部类成员,这可以让我们更好地组织管理我们的代码,增强代码的可读性。 *内部类可以用于创建适配器类,适配器类是用于实现接口的类。使用内部类来实现接口,可以更好地定位与接口关联的方法在代码中的位置。 java中的异常处理 1、java通过异常类表明异常,所有的异常都有个叫做Exception的基类。2、派生自Exception的异常类,不一定都在java.lang包中。3、Exception类派生自java.lang.Throwable,从Throwable派生了2个类: *Error类:从该类派生的有VitualMachineError(包含了OutOfMemoryError和 StackOverflowError)和AWTError *Exception:从该类派生的有RuntimeException和IOException等 RuntimeException通常代表了程序编写时的一些错误,这类错误在java中不需 要捕获,由java运行时系统自动抛出并自动处理。4、打开一个不存在的文件、网络连接中断、数组下标越界、正在加载的类文件丢失等都会引发 异常。5、java中的异常类定义了程序中遇到的轻微的错误条件。6、java中的错误类定义了程序中不能恢复的严重错误条件。如内存溢出、类文件格式错误等。 这一类错误由java运行系统处理,不需要我们去处理。7、java程序在执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给 java运行时系统,这个过程称为抛出(throw)异常。8、当java运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给 其处理,这一过程称为捕获(catch)异常。9、如果java运行时系统找不到可以捕获异常的方法,则运行时系统将终止,相应的java程序也将退出。10、try/catch/finally语句。11、一旦引发了异常,调用方法下面的语句就不会再执行了。12、Exception的方法来自其基类Throwable。 几个主要的方法: getMessage() toString() printStackTrace()13、告诉用户使用自己写的方法调用时有可能产生异常,让其做好准备,可以使用java的异常声明语法去声明一个异常,在方法的参数列表后面使用throws关键字抛出一个异常。当我们声明要抛出一个异常,java编译器在编译的时候就会强制我们去捕获这个异常。14、抛出一个异常的时候,会抛给它的调用者。 main函数可以将异常抛给java运行时系统。15、实际编写代码的时候,最好将自行捕获到,在自己的代码中进行处理,打印出一个准确而又 详尽的信息提示给用户,或者对发生的异常进行补救。16、所有的异常都是Exception所派生出来的,所以写异常捕获代码时一般将具体的异常捕获放 在前面,将通用型的放在后面。也就是说由特殊到一般的写catch语句。17、除了RuntimeException这类异常之外的异常,当我们抛出的时候java编译器都会强制的要 求我们去进行捕获。18、在catch到异常后,可以不进行处理,将异常对象再次抛出: throw e;19、throw和throws的区别: *当我们声明抛出异常的时候,使用throws关键字; *当我们抛出异常的时候,使用throw关键字。20、程序执行到throw语句,程序将会发生跳转。21、可以抛出一个新的异常: throw new Exception("new exception");22、定义自己的异常类: *派生自Exception; *构造方法; *构造方法中调用基类的构造方法;(super())23、声明异常的时候,可以同时声明抛出多个异常。24、不管程序执行有没有发生异常,finally语句中的代码都会执行(在return前也会执行)。25、当程序终止运行的时候,finally语句就不会执行了。26、System.exit(int status)静态方法终止当前正在运行的java虚拟机。其参数status表示 状态的代码,按照约定,一个非0的状态代码指示了一个非正常的终止。 java编程规范 1、package的命名 package的名字由全部小写的字母组成,例如:cn.mypackage2、class和interface的命名 class和interface的名字由大写字母开头而其它字母都小写的单词组成,例如: Exception、RuntimeException。3、class变量的命名 变量的名字用一个小写字母开头,后面的单词用大写字母开头,例如: myInfo、currentUser4、class方法的命名 方法的名字用一个小写字母开头,后面的单词用大写字母开头,例如: run()、getInstance()5、static final常量的命名 所有字母都大写,并且能表示完整含义。例如:PI6、参数的命名 参数的名字和变量的命名规范一致。7、数组的命名 数组应该总是用这样的方式命名:byte[] buffer java的常用包 1、java.applet:包含一些用于创建java小应用程序的类。2、java.awt: 包含一些用于编写与平台无关的图形界面(GUI)应用程序的类。3、java.io: 包含一些用作输入输出(I/O)处理的类。4、java.lang: 包含一些java语言的基本类与核心类,如String、Math、Integer、System 和Runtime,提供常用的功能,这个包中的所有类是被隐式导入的。5、java.net: 包含用于建立网络连接的类,与java.io同时使用完成与网络有关的读写。6、java.util:包含一些实用工具类和数据结构类。 ==equals的用法 1、在java中,boolean、byte、short、int、long、char、float、double这八种是基本数据 类型,其余的都是引用类型。2、“==”是比较两个变量的值是否相等 "equals"是比较两个对象变量所代表的对象的内容是否相等3、当我们声明了一个引用类型变量时,系统只为该变量分配了引用空间,并未创建一个具体的 对象;当用new为对象分配空间后,将对象的引用赋值给引用变量。 *可以将java中的引用理解为一个地址,也就是对象的首地址 String和StringBuffer 1、String str = "abc"; int i = 3; float f = 4.5f; char ch = 'a'; boolean b = true; System.out.println(str + i + f + ch + b);2、针对String的“+”和“+=”,是java中唯一被重载的操作符;在java中,不允许程序员重 载操作符。3、String类对象是一个常量对象。(String对象一旦给赋了一个引用之后,它就是一个常量对 象了) String str = "abc"; str = "def"; //"abc"此时成了垃圾内存,str保存了"def"的引用4、在处理大量字符串的程序中,我们通常用StringBuffer来替代String。5、构造一个空的StringBuffer,初始的容量是16个字符。 StringBuffer() 超出16个字符,容量会自动增加,这样就不用担心容量不够。 StringBuffer(int length); 构造时指定初始容量。 StringBuffer类的一些常用方法: int capacity() 返回当前StringBuffer的容量; char charAt() 获取一个char; StringBuffer delete(int start, int end) 删除StringBuffer中字符串的一个子 串,从开始位置到结束位置。 需要注意的是,删除的结束位置字符通 常是不包括的。可以理解为大于等于起 始位置和小于结束位置。 StringBuffer reverse() 数组 1、数组元素是基本数据类型将被初始化为0,是引用类型的将被初始化为NULL2、当给数组变量赋值为NULL时,相当于把它所保存的引用清除掉了。原来保存的引用的所在的 对象就成了垃圾对象。所以,如果想让内存成为垃圾内存,可以给保存了对象引用的变量赋 值为NULL。3、main方法是由java虚拟机调用的,所以必须是public的。由于java调用main方法时,不需要 产生任何的对象,所以它要声明为static。不需要返回值,所以声明为void。有一个引用类 型的数组参数String[] args。 *args是用来接收命令行参数的,不包括命令行输入的java和java后的类名称。 函数的调用 1、在java中,传递参数时,都是以传值的方式进行。2、对于基本数据类型,传递的是数据的拷贝;对于引用类型,传递的是引用的拷贝。 数组的相关操作 1、在java中,所有的数组都有一个缺省的属性length,用于获取数组中元素的个数。2、数组的复制:System.arraycopy( Object src, int srcPos, Object dest, int destPos, int length)。3、数组的排序:Arrays.sort()。4、在已排序的数组中查找某个元素: Arrays.binarySearch()。5、Arrays类在java.util包中。6、数组是一种引用类型的变量。7、Arrays.sort()对于对象排序的要求:数组中的所有元素必须实现了Comparable接口。数组中的所有元素都是可以互相比较的。 Comparable接口中有一个方法需要被实现: public int compareTo(Object o); //小于返回负数,等于返回0,大于返回正数。8、String类本身已经实现了Comparable接口,可以直接调用compareTo进行比较。 封装类 1、针对八种基本数据类型定义的相应的引用类型--封装类: 基本数据类型 封装类 boolean Boolean byte Byte short Short int Integer long Long char Character float Float double Double2、从Integer中取出int值使用函数intValue(); 从Long中取出long值使用函数longValue() 从Float中取出float值使用函数floatValue();3、将Integer转换为字符串类型toString()4、将String类型转换为Integer可以用Integer.valueOf(String s)静态方法。5、基本数据类型转换为String可以通过是使用封装类型的toString()方法达到目的。6、将String类型转换为基本数据类型可以通过调用封装类的parseXXX()静态方法来达到目 的。例:int n = Integer.parseInt("123"); float f = Float.parseFloat("12.34"); 注意Boolean类型没有类似的parse用法。7、所有的封装类都是只读类型,没有提供任何方法修改其内容。 程序、进程和线程 1、程序是计算机指令的集合,它以文件的形式存储在磁盘上。 进程:是一个程序在其自身的地址空间中的一次执行活动。 *进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申 请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,它不占用系统的运行资 源。 线程:是进程中的一个单一的连续控制流程。一个进程可以拥有多个线程。 *线程又成为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在 于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程 间的通信远较进程简单。2、一个进程中至少会有一个线程。 java对多线程的支持 1、java在语言级提供了对多线程程序设计的支持。2、实现多线程程序的两种方式: (1)从Thread类继承; (2)实现Runnable接口。3、一个Thread对象就代表了进程当中的一个线程。java虚拟机允许一个应用程序可以拥有多个并发运行的线程。当一个JVM启动的时候,通常由一个单一的非后台线程(典型的就是调用main方法的线程)4、可以利用线程类当中的静态方法 static Thread currentThread()返回当前正在执行的Thread对象的引用,也就是获取当前线程。5、可以利用线程类当中的方法 String getName()获取线程的名字。 例:Thread.currentThread().getName();6、让线程可以运行我们的代码需要覆盖其run()方法。7、让线程运行需要调用线程类中的一个方法start。当我们调用start方法的时候就会导致这个线程开始执行,然后java的虚拟机就会调用这个线程的run方法。可以把run方法理解为线程的入口函数。8、可以把一个线程设置为后台线程,利用线程类中的一个方法: void setDaemon(boolean on)标记这个线程为后台线程或者用户线程。 如果参数设置为真,标记该线程为后台线程。 *main方法所在的线程是非后台线程。9、一个正在执行的线程可以放弃它执行的权利让另一个线程运行,使用Thread类中的静态方法 static void yield()导致当前正在执行的thread对象临时暂停允许其它thread执行。10、java中每一个线程都有一个优先级,可以通过Thread类中的方法int getPriority()和void setPriority(int newPriority)去得到一个线程的优先级和设置线程的优先级。 *线程优先级的取值范围(1~10): 三个常量(MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY)表示线程最大、最小、默认优先级,它们表示的整数值分别是10、1、5。 *可以随时去设置线程的优先级,可以在start()前也可以在start()后去修改。 *java中如果一个线程的优先级较高,那么它将始终获得运行的机会 java多线程的支持 1、java运行时系统实现了一个用于调度线程执行的线程调度器,用于确定某一时刻由哪一个线程在CPU上运行。2、在java技术中,线程通常是抢占式的而不需要时间片分配进程(分配给每个线程相等CPU时间的进程)。抢占式调度模型就是许多线程处于可以运行状态(等待状态),但实际上只有一个线程在运行。该线程一直运行到它终止进入可运行状态(等待状态),或者另一个具有更高优先级的线程可变成可运行状态。在后一种情况下,低优先级的线程被高优先级的线程抢占,高优先级的线程获得运行的机会。(程序编写中,不要利用高优先级将始终运行这个特点来完成某些功能) 3、java线程调度器支持不同优先级线程的抢先方式,但其本身不支持相同优先级线程的时间片轮转。4、java运行时系统所在的操作系统支持时间片的轮转,则线程调度器就支持相同优先级线程的时间片轮换。 实现线程的第二种方式(实现Runnable接口) 1、 Thread类其实也实现了Runnable接口2、 对于一个类来说,如果说它的实例想要被一个线程去执行,就应该实现Runnable接口。该接口只有一个方法run()。3、 构造Thread的时候可以传递一个实现了Runnable接口的对象:Thread(Runnable target)作为Thread构造函数的参数。Java虚拟机会调用实现了Runnable接口对象当中的run()方法来执行我们的代码。 MyThread mt = new MyThread();New Thread(mt).start();4、 通常,如果不需要修改线程类当中除了run()方法之外的其它方法的行为之外,最好都是要去实现Runnable接口。5、 实现Runnable接口有两个好处:*已经继承了其他类无法再继承Thread类时,就实现Runnable接口;*如果多个线程访问同一种资源的话是很方便的:6、Thread.sleep(long millis)让当前正在执行的线程睡眠一会。7、 让一个线程进入一段代码后即使休眠了其它线程也不能进入这段代码,除非它将剩余的代码执行完成之后,其它的线程才能后进入到这段代码之中。可以利用java语言中的同步来完成。 线程的同步 1、在一个程序中,代码段访问了同一个对象从单独的、并发的线程当中,这个代码段就叫做“临界区”。采用同步的机制对临界区进行保护。2、同步的两种方式:同步块和同步方法。这两种方式都是通过一个synchronized关键字来完成的。A.对于同步块来说,在synchronized后面需要加上一个对象,这个对象是任意的一个对象。可以定义一个String类型的,也可以定义一个Object类型的对象:Object obj = new Object();在synchronized后面加上这个对象:synchronized(obj){ //保护的代码段 ……}这样就实现了一个同步。 B.*同步方法的实现:在方法的前面加上synchronizedPublic synchronized void method1(){} 同步的方法可以完成跟同步块一样的结果。同步块需要有一个对象,同步方法只需要在方法的前面加上一个synchronized关键字。 3、同步块的实现机制是怎样完成的呢?java语言中每一个对象都有一个监视器,或者叫做锁。当我们第一个线程进来的时候首先要判断一下obj这个对象的监视器是否被加锁,如果没有被加锁,那么它就会将obj的监视器加锁,然后往下执行(遇到sleep睡眠)…如果其它线程执行到Synchronized会判断到obj已经被加锁,那么它们只能等待。第一个线程执行完受保护的代码后,会将obj的监视器解锁。其它线程就可以进入到这个同步的代码段中。 同步方法的实现机制又是怎样的呢?当我们一个线程进入这个方法的时候,也需要加上一把锁,同步的方法是给类中的this变量的监视器加锁。当一个线程进入同步方法的时候,会察看this对象是否加锁,没有加锁则会加锁。然后进入到方法内部,方法执行完后,会将this解锁,其它线程在this解锁后才可以进入。 4、同步方法利用的是this所代表的对象的锁。5、因为静态方法只属于类本身,而不属于某个对象。每个类都对应有一个class对象,步静态方法使用的就是方法所在的类所对应的class对象的监视器。 线程的死锁 1、 哲学家进餐的问题。2、 线程1锁住了对象A的监视器,等待对象B的监视器,线程2锁住了对象B的监视器,等待对象A的监视器,就造成了死锁。3、 Thread类中两个废弃的方法就很容易造成死锁,suspend()、resume() Wait、notify、nofityAll方法 1、 每一个对象除了有一个锁之外,还有一个等待队列(wait set),当一个对象刚创建的时候,它的等待队列是空的。2、我们应该在当前线程锁住对象的锁后,去调用该对象的wait方法。也就是说wait方法只能够在同步方法或者同步块中被调用。但我们调用wait方法的时候,这个线程就进入了这个对象的等待队列当中。3、 当调用对象的notify方法时,将从该对象的等待队列中删除一个任意选择的线程,这个线程将再次成为可运行的线程。4、 当调用对象的notifyAll方法时,将从该对象的等待队列中删除所有等待的线程,这些线程将成为可运行的线程。5、 Wait和notify主要用于producer-consumer这种关系中。6、 Wait和notify方法必须在一个同步的块或者方法中被调用,并且针对同一对象的等待队列 线程的状态 1、 用new创建一个线程的时候,线程就处于new状态。当调用start方法的时候,线程处于可运行(Runnable)状态。一个尚未运行的线程是Runnable状态,一个正在运行的线程也是Runnable状态。当一个正在运行的线程调用yield方法的时候,线程调度器会选择另外一个线程去运行,调用yield方法的线程仍然处于Runnable状态。当我们调用sleep、wait、suspend或者I/O阻塞时线程就进入Not Runnable状态。当一个线程sleep结束,或者调用了notify方法、resume方法或者I/O操作完成,线程就会从Not Runnable状态返回Runnable状态。(resume和suspend这两个函数已经被废弃了,不建议我们再使用)当一个处于可运行状态的线程它的run方法执行完毕或者调用stop方法,线程就进入了Dead状态终止了。在Not Runnable状态的时候,调用了stop方法,线程也会终止。(stop方法是用来停止一个线程的,这个方法也已经废弃了,因为这个方法是不安全的,当调用stop方法时,它会解锁先前锁住的所有的监视器,这样的话,其它的线程就可以去访问被保护的对象,导致对象的状态不一致,结果是不可预料的) ↓new thread↓ yield start ○ sleep wait suspend I/O阻塞New →→→→→→ Runnable →→→→→→→→→→→→→→→→→→Not Runnable ↘ ←←←←←←←←←←←←←←←←←← ↙ Sleep结束、notify resume I/O操作完成 ↙ ↘ run方法退出 stop方法调用 ↘ stop调用 Dead 线程的终止(两种方式) 1、 设置一个flag变量2、 interrupt()方法(可以中断一个线程) File 1、 一个File类的对象,表示了磁盘上的文件或目录。2、 File类提供了与平台无关的方法来对磁盘上的文件或目录进行操作。3、 可以通过一个文件名来构造一个File类对象:File(String pathname) 也可以通过指定一个File对象,再指定它的文件名: File(String parent, String child)去构造一个File对象,这里的parent表示文件或目录所在的父目录。Child指定文件名。4、File类不只可以用来表示一个文件还可以表示一个目录。5、File类中: Boolean canRead() //检测文件是否可读 Boolean canWrite()//检测文件是否可写 Boolean createNewFile() 当我们构造一个File对象的时候可以调用createNewFile()去创建一个新的文件1、 在我们进行I/O操作的时候,经常会出现一些异常。如:磁盘空间不足、因为磁盘故障不能创建文件。所以在用java.io包中的类进行编程的时候,经常会需要捕获一些异常。为了简单起见,可以在main后throws出异常,让java虚拟机去处理。例:import java.io.*; class FileTest{public static void main(String[] args) throws Exception{ File f = new File("1.txt"); f.createNewFile();}}2、 File类还可以用来创建目录:Boolean mkdir(); //用来创建一个目录例: File f = new File("1.txt"); //f.createNewFile(); f.mkdir();3、 利用绝对路径创建一个文件:File f = new File("D:\\MyWorks\\lessons\\java\\lesson7\\1.txt");f.createNewFile();10、File类提供了常量表示目录的分隔符: Static String separator //系统独立的分隔符在windows下会被解释为“\”,//在linux下为”/” Static char separatorChar //也是用来表示分隔符,只不过一个使用String类//型来表示,一个是用char表示4、 在当前盘符下直接写一个目录分隔符,也可以表示当前盘符的根目录。 I/O 流概述   输入/输出处理是程序设计中非常重要的一部分,比如从键盘读取数据、从文件中读取数据或向文件中写数据等等。   Java把这些不同类型的输入、输出源抽象为流(stream),用统一接口来表示,从而使程序简单明了。   Jdk 提供了包java.io,其中包括一系列的类来实现输入/输出处理。下面我们对java.io包的内容进行概要的介绍。  I/O流的层次   1.字节流   2.字符流   3.对象流   4.其它 --------------------------------- 1.字节流:   从InputStream和OutputStream派生出来的一系列类。这类流以字节(byte)为基本处理单位。  ◇ InputStream、OutputStream  ◇ FileInputStream、FileOutputStream  ◇ PipedInputStream、PipedOutputStream  ◇ ByteArrayInputStream、ByteArrayOutputStream  ◇ FilterInputStream、FilterOutputStream  ◇ DataInputStream、DataOutputStream  ◇ BufferedInputStream、BufferedOutputStream  2.字符流:   从Reader和Writer派生出的一系列类,这类流以16位的Unicode码表示的字符为基本处理单位。  ◇ Reader、Writer  ◇ InputStreamReader、OutputStreamWriter  ◇ FileReader、FileWriter  ◇ CharArrayReader、CharArrayWriter  ◇ PipedReader、PipedWriter  ◇ FilterReader、FilterWriter  ◇ BufferedReader、BufferedWriter  ◇ StringReader、StringWriter  3.对象流   ◇ ObjectInputStream、ObjectOutputStream  4.其它   ◇ 文件处理:  File、RandomAccessFile;   ◇ 接口  DataInput、DataOutput、ObjectInput、ObjectOutput; -----------------------------------1.InputStream   ◇ 从流中读取数据:  int read( ); //读取一个字节,返回值为所读的字节  int read( byte b[ ] ); //读取多个字节,放置到字节数组b中,通常              //读取的字节数量为b的长度,返回值为实际              //读取的字节的数量  int read( byte b[ ], int off, int len ); //读取len个字节,放置                       //到以下标off开始字节                       //数组b中,返回值为实                       //际读取的字节的数量  int available( );   //返回值为流中尚未读取的字节的数量  long skip( long n ); //读指针跳过n个字节不读,返回值为实际             //跳过的字节数量   ◇ 关闭流:  close( ); //流操作完毕后必须关闭    ◇ 使用输入流中的标记:  void mark( int readlimit ); //记录当前读指针所在位置,readlimit                 //表示读指针读出readlimit个字节后                //所标记的指针位置才失效  void reset( );     //把读指针重新指向用mark方法所记录的位置  boolean markSupported( ); //当前的流是否支持读指针的记录功能   有关每个方法的使用,详见java API。  2.OutputStream   ◇ 输出数据:  void write( int b );   //往流中写一个字节b  void write( byte b[ ] ); //往流中写一个字节数组b  void write( byte b[ ], int off, int len ); //把字节数组b中从              //下标off开始,长度为len的字节写入流中   ◇ flush( )       //刷空输出流,并输出所有被缓存的字节  由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。   ◇ 关闭流:   close( );       //流操作完毕后必须关闭 -----------------------------------进行I/O操作时可能会产生I/O例外,属于非运行时例外,应该在程序中处理。如:FileNotFoundException, EOFException, IOException-----------------------------------文件的顺序处理   类FileInputStream和FileOutputStream用来进行文件I/O处理,由它们所提供的方法可以打开本地主机上的文件,并进行顺序的读/写。例如,下列的语句段是顺序读取文件名为text.txt的文件里的内容,并显示在控制台上面,直到文件结束为止。 例: FileInputStream fis;    try{    fis = new FileInputStream( "text.txt" );   System.out.print( "content of text is : ");     int b;     while( (b=fis.read())!=-1 ) //顺序读取文件text里的内容并赋值                    给整型变量b,直到文件结束为止。      {                     System.out.print( (char)b );     }   }catch( FileNotFoundException e ){   System.out.println( e );   }catch( IOException e ){   System.out.println( e );   } ----------------------------过滤流   过滤流在读/写数据的同时可以对数据进行处理,它提供了同步机制,使得某一时刻只有一个线程可以访问一个I/O流,以防止多个线程同时对一个I/O流进行操作所带来的意想不到的结果。类FilterInputStream和FilterOutputStream分别作为所有过滤输入流和输出流的父类。 过滤流类层次:    java.lang.Object | +----java.io.InputStream | +----java.io.FilterInputStream   为了使用一个过滤流,必须首先把过滤流连接到某个输入/出流上,通常通过在构造方法的参数中指定所要连接的输入/出流来实现。例如:   FilterInputStream( InputStream in );  FilterOutputStream( OutputStream out ); 几种常见的过滤流: ◇ BufferedInputStream和BufferedOutputStream    缓冲流,用于提高输入/输出处理的效率。   ◇ DataInputStream 和 DataOutputStream    不仅能读/写数据流,而且能读/写各种的java语言的基本类型,如:boolean,int ,float等。   ◇ LineNumberInputStream    除了提供对输入处理的支持外,LineNumberInputStream可以记录当前的行号。   ◇ PushbackInputStream    提供了一个方法可以把刚读过的字节退回到输入流中,以便重新再读一遍。   ◇ PrintStream    打印流的作用是把Java语言的内构类型以其字符表示形式送到相应的输出流。 -------------------------------字符流的处理   java中提供了处理以16位的Unicode码表示的字符流的类,即以Reader和Writer 为基类派生出的一系列类。  4.7.1 Reader和Writer   1.Reader类是处理所有字符流输入类的父类。   2. Writer类是处理所有字符流输出类的父类。 这两个类是抽象类,只是提供了一系列用于字符流处理的接口,不能生成这两个类的实例,只能通过使用由它们派生出来的子类对象来处理字符流。  1.Reader类是处理所有字符流输入类的父类。   ◇ 读取字符   public int read() throws IOException; //读取一个字符,返回值为读取的字符  public int read(char cbuf[]) throws IOException; /*读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量*/  public abstract int read(char cbuf[],int off,int len) throws IOException;   /*读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现*/   ◇ 标记流  public boolean markSupported(); //判断当前流是否支持做标记  public void mark(int readAheadLimit) throws IOException;    //给当前流作标记,最多支持readAheadLimit个字符的回溯。  public void reset() throws IOException; //将当前流重置到做标记处   ◇ 关闭流  public abstract void close() throws IOException;  2. Writer类是处理所有字符流输出类的父类。   ◇ 向输出流写入字符  public void write(int c) throws IOException;  //将整型值c的低16位写入输出流  public void write(char cbuf[]) throws IOException;  //将字符数组cbuf[]写入输出流  public abstract void write(char cbuf[],int off,int len) throws IOException;  //将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流  public void write(String str) throws IOException;  //将字符串str中的字符写入输出流  public void write(String str,int off,int len) throws IOException;  //将字符串str 中从索引off开始处的len个字符写入输出流   ◇ flush( )  刷空输出流,并输出所有被缓存的字节。   ◇ 关闭流  public abstract void close() throws IOException; -----------------------------------3 BufferedReader和BufferedWriter   生成流对象   读入/写出字符   下面是一个从键盘接收输入数据的例子: ◇ 生成流对象   public BufferedReader(Reader in); //使用缺省的缓冲区大小  public BufferedReader(Reader in, int sz); //sz为缓冲区的大小  public BufferedWriter(Writer out);  public BufferedWriter(Writer out, int sz);  ◇ 读入/写出字符   除了Reader和Writer中提供的基本的读写方法外,增加对整行字符的处理。  public String readLine() throws IOException; //读一行字符  public void newLine() throws IOException; //写一行字符 【例4-4】   import java.io.*;  public class NumberInput{   public static void main(String args[]){    try{      InputStreamReader ir;      BufferedReader in;      ir=new InputStreamReader(System.in);       //从键盘接收了一个字符串的输入,并创建了一个字符输入流的对象      in=new BufferedReader(ir);      String s=in.readLine();      //从输入流in中读入一行,并将读取的值赋值给字符串变量s      System.out.println("Input value is: "+s);      int i = Integer.parseInt(s);//转换成int型      i*=2;      System.out.println("Input value changed after doubled: "+i);    }catch(IOException e)    {System.out.println(e);}   }  }   注意:在读取字符流时,如果不是来自于本地的,比如说来自于网络上某处的与本地编码方式不同的机器,那么我们在构造输入流时就不能简单地使用本地缺省的编码方式,否则读出的字符就不正确;为了正确地读出异种机上的字符,我们应该使用下述方式构造输入流对象:      ir = new InputStreamReader(is, "8859_1");   采用ISO 8859_1编码方式,这是一种映射到ASCII码的编码方式,可以在不同平台之间正确转换字符。
网络编程  网络基础知识   计算机网络形式多样,内容繁杂。网络上的计算机要互相通信,必须遵循一定的协议。目前使用最广泛的网络协议是Internet上所使用的TCP/IP协议。 网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。   目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。 IP地址:标识计算机等网络设备的网络地址,由四个8位的二进制数组成,中间以小数点分隔。    如:166.111.136.3 , 166.111.52.80   主机名(hostname):网络地址的助记名,按照域名进行分级管理。    如:www.tsinghua.edu.cn      www.fanso.com    端口号(port number):网络通信时同一机器上的不同进程的标识。    如:80,21,23,25,其中1~1024为系统保留的端口号    服务类型(service):网络的各种服务。    http, telnet, ftp, smtp   在Internet上IP地址和主机名是一一对应的,通过域名解析可以由主机名得到机器的IP,由于机器名更接近自然语言,容易记忆,所以使用比IP地址广泛,但是对机器而言只有IP地址才是有效的标识符。   通常一台主机上总是有很多个进程需要网络资源进行网络通讯。网络通讯的对象准确的讲不是主机,而应该是主机中运行的进程。这时候光有主机名或IP地址来标识这么多个进程显然是不够的。端口号就是为了在一台主机上提供更多的网络资源而采取得一种手段,也是TCP层提供的一种机制。只有通过主机名或IP地址和端口号的组合才能唯一的确定网络通讯中的对象:进程。 服务类型是在TCP层上面的应用层的概念。基于TCP/IP协议可以构建出各种复杂的应用,服务类型是那些已经被标准化了的应用,一般都是网络服务器(软件)。读者可以编写自己的基于网络的服务器,但都不能被称作标准的服务类型。 两类传输协议:TCP;UDP   尽管TCP/IP协议的名称中只有TCP这个协议名,但是在TCP/IP的传输层同时存在TCP和UDP两个协议。 TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。   UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址和目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。   下面我们对这两种协议做简单比较:   使用UDP时,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。对于TCP协议,由于它是一个面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中多了一个连接建立的时间。   使用UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。而TCP没有这方面的限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大量的数据。UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。而TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。   总之,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。相比之下UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。   既然有了保证可靠传输的TCP协议,为什么还要非可靠传输的UDP协议呢?主要的原因有两个。一是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。二是在许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。 -----------------------------基于URL的高层次Java网络编程  一致资源定位器URL URL(Uniform Resource Locator)是一致资源定位器的简称,它表示Internet上某一资源的地址。通过URL我们可以访问Internet上的各种网络资源,比如最常见的WWW,FTP站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。   URL是最为直观的一种网络定位方法。使用URL符合人们的语言习惯,容易记忆,所以应用十分广泛。而且在目前使用最为广泛的TCP/IP中对于URL中主机名的解析也是协议的一个标准,即所谓的域名解析服务。使用URL进行网络编程,不需要对协议本身有太多的了解,功能也比较弱,相对而言是比较简单的. URL的组成: protocol://resourceName  协议名(protocol)指明获取资源所使用的传输协议,如http、ftp、file等,资源名(resourceName)则应该是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。例如:  http://www.sun.com/ 协议名://主机名  http://home.netscape.com/home/welcome.html 协议名://机器名+文件名  http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 协议名://机器名+端口号+文件名+内部引用 创建一个URL: 为了表示URL, java.net中实现了类URL。我们可以通过下面的构造方法来初始化一个URL对象:  (1) public URL (String spec);     通过一个表示URL地址的字符串可以构造一个URL对象。     URL urlBase=new URL("http://www. 263.net/")   (2) public URL(URL context, String spec);     通过基URL和相对URL构造一个URL对象。     URL net263=new URL ("http://www.263.net/");     URL index263=new URL(net263, "index.html")   (3) public URL(String protocol, String host, String file);     new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html");   (4) public URL(String protocol, String host, int port, String file);     URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html");   注意:类URL的构造方法都声明抛弃非运行时例外(MalformedURLException),因此生成URL对象时,我们必须要对这一例外进行处理,通常是用try-catch语句进行捕获。格式如下:   try{     URL myURL= new URL(…)  }catch (MalformedURLException e){  …  //exception handler code here  …  } 解析一个URL: 一个URL对象生成后,其属性是不能被改变的,但是我们可以通过类URL所提供的方法来获取这些属性:   public String getProtocol() 获取该URL的协议名。   public String getHost() 获取该URL的主机名。   public int getPort() 获取该URL的端口号,如果没有设置端口,返回-1。   public String getFile() 获取该URL的文件名。   public String getRef() 获取该URL在文件中的相对位置。   public String getQuery() 获取该URL的查询信息。   public String getPath() 获取该URL的路径   public String getAuthority() 获取该URL的权限信息   public String getUserInfo() 获得使用者的信息   public String getRef() 获得该URL的锚   下面的例子中,我们生成一个URL对象,并获取它的各个属性。   import java.net.*;  import java.io.*;   public class ParseURL{  public static void main (String [] args) throws Exception{   URL Aurl=new URL("http://java.sun.com:80/docs/books/");  URL tuto=new URL(Aurl,"tutorial.intro.html#DOWNLOADING");   System.out.println("protocol="+ tuto.getProtocol());  System.out.println("host ="+ tuto.getHost());  System.out.println("filename="+ tuto.getFile());  System.out.println("port="+ tuto.getPort());  System.out.println("ref="+tuto.getRef());  System.out.println("query="+tuto.getQuery());  System.out.println("path="+tuto.getPath());  System.out.println("UserInfo="+tuto.getUserInfo());  System.out.println("Authority="+tuto.getAuthority());  }  }   执行结果为:   protocol=http host =java.sun.com filename=/docs/books/tutorial.intro.html    port=80    ref=DOWNLOADING    query=null    path=/docs/books/tutorial.intro.html    UserInfo=null    Authority=java.sun.com:80 从URL读取WWW网络资源: 当我们得到一个URL对象后,就可以通过它读取指定的WWW资源。这时我们将使用URL的方法openStream(),其定义为:         InputStream openStream();    方法openSteam()与指定的URL建立连接并返回InputStream类的对象以从这一连接中读取数据。例:public class URLReader {  public static void main(String[] args) throws Exception {                       //声明抛出所有例外    URL tirc = new URL("http://www.tirc1.cs.tsinghua.edu.cn/");                       //构建一URL对象    BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));    //使用openStream得到一输入流并由此构造一个BufferedReader对象    String inputLine;    while ((inputLine = in.readLine()) != null)                  //从输入流不断的读数据,直到读完为止       System.out.println(inputLine); //把读入的数据打印到屏幕上    in.close(); //关闭输入流  }} 通过URLConnetction连接WWW: 通过URL的方法openStream(),我们只能从网络上读取数据,如果我们同时还想输出数据,例如向服务器端的CGI程序发送一些数据,我们必须先与URL建立连接,然后才能对其进行读写,这时就要用到类URLConnection了。CGI是公共网关接口(Common Gateway Interface)的简称,它是用户浏览器和服务器端的应用程序进行连接的接口,有关CGI程序设计,请读者参考有关书籍。   类URLConnection也在包java.net中定义,它表示Java程序和URL在网络上的通信连接。当与一个URL建立连接时,首先要在一个URL对象上通过方法openConnection()生成对应的URLConnection对象。例如下面的程序段首先生成一个指向地址http://edu.chinaren.com/index.shtml的对象,然后用openConnection()打开该URL对象上的一个连接,返回一个URLConnection对象。如果连接过程失败,将产生IOException. Try{    URL netchinaren = new URL ("http://edu.chinaren.com/index.shtml");    URLConnectonn tc = netchinaren.openConnection();  }catch(MalformedURLException e){ //创建URL()对象失败    }catch (IOException e){ //openConnection()失败    }   类URLConnection提供了很多方法来设置或获取连接参数,程序设计时最常使用的是getInputStream()和getOutputStream(),其定义为:     InputSteram getInputSteram();     OutputSteram getOutputStream();   通过返回的输入/输出流我们可以与远程对象进行通信。看下面的例子:  URL url =new URL ("http://www.javasoft.com/cgi-bin/backwards");   //创建一URL对象  URLConnectin con=url.openConnection();   //由URL对象获取URLConnection对象  DataInputStream dis=new DataInputStream (con.getInputSteam());   //由URLConnection获取输入流,并构造DataInputStream对象  PrintStream ps=new PrintSteam(con.getOutupSteam());  //由URLConnection获取输出流,并构造PrintStream对象  String line=dis.readLine(); //从服务器读入一行  ps.println("client…"); //向服务器写出字符串 "client…"    其中backwards为服务器端的CGI程序。实际上,类URL的方法openSteam()是通过URLConnection来实现的。它等价于    openConnection().getInputStream();    基于URL的网络编程在底层其实还是基于下面要讲的Socket接口的。WWW,FTP等标准化的网络服务都是基于TCP协议的,所以本质上讲URL编程也是基于TCP的一种应用。 --------------------------基于Socket(套接字)的低层次Java网络编程  Socket通讯 网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。   在传统的UNIX环境下可以操作TCP/IP协议的接口不止Socket一个,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。   说Socket编程是低层次网络编程并不等于它功能不强大,恰恰相反,正因为层次低,Socket编程比基于URL的网络编程提供了更强大的功能和更灵活的控制,但是却要更复杂一些。由于Java本身的特殊性,Socket编程在Java中可能已经是层次最低的网络编程接口,在Java中要直接操作协议中更低的层次,需要使用Java的本地方法调用(JNI),在这里就不予讨论了。 -------------------Socket通讯的一般过程 前面已经提到Socket通常用来实现C/S结构。   使用Socket进行Client/Server程序设计的一般连接过程是这样的:Server端Listen(监听)某个端口是否有连接请求,Client端向Server端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client端都可以通过Send,Write等方法与对方通信。    对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:  (1) 创建Socket;  (2) 打开连接到Socket的输入/出流;  (3) 按照一定的协议对Socket进行读/写操作;  (4) 关闭Socket.   第三步是程序员用来调用Socket和实现程序功能的关键步骤,其他三步在各种程序中基本相同。   以上4个步骤是针对TCP传输而言的,使用UDP进行传输时略有不同,在后面会有具体讲解。 ----------------创建Socket: java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。其构造方法如下:  Socket(InetAddress address, int port);  Socket(InetAddress address, int port, boolean stream);  Socket(String host, int prot);  Socket(String host, int prot, boolean stream);  Socket(SocketImpl impl)  Socket(String host, int port, InetAddress localAddr, int localPort)  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)  ServerSocket(int port);  ServerSocket(int port, int backlog);  ServerSocket(int port, int backlog, InetAddress bindAddr)   其中address、host和port分别是双向连接中另一方的IP地址、主机名和端口号,stream指明socket是流socket还是数据报socket,localPort表示本地主机的端口号,localAddr和bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既可以用来创建serverSocket又可以用来创建Socket。count则表示服务端所能支持的最大连接数。例如:  Socket client = new Socket("127.0.0.1", 80);  ServerSocket server = new ServerSocket(80);   注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。   在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建Socket或ServerSocket是必须捕获或抛出例外。 ----------------客户端的Socket 下面是一个典型的创建客户端Socket的过程。   try{     Socket socket=new Socket("127.0.0.1",4700);      //127.0.0.1是TCP/IP协议中默认的本机地址   }catch(IOException e){     System.out.println("Error:"+e);   }-----------------服务器端的ServerSocket: 下面是一个典型的创建Server端ServerSocket的过程。  ServerSocket server=null;  try {     server=new ServerSocket(4700);      //创建一个ServerSocket在端口4700监听客户请求  }catch(IOException e){     System.out.println("can not listen to :"+e);  }  Socket socket=null;  try {    socket=server.accept();     //accept()是一个阻塞的方法,一旦有客户请求,它就会返回一个Socket对象用于同客户进行交互  }catch(IOException e){    System.out.println("Error:"+e);  }   以上的程序是Server的典型工作模式,只不过在这里Server只能接收一个请求,接受完后Server就退出了。实际的应用中总是让它不停的循环接收,一旦有客户请求,Server总是会创建一个服务线程来服务新来的客户,而自己继续监听。程序中accept()是一个阻塞函数,所谓阻塞性方法就是说该方法被调用后,将等待客户的请求,直到有一个客户启动并请求连接到相同的端口,然后accept()返回一个对应于客户的socket。这时,客户方和服务方都建立了用于通信的socket,接下来就是由各个socket分别打开各自的输入/输出流。 ------------打开输入/出流 类Socket提供了方法getInputStream ()和getOutStream()来得到对应的输入/输出流以进行读/写操作,这两个方法分别返回InputStream和OutputSteam类对象。为了便于读/写数据,我们可以在返回的输入/输出流对象上建立过滤流,如DataInputStream、DataOutputStream或PrintStream类对象,对于文本方式流对象,可以采用InputStreamReader和OutputStreamWriter、PrintWirter等处理。   例如:  PrintStream os=new PrintStream(new BufferedOutputStreem(socket.getOutputStream()));  DataInputStream is=new DataInputStream(socket.getInputStream());  PrintWriter out=new PrintWriter(socket.getOutStream(),true);  BufferedReader in=new ButfferedReader(new InputSteramReader(Socket.getInputStream())); ----------------关闭Socket 每一个Socket存在时,都将占用一定的资源,在Socket对象使用完毕时,要其关闭。关闭Socket可以调用Socket的Close()方法。在关闭Socket之前,应将与Socket相关的所有的输入/输出流全部关闭,以释放所有的资源。而且要注意关闭的顺序,与Socket相关的所有的输入/输出该首先关闭,然后再关闭Socket。  os.close();  is.close();  socket.close();   尽管Java有自动回收机制,网络资源最终是会被释放的。但是为了有效的利用资源,建议读者按照合理的顺序主动释放资源。 ----------------简单的Client/Server程序设计   下面我们给出一个用Socket实现的客户和服务器交互的典型的C/S结构的演示程序,读者通过仔细阅读该程序,会对前面所讨论的各个概念有更深刻的认识。程序的意义请参考注释。  1. 客户端程序   import java.io.*;  import java.net.*;  public class TalkClient {    public static void main(String args[]) {      try{        Socket socket=new Socket("127.0.0.1",4700);         //向本机的4700端口发出客户请求        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));        //由系统标准输入设备构造BufferedReader对象        PrintWriter os=new PrintWriter(socket.getOutputStream());        //由Socket对象得到输出流,并构造PrintWriter对象        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));        //由Socket对象得到输入流,并构造相应的BufferedReader对象        String readline;        readline=sin.readLine(); //从系统标准输入读入一字符串        while(!readline.equals("bye")){         //若从标准输入读入的字符串为 "bye"则停止循环          os.println(readline);           //将从系统标准输入读入的字符串输出到Server          os.flush();           //刷新输出流,使Server马上收到该字符串          System.out.println("Client:"+readline);           //在系统标准输出上打印读入的字符串          System.out.println("Server:"+is.readLine());           //从Server读入一字符串,并打印到标准输出上          readline=sin.readLine(); //从系统标准输入读入一字符串        } //继续循环        os.close(); //关闭Socket输出流        is.close(); //关闭Socket输入流        socket.close(); //关闭Socket      }catch(Exception e) {        System.out.println("Error"+e); //出错,则打印出错信息      }  }}  2. 服务器端程序   import java.io.*;  import java.net.*;  import java.applet.Applet;  public class TalkServer{    public static void main(String args[]) {      try{        ServerSocket server=null;        try{           server=new ServerSocket(4700);         //创建一个ServerSocket在端口4700监听客户请求        }catch(Exception e) {          System.out.println("can not listen to:"+e);         //出错,打印出错信息        }         Socket socket=null;        try{          socket=server.accept();           //使用accept()阻塞等待客户请求,有客户          //请求到来则产生一个Socket对象,并继续执行        }catch(Exception e) {          System.out.println("Error."+e);           //出错,打印出错信息        }        String line;        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));         //由Socket对象得到输入流,并构造相应的BufferedReader对象        PrintWriter os=newPrintWriter(socket.getOutputStream());         //由Socket对象得到输出流,并构造PrintWriter对象        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));         //由系统标准输入设备构造BufferedReader对象         System.out.println("Client:"+is.readLine());         //在标准输出上打印从客户端读入的字符串        line=sin.readLine();         //从标准输入读入一字符串        while(!line.equals("bye")){         //如果该字符串为 "bye",则停止循环          os.println(line);           //向客户端输出该字符串          os.flush();           //刷新输出流,使Client马上收到该字符串          System.out.println("Server:"+line);           //在系统标准输出上打印读入的字符串          System.out.println("Client:"+is.readLine());          //从Client读入一字符串,并打印到标准输出上          line=sin.readLine();           //从系统标准输入读入一字符串        }  //继续循环        os.close(); //关闭Socket输出流        is.close(); //关闭Socket输入流        socket.close(); //关闭Socket        server.close(); //关闭ServerSocket      }catch(Exception e){        System.out.println("Error:"+e);         //出错,打印出错信息      }    }  } --------------------支持多客户的client/server程序设计: 前面提供的Client/Server程序只能实现Server和一个客户的对话。在实际应用中,往往是在服务器上运行一个永久的程序,它可以接收来自其他多个客户端的请求,提供相应的服务。为了实现在服务器方给多个客户提供服务的功能,需要对上面的程序进行改造,利用多线程实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听到客户请求,服务器就会启动一个专门的服务线程来响应该客户的请求,而服务器本身在启动完线程之后马上又进入监听状态,等待下一个客户的到来。 例:ThreadedEchoServer -----------------什么是Datagram: 所谓数据报(Datagram)就跟日常生活中的邮件系统一样,是不能保证可靠的寄到的,而面向链接的TCP就好比电话,双方能肯定对方接受到了信息。在本章前面,我们已经对UDP和TCP进行了比较,在这里再稍作小节:   TCP,可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。  UDP,不可靠,差错控制开销较小,传输大小限制在64K以下,不需要建立连接。   总之,这两种协议各有特点,应用的场合也不同,是完全互补的两个协议,在TCP/IP协议中占有同样重要的地位,要学好网络编程,两者缺一不可。 -------------------Datagram通讯的表示方法:DatagramSocket;DatagramPacket 包java.net中提供了两个类DatagramSocket和DatagramPacket用来支持数据报通信,DatagramSocket用于在程序之间建立传送数据报的通信连接, DatagramPacket则用来表示一个数据报。先来看一下DatagramSocket的构造方法:   DatagramSocket();   DatagramSocket(int prot);   DatagramSocket(int port, InetAddress laddr)    其中,port指明socket所使用的端口号,如果未指明端口号,则把socket连接到本地主机上一个可用的端口。laddr指明一个可用的本地地址。给出端口号时要保证不发生端口冲突,否则会生成SocketException类例外。注意:上述的两个构造方法都声明抛弃非运行时例外SocketException,程序中必须进行处理,或者捕获、或者声明抛弃。 用数据报方式编写client/server程序时,无论在客户方还是服务方,首先都要建立一个DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket类对象作为传输数据的载体。下面看一下DatagramPacket的构造方法 :   DatagramPacket(byte buf[],int length);   DatagramPacket(byte buf[], int length, InetAddress addr, int port);   DatagramPacket(byte[] buf, int offset, int length);   DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port);   其中,buf中存放数据报数据,length为数据报中数据的长度,addr和port旨明目的地址,offset指明了数据报的位移量。   在接收数据前,应该采用上面的第一种方法生成一个DatagramPacket对象,给出接收数据的缓冲区及其长度。然后调用DatagramSocket 的方法receive()等待数据报的到来,receive()将一直等待,直到收到一个数据报为止。  DatagramPacket packet=new DatagramPacket(buf, 256);  Socket.receive (packet);   发送数据前,也要先生成一个新的DatagramPacket对象,这时要使用上面的第二种构造方法,在给出存放发送数据的缓冲区的同时,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的方法send()实现的,send()根据数据报的目的地址来寻径,以传递数据报。  DatagramPacket packet=new DatagramPacket(buf, length, address, port);  Socket.send(packet); 在构造数据报时,要给出InetAddress类参数。类InetAddress在包java.net中定义,用来表示一个Internet地址,我们可以通过它提供的类方法getByName()从一个表示主机名的字符串获取该主机的IP地址,然后再获取相应的地址信息。 -------------基于UDP的简单的Client/Server程序设计: 可以看出使用UDP和使用TCP在程序上还是有很大的区别的。一个比较明显的区别是,UDP的Socket编程是不提供监听功能的,也就是说通信双方更为平等,面对的接口是完全一样的。但是为了用UDP实现C/S结构,在使用UDP时可以使用DatagramSocket.receive()来实现类似于监听的功能。因为receive()是阻塞的函数,当它返回时,缓冲区里已经填满了接受到的一个数据报,并且可以从该数据报得到发送方的各种信息,这一点跟accept()是很相象的,因而可以根据读入的数据报来决定下一步的动作,这就达到了跟网络监听相似的效果。 Email:allan_sun@yeah.net


↑返回目录
前一篇: J2SE 1.4 中assertion 功能介绍(转载)
后一篇: 如何才算掌握Java(J2SE篇)(摘自CSDN)