这是Java笔记第三篇内容,同样先来看一下这次内容的思维导图吧!这张思维导图里面是我想写内容的提纲,没有涉及到具体的东西,要看具体的内容,继续往下看哦~~~///(v)\~~~
异常指的是程序运行时出现的非正常情况。可能导致程序发生非正常情况的原因很多,如数组下标越界,算术运算被0除,空指针访问,试图访问不存在的文件等。
说明:
(1)try 语句块用来启动Java的异常处理机制。 一个try可以引导多个catch块。
(2)异常发生后,try 块中的剩余语句将不再执行。
(3)异常对象是依靠以catch语句为标志的异常处理语句块来捕捉和处理的。catch部分的语句块中的代码执行的条件是,首先在try块中发生了异常,其次是异常的类型与catch要捕捉的一致,在此情况下,运行系统会将异常对象传递给catch中的参变量,在catch块中可以通过该对象获取异常的具体信息。
(4)在该结构中,可以无finally部分,但如果存在,则无论异常发生否,finally 部分的语句均要执行。即便是try或catch块中含有退出方法的语句return,也不能阻止finally代码块的执行,在进行方法返回前要先执行finally块。除非执行中遇到System.exit(0)将停止程序运行,这种情形不会执行finally块。
多异常处理是通过在一个try块后面定义若千个catch 块来实现的,每个catch块用来接收和处理一种特定的异常对象。 每个catch块有一个异常类名作为参数。一个异常对象能否被-一个catch语句块所接收,主要看该异常对象与catch块的异常参数的匹配情况。
(5)同一个 try 对应有多个 catch 块,还要注意 catch 的排列次序。
排列次序应该为:子类异常在前,父类异常在后。
自定义异常类设计
抛出异常
所有系统定义的运行异常都可以由系统在运行程序过程中自动抛出。
而用户设计的异常,则要在程序中通过 throw 语句抛出。异常本质上是对象,因此 throw 关键词后面跟的是new运算符来创建一个异常对象。
如果某一方法中有异常抛出,有两种选择:
一是在方法内对异常进行捕获处理;
二是在方法中不处理异常,将异常处理交给外部调用程序,通常在方法头使用 throws 子句列出该方法可能产生哪些异常。
注意:
在编写类继承代码时,子类在覆盖父类带throws子句的方法时,子类的方法声明的throws子句抛出的异常不能超出父类方法的异常范围。换句话说,子类方法抛出的异常可以是父类方法中抛出异常的子类,子类方法也可以不抛出异常。如果父类方法没有异常声明,则子类的覆盖方法也不能出现异常声明。
多进程
大多数操作系统允许创建多个进程。当一个程序因等待网络访问或用户输入而被阻塞时,另一个程序还可以运行,这样就增加了资源利用率。但是进程切换要占用较多的处理器时间和内存资源,也就是多进程开销大。而且进程间的通信也不方便,大多数操作系统不允许进程访问其他进程的内存空间。(进程内存不共享)
多线程
多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务。因为线程只能在单个进程的作用域内活动,所以创建线程比创建进程要廉价得多,同一类线程共享代码和数据空间,每个线程有独立的运行栈,线程切换的开销小。因此多线程编程在现代软件设计中被大量采用。(线程内存共享)
线程的状态
Java语言使用Thread 类及其子类的对象来表示线程,新建的线程在它的一个完整的生命周期中通常要经历如下5种状态:新建状态、就绪状态、运行状态、阻塞状态、终止状态。
首先,一个线程通过对象创建方式建立,线程对象通过调用start()方法进入到“就绪状态”,一个处于“就绪状态”下的线程将有机会等待调度程序安排CPU时间片进入到“运行状态”。
在“运行状态”的线程,根据情况有如下3种可能的走向:
(1) 时间片执行时间用完它将重新回到“就绪状态”,等待新的调度运行机会。
(2)线程的run()方法代码执行完毕将进入到“终止状态”。
(3)线程可能因某些事件的发生或者等待某个资源而进入到“阻塞状态”。阻塞条件解除后线程将进入“就绪状态”。
线程调度与优先级
Java提供一个线程调度器来负责线程调度, Java 采用抢占式调度策略,在程序中可以给每个线程分配一个线程优先级,优先级高的线程优先获得调度。对于优先级相同的线程,根据在等待队列的排列顺序按“先到先服务”原则调度,每个线程安排一个时间片,执行完时间片将轮到下一线程。
下面几种情况下,当前线程会放弃CPU:
(1)当前时间片用完。
(2)线程在执行时调用了yield()或sleep()方法主动放弃。
(3)进行I/O访问,等待用户输入,导致线程阻塞;或者为等候一个条件变量,线程调用wai()方法。
(4)有高优先级的线程参与调度。
用 Java 编写多线程代码有两种方式:
第1种方式是直接继承 Java 的线程类Thread;
第2种方式是实现 Runnable 接口。
无论采用哪种方式均需要在程序中编写 run() 方法,线程在运行时要完成的任务在该方法中实现。
Thread 类简介
Thread 类综合了 Java 程序中一个线程需要拥有的属性和方法,它的构造方法为:
public Thread (ThreadGroup group,Runnable target,String name)
其中,group 指明该线程所属的线程组;
target 为实际执行线程体的目标对象,它必须
实现接口Runnable;
name为线程名。
线程组是为了方便访问一组线程引入的,例如,通过执行线程组的interrupt()方法,可以中断该组所有线程的执行,但如果当前线程无权修改线程组时将产生异常。实际应用中较少用到线程组。
Thread 类的主要方法及功能说明:
继承 Thread 类实现多线程
Thread 类封装了线程的行为,继承 Thread 类须重写run()方法实现线程的任务。注意, 程序中不要直接调用此方法,而是调用线程对象的start()方法启动线程,让其进入可调度状态,线程获得调度时将自动执行run()方法。
实现 Runnable 接口编写多线程
由于 Java 的单重继承限制,有些类必须继承其他某个类的同时又要实现线程的特性。
这时可通过实现 Runnable 接口的方式来满足两方面的要求。Runnable 接口只有一个方法run(),它就是线程运行时要执行的方法,只要将具体代码写入其中即可。
使用 Thread 类的构造函数public Thread(Runnable target);可以将一个 Runnable 接口对象传递给线程,线程在调度执行其run()方法时将自动调用 Runnable 接口对象的run()方法。
Thread 类本身实现了 Runnable 接口,从其run()方法的设计可看出线程调度时会自动执行 Runnable 接口对象的run()方法。
上面的文字不太好懂,下面贴了两个的代码,主要的区别看红色的部分,希望对你们有帮助。
临界资源问题
多个线程共享的数据称为临界资源,由于是线程调度程序负责线程的调度,程序员无法精确控制多线程的交替次序,如果没有特殊控制,多线程对临界资源的访问将导致数据的不一致性。(以堆栈操作为例,涉及进栈和出栈两个操作。)
为避免此种情况,可以采用synchronized 给调用方法的对象加锁,保证一个方法处理的对象资源不会因其他方法的执行而改变。synchronized 关键字的使用方法有如下两种:
(1)用在对象前面限制一段代码的执行,表示执行该段代码必须取得对象锁。
(2)在方法前面,表示该方法为同步方法,执行该方法必须取得对象锁。
wait() 和 notify() 方法
这两个方法配套使用,wait()方法使得线程进入阻塞状态,执行notify()方法时将释放相应对象占用的锁,从而可使因对象资源锁定而处于等待的线程得到运行机会。
wait()方法有两种形式,
一种允许指定以毫秒为单位的一段时间作为参数; 另一种没有参数。
前者当对应的notify()方法被调用或者超出指定时间时使线程重新进入可执行状态,后者则必须由对应的notify()方法将线程唤醒。
因调用wait()方法而阻塞的线程将被加入到一个特殊的对象等待队列中,直到调用该wait()方法的对象在其他线程中调用notify()或notifyAll()方法,这种等待才能解除。
这里要注意,notify()方法是从等待队列中随机选择一个线程唤醒,而notifyAll()方法则将使等待队列中的全部线程解除阻塞。
注意: wait()方法与notify()方法在概念上有如下特征:
(1)这对方法必须在synchronized 方法或块中调用,只有在同步代码段中才存在资源锁定。
(2)这对方法直接隶属于Object类,而不是Thread类。也就是说,所有对象都拥有这一对方法。
死锁
线程因等待某个条件而阻塞,而该条件因某个原因不可能发生,这时线程处于死锁状态。
suspend()和resume()方 法天生容易发生死锁。调用suspend()时,目标线程会停下来,
但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被
“挂起”的线程恢复运行。
对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成令人难堪的死锁。因此从JDK1.2 开始就建议不再使用suspend()和resume(),更强调使用wait()和notify()方法。可以在自己的线程类中置入一个标志变量,指出线程应该活动还是挂起,若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。
java.util.Timer 也叫定时器,实际上是个线程,可定时调度执行 TimerTask 类型任务的对象。
TimeTask 是一个抽象类,实际任务对象是其子类对象,TimeTask 的子类的 run() 方法用来安排任务的具体执行逻辑,任务每次被调度执行将执行run()方法。
通过 Timer 类的 schedule() 方法安排任务的执行时间,以下为schedule()方法的几种形态。其中,参数task代表任务对象,time 代表启动时间,delay 代表延时,period 代表间隔时间。
schedule(TimerTask task, long delay):从现在起过delay毫秒执行任务一次。
schedule(TimerTask task, Date time):在指定时间执行任务一次 。
scheduleAtFixedRate(TimerTask task, long delay, long period):从现在起过delay毫秒以后,每隔period亳秒执行任务一次。
scheduleAtFixedRate(TimerTask task, Date time, long period):从time时刻开始,每隔period毫秒执行任务一次。
I/O设备与文件
外部设备分为两类:存储设备与输入/输出设备。
存储设备包括硬盘,软盘,光盘等,在这类设备中,数据以文件的形式进行组织。
在操作系统中,将输入/输出设备也看作一类特殊文件,从数据操作的角度将,文件内容可以被看作是字节的序列。
根据数据的组织方式,文件可以分为文本文件和二进制文件,文本文件存放的是ASCII码(或其他编码)表示的字符,而二进制文件则是具有特定结构的字节数据。
流的概念
Java的输入/输出是以流的方式来处理的,流是输入/输出操作中流动的数据系列;流系列中的数据有未经加工的原始二进制数据,也有经过特定包装过滤处理的格式数据。流式输入、输出的特点是数据的获取和发送均沿数据序列顺序进行。
输出流是向存储介质或数据通道中写入数据,而输入流是从存储介质或数据通道中读取数据。
流具有以下几点特性。
(1)==先进先出:==最先用输出流写入到存储介质的数据最先被输入流读取到。
(2)==顺序存取:==写入和读出数据均按顺序逐个字节进行,不能随机访问中间的数据。
(3)==只读或只写:==每个流只能是输入流或输出流的一种,不能同时具备两个功能,在一个数据传输通道中,如果既要写入数据,又要读数据,则要分别提供两个流。
总的来说,Java API提供了两套流来处理输入/输出,
一套是面向字节的流,数据的处理是以字节为基本单位;
另一套是面向字符的流,用于字符数据的处理。
面向字节的输入流
面向字节的输入流类都是类 InputStream 的子类。
类 InputStream 是一个抽象类,定义了如下方法:
public int read():读一个字节,返回读到字节的 in t表示方式,读到流的未尾时返回-1。
public int read(byte b[ ]):读多个字节到字节数组,返回结果为读到的实际字节个数,当输入流中无数据可读时返回-1。
public int read(byte[ ] b, int off, int len):从输入流读指定长度的数据到字节数组,数据从字节数组的off处开始存放,当输入流中无数据可读时返回-1。
public long skip(long n):指针跳过n个字节,定位输入位置指针的方法。
public void mark():在当前位置指针处做一标记。
public void reset():将位置指针返回标记处。
public void close():关闭流。
数据的读取通常按照顺序逐个字节进行访问,在某些特殊情况下,要重复处理某个字节可通过mark()加标记,以后用reset()返回该标记处再处理。
面向字节的输出流
面向字节的输出流都是类 OutputStream 的后代类。
类 OutputStream 是一个抽象类,含一套所有输出流均需要的方法。
public void write(int b):将参数b的低字节写入输出流。
public void write(byte b[ ]):将字节数组全部写入输出流。
public void flush():强制将缓冲区数据写入输出流对应的外设。
public void close():关闭输出流。
如果使用字节流的访问方式读某个包含汉字的文本文件,读到的数据转换为字符会出现乱码,
因为汉字字符需要存储两个字节的编码数据,如果逐个字节的进行读取,这两个字节分开读取逐个解析就会形成乱码。
面向字符的输入流
面向字符的输入流类都是类 Reader 的后代。
Reader 类是一个抽象类,提供的方法与InputStream 类似,只是将基于 byte 的参数改为基于 char。下面列出了几个常用的方法。
public int read():从流中读一个字符, 返回字符的整数编码,如果读至流的末尾,则返回 -1。
public int read(char[ ] b, int off, int len):从输入流读指定长度的数据到字符数组,数据从字节数组的 off 处开始存放,如果输入流无数据可读,则返回 -1。
public int read(char b[ ]):等价于read(buf,0,buf.length)形式。
public long skip(long n):指针跳过n个字符,定位输入位置指针的方法。
面向字符的输出流
面向字符的输出流类都是类 Writer 的后代。
Writer 类是一个抽象类,提供的方法与OutputStream 类似,只是将基于 byte 的参数改为基于 char。下面列出了常用的几个方法。
public void write(int c):往字符输出流写入一个字符,它是将整数的低16位对应的数据写入流中,高16位将忽略。
public void write(char[ ] cbuf):将一个字符数组写入到流中。
public void write(String str):将一个字符串写入到流中。
FileWriter类的使用:该类的直接父类是OutputStreamWriter,后者又继承Writer类。FileWriter 的常用构造方法如下。
FileWriter(String fileName):根据文件名构造一个 FileWriter对象。
FileWriter(String fleName, boolean append):第1个参数为文件名,第2个参数用于指示是否可以往文件中添加数据。
如果想要获得文件的信息或进行文件的复制、删除、重命名等操作,则需要使用 File 类的方法。
转换流 InputStreamReader 和OutputStreamWriter 完成字符与字符编码字节的转换,在字节流和字符流间架起了一道桥梁。 类 FileReader 和 FileWriter 分别是两个转换流的子类,用于实现对文本文件的读写访问。
前面介绍的文件访问均是顺序访问,对同一文件操作只限于读操作或者写操作,不能同时进行,而且只能按记录顺序逐个读或逐个写。==RandomAccessFile 类提供了对流进行随机读写的能力。==该类实现了 DataInput 和DataOutput 接口,因此,可使用两接口中定义的所有方法实现数据的读写操作。为支持流的随机读写访问,该类还添加定义了如下方法。
long getFilePointer():返回当前指针。
void seek(long pos):将文件指针定位到一个绝对地址。地址是相对于文件头的偏移量,地址0表示文件的开头。
long length():返回文件的长度。
setLength(long newLength):设置文件的长度,在删除记录时可以采用,如果文件的长度大于设定值,则按设定值设定新长度,删除文件多余部分;如果文件长度小于设定值,则对文件扩展,扩充部分内容不定。
RandomAccesFile 类的构造方法如下。
public RandomAccessFile(String name,String mode)
public RandomAccessFile(File file,String mode)
其中,第1个参数指定要打开的文件;第2个参数决定了访问文件的权限,其值可以
为’r’或’rw’,'r’表示只读,'rw’表示可进行读和写两种访问。创建 RandomAccessFile 对象时,如果文件存在,则打开文件,如果不存在将创建一个文件。
对象输入流 ObjectInputStream 和对象输出流 ObjectOutputStream 将 Java 流系统扩充到能输入/输出对象,它们提供的 writeObject()和 readObject() 方法实现了对象的串行化(Serialized )和反串行化(Deserialized) 。
要将对象写入到输出流实际上要建立对象和字
节流之间的一种映射关系, 这里利用文件保存对象信息或者利用网络实现对象的传递只是
实现一种轻量级对象持久性。进一步,借助对象串行化技术可以建立对象和数据库之间的
映射,从而将对象中需要持久保存的信息写入到数据库。这是实现重量级对象持久性的常
用方法。
值得注意的是,==为了实现用户自定义对象的串行化,相应的类必须实现 Serializable 接口,==否则不能以对象形式正确写入文件。Serializable 接口中没有定义任何方法。
泛型是 Java 语言的新特性,==泛型的本质是参数化类型,==也就是说,程序中的数据类型被指定为-一个参数。泛型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型在定义时不指定参数的类型,用的时候来确定,这增加了程序的通用性,起到了程序“模板”化的效果。泛型的好处是在编译时检查类型安全,并且所有的强制转换都是自动和隐式的。
泛型在使用中还有一些如下规则和限制:
(1)泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
(2)泛型的类型参数可以有多个,例如,Map
(3)泛型的参数类型可以使用 extends 语句,例如< T extends Number>, extends 并不代表继承,它是类型范围限制,表示T≤Number。
(4)泛型的参数类型还可以是通配符类型。例如,ArrayList extends Number>,表示Number范围的某个类型,其中“?”代表未定类型。
关于Comparable< T >与Comparator< T >接口
Java 提供了 Comparable< T > 与 Comparator < I > 两个接口定义对数组或集合中对象进行排序,实现此接口的对象数组或列表可以通过 Arrays.sort 或 Collections.sort 进行自动排序。
Comparable< T > 接口定义了如下方法:
int compareTo(T obj);
功能是将当前对象与参数 obj 进行比较,在当前对象小于、等于或大于指定对象 obj 时,分别返回负整数、零或正整数。
一个类实现了 Comparable 接口,则表明这个类的对象之间是可以相互比较的,这个类对象组成的集合元素就可以直接使用sort()方法进行排序。
Comparator< T > 接口中定义了如下方法:
int compare(T obj1, T obj2);
当obj1小于、等于或大于obj2时,分别返回负整数、零或正整数。
Comparator 接口可以看成一种对象比较算法的实现,不妨称为“比较算子”,它将算法和数据分离。Comparator 接口常用于以下两种环境:
(1)类的设计师没有考虑到比较问题,因而没有实现Comparable 接口,可以通过Comparator 比较算子来实现排序而不必改变对象本身。
(2)对象排序时要用多种排序标准,如升序、降序等,只要在执行 sort() 方法时用不
同的 Comparator 比较算子就可适应变化。
接口 Collection 处于 Collection API 的最高层,在该接口中定义了所有低层接口或类的公共方法。
Collection 接口
Collection 接口的定义如下:
public interface Collection< E > extends Iterable < E >
如何遍历 Collection 中的每一个 元素?不论Collection 的实际类型如何,它都支持一个iterator() 方法,该方法返回一个迭代子,使用该迭代子即可逐一访问 Collection 中每一个元素。
通过 Iterator 接口定义的 hasNext() 和 next()方法实现从前往后遍历元素,其子接口
ListIterator 进一步增加了 hasPrevious() 和 previous() 方法,实现从后向前遍历访问列表元素。
Iterator 接口定义的方法介绍如下。
(1)boolean hasNext():判断容器中是否存在下一个可访问元素。
(2)Object next():返回要访问的下一个元素,如果没有下一个元素,则引发NoSuchElementException异常。
(3)void remove(): 是 一个可选操作,用于删除迭代子返回的最后一个元素。该方法
只能在每次执行next()后执行一次。
目前,JDK 中并没有提供一个类直接实现 Collection 接口,而是实现它的两个子接口,一个是 Set,另一个是 List。当然子接口继承了父接口的方法。
Set 接口
Set 接口是数学上集合模型的抽象,特点有两个:一是不含重复元素;二是无序。
该接口在 Collection 接口的基础上明确了一些方法的语义。
例如 add(Object) 方法不能插入已经在集合中的元素; addAll(Collection c) 方法将当前集合与收集c的集合进行并运算。
判断集合中重复元素的标准是按对象值比较,即集合中不包括任何两个元素e1和e2,它们之间满足条件el.equals(e2)。
SortedSet 接口用于描述按“自然顺序”组织元素的收集,除继承Set接口的方法外,其中定义的新方法体现存放的对象有序的特点。例如,方法first() 返回SortedSet 中的第一个元素;方法last() 返回SortedSet中的最后一个元素;方法comparator() 返回集合的比较算子,如果该集合使用自然顺序,则返回null。
List 接口
List 接口类似于数学上的数列模型,也称序列。其特点是可含重复元素,而且是有序的。
用户可以控制向序列中某位置插入元素,并可按元素的顺序访问它们。元素的顺序从0开始,最后一个元素为list.size()-1。
其中,elem 代表数据对象,pos 代表操作位置,start_ pos 为起始查找位置。
ArrayList 类和 LinkedList 类
类 ArrayList 是最常用的列表容器类;类ArrayList 内部使用数组存放元素,实现了可
变大小的数组,访问元素效率高,但插入元素效率低。
类 LinkedList 是另一个常用的列表容器类,其内部使用双向链表存储元素,插入元素效率高,但访问元素效率低。
LinkedList 的特点是特别区分列表的头位置和尾位置的概念,提供了在头尾增、删和访问元素的方法。
例如,方法addFirst(Object) 在头位置插入元素。如果需要快速插入、删除元素,应该使用
LinkedList;如果需要快速随机访问元素,则应该使用ArrayList.
Vector 类和 Stack 类
向量(Vector))是List接口中的另一个子类,Vector 非常类似ArrayList,早期的程序
中使用较多,现在通常用 ArrayList 代替。两者一个重要差异是,Vector 是线程同步的,线程在更改向量的过程中将对资源加锁,而ArrayList 不存在此问题。Vector 也是实现了可变大小的对象数组,在容量不够时会自动增加。
以下构造方法规定了向量的初始化容量及容量不够时的扩展增量:
public Vector< E >(int initCapacity, int capacityIncrement);
无参构造方法规定的初始容量为10,增量为10。
用size()方法可获取向量的大小,而 capacity() 方法则用来获取向量的容量。
除了支持 List 接口中定义的方法外,向量还有从早期JDK 保留下来的一些方法。例如:
(1)void addElement(E elem):在向量的尾部添加元素。
(2)void insertElementAt(E obj, int index):在指定位置增加元素。
(3)E elementAt(int index):获取指定位置元素。
(4)void setElementAt(E obj, int index):设置index 处元素为obj。
(5)boolean removeElement(E obj):删除首个与 obj 相同元素。
(6)void removeElementAt(int index):删除index 指定位置的元素。
(7)void removeAllElements():清除向量序列所有元素。
(8)void clear():清除向量序列所有元素。
堆栈(Stack) 是一种特殊的数据结构,其特点是后进先出,也就是新进栈的元素总在栈顶,而出栈时总是先取栈项元素。
堆栈常用操作是进栈和出栈,使用堆栈对象的push() 方法可将一个对象压进栈中,而用pop() 方法将弹出栈顶元素作为返回值。另外还有一个peek() 方法可获取栈项元素但不从栈中删除它,empty() 方法可判断栈是否为空。
除了 Collection 接口表示的这种单一对象数据集合, 对于 “关键字-值” 表示的数据集合在Collection API中提供了 Map 接口。Map接口及其子接口的实现层次如图14-2所示。其中,K为关键字对象的数据类型,而V为映射值对象的数据类型。
==Map 是包括了关键字、值以及它们的映射关系的集合,==可分别使用如下方法得到。
(1)public Set< K > keySet():关键字的集合。
(2)public Collection< V > values():值的集合。
(3)public Set
Map中还定义了对Map数据集合的操作方法,如下所示。
(1)public void clear():清空整个数据集合。
(2)public V get(K key):根据关键字得到对应值。
(3)public V put(K key,V value):加入新的“关键字-值”,如果该映射关系在map中已存在,则修改映射的值,返回原来的值;如果该映射关系在map中不存在,则返回null。
(4)public V remove(Object key):删除Map中关键字所对应的映射关系,返回结果同
put()方法。
(5)public boolean equals(Object obj):判断Map 对象与参数对象是否等价,两个 Map
相等,当且仅当其 entrySet() 得到的集合是一致的。
(6)public boolean containsKey(Object key):判断在Map中是否存在与关键字匹配的映射关系。
(7)public boolean containsValues(Object value):判断在Map中是否存在与键值匹配的映射关系。
实现 Map 接口的类有很多,其中最常用的有 HashMap 和 Hashtable,
两者使用上的最大差别是Hashtable 是线程访问安全的,而HashMap需要提供外同步。
Hashtable 还有个子类Properties,其关键字和值只能是String 类型,经常被用来存储和访问配置信息。
网络上的计算机要互相通信,必须遵循一定的协议。目前使用最广泛的网络协议是应用于Internet 的 TCP/IP协议。
TCP/IP 协议在设计上分为5层:物理层、数据链路层、网络层、传输层、应用层。
不同层有各自的职责,下层为上层提供服务。其中==网络层也称IP层,==主要负责网络主机的定位,实现数据传输的路由选择。IP地址可以唯一地确定Internet 上的一台主机,为了方便记忆,实际应用中常用域名地址,域名与IP地址的转换通过域名解析完成。
传输层负责保证端到端数据传输的正确性,在传输层包含两类典型通信协议:TCP和UDP。
TCP是传输控制协议的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。使用TCP通信,发送方和接收方首先要建立 socket 连接,在客户/服务器通信中,服务方在某个端口提供服务等待客户方的访问连接,连接建立后,双方就可以发送或接收数据。UDP是用户数据报协议的简称,UDP无须建立连接,传输效率高,但不能保证传输的可靠性。
在 java.net 包中提供了丰富的网络功能,例如用 InetAddress 类表示IP地址,用 URL类封装对网络资源的标识访问,用 ServerSocket 和 Socket 类实现面向连接的网络通信,用 DatagramPacket 和DatagramSocket 实现数据报的收发。
InetAddress 类不对外提供构造方法,但提供了一些静态方法来得到 InetAddress 类的实例对象,该类的常用方法如下。
(1)static InetAddress getByName(String host):根据主机名构造一个对应的InetAddress 对象,当主机在网上找不到时,将抛出 UnknownHostException 异常。
(2) static InetAddress getL ocalHost():返回本地主机对应的 InetAddress 对象。
(3) String getHostAddress():返回InetAddress 对象的IP地址。
(4) String getHostName():返回InetAddress 对象的域名。
Java 提供了 Socket 类和 ServerSocke t类分别用于 Client 端和 Server 端的 Socket 通信编程,可将联网的任何两台计算机进行Socket 通信,一台作为服务端,另一台作为客户端。也可以用一台计算机上运行的两个进程分别运行服务端和客户端程序。
Socket 类
Socket 类用在客户端,通过构造一个 Socket类来建立与服务器的连接。
Socket 连接可以是流连接,也可以是数据报连接,这取决于构造 Socket 类时使用的构造方法。
一般使用流连接,流连接的优点是所有数据都能准确、有序地送到接收方,缺点是速度较慢。
Socket 类的构造方法有如下4种。
(1)Socket(String host, int port):构造一个 连接指定主机、指定端口的流 Socket。
(2) Socket(String host, int port, boolean kind):构造 一个连接指定主机、指定端口的Socket类,boolean 类型的参数用来设置是流Socket 还是数据报 Socket。
(3)Socket(InetAddress address, int port):构造一个 连接指定 Internet 地址、指定端口的流 Socket。
(4) Socket(InetAddress address, int port, boolean kind):构造一个连接指定 Internet 地
址、指定端口的 Socket 类,boolean 类型的参数用来设置是流 Socket 还是数据报Socket。
在构造完 Socket 类后,就可以通过 Socket 类来建立输入、输出流,通过流来传送数据。
ServerSocket 类
ServerSocket类用在服务器端,构造方法有两种。
(1)ServerSocket(int):在指定端口上构造一个 ServerSocket 类。
(2) ServerSocket(int, int):在指定端口上构造一个 ServerSocket 类,并进入监听状态,第2个int类型的参数是监听时间长度。
建立连接与数据通信
Socket 通信的基本过程:
首先,在服务器端创建一个ServerSocket 对象,通过执行accept() 方法监听客户连接,这将使线程处于等待状态,然后在客户端建立Socket 类,与某服务器的指定端口进行连接。服务器监听到连接请求后,就可在两者之间建立连接。连接建立之后,就可以取得相应的输入/输出流进行通信。一方的输出流发送的数据将被另一方的输入流读取。
数据报是一种无连接的通信方式,它的速度比较快,但是由于不建立连接,不能保证所有数据都能送到目的地,所以一般用于传送非关键性的数据。发送和接收数据报需要使用 Java 类库中的 DatagramPacket 类和DatagramSocket类。
DatagramPacket 类
DatagramPacket 类是进行数据报通信的基本单位,它包含了需要传送的数据、数据报的长度、IP 地址和端口等。
DatagramPacket 类的构造方法有如下两种。
(1) DatagramPacket(byte [ ] buf, int n):构造一个用 于接收数据报的 DatagramPacket对象,buf 是接收数据报的缓冲区,n是接收的字节数。
(2) DatagramPacket(byte [ ] buf, int n, InetAddress address, int port):构造一个用于发送数据报的 DatagramPacket 对象,buf 是发送数据的缓冲区,n是发送的字节数,address 是接收机器的 Internet 地址,port 是接收的端口号。
也可以通过 DatagramPacket 类提供的方法获取或设置数据报的参数,如地址、端口等,例如通过以下命令设置和获取数据报的收发数据缓冲区。
void setData(byte[ ] buf):设置数据缓冲区。
byte[ ] getData():返回数据缓冲区。
另外,DatagramPacket 类还提供有如下常用方法。
int getLength():可用来返回发送或接收的数据报的长度;
InetAddress getAddress();返回数据报的主机地址。
DatagramSocket 类
DatagramSocket 类用来创建发送或接受数据报的 DatagramSocket 对象,它的构造方法
有如下两种。
DatagramSocket():构造发送数据报的DatagramSocket对象。
DatagramSocket(int port):构造接收数据报的DatagramSocket对象,参数为端口号。
发送和接收过程
要完成发送和接收数据报的过程,需要在接收端构造一个 DatagramPacket 对象指定接
收的缓冲区,建立指定监听端口的DatagramSocket 对象,并通过执行其receive() 方法等待接收数据报。在发送端首先要构造 DatagramPacket 对象,指定要发送的数据、数据长度、接收主机地址及端口号,然后创建 DatagramSocket 对象,利用其send() 方法发送数据报。接收端接收到后,将数据保存到缓冲区。
==在 Internet 上的所有网络资源都是用URL (Uniform Resource Locator)来表示的,一个
URL地址通常由4部分组成,包括协议名、主机名、路径文件、端口号。==例如华东交通大
学Java课程的网上教学地址为hp:/cai.ecju.jx.cn:80/java/index.htm。
以上网址中表示协议为http,
主机地址为cai.ecjtu.jx.cn,
路径文件为java/index.htm,
端口号为80。
当端口号为协议的默认值时可省略,如http的默认端口号是80。
URL类
使用 URL 进行网络通信,就要使用 URL 类创建对象,利用该类提供的方法获取网络数据流,从而读取来自 URL 的网络数据。URL类安排在 java.net 包中,以下为 URL 的几个构造方法及说明。
URL(String protocol, String host, int port, String path):其中, protocol是协议的类型,可以是http、ftp、 file 等; host 是主机名,port 是端口号,path 给出文件名或路径名。
URL(String protocol, String host, String path):参数含义与上相同,使用协议默认端口号。
URL(URL url, String path):利用给定url中的协议、主机,加上path指定的相对路径拼接新URL。
URL(String url):使用URL字符串构造一个URL类。
如果URL信息错误将产生MalformedURLException 异常,在构造完一个 URL 类后,可以使用URL中的openStream() 方法与服务器上的文件建立一个流的连接,但是这个流是输入流(InputStream) ,只能读而不能写。
URL类提供的典型方法如下。
String getFile():取得URL的文件名,它是带路径的文件标识。
String getHost():取得URL的主机名。
0 String getPath():取得URL的路径部分。
int getPort():取得URL的端口号。
URLConnection openConnection():返回代表与URL进行连接的 URLConnection 对象。
InputStream openStream():打开与URL的连接,返回来自连接的输入流。
Object getContent():获取URL的内容。
URLConnection 类
前面介绍的 URL 访问只能读取 URL 数据源的数据,实际应用中,有时需要与 URL 资源进行双向通信,则要用到 URLConnection类。
URLConnection 将创建一个对 指定URL的连接对象,其构造方法 URLConnection(URL)。但构建 URLConnection 对象并未建立与指定URL的连接,还必须使用 URLConnection 类中的 connect() 方法建立连接。另一种与 URL建立双向连接的方法是使用 URL类中的openConnection() 方法, 它返回建立好连接的 URLConnection 对象。
URLConnection类的几个主要方法如下。
void connect():打开URL所指资源的通信链路。
int getContentLength():返回URL的内容长度值。
InputStream getInputStream():返回来自连接的输入流。
OutputStream getOutputStream():返回写往连接的输出流。
用 Applet 方法访问URL资源
Applet 通过 AppletContext 接口与环境进行通信。通过 Applet 类中的 getAppletContext()
方法获取 AppletContext 接口,使用该接口提供的showDocument()方法可以通知浏览器在指定窗口中显示另一个URL的内容。
该访法的具体格式是:
public void showDocument(URL url,String target)
其中,第2个参数规定URL内容显示的窗体帧