• 沒有找到結果。

中文书名:《Java 2 高级程序设计百事通》

N/A
N/A
Protected

Academic year: 2022

Share "中文书名:《Java 2 高级程序设计百事通》 "

Copied!
347
0
0

加載中.... (立即查看全文)

全文

(1)

中文书名:《Java 2 高级程序设计百事通》

英文书名:《The Bestbook For Java 2 Advanced Programming》

作者名:张洪斌

封面其它名字

采用先进的教育思想创作

本文件的内容清单

内容摘要 读者选购指南

自序兼谈本书应用的教育思想

光盘使用说明

(2)

内容摘要

本书讲解了 Java 2 程序设计中重要的高级主题,本书也是作者已出版的著作《Java 程序 设计百事通》续篇。本书的主要内容包括 JavaBeans、Java 安全技术、Java 命名与目录服务、

RMI 远程方法调用、CORBA 公共对象请求代理、RMI-IIOP、Servlet 服务器小程序、JSP 网 页技术和其它主题。本书面向有 Java 程序设计基础,特别是学习过《Java 程序设计百事通》

希望进一步提高 Java 技术水平的读者选用。本书可作为在校学生、中高级技术开发工程师 和其它 IT 技术人员的参考书,也可作为大专院校和培训机构的教学用书。

本书作者拥有先进的教育思想,同时在计算机图书写作方面具备高超的技巧,读者将会 发现这是目前写得最容易阅读、收获最大的论述 Java 程序设计高级主题的著作。

读者选购指南

选购一本电脑书从来就不是件轻松的事,因为同类书实在是琳琅满目、难以取舍。购电 脑书的第一项是看看书的起点或者说是读者对象,不要买了太浅或者太深的书。本书适合有 Java 程序设计基础的读者选购,虽然“基础”一词一般很笼统,难以限定,但是本书恰巧有 一个准确的基础,那就是作者已经出版的著作《Java 程序设计百事通》。如果读者已经学习 过该书或者学习过与该书相当的 Java 知识,那么已经具备了看懂本书的技术基础。实际上 上,本书正是《Java 程序设计百事通》续篇。

选购电脑书的第二项是确定是否需要本书的内容。读者从目录上可以看到,本书包括 JavaBeans、Java 安全技术、Java 命名与目录服务、RMI 远程方法调用、CORBA 公共对象 请求代理、RMI-IIOP、Servlet 服务器小程序、JSP 网页技术等高级主题。另外还有国际化程 序、对象序列化、Reflection 等其它不算高级、也并非基础的主题,其中部分内容是阅读高 级主题所必须的预备知识。另外,本书的最后一章是关于《Java 程序设计百事通》的 FAQ。

上面讲述的是本书的读者对象和内容概貌,接着作者要解释的是本书如何组织和讲解内 容,即本书的写作原则或者指导思想。这就是选购电脑书的第三项:了解作者是怎样讲解书 中知识的。

任何一本书的作者写书时都有指导思想,无论他是否觉察到或者这个指导思想是否恰 当。本书除了具备科技著作的起码要求即技术讲解正确、内容全面完整外,全书的写作思想 和具体的写作方法尤其具有与众不同的特色,使读者不仅看得懂、看得轻松,还看得愉快。

本书的指导思想是 Know Unknown Through Known(通过已知知未知),这是法国教师 Piggysong 的名言和教育思想,意思是人类了解未知的最好方式是通过已知。 关于

Piggysong

和他的思想详见本自序后面的文章。

本书的具体写作策略是站在读者的角度,按照普通人的学习习惯而不是知识本身的固有

体系布局谋篇,全书章节都是按照基本-常用-深入来安排内容结构,突破了传统电脑书不分

主次和轻重缓急的字典式写作惯例。因为每当提到一个名词、概念或者其它知识点,作者要

问自己三个问题:“读者可能学过吗?前面讲过吗?应该在这里讲吗?” ,尽量保证每一句

话、每一个段落的知识只涉及前面的内容而不涉及后面的内容,使读者从第一页看起,基本

(3)

没有看不懂的地方,这就是不用未知解释另外一个未知。万一要提及后面才能详细讲解的知 识,就使用这样的说法:“先照葫芦画瓢,后面还要详细讲解”,以避免读者产生没有讲清楚 的错觉,仅此一点,就可保证读者看得不累。由于本书是《Java 程序设计百事通》的续篇,

因此会涉及到该书的内容。

本书对知识点,不管是一个专业名词还是一章一节的内容,大多评价其实用效果,以便 读者知道如何在实践中运用知识,使读者为应用而学习,而不是为知识而学习。这样的例子 也比比皆是,例如“评论”图标后的内容。

本书对知识点基本采用先举例、从已知知识切入或者先比喻的方式引入,这些都是读者 已知或者很容易“已知”的,作者以为,这是最好的知识点引入方式,类似的例子也不胜枚 举。

本书在语言方面,尽量地运用比喻来讲解知识点,这在科技著作中极为罕见,没有学过 本书内容的读者都可以很容易地在书中找到证据。

除了运用比喻外,本书的语言叙述尽量平白、简洁、生动又不乏幽默,因为作者厌恶以

“专业”术语唬人或者自我炫耀,反喜欢“化神奇为腐朽”,把艰深干涩的电脑知识通俗化、

生动化、趣味化,使读者在阅读时,不仅读得懂、读得轻松,还读得愉快,甚至会发出“Java 不过如此!”的感叹。

上面所述仅是本书的几个突出特点而已,其它特色就不必一一列出了,读者在阅读时,

自然能够体会到。作者相信,读者只要在书店里站着读几分钟,就能够发现本书的几个特色,

如果不是全部特色的话。随便说一句,本书的写作思想和写作方法与《Java 程序设计百事通》

相同,实际上,在《Java 程序设计百事通》出版后,作者也接到很多读者的来信,希望按照 同样的写作原则创作一本内容较深入的 Java 著作,读者的支持也是作者撰写本书的动力之 一。

本书中文书名为“百事通” ,作者已经出版的著作也是用同一名字。 “百事”说明本书的 内容广泛坚实,知识含量高,这个在本自序中首先讲到了,读者从目录中也能够看出来。书 名中的有一个字是“通”,正是作者写作思想所追求的目标,因为一本书,无论讲的是什么、

讲得多么权威、讲得多么深入,如果读者看不懂或者很难看懂,也如废纸一般。

读者可能会注意到作者没有用 “通俗易懂”、“循序渐进”、“快速入门”、“轻松上手”

等来形容本书,因为所有的电脑书都这样自称,而尽管本书当之无愧,却只想用一个“通”

字。为了支持作者的这句“广告词” ,作者还拿出诸多可判定的证据来证明读者读了后,会 真“通”而不是假“通。

其实,书名中的“百事通”源自英文“Bestbook”的发音,因为作者在刚写作时,就希 望创作出最好的电脑书奉献给读者,因此以“Bestbook”作为英文书名,鞭策自己精心构思、

认真写作,不辱此名。

本书的全部程序和有关软件在本书所附的光盘上,光盘的内容可参考后面的“光盘使用 说明” 。如果读者对本书有什么批评和建议,既可以给作者发送电子邮件,也可以在作者的 个人网站上进行交流。

张洪斌

bestbooks@netease.com

http://www.bestpapers.net/

(4)

自序兼谈本书应用的教育思想

Know Unknown Through Known 是十九世纪法国教师 Piggysong 的名言,意思是人类了 解未知的最好方式是通过已知,即 The best way to know what is unknown is through what is known。我是在上大学时,从图书馆借的一本英文名人传中知道这句话的,记得那是一本发 黄的书,好象是解放前的馆藏。我能把这句话记到现在不是因为我当时明白了其中的深邃思 想,而是这句话表现出的英文的优美特点:只有 4 个单词,并且其中的 3 个都是 Know。我 曾竭力地想把它翻译成旗鼓相当的中文,可是至今都没有完全成功,我所做的最好译文是“通 过已知知未知”,意思表现出来了,而且与英文一样也有 3 个“知”,但却用了 7 个汉字(一 般认为汉语译文比英文原文篇幅要少)。

那本英文传记介绍说,作为教师,Piggysong 非常善于教学,他的学生总是能以最快的 速度掌握知识,他用的就是 Through Known 的原则。例如,他大量地使用比喻,把生活中 学生已经了解的事物与要学习的专业知识比较。在做试验时不是像现在的教师那样先讲原 理,而是先做试验,再讲原理。在教学顺序上,他按照内容的深浅程度而不是知识固有的体 系安排,虽然被同行批评教授的知识显得缺少体系和章法,但是他的学生学得快学得好却是 不争的事实,不然怎么上了名人传,让我这个几百年以后的学生也知道他的大名呢?

当时我在看传记时,对他的思想并没有留下太多的感触,因为自己当初并非教育工作者,

只是刚上大学的学生而已,况且他的教学方法就是由简到繁、由浅入深、由具体到抽象之类,

这些大家不都知道吗?很 plain(平白)吗!我学得是计算机专业,得听课还得看书,自然 地感到同样的内容,有些老师和有些书就比别的老师和教材讲得好、讲得更容易懂。现在已 经工作了,主要是通过看书来提高自己,逐渐感到现在的电脑书越来越难看了,一开始认为 也许是电脑技术进步的“恶果”,可是后来逐渐发现并非完全如此。例如很多书的第一章都 是告诉你这个软件有什么强大的功能、新版本比老版本有什么改进,其中会涉及一大堆后面 的章节才能讲解的术语,看得人头皮发麻。试问,还没有学它如何明白它的强大之处?没有 用过旧版本何以明白新版本高明的地方?

笔者至今还记得自己刚学习“文件”这个概念时的艰难历程。笔者刚学电脑时,书中告 诉我:文件用于记录电脑信息(大意)。我不知道文件是什么,当然也不知道信息是什么,用 我不理解的名词来解释另一个不理解的名词岂不是雪上加霜?接着开始讲解文件的命名法,

我不认识老虎却要认老虎这二个字干吗?然后书中又告诉我,文件有可执行文件、文本文件 等类型,既然我不知道什么是老虎又怎么知道什么是东北虎、华南虎呢?书中讲完了全部的 文件概念才开始学习 DOS 命令,这时才明白什么是文件,可是都已经“学”了很多页了。

如果一边学习 DOS 命令一边讲解文件的概念岂不是轻松得多,例如要讲解可执行文件,只 要运行一个游戏文件就可以了。作者也注意到目前很多 Windows 的书也是先讲文件的概念,

再讲文件操作的。

我在专业上学习的同时随便也在研究电脑书的写作方法,慢慢地发现 电脑书写得差的根 本原因就是:应该在后面讲解的知识在讲解前出现, 就像笔者在前面举的文件例子一样。于 是我就想起了 Piggysong 和他的名言,尽管我当时认为多么 plain、多么 simple,可是不是有 很多教育工作者包括他们写的书都没有运用吗?作者已经出版的著作全部采用 Piggysong 的 思想创作,全部以 bestbook 作为英文书名(即 The Bestbook For XXX),结果也是全部重印,

这当然也包括本书的基础篇《Java 程序设计百事通》,这至少可以证明 Piggysong 的思想 good

(不错),如果不是 best(最好)的话。所以作者依然像以前一样“大胆”,还把这本书叫

bestbook,同时作者也希望 Piggysong 的教育思想对其他电脑书作者和教育工作者能有参考

(5)

价值,促进书店里出现更多看得懂、看得轻松、看得愉快的好书。

Piggysong 不是个太有名的人,甚至不能作为教育家流传后世,也许是因为他不太“善 于”总结自己的思想,如果把自己的思想叫做“XXX 思想”或者“XXX 主义”(例如目前 最流行的“建构主义”教育理论) ,名字最好如雷贯耳又莫名其妙并且拌有枯燥加令人困惑 的长篇大论,这样后人才会不知疲倦、兴致勃勃地研究和探讨。他的 Know Unknown Through Known 太白了、太容易理解了,根本不需要什么人做进一步的发挥和解释。不过,笔者认 为,这正是一个教育工作者的追求: 授业的目的不正是解惑,并且解得越快越好吗?

张洪斌

bestbooks@netease.com http://www.bestpapers.net/

光盘使用说明

本书所附光盘包含下列目录:

源程序

本书的源程序均在该目录中,每章的程序分别对应于该目录中的相应子目录。本书全部 程序在支持 Java 2 平台的 JDK1.2.2 和 JDK1.3 上测试通过,并且相信也能在更高的 JDK 版 本和企业版上运行通过。

JDK1.3

JDK1.3 开发软件包,双击文件名执行安装程序即可。

JDK1.3 文档

JDK1.3 的文档,它已经在光盘上安装完毕,读者也可以把整个目录复制到硬盘上。

Bdk1.1

第 10 章所用的程序,双击文件名执行安装程序即可。

fscontext1_2beta3

第 13 章所用的类库。

(6)

jakarta-tomcat-3.2.1

Tomcat 3.2.1 服务器,用于运行第 17 章 Servlet 和 18 章的 JSP 程序。它没有安装程序,

直接把它解压缩到硬盘上即可。

jakarta-servletapi-3.2

支持 Servlet 和 JSP 的类库及其类库的文档,用于第 17 章和第 18 章。它没有安装程序,

直接把它解压缩到硬盘上,类库的文档可以在安装后的目录中浏览,而类库则要按照书中的 讲解把它们安装到适当的位置。

SimpleServletServer

第 17 章 SimpleServletServer 服务器所使用的类库,按照书中的讲解把它们安装到适当 的位置。

ext

作者计算机上的 ext 目录的内容,即 JDK 安装目录的 jre\lib\ext 目录,如果读者编译运 行 17 章和第 18 章的程序有问题,那么可以将该目录的内容复制到自己计算机上的对应目录 中。

第 1 章 网络编程实例

在《Java 程序设计百事通》中,读者已经学习了网络编程的一些基础知识,本章将以例 子的方式,进一步讲解更多、更深入的网络编程技术。在网络时代,读者多学些有关技术总 是有益的。

本章会首先回顾和总结一些将用到的基础概念,然后讲解几种 URL 编程和网络协议的 编程实例,其中也包括创造自己的网络协议并据此编程。

1.1 网络基础概念

本节首先回顾下面的章节需要用到的网络基础概念,这些内容可能是部分读者已经熟悉 的,那么粗粗地浏览一遍就可以了。对于没有太多网络编程经验的读者,毫无疑问,这些内 容是很重要的,希望不要轻易地错过。

读者都知道 Internet 是以 TCP/IP 协议建立的网络,但是在 Internet 上还有 HTTP、TELNET 等协议,那么它们之间的关系是什么?

Internet 上的计算机彼此之间的通信是通过 TCP 或者 UDP 协议,这是针对传输层而言

(7)

的,如图 1.1 所示。

图 1.1 简单的协议层次图

换句话说,HTTP、TELNET 之类的协议是在应用层上的协议,像浏览器就是实现 HTTP 协议的应用产品,它们均建立在传输层协议的基础之上。而 IP 协议则更加底层,Link(连 接层)通常指硬件如生产网卡、网线所要遵守的协议(或者产品标准)。

当用 Java 语言进行网络编程时,读者针对的是应用层,通常不需要了解底层的协议,

用 java.net 软件包中的类库就可以了。但是,要决定程序应该用哪些类,必须了解 TCP 和 UDP 的区别。

1.TCP (Transmission Control Protocol)

TCP 协议的目的是在计算机间建立可靠的连接, “可靠”的含义是保证数据的接收顺序 与发送时的顺序一致,否则就出错。HTTP、TELNET 都是 TCP 协议的例子,实际上多数 Internet 协议采用 TCP。

2.UDP (User Datagram Protocol)

UDP 的情况与 TCP 相反,它不保证发送的数据一定到达目的地,也不保证接收顺序与 发送顺序一致。它发送的数据称为 Datagram。gram 的意思是表示重量的“克”,克在实际生 活中是很小的重量单位,所以 Datagram 表示 UDP 发送接收数据的基本单位。每个 Datagram 独立发送,彼此没有关联,因此速度快,但是不可靠,所谓“欲速而不达”吗。

U 评论:Datagram 好象被某些书翻译成“数据报文”,字面上根本看不出来英文原 意,猛一看这个中文还可能以为和拍电报有什么瓜葛呢。

网络中另外一个著名的中文术语是“套接字”,这是个什么“字”?恐怕连第一个造这 个“字”的人都不知道。这个“字”译自英文的 Socket,其原意是插座、插孔的意思,和 任何“字”都没有关系。

本书在谈到这两个词时,一律使用英文,而不用上述莫名其妙的中文术语。

使用 UDP 协议的例子是 Ping 命令,它用来测试网络连接的情况,如果采用可靠的 TCP 协议,那怎么知道网络连接是否可靠呢?

3.Port

Port 的意思是“端口” 。计算机与网络的连接一般只有一个真实的端口,例如电话线或 者网线。由于网络提供各种各样的服务,例如 HTTP、TELNET 等,为了区分这些服务类型,

就使用“端口”,当然是虚拟的。或者说,一种网络服务对应一个端口,端口一般由数字代

表,例如 HTTP 协议的端口通常是 80。

(8)

常用的网络服务和它们对应的端口如下:

ECHO 7 回送(显示)收到的信息 DISCARD 9 放弃收到的信息

DAYTIME 13 产生目标机上的当地时间 FTP 21 FTP 协议端口

Telnet 23 TELNET 协议端口 SMTP 25 发送邮件协议 SMTP 端口 HTTP 80 WEB 协议端口

POP3 110 POP3 邮件接收协议端口

NNTP 119 网络新闻传输协议(新闻服务器)端口 上面列举的网络服务都是 TCP 协议。

端口以 16 位数字表示,其范围在 0 到 65535 之间。被广泛接受的网络协议(例如上面 这个清单中的就是),使用 0 到 1024 范围内的端口。

自己开发的应用程序如果不遵循上面的协议,那么不能使用 1024 以下的端口号。本章 会给出根据已知网络协议开发应用程序的例子,也会给出按照自己设计的协议开发应用程序 的例子。

在 java.net 软件包中,有些类用于 TCP,有些类用于 UDP。例如 URL、URLConnection、

Socket 和 ServerSocket 类用于 TCP,而 DatagramPacket、DatagramSocket 和 MulticastSocket 用于 UDP,具体情况可参考 Java 文档的说明。

4.URL

URL 用来表示网络地址。在《Java 程序设计百事通》中介绍过创建 URL 对象的方法,

例如下面的代码:

URL url = new URL("http://bestbookedu.top263");

URL 有一种构造方法的语法是:

URL(String protocol, String host, int port, String file)

它有 4 个参数,分别是协议、主机、端口和文件,下面是用这种构造方法建立的 URL 对象:

URL url = new URL("http","bestbookedu.top263.net",

"80",+ "/index.html");

该对象对应的 URL 地址是 http:\\bestbookedu.top263.net:80\index.html。

1.2 URL 类编程实例

通过 URL 类的 getXxx 方法可以得到 URL 对象的协议、文件等信息,下面的程序用了 多个 getXxx 方法:

//c1:ParseURL.java //author:ZhangHongbin

//This program is protected by copyright laws.

//Parse URL.

import java.net.*;

import java.io.*;

public class ParseURL {

public static void main(String[] args) throws Exception

(9)

{

URL url = new URL("http://bestbookedu.top263.net:80"

+ "/index.html#Outline");

System.out.println("protocol = "+url.getProtocol());

System.out.println("host = "+url.getHost());

System.out.println("filename = "+url.getFile());

System.out.println("port = "+url.getPort());

System.out.println("ref = " +url.getRef());

} }

该程序的输出如下:

protocol = http

host = bestbookedu.top263.net filename = /index.html port = 80

ref = Outline

其中 ref 代表文件的某个位置,有时也叫“书签”,它指在一个网页中的链接,即单击 这个链接不是转向另外一个页面,而是跳到该网页的另外一个位置上,例如从页面头部跳到 页面的底部。

下面的程序把网络上的一个 HTML 文件读入到本地的文件中,实际上,程序读的是 HTML 源文件:

//c1:ReadURL2.java //author:ZhangHongbin

//This program is protected by copyright laws.

//Read HTML source codes.

import java.net.*;

import java.io.*;

public class ReadURL2 {

public static void main(String[] args) throws Exception {

URL u = new URL("http://bestbookedu.top263.net/index.htm");

BufferedReader in = new BufferedReader(

new InputStreamReader(u.openStream()));

PrintWriter out=new PrintWriter(

new FileWriter("d:\\javaplus\\index.htm"));

String inputLine;

while ((inputLine = in.readLine()) != null) out.println(inputLine);

in.close();

out.close();

} }

要看这个程序的运行结果,必须打开写入的文件。

% 提醒:读者在运行这个程序时,可能需要更改网络地址、写入文件的位置及其文

件名。

(10)

URL 的 openConnection 方法返回 Connection 对象,代表与这个 URL 的连接,通过 Connection,可以了解 URL 所代表文件的下述内容:

· 编码 Connection 的 getContentEncoding 方法返回该值。

· 长度 Connection 的 getContentLength 方法返回该值。

· 内容的类型 Connection 的 getContentType 方法返回该值。

· 日期 Connection 的 getDate 方法返回该值。

· 头部域的信息 Connection 的 getHeaderField 方法返回该值。

下面是输出 Connection 相关信息的程序:

//c1:ReadURL3.java //author:ZhangHongbin

//This program is protected by copyright laws.

//URL info codes.

import java.net.*;

import java.io.*;

public class ReadURL3 {

public static void main(String[] args) throws Exception {

URL u = new URL("http://bestbookedu.top263.net/index.htm");

URLConnection uc = u.openConnection();

System.out.println("getContentEncoding= "+

uc.getContentEncoding());

System.out.println("getContentLength= "+

uc.getContentLength());

System.out.println("getContentType= "+

uc.getContentType());

System.out.println("getDate= "+

uc.getDate());

System.out.println("getHeaderField= "+

uc.getHeaderField(1));

} }

程序的输出如下:

getContentEncoding= null getContentLength= 21801 getContentType= text/html getDate= 992167164000

getHeaderField= Sun, 10 Jun 2001 09:59:24 GMT

不同的 URL 返回的内容不太一样,上面的程序没有返回编码格式,可能是服务器不支 持该功能的原因。

% 提醒:读者可以对照程序的运行结果和 Java 文档了解 Connection 类方法的细节,

这些内容很简单,本书没有太细地讲解。

(11)

1.3 DatagramSocket 编程实例

在《Java 程序设计百事通》,已经讲解了怎样利用 Socket 编程,那是面向 TCP 协议的。

DatagramSocket 是针对 Datagram 的 Socke 编程,本节要分别编写一对服务器和客户机程序,

其过程和 TCP 协议的 Socket 有相似之处,所以学过作者前一本 Java 著作的读者会比较容易 理解本节的内容。如果读者对阅读本节的内容有困难,不妨阅读作者的前一本 Java 著作即

《Java 程序设计百事通》。

1.3.1 DatagramSocket 服务器程序

编写 DatagramSocket 服务器的步骤如下:

(1)创建 DatagramSocket 对象

下面的代码在 4444 端口上创建了一个 DatagramSocket 对象:

DatagramSocket socket = new DatagramSocket(4444);

(2)创建 DatagramPacket 对象 下面的代码建立了 DatagramPacket:

byte[] buf = new byte[200];

DatagramPacket packet = new DatagramPacket(buf, buf.length);

这两行代码决定从客户机接收的 Datagram 的大小。在 TCP 协议的 Socket 编程中,没有 这一步骤。

(3)准备从客户机接收数据

下面的代码使服务器处于接受客户机访问的状态:

socket.receive(packet);

如果没有客户机访问,那么服务器程序就停留在这里,不执行后面的代码。

(4)接收和处理数据

当客户机发送数据后,数据存放在 packet,可以用下面的代码输出它:

byte[] received=packet.getData();

System.out.println("Received from client= "+new String(received));

(5)向客户机发送数据

服务器同样可以向客户机发送数据,它能从 packet 中得到客户机的地址和端口,然后 建立 DatagramPacket 对象,最后用 send 方法发送数据。

下面是向客户机发送数据的代码片断:

InetAddress address = packet.getAddress();

int port = packet.getPort();

packet = new DatagramPacket(buf, buf.length, address, port);

socket.send(packet);

其中 buf 是存放发送数据的 byte 数组。对上述代码中的细节如 getAddress 是什么意思,

读者可以阅读 Java 文档和《Java 程序设计百事通》中的有关内容。

(6)关闭 DatagramSocket

服务器程序结束后,关闭 DatagramSocket,见下面的代码:

socket.close();

根据上面所述,就可以编写支持多线程的 DatagramSocket 服务器程序了,见下面的程 序清单:

//c1:DatagramServer1.java //author:ZhangHongbin

(12)

//This program is protected by copyright laws.

//Datagram Server.

import java.io.*;

import java.net.*;

import java.util.*;

public class DatagramServer1 extends Thread {

DatagramSocket socket ; DatagramPacket packet;

int client;

public DatagramServer1(DatagramSocket socket,DatagramPacket packet,int client) {

this.packet=packet;

this.socket=socket;

this.client=client;

start();

}

public void run() {

try {

byte[] received=packet.getData();

System.out.println("Received from client= "+

client+" "+new String(received));

String response = "You are client"+client;

byte[] buf = received;

InetAddress address = packet.getAddress();

int port = packet.getPort();

packet = new DatagramPacket(buf, buf.length, address, port);

socket.send(packet);

}

catch (IOException e) {

e.printStackTrace();

} }

public static void main(String[] args)throws Exception {

DatagramSocket socket = new DatagramSocket(4444);

System.out.println("Server Started");

byte[] buf = new byte[200];

int i=1;

try {

while(true) {

DatagramPacket packet = new DatagramPacket(buf, buf.length);

System.out.println("Waiting for client= "+i);

socket.receive(packet);

(13)

new DatagramServer1(socket,packet,i);

i++;

} } finally {

socket.close();

} } }

为了帮助读者看懂程序细节,下面先给出这个服务器程序的运行结果:

Server Started Waiting for client= 1 Waiting for client= 2

Received from client= 1 abc

Waiting for 信息显示它正等待第几个用户的访问。

Received from client 信息说明它从第几个用户那里接收到信息,以及信息是什么。

上面的结果表明程序正等待第二个用户的访问,而第一个用户发送的信息是 abc。

这个服务器服务器程序的功能是每次从客户机接收一行信息,然后再把该信息返回给客 户机。

1.3.2 DatagramSocket 客户机程序

编写 DatagramSocket 客户机程序的步骤与服务器程序类似,其步骤如下:

(1)建立 DatagramSocket 对象 见下面的代码:

DatagramSocket socket = new DatagramSocket();

(2)建立 DatagramPacket 对象 见下面的代码:

DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4444);

其中 buf 是向服务器发送数据的 byte 数组,address 是服务器的地址,4444 是服务器的 端口号。

(3)向服务器发送数据 见下面的代码:

socket.send(packet);

(4)从服务器接收数据

客户机程序可能需要从服务器接收数据,可参考下面的代码:

packet = new DatagramPacket(buf, buf.length);

socket.receive(packet);

其中 buf 是 byte 数组,可以不事先赋值。

(5)处理从服务器接收的数据

下面的代码输出从服务器接收的数据:

String received = new String(packet.getData());

System.out.println("The server returns my messaget: "

+ received);

(6)关闭 DatagramSocket

(14)

见下面的代码:

socket.close();

完整的客户机程序如下:

//c1:DatagramClient.java //author:ZhangHongbin

//This program is protected by copyright laws.

//Datagram Client.

import java.io.*;

import java.net.*;

import java.util.*;

public class DatagramClient {

public static void main(String[] args) throws IOException {

if (args.length != 2) {

System.out.println("Usage:"+

" java DatagramClient <hostname> <message>");

return;

}

// get a datagram socket

DatagramSocket socket = new DatagramSocket();

// send request

System.out.println("My message="+args[1]);

byte[] buf = new byte[200];

buf=args[1].getBytes();

InetAddress address = InetAddress.getByName(args[0]);

DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4444);

socket.send(packet);

// get response

packet = new DatagramPacket(buf, buf.length);

socket.receive(packet);

// display response

String received = new String(packet.getData());

System.out.println("The server returns my message: "

+ received);

socket.close();

} }

客户机程序从命令行输入服务器的计算机名和要发送的信息,然后显示要发送的信息,

最后输出从服务器接收的信息,因为服务器返回的就是客户机发送的信息,两次输出的信息 应该是一致。

客户机程序的运行结果如下:

My message=abc

The server returns my message: abc

% 提醒:这对服务器程序可以在一台计算机上运行,运行要打开一个 DOS 窗口,首

先启动服务器程序,再打开另外一个 DOS 窗口启动客户机程序。

(15)

这对服务器/客户机程序在运行时可能会出点小毛病,下面是服务器接受客户机 3 次访 问的结果:

D:\javaplus>java DatagramServer1 Server Started

Waiting for client= 1 Waiting for client= 2

Received from client= 1 abc

Waiting for client= 3

Received from client= 2 12c

Waiting for client= 4

Received from client= 3 789

下面为客户机程序向服务器程序发送数据的情况:

D:\javaplus>java DatagramClient zhanghongbin abc My message=abc

The server returns my message: abc

D:\javaplus>java DatagramClient zhanghongbin 12 My message=12

The server returns my message: 12

D:\javaplus>java DatagramClient zhanghongbin 789 My message=789

The server returns my message: 789

客户机第一次向服务器输出 3 个字符 abc,服务器和客户机的显示正常。

客户机第二次向服务器输出 2 个字符 12,客户机的显示正常。但是服务器显示收到 12c,

最后一个字符 c 实际上是第一次客户机发送的字符,服务器程序好象没有清除掉,不过它返 回的数据是正确的,因为客户机收到的返回数据与发送数据一致。

客户机第三次向服务器输出 3 个字符 789,服务器和客户机的显示均正常。

读者可以多次运行客户机程序,分别向服务器发送不同长度的字符串,这时就会出现服 务器不能正确显示接收数据的情况,就像上面分析的那样。

出现这种情况的原因可能是服务器程序中下面的语句有问题:

System.out.println("Received from client= "+

client+" "+new String(received));

不知读者认为这个问题是作者程序的 Bug,还是 Java 自己的语句或者方法有 Bug?

1.4 自定义协议的服务器/客户机程序

真实的服务器程序多是根据公认的协议例如 HTTP、SMTP 等来设计,为了方便对“协 议”一词感到陌生的读者学习,作者编写了一个拥有极简单协议的多线程服务器程序,它根 据《Java 程序设计百事通》第 13 章的 Server3.java 程序修改而来:

//c1:Server4.java //author:ZhangHongbin

(16)

//This program is protected by copyright laws.

//Self-defined protocol server.

import java.io.*;

import java.net.*;

public class Server4 extends Thread {

Socket socket1;

public Server4( Socket s) {

socket1=s;

start(); // Calls run() }

public void run() {

try {

BufferedReader in=new BufferedReader(

new InputStreamReader(

socket1.getInputStream()));

PrintWriter out=new PrintWriter(

new BufferedWriter(

new OutputStreamWriter(

socket1.getOutputStream())), true);

out.println("Welcome to server");

String str=in.readLine();

if (str.startsWith("HELP"))

out.println("Your Help Command Accepted");

else

out.println("Error Command: "+str);

System.out.println("closing...");

}

catch(IOException e) {

System.err.println("IO Exception");

} finally {

try {

socket1.close();

}

catch(IOException e) {

System.err.println("Socket not closed");

} } }

static final int PORT=23;

public static void main(String[] args)throws IOException

(17)

{

ServerSocket s=new ServerSocket(PORT);

System.out.println("Server Started");

try {

while(true) {

Socket socket1=s.accept();

try {

new Server4(socket1);

}

catch(Exception e) {

socket1.close();

} } } finally {

s.close();

} } }

该程序本身在技术细节方面不比作者前一本书的内容深入,但是它却演示了“协议”的 概念和实现方法。

首先,服务器向每个连接成功的用户发送 Welcome to server 的欢迎信息,然后接收用户 发送的一行信息,这行信息如果以 HELP 开头,那么提示用户输入了正确的命令,反之提示 用户命令输入错误。也就是说,这个服务器程序只能接受 HELP 命令,不能接受其它的命令,

随便说一句,多数网络协议支持 HELP 命令。无论用户输入的命令是否正确,都断开与 用户的连接,这是通过关闭 Socket 来实现的。

上面所述就是这个服务器程序采用的协议,即下面三点:

· 向每个连接成功的用户发送 Welcome to server 的欢迎信息。

· 接收用户发送的一行信息判断是否为合法的命令,这里合法的命令只有 HELP。

· 断开与用户的连接。

有了服务器的协议,客户机的程序也要根据协议编写,下面是客户机的程序,它根据

《Java 程序设计百事通》第 13 章的 Client2.java 程序修改而来:

//c1:Client4.java //author:ZhangHongbin

//This program is protected by copyright laws.

//Self-defined protocol client.

import java.net.*;

import java.io.*;

public class Client4 {

public static void main(String[] args)throws IOException {

Socket socket1=new Socket(args[0], Integer.parseInt(args[1]));

(18)

try {

System.out.println("Connection to server accepted: "+ socket1);

PrintWriter out=new PrintWriter(

new BufferedWriter(new OutputStreamWriter(

socket1.getOutputStream())),true);

BufferedReader in=new BufferedReader(

new InputStreamReader(socket1.getInputStream()));

System.out.println(in.readLine());

out.println(args[2]+args[3]);

in=new BufferedReader(

new InputStreamReader(socket1.getInputStream()));

System.out.println(in.readLine());

} finally {

System.out.println("closing socket");

socket1.close();

} } }

运行客户机程序时要从命令行输入参数,这些参数分别是:

· 服务器名或者地址

· 服务器的端口号

· 服务器命令

· 服务器命令的参数

服务器命令就是服务器程序可接受的命令,这里只有 HELP 是合法命令。

客户机程序的工作原理要与服务器程序的协议一致,因此与服务器连接成功后,要从服 务器读入服务器发送的欢迎信息,然后向服务器发送命令,接收服务器的返回信息以判断命 令是否正确。显然,如果只读一次服务器返回的信息,那么就不知道命令是否正确。

这对服务器和客户机程序同样可以在一部电脑上运行,首先启动服务器程序,然后运行 客户机程序,下面为两次运行客户机程序的输出:

D:\javaplus>java Client4 zhanghongbin 23 HELP quit

Connection to server accepted: Socket[addr=zhanghongbin/127.0.0.1,port=23,localp ort=1035]

Welcome to server

Your Help Command Accepted closing socket

D:\javaplus>java Client4 zhanghongbin 23 Help quit

Connection to server accepted: Socket[addr=zhanghongbin/127.0.0.1,port=23,localp ort=1036]

Welcome to server Error Command: Helpquit closing socket

第一次输入的命令正确,第二次则因为大小写错误而被服务器拒绝。

(19)

如果读者用过 Telnet 客户程序,应该知道 Telnet 访问的服务器是 23 号端口,而上面的 服务器程序正是使用这个端口,那么 Telnet 是否可访问?只要试试就知道了。

要用 Telnet 访问服务器,采用下面的步骤:

(1)单击 Windows“开始”菜单上的“运行”命令,在文本框中输入 telnet,这样就进 入了 Telnet 程序的窗口。

(2)单击 Telnet 程序“连接”菜单上的“远程系统”,结果如图 1.2 所示。

图 1.2 选择要连接的主机

在“主机名”中输入主机的名字,然后单击“连接”按钮。本步骤的作用是与主机连接,

所谓“主机”就是上面的服务器程序所在的计算机。

(3)与主机连接成功后就会在 Telnet 窗口中出现服务器程序返回的欢迎信息,如图 1.3 所示。

图 1.3 与主机连接成功

(4)此时可以输入命令来观察服务器的响应情况,如图 1.4 所示为输入 help ok 命令的

情况。

(20)

图 1.4 主机返回的信息

Telnet 窗口中出现“到主机的连接丢失”的信息,这是正常的,因为服务器接收用户的 一行信息后就断开连接了。

如果在 Telnet 中输入命令时没有字符出现或者有其它意外,那么得打开“终端”菜单,

单击“首选”,如图 1.5 所示。

图 1.5 选择连接时的有关参数

当选定“本地响应”后,输入的命令字符会出现在 Telnet 窗口中,其它设置读者可以参 考本书这里的图示。

1.5 发送邮件的客户机程序

上面一节介绍了怎样设计自定义协议的服务器程序,以及根据此协议编写的客户机程 序,但是实际工作中,读者经常遇到的是根据公认协议例如 SMTP、POP3 等来编写程序。

例如要编写一个发送邮件的客户机程序,那么就首先要与 SMTP 服务器连接,然后根 据 SMTP 协议编写程序。

可以通过 TCP 协议的 Socket 与 SMTP 服务器连接,这是在《Java 程序设计百事通》中 已经讲述的内容,因此这里只是简单地把步骤列举如下:

(1)根据 SMTP 服务器地址和端口建立 Socket 对象

如果要发邮件,那么可以选择任何一个可用的服务器,因为进入 SMTP 服务器发送邮

件不需要用户名和密码,例如可以选择网易的服务器 smtp.netease.com,SMTP 服务器的端

口一般为 25。

(21)

下面的代码建立了 smtp.netease.com 服务器的 Socket 对象:

Socket socket1=new Socket(InetAddress.getByName("smtp.netease.com"), 25);

(2)建立该 Socket 对象的输入流和输出流

正像《Java 程序设计百事通》中已经讲解过的那样,输出流用于向 Socket 对象写入数 据,这相当于向服务器发送数据,输入流用于从 Socket 对象接收数据,这相当于接收服务 器返回的信息。

下面的代码建立了 Socket 对象的输出流和输入流:

PrintWriter out=new PrintWriter(

new BufferedWriter(new OutputStreamWriter(

socket1.getOutputStream())),true);

BufferedReader in=new BufferedReader(

new InputStreamReader(socket1.getInputStream()));

当上面的步骤完成后,就可以根据 SMTP 协议编写程序了。

SMTP 协议可总结为下面的几步:

(1)SMTP 服务器发送一行字符串

当与服务器连接成功后,服务器会向用户发送一行字符串,因此客户机程序必须接收它,

但是不一定非处理它。

下面的代码接收这行字符串:

String response=in.readLine();

客户机程序可以用 System.out.println 输出这行字符串,通常这行字符串的内容包含该 SMTP 服务器的地址,但是结果随服务器的不同而有差异。

(2)客户机向服务器发送客户机所在的地址来应答

当接收了上面一行字符串后,客户机要做的下一步是告诉服务器自己的地址,在 Java 中,这个地址是 InetAddress 类,有关这个类的使用方法见《Java 程序设计百事通》。

下面的代码向服务器发送客户机的地址:

InetAddress localClient=InetAddress.getLocalHost();

out.println("HELO "+localClient.getHostName());

(3)服务器发送一行欢迎信息

服务器收到客户机地址后会发送一行字符串,客户机可以用与第一步相似的代码接收这 行字符串。

例如下面的代码接收该字符串并且在客户机上输出其内容:

response=in.readLine();

System.out.println("Welcome Info= "+response);

这行字符串的内容也可能随服务器的不同而有差异。

(4)客户机向服务器发送自己的电子邮件地址

在接收到上一步骤服务器发送的字符串后,客户机要发送自己的电子邮件地址来应答,

这个地址就是邮件发送者的地址。

假定发送者为 zhanghongbin@netease.com,那么下面的代码向服务器发送该地址:

out.println("MAIL From:<"+"zhanghongbin@netease.com"+">");

(5)如果上一步骤成功,服务器发送说明成功的 OK 信息

服务器收到邮件发送者的地址后同样会发送一行字符串,如果成功,这行字符串通常包 含 OK 信息,客户机可以用与步骤 3 相同的代码接收这行字符串并且输出其内容。

(6)客户机发送邮件接收者的电子邮件地址

假定邮件接收者的电子邮件地址为 bestbooks@netease.com,下面的代码向服务器发送

(22)

该地址:

out.println("RCPT TO:<"+"bestbooks@netease.com"+">");

(7)如果上一步骤成功,服务器发送说明成功的 OK 信息

服务器收到邮件接收者的地址后同样会发送一行字符串,如果接收成功,这行字符串通 常包含 OK 信息,客户机可以用与步骤 3 相同的代码接收这行字符串并且输出其内容。

(8)客户机开始发送邮件内容

首先向服务器发送 DATA 字符串,说明开始发送邮件正文的内容:

out.println("DATA");

然后一行一行地向服务器发送邮件正文的内容,例如下面的代码向服务器发送的字符串 就是邮件的一行内容:

out.println("This is mail message");

在发送邮件正文的过程中,不需要接受服务器的响应。

(9)向服务器发送包含小数点的一行来结束邮件正文 下面的代码告知邮件正文发送完毕:

out.println(".");

(10)如果邮件正文发送完毕,服务器发送一行字符串告知

客户机可以用与步骤 3 相同的代码接收这行字符串并且输出其内容。

(11)客户机发送 QUIT 命令,说明发送过程结束 下面的代码实现退出功能:

out.println("QUIT");

清楚了服务器的协议,下面就可以编写完整的发送邮件程序了:

//c1:MailClient.java //author:ZhangHongbin

//This program is protected by copyright laws.

//MailClient.

import java.net.*;

import java.io.*;

public class MailClient {

public static void main(String[] args)throws IOException {

Socket socket1=new Socket(InetAddress.getByName("smtp.netease.com"), 25);

try {

System.out.println("Connection to server accepted: "+ socket1);

PrintWriter out=new PrintWriter(

new BufferedWriter(new OutputStreamWriter(

socket1.getOutputStream())),true);

BufferedReader in=new BufferedReader(

new InputStreamReader(socket1.getInputStream()));

//SMTP server send initial info.

String response=in.readLine();

System.out.println("initial Info= "+response);

//client reply with his machine address.

InetAddress localClient=InetAddress.getLocalHost();

out.println("HELO "+localClient.getHostName());

//SMTP send welcome info.

(23)

response=in.readLine();

System.out.println("Welcome Info= "+response);

//client reply with sender mail address.

out.println("MAIL From:<"+

"zhanghongbin@netease.com"+">");

System.out.println("MAIL From:<"+

"zhanghongbin@netease.com"+">");

//if OK,SMTP send ok info.

response=in.readLine();

System.out.println("Sender OK= "+response);

//client reply with receiver email address.

out.println("RCPT TO:<"+

"bestbooks@netease.com"+">");

System.out.println("RCPT TO:<"+

"bestbooks@netease.com"+">");

//if OK,SMTP send ok info.

response=in.readLine();

System.out.println("receiver OK= "+response);

//client begin to send mail content.

out.println("DATA");

System.out.println("DATA");

//this is a line of mail content.

out.println("This is mail message");

//send dot if end of mail content.

System.out.println(".");

out.println(".");

//if OK,SMTP send ok info.

response=in.readLine();

System.out.println("accept OK= "+response);

//client send "QUIT" to quit sending mail.

System.out.println("QUIT");

out.println("QUIT");

} finally {

System.out.println("closing socket");

socket1.close();

} } }

程序在客户机上的输出如下:

Connection to server accepted: Socket[addr=smtp.netease.com/202.103.134.9,port=2 5,localport=1277]

initial Info= 220 smtp2.netease.com ESMTP Welcome Info= 250 smtp2.netease.com MAIL From:<zhanghongbin@netease.com>

Sender OK= 250 Ok

RCPT TO:<bestbooks@netease.com>

(24)

receiver OK= 250 Ok DATA

.

accept OK= 354 End data with <CR><LF>.<CR><LF>

QUIT closing socket

读者自己练习时,应该修改邮件接收者的地址为读者自己的地址,然后查看邮件的发送 结果。

如图 1.6 所示为邮件接收者 bestbooks@netease.com 收到邮件的情况。

图 1.6 收到 MailClient 程序发送的邮件

上面的程序仅发送了一行信息,并且没有邮件标题、抄送者地址和附件等内容,这是因 为本书对 SMTP 协议的描述进行了简化,读者可以查阅 SMTP 协议的细节来完善上面的程 序。

1.6 本章小结

本章运用若干例子,进一步讲解了网络编程的有关技术,网络编程是个范围很广泛的主 题(也许这是因为网络编程这个概念比较模糊的原因),例如可以包含本书后面要讲解的 Servlet 和 JSP 的内容。

本章的内容主要面向 URL 和网络协议,这些内容还与《Java 程序设计百事通》有密切

的关联,所以如果读者对本章的某些细节不太清楚,可以先阅读作者的前一本 Java 书,就

(25)

像作者在前言中所述,看懂本书的内容只需要读者具备《Java 程序设计百事通》的知识基础。

第2章 标记流

Java 的标记流是一种有趣的流,例如它可以分析和统计文章中某些敏感词语出现的频 率,它的另外一个特点是能够考验编程者的细心,一旦不留神,使用标记流的程序就可能得 不到所要求的结果。

2.1 StreamTokenizer 标记流

StreamTokenizer 用来分析流中的 Token,Token 的原意是“标记、记号、标志” ,Token 在 Java 中大体指流中有意义的内容,例如一个字母、一个数字各是一个 Token,但是空格、

回车则不是 Token。所以,读者可以看到,StreamTokenizer 可用来统计文章中的字符数,可 完成 Office 的“字数统计”功能,当然这只是 StreamTokenizer 应用的一个方面。

U 评论:Token 被某些计算机“权威”翻译成“令牌”,例如所谓的“令牌网”,

这听起来像古代打仗时下达作战命令的“令牌”,此含义与本章的 Token 风马牛不相及,所 以作者在讲解中还是用英文罢了。

把流包裹进 StreamTokenizer 中就可以分析该流,代码如下所示:

StreamTokenizer st = new StreamTokenizer( new BufferedReader(file));

其中 file 代表一个 FileReader 对象。

查看 Java 文档可知,在默认情况下,StreamTokenizer 认为下列内容是 Token:

· 字母 其类型为 TT_WORD。

· 数字 其类型为 TT_NUMBER

· 除 C 和 C++注释符号以外的其它符号 如符号“/”不是 Token,注释后的内容也不 是,而“\”是 Token,非注释符号没有类型定义。

· 单引号和双引号以及其中的内容 只能算一个 Token。

看过上述关于 Token 的描述,读者就知道,如果要编写一个统计文章字符数的程序,不 能简单地统计 Token 数就算万事大吉,因为字符数不等于 Token 数。例如文章中经常有引号,

按照 Token 的规定,引号中的内容有十页长也算一个 Token,那么算稿费时可要吃大亏了。

如果希望引号和引号中的内容都算作 Token,应该调用下面的代码:

st.ordinaryChar('\'');

st.ordinaryChar('\"');

其中 st 是 StreamTokenizer 对象,ordinaryChar 方法的意思就是把方法中的参数当作和字 母一样的 Token。上面的代码把英文单引号和双引号作为 Token,那么引号之间的内容也是 Token。

如果文章中有“\”等注释符号,可同样处理,否则,程序不会统计“\”和其之后的内 容。

下面是一个统计文章字数的程序:

//c2:WordCount1.java //author:ZhangHongbin

(26)

//This program is protected by copyright laws.

//Word Counter.

import java.util.*;

public class WordCount1 {

public static void main(String[] args) throws Exception {

String s;

int numberSum=0;

int wordSum=0;

int symbolSum=0;

int total=0;

FileReader file = new FileReader(args[0]);

StreamTokenizer st = new StreamTokenizer(

new BufferedReader(file));

st.ordinaryChar('\'');

st.ordinaryChar('\"');

while(st.nextToken() !=StreamTokenizer.TT_EOF) {

switch(st.ttype) {

case StreamTokenizer.TT_EOL:

//s = new String("EOL");

break;

case StreamTokenizer.TT_NUMBER:

s = Double.toString(st.nval);

System.out.println( "double="+ s);

numberSum=numberSum+s.length();

break;

case StreamTokenizer.TT_WORD:

s = st.sval;

wordSum=wordSum+s.length();

break;

default: // symbol in ttype

s = String.valueOf((char)st.ttype);

symbolSum=symbolSum+s.length();

} }

System.out.println("sum of number="+numberSum);

System.out.println("sum of word="+wordSum);

System.out.println("sum of symbol="+symbolSum);

total=symbolSum+wordSum+numberSum;

System.out.println("Total="+total);

file.close();

} }

运行程序时, 要从命令行输入一个文件名, StreamTokenizer 就对该文件进行分析和统计。

这个程序处理了引号问题,但是没有处理注释符号。另外,如果文章中有数字,那么这

些数字全部变成 double 数,这就意味着,数字 278 变成了 278.0,程序也没有处理这种情况,

(27)

因此如果文章中的数字没有小数点,那么统计就有误差。

default 语句的作用是处理非字母和数字的符号。

程序将对如图 2.1 所示的一篇文章进行统计。

图 2.1 程序要统计的文章

% 提醒:这篇文章也在本书的程序中。

文章中有两个数字,程序的运行结果如下:

double=278.0 double=278.0 sum of number=10 sum of word=240 sum of symbol=12 Total=262

程序统计文章中有 262 个字符,它把 6 个字符的两个数字算成了 10 个,利用 Word 统

计同一篇文章内容的结果如图 2.2 所示。

(28)

图 2.2 Word 的统计结果

Word 统计的不包含空格的字符数为 258 个。

读者要注意,上面用程序和 Word 统计时,一个汉字和英文字母都是算作一个字符,并 且所有的中文符号例如中文标点、中文引号也全部是 TT_WORD 类型即 Token,而英文引号 如果不特别处理就不算 Token。

2.2 StringTokenize 标记流

StringTokenizer 是另外一种标记流,它的 Token 是连续的字符串,StreamTokenizer 的 Token 则是单个的字符。

在默认情况下,Token 的分隔符是空格、Tab 和回车。查看 Java 文档可知,它的构造方 法参数是字符串,即它分析的是字符串而不是流。

例如下面的字符串:

String s="This is a string."

可以很容易算出来共有 4 个 Token,分别是:

This is a string.

请读者注意最后一个 Token 由单词 string 和英文句号组成,因为在默认情况下,英文标 点不作为分隔符。

现在要编写一个程序统计文章中某个单词的数目,根据上面所述,只有把英文标点、数 字和其它非字母符号作为分隔符,才能准确统计。所以要用有两个参数的构造方法创建 StringTokenizer 对象:

String delimiter=","+"?"+"."+":"+" "+"\n";

StringTokenizer st = new StringTokenizer(s,delimiter);

代码中的 s 代表一个字符串,第一条语句定义了一个字符串 delimiter,它包括标点、空 格和回车。第二条语句是创建 StringTokenizer 对象,并且用 delimiter 作为它的一个参数,

其意思就是 delimiter 中的内容是分隔符,就是说,分析文章时,标点之类的非字母符号如 同空格一样会被忽略,不会被当作 Token 的一部分。

下面的程序要在命令行输入两个参数,第一个是文件名,第二个是字符串,程序的功能

就是统计该字符串在文章中出现的次数:

(29)

//c2:WordCount1.java //author:ZhangHongbin

//This program is protected by copyright laws.

//Word Counter.

import java.io.*;

import java.util.*;

public class WordCount2 {

public static void main(String[] args) throws Exception {

String s;

String token;

String delimiter=","+"?"+"."+":"+" "+"\n";

int total=0;

StringTokenizer st;

BufferedReader in=new BufferedReader(

new FileReader(args[0]));

while((s=in.readLine())!=null) {

st = new StringTokenizer(s,delimiter);

while (st.hasMoreTokens()) {

token=st.nextToken();

if(token.equals(args[1])) total=total+1;

} }

System.out.println("Total of "+args[1]+" = "+total);

in.close();

} }

程序的运行结果如下:

Total of Wife = 3

程序统计文章中 Wife 出现的次数。请读者注意,这个程序大小写敏感,就是说 Wife 和 wife 不是同一个 Token。

" 解疑:作者在刚开始学习 StringTokenizer 时就忘了定义分隔符,结果程序的结 果总是不对。不过本程序也只是统计本书附带的 humor1.txt 文件才准确,如果文章中有括 号或者其它 delimiter 没有指定的分隔符,那么程序统计结果就不对。例如把文章中的一个 Wife 用括号括起来,那么程序就找不到“躲”在括号里的老婆(Wife)了。这个程序统计 汉字时,误差更大,因为汉字之间没有分隔,需要修改程序才行。

2.3 本章小结

本章讲述了 Java 中的两种标记流,使用这些流的难度是要考虑的细节太多,需要很大 的细心和耐心才能编写出一个得到正确结果的程序。为了教学的需要,本章的程序比较短小,

读者需要修改它们才能把它们用于分析其它类型的文章。

(30)

第 3 章 Collection 和 Map

在 Java 中,Collection 一词用于描述由若干对象组成的一个整体(group),例如常见的 集合属于 Collection,这些对象被称为元素(element),元素的类型可以不同,而数组中的 所有元素都是同一类型。

" 解疑:Collection 的原意是“收集、聚集”。某些中文书把它翻译成“集合”,

字面上不能说翻译得不对,甚至可以说翻译得很贴切,但是很容易与 Set 这个单词混淆,因 为 Set 一直被翻译为“集合”。随便说一句,在 Java 中,Set 只是 Collection 中的一种而 已。

Collection 与计算机专业术语“数据结构”的概念很类似:很多数据按照某种方式成 为一个整体。因为 Collection 不太好翻译,所以下面讲解中直接用英文或者用数据结构一 词。

3.1 Collection 和 Map 入门

Java 早期版本的 Collection 有 Vector、HashTable 和数组,新版本增加了 Set 和 List。新 版本内容的重要之处是具有 FrameWork(框架结构),所谓框架结构包括:

· 接口 例如 Set 是接口。

· 接口的实现 定义完接口后,接着定义对接口的实现类,例如 Hashset 是实现 Set 接口的类。

· 算法 对每一种数据结构都可以设计各种各样的算法,这些算法的实现就是接口和 类中的各种方法,它们完成 Collection 中相关的操作。简单地说,方法可看作是算 法。

换句话说,新版本的 Collection 都是首先设计一个接口,然后从这个接口给出多种实现 的类,接口和类中的方法就是这些数据结构的算法。

查看 Java 文档可知,Java 中有接口 Collection,它定义了 Collection 总体的内容。在该 接口下还有一些子接口,这些子接口可能还有子接口或者子类。

本章主要讲述 Collection 接口下面的 Set 接口和 List 接口,另外还有 Map 接口,Map 也 是一种数据结构,不过它不在接口 Collection 中。

Set 的意思是集合,集合的概念在中学数学中就已经引入了。集合中不能有重复的元素,

集合中元素的顺序也无关紧要。

List 中可以有重复的元素,并且元素有顺序关系。

Set 和 List 都是接口,一般使用实现它们的类,例如 HashSet 或者 ArrayList。

Map 中的元素总是一对,前者称为 key(键) ,后者为 value(值),每个元素的类型可 以不同,并且键和值的类型也不必相同。

Map 同样是接口,所以一般使用实现它的类,如 HashMap 或者 HashTable 等。Map 中 不能有重复的 key,但是可以有重复的 value。Map 在这里的意思不是“地图”而是“映射”,

它把 key 映射到 value,即把二者关联起来。

把一个元素加入到 Collection 中用 add 方法,该方法的参数为 Object 类型,把一个元素 加入到 Map 中用 put 方法,其参数是两个 object 类型,前者表示键,后者表示值。

下面的程序演示了 Collection 和 Map 的基本概念:

(31)

//c3:CollectionMap1.java //author:ZhangHongbin

//This program is protected by copyright laws.

//Collection and Map basics.

import java.util.*;

public class CollectionMap1 {

public static void main(String[] args) {

String[] s1={"dog","pig","dog","cat","mouse"};

//Set set1 = new TreeSet();

Set set1 = new HashSet();

ArrayList list1=new ArrayList();

Map map1=new HashMap();

for (int i=0; i<s1.length; i++) {

if (!set1.add(s1[i]))

System.out.println("Duplicate found= "+s1[i]);

list1.add(s1[i]);

}

map1.put("dog","black");

map1.put("pig","grey");

map1.put("cat","white");

System.out.println("set1= "+set1);

System.out.println("list1= "+list1);

System.out.println("map1= "+map1);

} }

程序的输出如下:

Duplicate found= dog set1= [cat, mouse, dog, pig]

list1= [dog, pig, dog, cat, mouse]

map1= {cat=white, dog=black, pig=grey}

程序中需要说明的是集合的 add 方法,查看 add 的语法可知,它返回布尔值,如果要加 入的元素已经在集合中,返回值为 false,并且也不会把该元素加入。

程序中使用的 Set 是 HashSet,其中的元素没有经过排序,而 TreeSet 和 SortedSet 是按 照升序排列的集合。从结果看,HashSet 中元素的顺序和加入元素的顺序不一致。

程序中有注释掉的一条语句,读者可以去掉注释,观察输出的变化情况。

程序中使用的 List 是 ArrayList,从结果看,其中的元素顺序和加入元素的顺序完全一 样。

程序中使用的 Map 是 HashMap,从结果看,HashMap 中元素的顺序和加入元素的顺序 不一致,这和 HashSet 的情况相似。

" 解疑:Hash 是计算机科学术语,名为“散列”,有时也用音译“哈希”,可理 解为“不规则、无顺序”。所以上面结果中才会出现加入元素的顺序和 Collection 的元素顺 序不一致的情况。

Collection 中的子类都有一个把 Collection 作为参数的构造方法,这样可以很容易地用

(32)

一种 Collection 中的元素作为另外一种 Collection 的元素,例如下面的代码:

new ArrayList(c);

假定 c 是集合,那么上面的代码创建了一个 ArrayList,并且用集合中的元素作为自己 的元素。

3.2 Collection 的基本方法

接口 Collection 中定义了若干方法,它们对所有实现它的接口都适用,下面的程序演示 了其中几个常用方法的功能:

//c3:Collection1.java //author:ZhangHongbin

//This program is protected by copyright laws.

//Methods in Collection.

import java.util.*;

public class Collection1 {

public static void main(String[] args) {

String[] s1={"dog","pig","dog","cat","mouse"};

Set set1 = new HashSet();

Object[] array;

for (int i=0; i<s1.length; i++) set1.add(s1[i]);

System.out.println("set1= "+set1);

System.out.println("set1 has a dog= "+set1.contains("dog"));

System.out.println("size of set1= "+set1.size());

array=set1.toArray();

System.out.println("transform set1 to array = ");

for (int i=0; i<array.length; i++) System.out.print(array[i]+" ");

System.out.println();

set1.clear();

System.out.println("set1 is empty= "+set1.isEmpty());

} }

程序的运行结果如下:

set1= [cat, mouse, dog, pig]

set1 has a dog= true size of set1= 4

transform set1 to array = cat mouse dog pig set1 is empty= true

读者可对照 Java 文档和运行结果,了解程序中几个方法的功能,它们都很简单直观,

作者就不赘述了。

參考文獻

相關文件

[r]

A clever and simplifying strategy: pairing up all the rays coming through the slit and then finding what conditions cause the waves of the rays in each pair to cancel each other.

This essay wish to design an outline for the course &#34;Taiwan and the Maritime Silkroad&#34; through three planes of discussion: (1) The Amalgamation of History and Geography;

[r]

 Request.Cookies[ &#34;Cookie 名稱&#34; ].Value –取得使用者所傳送的 Cookie 內容. 

Beauty Cream 9001, GaTech DVFX 2003.

Life in Paints, GaTech DVFX 2003.. Tour

y A stochastic process is a collection of &#34;similar&#34; random variables ordered over time.. variables ordered