基于jsp网上书店设计与实现毕业设计论文

获取项目源文件程序,联系Q:1415736481,可指导毕设,课设

二十一世纪是一个集数字化,网络化,信息化的,以网络为核心的社会。当钱天白教授于1986年9月14日在北京计算机应用技术研究所内向德国卡尔斯鲁厄大学发出第一封电子邮件“穿越长城,走向世界”的时候,他也许不知道自己推开了中国信息时代的大门;1994年4月20日,中国科学院计算机中心通过美国Sprint公司连入Internet的64K国际专线开通,实现了国际互联网的全功能连接,从此中国迈入互联网信息时代。转眼间互联网在中国已有10余年的发展,中国的网民充分领略到“畅游天地间,网络无极限” 所带来的畅快。随着Internet的飞速发展,使得网络的应用日益的广泛。如电子商务,电子政务,网上医疗,网上娱乐,网络游戏,网络教学等。

本次毕业设计的题目就是网上书店系统。

本论文就毕业设计的内容,系统地阐述了整个网上书店系统的功能及实现。我们小组人员在指导老师的带领下设计并实现了从商品管理,商品分类和查询,到购物车实现,用户订单处理,再到聊天室,管理员系统。基本上实现了电子商务的功能流程,能够实现用户与商家在网上进行商品交易。本系统界面简单直观,易于操作和使用,交互性强,完全基于Internet网络。

 本系统在XX老师指导下,由我们小组共同开发完成。限于时间有限,在系统安全性等方面仍需进一步深入研究。另外,疏漏和不妥之处,在所难免。真诚的希望老师予以指导和纠正。

 

Abstract:

The 21st century is a collection digitization, the network, the informationization, take network as core society. When Qian Tian taught without charge gives sends out the first email in September 14, 1986 in Beijing computer applied technology research institute introversion Germany Karlsruhe University “to pass through the Great Wall, moved toward the world” time, perhaps he did not know that he has opened China information age front door; on April 20, 1994, the Chinese Academy of Science computer center entered Internet through the American Sprint Corporation company the 64K international special line clear, has realized internet's entire function binding, henceforth China entered into the Internet information age. The Internet had 10 remaining years of life development in a moment in China, China's web cams understands fully “enjoys a trip to between the world, the network does not have carefree which the limit” brings. Along with the Internet swift development, causes the network application day-by-day widespread. If electronic commerce, E-government, on-line medical service, on-line entertainment, network game, network teaching and so on.

This graduation project's topic is on-line books management system managemen
第一章   开发背景

1.1 目的和意义

本系统的设计目的是为了满足消费者只要通过互联网就可以足不出户的购买自己喜欢的图书,改变传统商业交易,在互联网上进行交易,实现网上购买图书。为了实现上述目的,我对网上书店系统有了深一步的了解,从而满足客户的要求,让他们可以随时找到自己想要购买的图书。

本论文课题新颖,主要涉及软件,数据库与网络技术等。涵盖知识面广,可有效地提高学生综合运用所学知识分析解决问题的能力,增强学生对事物的理解与掌握能力,培养学生掌握科学的研究方法,正确的设计思想,独立思考,勇于进取,探索创新,为今后进一步学习与工作奠定了良好的基础。

1.2 开发设计思想

本系统用JSP语言来编写网络书店系统,数据库用Microsoft SQLServer2000来连接系统,通过编写JavaBeans来进行后台业务逻辑控制,即JSP +JavaBeans + SQLServer2000三层模式完成整个设计工作。本系统全部基于Internet网络,以JSP语言对网站进行开发,注重用户与网站的交互性。因此在这样的背景下,针对当前Internet网络发展趋势来计网络书店系统就成为了当今应用软件的首选体系结构。

1.3 开发目标

 网上购书的优势在于选择面大、价格便宜、交易方便、节省时间和精力等。整个图书市场一片繁荣,在这种情况下,网上书店的加入无疑将使得竞争更加激烈,但从另一个方面看,只有在这种激烈的竞争下,网上书店的优势才能得以体现。在中国,网上书店有发展的必要,也有发展的基础,发展网上书店的各方面条件也日趋成熟,但是还存在一些问题,只有把问题解决好了,才能保证网上书店的蓬勃发展,

第二章  开发工具和环境简介

2.1 Java Server Page 简介

Java Server Page或简称为JSP是由Sun公司在Java语言上开发出来的一种动态网页制作技术,它提供了一种建立动态网页的简单方法,并为开发人员提供了一个Server端框架,基于这个框架,开发人员可以综合使用 HTML,XML,JAVA语言以及其他脚本语言,灵活,快速地创建和维护动态网页,特别是目前的商业系统。作为JavaTM 技术的一部分,JSP能够快速的开发出基于所有Web服务器和应用服务器环境,独立于平台的应用程序,而且具有非常强的可伸缩性。同时,JSP把用户界面从系统内容中分离开来,使得设计人员能够在不改变底层动态内容的前提下改变祖国网页布局。

这样跨平台的特性己包含了目前大部份网站服务器配置环境,再加上它的语法写作方式可媲美ASP般的易学易懂,而且在网络安全技术方面甚至已超越ASP技术。所以,相信很快的时间内极有可能取代现有网页编译技术,而成为商业网站的新标准。在本节中我们将会讨论JSP技术的特性,让您更加了解这项新技术。

2.2 Microsoft SQLServer2000 简介

SQLServer2000是一个基于关系型数据库模型建立的数据库管理系统软件(DBMS)。它帮助用户方使地得到所需信息,并提供强大的数据处理工具。它可以帮助用户组织和共享数据库信息,以便于根据数据库信息作出有效的决策。另外,仅有这样—个数据库管理系统软件(DBMS),则只能进行一些信息系统所需要的简单数据处理,且对操作者有较高的操作技能要求。因此,信息系统的开发者都是在某种数据库管理系统软件〔DBMS〕环境下编写相应的应川程序,以形成一个能够满足应用需求且操作尽可能简单的应用型信息系统,这被称之为二次开发。

SQLServer2000 还具有以下特点:

1.使信息更易于查找和使用

SQLServer2000继续为简便地查找信息提供易于使用的工具。

2.支持 Web 功能的信息共享

SQLServer2000 可以通过企业内部网络Internet 很简便地实现信息共享,而且它可以很容易地将数据库定位到浏览器中,它将桌面数据库的功能和网站的功能结合在一起。

3.用于信息管理的强大解决方案

高级用户和开发人员可以创建那些将SQLServer2000界面(客户端)的易用性和SQL服务器的可扩展性和可靠性结合在一起的解决方案。

4.改变了数据库窗口

可在SQLServer2000容纳并显示新的对象,增强了SQLServer2000 数据库的易用性。

5.提供名称自动更正功能

自动解决当用户重新命名数据库对象时出现的常见负面效应。例如,当用户重命名表中的字段时,将自动在诸如查询的相关对象中进行相应的更改。

6.具有子数据表功能

子数据表在同一窗口中,提供了嵌套式的视图,这样就可以在同一窗口中专注于相关的数据并对其进行编辑。

7. 用户只需简单地将SQLServer2000对象(表、查询等)从数据库放到ODBC数据源中,即可从 Microsoft SQLServer2000中将数据导出到Microsoft ACCESS 2000 。

8.数据访问页功能

该功能可使用户快捷方便地创建数掂 HTML页,并通过数据 HTML页,将数据库应用扩展到企业内部网络 Internet上。这将帮助用户比以往更快捷、高效的方式共享信息。

9.共享组件的集成

SQLServer2000利用新的  Web组件和位于浏览器中的 COM 控件,为用户提供了多种查看和分析数据的方式。

10.Microsoft SQL Server 交互性

Microsoft SQLServer2000支持OLE DB,使用户可以将SQLServer2000 界面的易用性与诸如 Microsoft SQL Server的后端企业数据库的可升级性相结合。

2.3 JDBC 驱动程序简介

从编程的角度出发,有两个主要的类负责建立与数据库的连接。第一个类DriverManager是在 JDBC API 中提供的为数不多的实际类。 DriverManager负责管理已注册驱动程序的集合,实质上就是提取使用驱动程序的细节,这样程序员就不必直接处理它们。第二个类是实际的 JDBC Driver类。JDBC驱动程序有四种类型。

第一种驱动程序:它们都使用 JDBC-ODBC 桥,这是作为 JDK 的一个标准部分包括的。第一种驱动程序通过附加在JDBC-ODBC 桥的“开放式数据库连接性”(Open DataBase Connectivity(ODBC))驱动程序来区分。要连接到一个不同的数据源,您只需要使用 ODBC 管理员注册(或有效地绑定)一个不同的 ODBC 数据源到合适的数据源名称即可。

第二种驱动程序也称为部分 Java 驱动程序,因为它们直接将 JDBC  API 翻译成具体数据库的API。对于分布式应用程序,这种需求会产生额外的许可证问题,还可能带来可怕的潜在代码分布问题。

第三种驱动程序是纯 Java 驱动程序,它将 JDBC API 转换成独立于数据库的协议。JDBC 驱动程序并没有直接和数据库进行通讯;它和一个中间件服务器通讯,然后这个中间件服务器和数据库进行通讯。这种额外的中间层次提供了灵活性:可以用相同的代码访问不同的数据库,因为中间件服务器隐藏了 Java 应用程序的细节。要转到不同的数据库,您只需在中间件服务器上改变参数。

第四种驱动程序是纯 Java 驱动程序,它直接与数据库进行通讯。很多程序员认为这是最好的驱动程序,因为它通常提供了最佳的性能,并允许开发者利用特定数据库的功能。当然,这种紧密耦合会影响灵活性,特别是如果您需要改变应用程序中的底层数据库时。这种驱动程序通常用于 applet 和其它高度分布的应用程序。

2.4  JavaBeans 简介

JavaBeans是一个可以重复使用的软件组件。实际上JavaBeans是一种Java类,通过封装属性和方法成为具有某种功能或者处理某个业务的对象,简称beans。JavaBeans是基于Java语言的,具有以下特点:

(1)可以实现代码的重复利用。

(2)易编写,易维护,易使用。

(3)可以在任何安装了Java运行环境的平台上的使用,而不需要重新编译。

2.5  JAVA 简介

      Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象。Java的源代码在传递到客户端执行之前,必须经过编译,因而客户端上必须具有相应平台上的仿真器或解释器,它可以通过编译器或解释器实现独立于某个特定的平台编译代码的束缚。Java采用强类型变量检查,即所有变量在编译之前必须声明。Java是一种与HTML无关的格式,必须通过像HTML中引用外媒体那么进行装载,其代码以字节代码的形式保存在独立的文档中。Java采用静态联,即Java的对象引用必须在编译时的进行,以使编译器能够实现强类型检查。

Java语言是一种与平台无关的编程语言,它具有“一次编写,随处运行”的特点,所以,非常适合于分布式的网络编程。随着 Internet网络在我国的迅速普及,参与和从事网络编程的人员也在不断地增加,同时,也将有更多的网络开发者选用Java语言作为编程工具,而一些C和C++程序员也在逐步转向应用Java语言编写程序。

 

2.6  电子商务简介

电子商务源于英文ELECTRONIC COMMERCE,简写为EC。顾名思义,其内容包含两个方面,一是电子方式,二是商贸活动。

  电子商务指的是利用简单、快捷、低成本的电子通讯方式,买卖双方不谋面地进行各种商贸活动。 电子商务可以通过多种电子通讯方式来完成。简单的,比如你通过打电话或发传真的方式来与客户进行商贸活动,似乎也可以称作为电子商务;但是,现在人们所探讨的电子商务主要是以EDI(电子数据交换)和INTERNET来完成的。尤其是随着INTERNET技术的日益成熟,电子商务真正的发展将是建立在INTERNET技术上的。所以也有人把电子商务简称为IC(INTERNET COMMERCE)。

要实现完整的电子商务还会涉及到很多方面,除了买家、卖家外,还要有银行或金融机构、政府机构、认证机构、配送中心等机构的加入才行。由于参与电子商务中的各方在物理上是互不谋面的,因此整个电子商务过程并不是物理世界商务活动的翻版,网上银行、在线电子支付等条件和数据加密、电子签名等技术在电子商务中发挥着重要的不可或缺的作用。

总的来说,正如大家所熟知的那样,电子商务可以分为企业(Business)对终端客户(Customer)的电子商务(即B2C)和企业对企业的电子商务(B2B)两种主要模式。

  提起B2C,大家可能更为熟悉一些,它是从企业到终端客户(包括个人消费者和组织消费者)的业务模式。今天所谈的电子商务时代的B2C是通过电子化、信息化的手段,尤其是互联网技术把本企业或其它企业提供的产品和服务不经任何渠道,直接传递给消费者的新型商务模式。因为它与大众的日常生活密切相关,所以被人们首先认识和接受。

电子商务B2C模式的一种最为大家所熟悉的实现形式就是新兴的专门做电子商务的网站。现在,仿佛一夜之间,涌现出无数的这类公司,其中有网上商店、网上书屋、网上售票等等,甚至还有一些什么都做,什么都卖的电子商务网站,人们戏称为“千货公司”的。但无论怎样,这些新型模式企业的出现,使人们足不出户,通过因特网,就可以购买商品或享受咨讯服务。这无疑是时代的一大进步。在这些新涌现出来的互联网公司中,亚马逊公司可以说是最具代表性的一例。

  人们在反思亚马逊的亏损原因时意识到,也许不应该将建立起电子商务时代B2C的任务全都寄托于这些白手起家的网站上,传统行业自觉的互联网和电子商务革命也许会更经济,更实惠,也更必要,不至于给投资人、给股民带去那么多的压力和担忧。也许,只有当这两股力量都齐齐奔向同一个山顶时,这样的电子商务世界才更精彩,真正的电子商务时代也才会更快一些到来。

  传统企业成功向互联网和电子商务转型最成功的例子是DELL,DELL一开始还只是一家通过电话直销电脑的公司,尽管也很成功,但当互联网革命开始之时,它毫不犹豫地选择了把握机遇,将自己的全部业务搬到了网上去,并按照互联网的要求来对自己原有的组织和流程进行梳理,开发了包括销售、生产、采购、服务全过程的电子商务系统,并充分利用了互联网手段,为用户提供个性化定制和配送服务,大大提高了客户的满意度,奇迹般地保持了多年50%以上的增长,成为今天世界最大的电脑厂商之一,也对其它转型较慢的竞争对手造成了巨大的威协和挑战。

B2C的这二种实现方式还有一点很大的不同,由网站起家的B2C较难发展起自有品牌的产品、实业,因此它们更象是一个百货商店,当然与百货商店最不同的就是百货店是用户上门的,而B2C网站是送货上门的,而由传统企业改造而来的B2C更可能象是一个专卖店,专营自己品牌的产品,与传统专卖店不同的是:这里用户和厂商互动性更强,可以量身定做,同时由于省去了建物理店的开销,成本可能会降低。

  谈完了B2C再来说说B2B,企业与企业之间的业务模式被称作B2B,电子商务B2B的内涵是企业通过内部信息系统平台和外部网站将面向上游的供应商的采购业务和下游代理商的销售业务都有机地联系在一起,从而降低彼此之间的交易成本,提高满意度。实际上面向企业间交易的B2B,无论在交易额和交易领域的覆盖上,其规模比起B2C来都更为可观,其对于电子商务发展的意义也更加深远。与B2C相似,B2B在企业间的应用也有两种主要实现形式。

  B2B的一种实现是其在传统企业中的应用。一些传统企业的实质性业务,正在逐步向B2B转变,更多地以WEB方式来传递信息和实现网上订单,但物流方式就和以前没什么变化,依然是供应商到本企业,本企业再到代理商或最终客户。以通用汽车为例,通用汽车建立了一个B2B电子商务网站——TradeXchange,计划在今年年底之间,将其每年高达870亿美元的采购业务完全通过该网站进行。并且这个网站不仅满足通用自身的采购业务,其30000多家供应商也将在这一系统上进行交易,它将对通过TradeXchange进行的电子商务交易收取1%的的手续费,专家们估计这将为通用汽车带来每年50亿美元的收入。        但正如我在前面第一部份举例时所提醒的那样,不要把互联网和电子商务仅仅看作是一个工具,它同样可能对营销模式和管理模式带来变革,B2B的第二种实现方式就有这样的意味。这一类的B2B公司并不是为自身企业的采购或销售服务的,它自身可能不生产任何产品,但它通过建立统一的基于WEB的信息平台,为某一类或某几类的企业采购或销售牵线搭桥,此时物流的方式就和上一类有很大不同了,它是由供应商直接到代理商。比如说我们前面第一部份中例举的那间B公司就有一点类似这种公司。它搭建了计算机零部件这一类商品卖家和买家的桥梁,因此它没有厂房,甚至没有库房,而只是通过信息系统来调配、组织供货与销售,并提供一些增值性服务,从而获得佣金或增值性服务收入。当然这一类公司成功的关键是它要能聚拢这一类产品的卖家和买家,通过特色服务,让它们愿意到你的平台上来交易,但究竟愿不愿意,这也和B2C中所谈到的是选择百货店还是专卖店方式有些类似。

 

 

 

 

 

 

 

 

 

第三章  书店系统功能分析

为了最终实现目标系统,必须设计出组成这个系统的所有程序和文件(或数据库)。模块是数据说明、可执行语句等程序设计对象的集合,它是单独命名的而且可通过名字来访问。模块化就是把程序化分成若干个模块,每个模块完成一个子功能,把这些模块集起来组成一个整体,可以完成指定的满足问题的要求。

3.1  系统功能分析

首先对现有系统进行分析,现有系统是信息的重要来源。分析已有系统的功能和实现,从而确定新系统的设计目标和模型。由于条件有限,调研主要是在网上进行。即通过在网上已有的图书网站注册成会员来了解其具备的功能。

  1. 从用户角度来看:

用户通过在线注册成为网站的用户,可以获得以下功能:书目浏览,购买图书,查看订单,修改订单,修改密码,修改个人信息,书籍简介,投票箱等。另外,用户注册成功后,可以进入到网站的留言板块进行留言等。

2.从网站的角度看:

(1)网站应该包含商品搜索功能:按书名(模糊)查询。

(2)订单处理功能,确认订单方式:

a). 电话通知方式

b).  E-mail方式

(3)管理员管理:

 

a).书名

b).出版社

c).作者

d).价格

e).订购号

(5)好书的投票

查看用户好书投票的信息及投票百分比的评比。

(6)论坛

用户进行信息交流的地方,可以在留言板进行留言,为查找图书提供好的网站等等。

3.2  可行性研究

可行性研究阶段的主要任务是在系统初步调查的基础上,对新系统是否能够实现和值得实现等问题做出判断,避免在花费了大量的人力和物力之后才发现系统不能实现 越来越多的人掌握了计算机的基本使用方法和技能。随着Internet的发展,用户对于网络、WINDOWS等环境下的软件使用比较熟悉,对于新鲜事物,用户表现出极大的兴趣和热情。

③经济可行性分析:

      网上图书系统给人们带来了方便,成为一种全新的商务模式。因此,不用出门就可以在家购物的新时尚已经到来,构建一个网上书店系统在经济上是完全可行的。

就本系统而言,随着各地大学城的兴建,校园远离市区,学生出校买书的代价提高。另一方面,随着宽带网络进入校园,为学生在网络上进行购物提供有利的条件。而一个网上书店系统可以为学生提供软件条件,这样学生便可以足不出户的买书了。商家可以从中

(g)修改密码

(h)修改个人信息

(i)留言板

(j)管理员

(2)系统性能分析

对数据的安全性、完整性要求:用户信息保密,只有管理员可见(可查),但不能任意修改。确保网上支付安全。商品信息、用户信息必须保证其完整性。防止恶意删改。以下是系统性能需求:

(a)准确性和可靠性高

(b)页面友好,功能齐全,且可以使用

(c)系统便于维护和升级。

(d)数据库访问效率高

3.4   系统总体结构图:

 

网上书店系统

用户注册

用户登录

修改个人密码

订单查询

修改个人信息

 

修改订单

 

留言板

图书浏览

图书查询

 

管理员

 

 
 

 

 

 

 

 

 

 

 

数据流图:

   数据流图:是一种描述软件系统逻辑模型的图形符号。这种图形表示即可以从本质上描述计算机软件系统的工作情况,又适合非计算机专业人员学习和掌握,在需求分析中是一种很好的交流和表达工具。

   

 带箭头的线表示数据流,其中箭头表示了数据的流动方向。

 圆框表示对数据的加工。

 方框表示数据的起点和终点。画分层数据流图。

分层数据流图:

 

用户注册信息

 

 
 

 

 

 

用户登陆信息

网上书店系统

                                                                   

 

 

系    统

 

       
 

图书浏览信息

 
   
 

留言板信息

 
 
 

用户信息修改信息

 
 

用户订单修改信息

 

 

 

 

 

 

(a)书店系统E-R

 

检查合法性

用户信息处理

查询处理

不合法处理

留言处理

投票处理

订单处理

 

 
 

 

 

 

 

 

 

 

 

(b)检查书店系统E-R图

 

用户信息

处理

 

录入处理

修改信息

 

 
 

 

 

 

     

    

 

修改密码

 

 
 

 

 

 

 

(c)用户基本信息处理E-R图

             

 

留言处理

添加留言

处理

 

公共留言

 

 
 

 

 

 

 

 

 

 

私人留言删除处理

 

    

 

 

 

                               (d)用户留言处理E-R图

 

 

订单处理

添加处理

修改处理

删除处理

 

订单信息文件

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(e)用户订单处理E-R图

 

 

 

查询处理

书籍总览

具体书名查询

 

 
 

 

 

 

 

 

 

(f)书籍查询处理E-R图

 

 

 

 

 

投票处理

投票结果百分比处理

投票信息文件

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第四章  网上书店总体设计

4.1.系统功能设计目标

理论系统功能设计目标如下:

(1) 实用性强:我们努力使系统符合实际操作流程的习惯,并尽量减少用户的输入,易学易用的友好的用户界面,满足各层次的用户使用的需求;

(2) 先进的程序结构:使用当代前卫的软件编程,能延长其生命周期,易于维护与管理;

(3) 安全可靠性高:后台维护功能齐全,根据平台在各个阶段不同的使用情况,管理人员可以设置相应的操作权限,增加系统注册,分配各个栏目的管理权限,实现系统的维护,保证系统的安全、可靠;

(4) 使用模块化设计的方法:使系统具有良好的可扩充性,以适应其不同阶段的发展需要,便于后来者分析、维护;

(5) 操作简单,维护方便:每个子系统都具有相对独立的系统维护功能

对可变化的项目可自行维护;

(6) 查询功能强大:可以对商品的基本情况、用户基本情况,书评信息,用户留言等按各种方式查询,可形成各种表单,同时还可对其进行汇总,使管理人员能及时准确地掌握用户和商品等的基本情况。

4.2.网上书店系统功能模块划分:

根据需求分析与系统功能设计目标,结合实际情况本系统功能模块设计分为如下几个模块:

 

1.主页:在这里我们可以看见本系统的主要功能和信息。

2.用户注册:在这里我们可以注册我们的基本信息,其中电话和Email是比较重要的,因为我们需要对这些信息进行处理,以方便用户的付款和邮购。

3.用户登陆:为了方便用户的付款,邮购和管理,我们需要变成会员后才可以进行消费。

4.书目浏览:成功登陆后的用户可以分页浏览图书书目,并将想要的图书提交到填写订单页面。如果用户还没有注册就直接进入或者没有成功登陆就进入页面,将被连接到“用户登录页面”。

5.订购图书:成功登陆的用户可以在该页面定购所需要的图书。如果用户还没有注册就直接进入或者没有成功登陆就进入页面,将被连接到“用户登陆页面”。

6.查看订单:成功登陆的用户可以在该页修改已经订购的图书。如果用户还没有注册就直接进入或者没有成功登陆就进入页面,将被连接到“用户登陆页面”。

7.修改订单:成功登陆的用户可以在该页修改或删除已经订购的图书。如果用户还没有注册就直接进入或者没有成功登陆就进入页面,将被连接到“用户登陆页面”。

8.书目查询:成功登陆的用户可以在该页查找自己需要的图书。如果用户还没有注册就直接进入或者没有成功登陆就进入页面,将被连接到“用户登陆页面”。

9.修改密码:成功登陆的用户可以在该页修改自己的密码。如果用户还没有注册就直接进入或者没有成功登陆就进入页面,将被连接到“用户登陆页面”。

10.修改个人信息:成功登陆的用户可以在该页修改自己当初注册时的信息。如果用户还没有注册就直接进入或者没有成功登陆就进入页面,将被连接到“用户登陆页面”。

11.留言板:成功登录的用户可以在该页留言。

12.管理员:查看用户订单,查看用户信息,并根据用户信息和用户订单对商品进行发派。

4.3 网上书店的体系结构

三层结构:

 

视  图

逻辑处理

数 据 库

 

 
 

 

 

即:JSP技术 + JavaBeans + SQLServer2000 数据库

 

 

 

 

 

 

 

第五章 数据库设计

5.1  数据库系统概述:

数据库系统是在文件系统的基础上发展而来的,经历了层次数据库、网状数据库、关系数据库三个阶段。由于关系数据库采用人们比较容易理解和接受的二维表格来组织数据,发展迅速,已成为数据库产品的主流。

本系统的前端开发是使用JSP技术,通过JavaBeans进行逻辑控制和数据库连接,而后台数据库采用的是SQLServer2000。SQLServer2000数据库管理系统是一项全面完整的数据库与分析产品。SQLServer2000非常容易学习、使用,介绍、学习资料比较多,SQLServer2000全面支持Web功能的数据库解决方案,与此同时,SQLServer2000还在可伸缩性与可靠性方面保持着多项基准测试纪录,而这两方面特性又都是企业数据库系统在激烈市场竞争中克敌致胜的关键所在。无论以应用程序开发速度还是以事务处理运行速度来衡量,SQLServer2000都堪称最为快捷的数据库系统。

对比SQLServer2000和其他的大型数据库管理系统,SQLServer2000 具有可靠的安全性,较快的存储速度,高度的兼容性,简单易用,应用SQLServer2000作为后台数据库为系统的开发提供了强有力的支持,并对以后软件的运行提供了坚实的基础,因此,我们采用了SQLServer2000作为后台数据库。

5.2  数据库表设计:

本系统的数据库其中包括九个表,分别为图书书目表book, 用户注册表member, 管理员表admin, 书籍类型表booktype, 评论表commont, 购物车表gouwuche, 新闻表news,定单表num,定单详细信息表orderbook。详情请见下面的数据库表:

  1. 图书书目表booK

2、管理员表admin

 

3、新闻表news

 

4、书籍类型表booktype

 

5、用户注册表member

 

 

6、定单详细信息表orderbook

 

 

7、定单表num

 

 

 

 

 

第六章  程序设计

6.1  程序说明

根据前边的需求分析和系统总体设计内容进行程序设计。本系统是以Java 语言为基础进行开发的。我个人负责的模块主要是采用JSP技术+JavaBeans+ SQLServer2000 模式进行模块开发与实现的。全部都以面向对象的方法进行设计和实现。

一.在程序设计时,我用到了下面的一些定义,解释如下:

  1. Page用来定义整个JSP页面的一些属性和这些属性的的值。
  2. Page import该属性的作用是为JSP页面引入JAVA核心包中的类,这样就可以在JSP页面的程序片部分,变量及函数声明部分,表达式部分使用的类。可以为该属性指定多个值,该属性的值可以是JAVA某个包中的所有类或一个具体的类。
  3. include file 表示的是静态的插入一个文件。

 

 

6.2  具体模块划分

1 .主页面模块 

   主页面中上部包含:①标题:欢迎光临网上书店

②此刻访问的时间和访问本站的人数等部分。

 

 

 

 

 

 

 

  1. 用户注册模块

用户注册是为第一次登录网站的用户所设计的。在用户成为本站用户之前一定要注册才可以访问我网站的其它网页。

①用户注册包括:登录名称,真实姓名,设置密码,电子邮件等。

②注册成功后,才可以访问本站的其他页面。

③此模块是我负责的。

 

 

 

 

 

 

   

 

 

 

 

 

 

 

 

 

 

 

 

 

3.用户登录模块

①用户登录包括:登录名称,输入密码。

②当用户登录后由管理员核对该用户的名称和密码是否正确,如果无误的话用户可以通过检查直接进入网站的其它页面进行浏览和订购。

 

7.3  软件测试:

软件的测试是系统开发周期中一个十分重要的环节,其重要性体现在它是保证系统质量与可靠性的最后关口,是对整个系统开发过程的最终审查,如果错误不能在测试阶段被发现并纠正,就可能会造成不堪设想的后果。

在网上书店电子商务平台中,我们以黑盒测试为主,白盒测试为辅。对关键模块采用白盒测试。

测试结果:

★中文乱码问题,从数据库中取出的中文数据出现乱码。

★参数传递出错,无法传递数据。

★当大量用户同时对数据库进行访问时,效率低,有的用户访问失败。

主要的解决方法:

☆我使用如下方法来解决乱码问题:

request.getParameter("choose").getBytes("8859_1"),"GB2312"

<%@ page contentType="text/html;charset=GB2312"%>

  • 对于各个模块接参数的格式进行了统一
  • 本程序以中小型为基础,采用JDBC数据源进行连接数据库这就决定了本系统的先天缺陷。纯正的电子商务网站均采用连接池,出于技术难度大和资料缺乏放弃此最佳方案。

 

 

 

 

 

 

 

 

 

 

 

 

 

第八章    结束语

经过十个月的不懈努力,和指导老师的谆谆教导,以及同组同学的团结协作,充分利用大学四年所学的专业知识,通过大量阅读与设计相关的专业参考文献,我终于完成了本次毕业设计,已经基本上实现了网上书店系统的各项功能。

在这次毕业设计过程中,我独立设计和实现了用户注册,用户登录,修改密码,留言板,书目浏览等五个模块的全部功能,以及这几个模块的数据库设计。

通过这次毕业设计,我掌握了SQLServer2000这种大型数据库的编程方法,掌握了JAVA语言和JSP技术。了解到电子商务的理论以及平台开发的模式,掌握了网络开发的方法与模式。对团队的协作,对软件开发方法和手段,有了一定的认识,丰富了我的软件开发的经验,提高了程序编写的水平,并加深理解了许多课程中、书本上学到的知识和理论。同时,通过编写毕业设计论文,我还基本掌握了软件文档的书写方法和书写格式。

鲁迅说,世上倘若有完全的人,那么配活的也就相当有限。本系统尽管有自己的特色,如聊天室,游戏。但缺陷也是明显的,如安全性,效率问题等等。由于时间,能力,国内JAVA,JSP技术资料有限等原因,有些功能实现的并不完美,在已经完成的程序中,也存在许多不尽人意的算法,也没有统一优化,系统有待进一步改善,而这些问题也让我充分认识到了软件开发的困难。

通过毕业设计,我学会了如何去了解一种新型的技术,去掌握一种技术。以及软件开发的基本流程。在学习过程中,我们遇到问题经常上网求助,去书店查询资料,扩大了自己的知识面。

总之,这次毕业设计为我今后继续学习、深造奠定了基础,我非常感谢各位老师、同学的支持与帮助。

 

 

 

  •  附 录

 

9.1  致谢

在本次毕业设计过程中,得到了指导老师的指导与支持。在此特别感谢XX老师、沈辉老师的大力帮助。指导老师的悉心指导和大力支持,在总体结构、功能的把握上给予了非常大的帮助,同时根我们提供了非常优越的设计环境,并对我在编程、数据库设计等细节工作上给予了耐心的指导,对于我们小组顺利完成这次毕业设计起到了关键性的作用。

另外和我同组同学大家始终团结协作,努力拼搏,增强了我的团队意识,并且我们接下了深厚的友谊,我们自始至终在一种愉快的气氛中学习工作。此次毕业设计对提高我的编程技术、协调团队成员的关系等方面都由许多益处。在此我一并向他表示感谢。我还要感谢我的母校——沈阳航空学院大学,以及在大学四年生活中给予我关心和帮助的老师和同学,是他们教会了我专业的知识和做人的道理。通过这次毕业设计我还明白了作为一名计算机专业的大学毕业生,我们要会的不仅仅是编写代码,更重要的是要有整体把握系统设计的能力。我会在以后的工作和学习中不断完善自己,为我最热爱的母校争光,为自己翻开辉煌的新篇章。

9.2  参考文献

[1]孙卫琴,李洪成.《Tomcat 与 Java Web 开发技术详解》.电子工业出版社,2003年6月:1-205

[2]BruceEckel.《Java编程思想》. 机械工业出版社,2003年10月:1-378

[3]FLANAGAN.《Java技术手册》. 中国电力出版社,2002年6月:1-465

[4]孙一林,彭波.《Java数据库编程实例》. 清华大学出版社,2002年8月:30-210

[5]Lee Anne Phillips.《巧学活用HTML4》.电子工业出版社,2004年8月:1-319

[6]飞思科技产品研发中心.《JSP应用开发详解》.电子工业出版社,2003年9月:32-300

[7]耿祥义,张跃平.《JSP实用教程》. 清华大学出版社,2003年5月1日:1-354

[8]孙涌.《现代软件工程》.北京希望电子出版社,2003年8月:1-246

[9]萨师煊,王珊.《数据库系统概论》.高等教育出版社,2002年2月:3-460

[10]Brown等.《JSP编程指南(第二版)》. 电子工业出版社 ,2003年3月:1-268    

[11]清宏计算机工作室.《JSP编程技巧》. 机械工业出版社, 2004年5月:1-410  

[12]朱红,司光亚.JSP Web编程指南》.电子工业出版社, 2001年9月:34-307     

[13]赛奎春.JSP工程应用与项目实践》. 机械工业出版社, 2002年8月:23-294     

9.3 部分源代码

  1. 用户注册 userRegister,jsp:

<%String str=response.encodeURL("userRegister.jsp");

%>

输入您的信息,带*号项必须填写:


登录名称*


真实姓名

*

 

 设置密码*

 电子邮件

 *

  

 联系电话*


通信地址*


 <% //提交信息后,进行注册操作:

   String logname="",realname="",password="",email="",phone="",address="";

   if(!(session.isNew()))

    { logname=request.getParameter("logname");

      if(logname==null)

        {logname="";

        }

     logname=codeString(logname);

     realname=request.getParameter("realname");

       if(realname==null)

        {realname="";

        }

     realname=codeString(realname);

     password=request.getParameter("password");

         if(password==null)

        {password="";

        }

     password=codeString(password);

     email=request.getParameter("email");

         if(email==null)

        {email="";

        }

     email=codeString(email);

     phone=request.getParameter("phone");

         if(phone==null)

        {phone="";

        }

     phone=codeString(phone);

     address=request.getParameter("address");

        if(address==null)

        {address="";

        }

     address=codeString(address);

   }

 %>

 <%   //为了以后处理汉字方便,我们采用了第1种方式初始化beans

   if(!(logname.equals(""))&&!(address.equals(""))&&!(phone.equals(""))

        &&!(realname.equals(""))&&!(password.equals("")))

   {%>

    

    

    

    

    

    

    <%

     login.addItem();

    }

   else

    {out.print("你还没有填写信息,或信息填写不完整");

    }

 %>

  <% //返回注册信息

     if(!(session.isNew()))

    {

  %>

本程序用了Bean: Rigister.java

mport java.sql.*;

public class Register

{   String logname,

           realname,

           password,

           email,

           phone,

           address;

    String message;

    Connection con;

    Statement sql;

    ResultSet rs;

    public Register()

    {  //加载桥接器:

       try{Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

          }

       catch(ClassNotFoundException e){}

}

//添加记录到数据库的user表:

public void addItem()

    {try{

        con=DriverManager.getConnection("jdbc:odbc:shop","","");

        sql=con.createStatement();

        String s=

        "'"+logname+"'"+","+"'"+realname+"'"+","+"'"+password+"'"+","+"'"+

          email+"'"+","+"'"+phone+"'"+","+"'"+address+"'";

        String condition="INSERT INTO user VALUES"+"("+s+")";

        sql.executeUpdate(condition);

        message="注册成功了";

        con.close();

       }

     catch(SQLException e)

       {message="你还没有注册,或该用户已经存在,请你更换一个名字";

       }

    }

}

  1. 用户登录 userLogin.jsp:

<%String string=response.encodeURL("userLogin.jsp");

%>

输入用户名和密码:


登录名称


输入密码


 <% //提交信息后,验证信息是否正确:

   String  message="",

           logname="",

           password="";

    if(!(session.isNew()))

    {logname=request.getParameter("logname");

        if(logname==null)

           {logname="";

           }

     logname=codeString(logname);

     password=request.getParameter("password");

        if(password==null)

           {password="";

           }

     password=codeString(password);

   }

 %>

 <%

   if(!(logname.equals("")))

    {

   %>

    

    

    

         <%

     message=login.getMessage();

        if(message==null)

          {message="";

          } 

   }

 %>

        <%  if(!(session.isNew()))

      { if(message.equals("ok"))

          { String str=response.encodeURL("buybook.jsp");

            response.sendRedirect(str);

          }

        else

          {out.print(message);

          }

      }

  %>

本程序用了Bean: Login.java :

package book;

import java.sql.*;

public class Login

{   String logname,

           realname,

           password,

           phone,

           address;

    String success="false",message="";

    Connection con;

    Statement sql;

    ResultSet rs;

    public Login()

    {  //加载桥接器:

       try{Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

          }

       catch(ClassNotFoundException e){}

}

//查询数据库的user表:

   public String getMessage()

    {try{

         con=DriverManager.getConnection("jdbc:odbc:shop","","");

         sql=con.createStatement();

          String condition=

         "SELECT * FROM user WHERE logname = "+"'"+logname+"'";

          rs=sql.executeQuery(condition);

          int rowcount=0;

          String ps=null;

         while(rs.next())

           { rowcount++;

             logname=rs.getString("logname");

             realname=rs.getString("realname");

             ps=rs.getString("password");

             phone=rs.getString("phone");

             address=rs.getString("address");

           }

         if((rowcount==1)&&(password.equals(ps)))

           { message="ok";

             success="ok";

           }

        else

           {message="输入的用户名或密码不正确";

            success="false";

           }

        con.close();

        return message;

       }

     catch(SQLException e)

       { message="输入的用户名或密码不正确";

         success="false";

         return message;

       }

    }

}

  1. 修改密码 modifyPassword.jsp:

String success=login.getSuccess();

         if(success==null)

         {success="";

         }

   if(!(success.equals("ok")))

     {response.sendRedirect("userLogin.jsp");

     }

%>

<%@ include file="head.txt" %>

修改密码,密码长度不能超过30个字符:

<%String str=response.encodeURL("modifyPassword.jsp");

%>


输入您的用户名:



输入您的密码:



输入您的新的密码:



请再输入一次新密码:



<%!//处理字符串的一个常用方法:

    public String getString(String s)

     { if(s==null) s="";

       try {byte a[]=s.getBytes("ISO-8859-1");

            s=new String(a);

           }

       catch(Exception e)

           {  }

       return s;

     }

 %>

<%

    String logname=request.getParameter("logname"); //获取提交的用户名。

           logname=getString(logname);

    String password=request.getParameter("password"); //获取提交的密码。

           password=getString(password);

    String newPassword1=request.getParameter("newPassword1"); //获取提交的新密码1。

           newPassword1=getString(newPassword1);

    String newPassword2=request.getParameter("newPassword2"); //获取提交的新密码2。

       newPassword2=getString(newPassword2);

     try{Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

       }

     catch(ClassNotFoundException event){}

    //验证身份:

    Connection con=null;

    Statement sql=null;

    boolean modify=false;

    boolean ifEquals=false;

         ifEquals=(newPassword1.equals(newPassword2))&&(newPassword1.length()<=30);

    if(ifEquals==true)

        {  try{ con=DriverManager.getConnection("jdbc:odbc:shop","","");

                sql=con.createStatement();

                boolean bo1=logname.equals(login.getLogname()),

                      bo2=password.equals(login.getPassword());

                if(bo1&&bo2)

                    {//修改密码:

                     modify=true;

                     out.print("您的密码已经更新");

                     String c="UPDATE user SET password = "+"'"+newPassword1+"'"+

                     " WHERE logname = "+"'"+logname+"'";

                     sql.executeUpdate(c);

                    }

                con.close();

               }

             catch(SQLException e1) {}

        }

    else

       { out.print("你两次输入的密码不一致或长度过大");

       }

    if(modify==false&&ifEquals==true)

 { out.print("
您没有输入密码帐号或
您输入的帐号或密码不正确"+logname+":"+password);

 }

%>

  1. 书目浏览 showBookList.jsp:

图书目录:

 <%! //声明一个共享的连接对象:

    Connection con=null;

    //显示数据库记录的方法:

    public void showList(ResultSet rs,javax.servlet.jsp.JspWriter  out,int n,String buybook)

     {try

      {

        out.print("

");

         out.print("

");

          out.print("

");

       for(int i=1;i<=n;i++)

       {   out.print("

");

             String id=rs.getString(1);//图书id号。

             out.print("

");

             out.print("

");

             out.print("

");

             out.print("

");

             out.print("

");

             out.print("

");

             out.print("

");

             out.print("

");

            //在本书的的后面显示一个表单,该表单将内容提交到buybook.jsp,

            //将id号提交给buybook.jsp,以便订购该书:

            String s1="
";

            String s2="";

            String s3=" ";

            String s=s1+s2+s3;

            out.print("

");

           out.print("

") ;

         rs.next();

        }

       out.print("

"+""+"id"+"");

          out.print("

"+""+"订购号"+"");

          out.print("

"+""+"书名"+"");

          out.print("

"+""+"作者"+"");

          out.print("

"+""+"出版社"+"");

          out.print("

"+""+"出版时间"+"");

          out.print("

"+""+"价钱"+"");

          out.print("

"+""+"分类"+"");

          out.print("

"+""+"添加到订单"+"");

         out.print("

"+id+" "+rs.getString(2)+" "+rs.getString(3)+" "+rs.getString(4)+" "+rs.getString(5)+" "+rs.getDate(6)+" "+rs.getString(7)+" "+rs.getString(8)+" "+s+"
");

      }

      catch(Exception e1) {}

    }

  %>

 <% Statement sql=null;

    ResultSet rs=null;

   int rowCount=0;   //总的记录数。

    //第一个客户负责建立连接对象:

    if(con==null)

      { try{Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

           }

           catch(ClassNotFoundException e)

           {out.print(e);

           }

        try

          {con=DriverManager.getConnection("jdbc:odbc:shop","","");

           sql=

    con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);

           rs=sql.executeQuery("SELECT * FROM book");//返回可滚动的结果集。

            rs.last();//将游标移动到最后一行。

           int number=rs.getRow();//获取最后一行的行号。

            rowCount=number; //获取记录数。

           handlePage.setPageSize(3); //设置每页显示的记录数。

           handlePage.setPageCount(rowCount,handlePage.getPageSize());//计算总页数。

           out.print("共有"+handlePage.getPageCount()+"页,");

           out.print("每页显示"+ handlePage.getPageSize()+"条记录");  

          }

        catch(SQLException e)

          {out.print(e);

          }

      }

     //其它客户通过同步块使用这个连接:

     else

      { synchronized(con)

        {  try { sql=

    con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);

           rs=sql.executeQuery("SELECT * FROM book"); //返回可滚动的结果集。

            rs.last();           //将游标移动到最后一行。

           int number=rs.getRow();//获取最后一行的行号。

            rowCount=number; //获取记录数。

           handlePage.setPageSize(3); //设置每页显示的记录数。

            handlePage.setPageCount(rowCount,handlePage.getPageSize());//计算总页数。

           out.print("共有"+handlePage.getPageCount()+"页,"); 

           out.print("每页显示"+ handlePage.getPageSize()+"条记录");

          }

        catch(SQLException e)

          {out.print(e);

          }

        }

      }

  %>

  <%--选择显示某页的表单 --%>

<%String str=response.encodeURL("showBookList.jsp");

  String buybook=response.encodeURL("buybook.jsp");

%>

 

  显示下一页:

   

 

 

  显示上一页:

 

 

 

  输入欲要显示的页

 

 

 <% //获取表单提交的信息:

    String s=request.getParameter("a");

       if(s==null)

          {s="1";

          }

    if(s.equals("next"))

        { int n=handlePage.getShowPage(); //获取目前的页数。

              n=(n+1); //将页数增1。

           if(n>handlePage.getPageCount())

              { n=1;

              }

           handlePage.setShowPage(n);              //显示该页。

          out.print("目前显示第"+handlePage.getShowPage()+"页");

          //将游标移到:

          rs.absolute((n-1)*handlePage.getPageSize()+1);

         showList(rs,out,handlePage.getPageSize(),buybook); //显示第该页的内容。

         }

    else if(s.equals("previous"))

        { int n=handlePage.getShowPage(); //获取目前的页数。

              n=(n-1); //将页数减1。

          if(n<=0)

              { n=handlePage.getPageCount();

              }   

           handlePage.setShowPage(n);              //显示该页。

          out.print("目前显示第"+handlePage.getShowPage()+"页");

          rs.absolute((n-1)*handlePage.getPageSize()+1);  //移动游标。

         showList(rs,out,handlePage.getPageSize(),buybook); //显示第该页的内容。

       }

    else

        { int m=Integer.parseInt(s);

          handlePage.setShowPage(m);

          out.print("目前显示第"+handlePage.getShowPage()+"页");

          int n=handlePage.getShowPage();

          rs.absolute((n-1)*handlePage.getPageSize()+1); //移动游标。

          showList(rs,out,handlePage.getPageSize(),buybook); //显示该页的内容。

       }

   %>

本程序用了Bean: PageNumber.java

package book;

public class PageNumber

{  int  rowCount=1,   //总的记录数。

        pageSize=1,  //每页显示的记录数。

        showPage=1,  //设置欲显示的页码数。

        pageCount=1; //分页之后的总页数。

   public void setRowCount(int n)

       { rowCount=n;

       }

   public int getRowCount()

       { return rowCount;

       }

   public void setPageCount(int r,int p)

       { rowCount=r;

         pageSize=p;

         int n=(rowCount%pageSize)==0?(rowCount/pageSize):(rowCount/pageSize+1) ;

         pageCount=n;

       }

   public int getPageCount()

       {return pageCount;

       }

   public void setShowPage(int n)

       {showPage=n;

       }

   public int getShowPage()

       { return showPage;

       }

   public void setPageSize(int n)

       { pageSize=n;

       }

   public int getPageSize()

       { return pageSize;

       }

}

5.留言板message.jsp:

你可以在这里实现公共留言、私人留言。

<%String str=response.encodeURL("leaveword.jsp");

%>

输入你的会员名字
>


输入你的留言:



公共留言(所有的会员都能查看到):



私人留言(输入他(她)的会员名)To:


进行留言的文件leaveword.jsp:

<% //获取提交键的的串值:

    String s=request.getParameter("submit");

           s=getString(s);

     //根据s的不同情况分开处理:

    if(s.equals("提交为公共留言"))

       { //获取提交的留言:

         String ms=request.getParameter("message");

         ms=getString(ms);

         publicbean.setLogname(login.getLogname());

         publicbean.setMessage(""+login.getLogname()+"的留言:"+ms);

        //留言:

         publicbean.addItem();

         out.print(publicbean.getBackNews());

       }

    else if(s.equals("提交为私人留言"))

       { //获取会员的名字:

         String name=request.getParameter("person");

           name=getString(name);

         String ms=request.getParameter("message");

         ms=getString(ms);

         if(name.equals(""))

            {out.print("您没有输入他(她)的名字,不能留言给人家");

            }

         else

            {  secretbean.setLogname(name);

               secretbean.setMessage(""+login.getLogname()+"留言给你:"+ms);

               //留言时间:

               long n=System.currentTimeMillis();

                String time=String.valueOf(n);

               secretbean.setTime(time);

               //留言:

               secretbean.addItem();

               out.print(secretbean.getBackNews());

}

查看公共留言的文件publicmessage.jsp:

公共留言列表:

 <%! //声明一个共享的连接对象:

    Connection con=null;

    //显示数据库记录的方法:

   public void showList(ResultSet rs,javax.servlet.jsp.JspWriter  out,int n,book.HandleMessage h)

     {try

      {

        out.print("

");

         out.print("

");

          out.print("

");

       for(int i=1;i<=n;i++)

       { 

          String logname=rs.getString("logname");

          String message=rs.getString("public"); 

         if(logname==null)

               {logname="";

               }

            if(message==null)

               {message="";

               }

          //为了能显示原始的HTML或JSP文件格式的信息,需对信息进行回压流处理:

           h.setContent(message);

           message=h.getContent();

           //将信息显示在表格中:

           out.print("

");

             out.print("

");

             out.print("

");

           out.print("

") ;

            rs.next();   

        }

       out.print("

"+""+"会员名"+"");

          out.print("

"+""+"留言信息"+"");

         out.print("

"+logname+" "+message+"
");

      }

      catch(Exception e1) {}

    }

  %>

 <% Statement sql=null;

    ResultSet rs=null;

    int rowCount=0;   //总的记录数。

    String logname="";

     //第一个客户负责建立连接对象:

    if(con==null)

      { try{Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

           }

           catch(ClassNotFoundException e)

           {out.print(e);

           }

        try

          {con=DriverManager.getConnection("jdbc:odbc:shop","","");

           sql=

     con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);

           rs=sql.executeQuery("SELECT * FROM wordpad");  //返回可滚动的结果集。

            rs.last();  //将游标移动到最后一行。

           int number=rs.getRow();  //获取最后一行的行号。

            rowCount=number;  //获取记录数。

           handlePage.setPageSize(3);   //设置每页显示的记录数。

           handlePage.setShowPage(1);  //设置欲显示的页码数。

           handlePage.setPageCount(rowCount,handlePage.getPageSize());  //计算总页数。

           out.print("共有"+handlePage.getPageCount()+"页,");

           out.print("每页显示"+ handlePage.getPageSize()+"条记录");  

          }

        catch(SQLException e)

          {out.print(e);

          }

      }

     //其它客户通过同步块使用这个连接:

     else

      { synchronized(con)

        {  try {   sql=

    con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);

           rs=sql.executeQuery("SELECT * FROM wordpad");  //返回可滚动的结果集。

            rs.last();  //将游标移动到最后一行。

           int number=rs.getRow();  //获取最后一行的行号。

            rowCount=number;  //获取记录数。

           handlePage.setPageSize(3);  //设置每页显示的记录数。

           handlePage.setShowPage(1);   //设置欲显示的页码数。

            handlePage.setPageCount(rowCount,handlePage.getPageSize());  //计算总页数。

           out.print("共有"+handlePage.getPageCount()+"页,"); 

           out.print("每页显示"+ handlePage.getPageSize()+"条记录");

          }

        catch(SQLException e)

          {out.print(e);

          }

        }

      }

  %>

  <%--选择显示某页的表单 --%>

 

  输入欲要显示的页

 

 

 <% //获取表单提交的信息:

     String s=request.getParameter("ok");

       if(s==null)

          {s="1";

          }

     int m=Integer.parseInt(s);

          handlePage.setShowPage(m);

          out.print("目前显示第"+handlePage.getShowPage()+"页");

          int n=handlePage.getShowPage();

           //将游标移到:

          rs.absolute((n-1)*handlePage.getPageSize()+1);

          showList(rs,out,handlePage.getPageSize(),handle);  //显示该页的内容

%>

负责公共留言的beans publicword.java:

//添加记录到数据库的wordpad表:

   public void addItem()

    {try{

        con=DriverManager.getConnection("jdbc:odbc:shop","","");

        sql=con.createStatement();

        String s="'"+logname+"'"+","+"'"+message+"'";

        String condition="INSERT INTO wordpad VALUES"+"("+s+")";

        sql.executeUpdate(condition);

        backNews="添加成功了";

        con.close();

       }

     catch(SQLException e)

      {//由于表wordpad和member表通过字段logname做了关联,所以如果输入的logname

        //不对,就会出现异常

        backNews="你没有登录,不能留言";

}

查看私人留言的文件secretmessage.jsp:

公共留言列表:

 <%! //声明一个共享的连接对象:

    Connection con=null;

    //显示数据库记录的方法:

   public void showList(ResultSet rs,javax.servlet.jsp.JspWriter  out,int n,book.HandleMessage h)

     {try

      {

        out.print("

");

         out.print("

");

          out.print("

");

       for(int i=1;i<=n;i++)

       { 

          String logname=rs.getString("logname");

            if(logname==null)

               {logname="";

               }

          String message=rs.getString("message");

             if(message==null)

               {message="";

               }

          String time   =rs.getString("time");  //获取该信息的留言时间

            if(time==null)

               {time="";

               }

          //为了能显示原始的HTML或JSP文件格式的信息,需对信息进行流处理:

           h.setContent(message);

           message=h.getContent();

           //将信息显示在表格中:

           out.print("

");

             out.print("

");

             out.print("

");

             out.print("

");

           //添加一个删除该信息的表单:

            String s1="
";

            String s2="";

            String s3=" ";

            String s=s1+s2+s3;

            out.print("

");

           out.print("

") ;

            rs.next();   

        }

       out.print("

"+""+"会员名"+"");

          out.print("

"+""+"留言信息"+"");

          out.print("

"+""+"留言时间"+"");

          out.print("

"+""+"删除留言"+"");

         out.print("

"+logname+" "+message+" "+time+" "+s+"
");

      }

      catch(Exception e1) {}

    }

  %>

 <% Statement sql=null;

    ResultSet rs=null;

    int rowCount=0;   //总的记录数。

    String logname="";

     //第一个客户负责建立连接对象:

    if(con==null)

      { try{Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

           }

           catch(ClassNotFoundException e)

           {out.print(e);

           }

        try

          {con=DriverManager.getConnection("jdbc:odbc:shop","","");

           sql=

    con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);

            String s=login.getLogname();

                 if(s==null)

                   {s="";

                   }

           //得到自己的私人留言:

          String condition="SELECT * FROM secretwordpad WHERE logname = "+"'"+s+"'";

            rs=sql.executeQuery(condition);   //返回可滚动的结果集。

            rs.last();  //将游标移动到最后一行。

           int number=rs.getRow();   //获取最后一行的行号。

            rowCount=number;   //获取记录数。

           handlePage.setPageSize(3);   //设置每页显示的记录数。

           handlePage.setShowPage(1);   //设置欲显示的页码数。

           handlePage.setPageCount(rowCount,handlePage.getPageSize());   //计算总页数。

           out.print("共有"+handlePage.getPageCount()+"页,");

           out.print("每页显示"+ handlePage.getPageSize()+"条记录");  

          }

        catch(SQLException e)

          {out.print(e);

          }

      }

     //其它客户通过同步块使用这个连接:

     else

      { synchronized(con)

        {  try {   sql=

    con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);

              String s=login.getLogname();

                 if(s==null)

                   {s="";

                   }     

            //得到自己的私人留言:

         String condition="SELECT * FROM secretwordpad WHERE logname = "+"'"+s+"'";

            rs=sql.executeQuery(condition);  //返回可滚动的结果集。

             rs.last();  //将游标移动到最后一行。

           int number=rs.getRow();  //获取最后一行的行号。

            rowCount=number;  //获取记录数。

           handlePage.setPageSize(3);   //设置每页显示的记录数。

           handlePage.setShowPage(1);  //设置欲显示的页码数。

            handlePage.setPageCount(rowCount,handlePage.getPageSize());  //计算总页数。

           out.print("共有"+handlePage.getPageCount()+"页,"); 

           out.print("每页显示"+ handlePage.getPageSize()+"条记录");

          }

        catch(SQLException e)

          {out.print(e);

          }

         catch(IOException ee ){}

        }

      }

  %>

  <%--选择显示某页的表单 --%>

 

  输入欲要显示的页

 

 

 <% //获取表单提交的信息:

     String s=request.getParameter("ok");

       if(s==null)

          {s="1";

          }

     int m=Integer.parseInt(s);

          handlePage.setShowPage(m);

          out.print("目前显示第"+handlePage.getShowPage()+"页");

          int n=handlePage.getShowPage();

           //将游标移到:

          rs.absolute((n-1)*handlePage.getPageSize()+1);

          showList(rs,out,handlePage.getPageSize(),handle);  //显示该页的内容。

 %>

负责私人留言的beans secretword.java:

  //添加记录到数据库的wordpad表:

   //我们要求留言的时间是唯一的,所以下面的方法声明为synchronized

   public synchronized void addItem()

    {try{

        con=DriverManager.getConnection("jdbc:odbc:shop","","");

        sql=con.createStatement();

        String s="'"+logname+"'"+","+"'"+message+"'"+","+"'"+time+"'";

        String condition="INSERT INTO secretwordpad VALUES"+"("+s+")";

        sql.executeUpdate(condition);

        backNews="添加成功了";

        con.close();

       }

     catch(SQLException e)

       {//由于表wordpad和member表通过字段logname做了关联,所以如果输入对方的logname

        //不存在,就会出现异常

        backNews="该会员不存在,不能留言给他(她)";}

删除私人留言的文件delete.jsp:

<% //获取提交的信息的时间:

    String time=request.getParameter("time");

          if(time==null)

            {time="";}

   byte b[]=time.getBytes("ISO-8859-1");

     time=new String(b);

    Connection con=null;

    Statement sql=null;

    ResultSet rs=null;

       try{Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

          }

       catch(ClassNotFoundException event){}

       try

        {con=DriverManager.getConnection("jdbc:odbc:shop","","");

         sql=con.createStatement();

         //删除操作:

          String s=login.getLogname();

          String condition1=

              "DELETE  FROM secretwordpad WHERE logname ="+"'"+s+"'";

          String condition2=

               "AND time ="+"'"+time+"'";

          String condition=condition1+condition2;

          sql.executeUpdate(condition);

          out.print("删除了该留言");

          con.close();

        }

       catch(SQLException event)

        {out.print(""+event);

        }

%>

9.4  翻 译

The Java I/O System

Creating a good input/output (I/O) system is one of the more difficult tasks for the language designer.

This is evidenced by the number of different approaches. The challenge seems to be in covering all eventualities. Not only are there different sources and sinks of I/O that you want to communicate with (files, the console, network connections, etc.), but you need to talk to them in a wide variety of ways (sequential, random-SQLServer2000, buffered, binary, character, by lines, by words, etc.).

The Java library designers attacked this problem by creating lots of classes. In fact, there are so many classes for Java’s I/O system that it can be intimidating at first (ironically, the Java I/O design actually prevents an explosion of classes). There was also a significant change in the I/O library after Java 1.0, when the original byte-oriented library was supplemented with char-oriented, Unicode-based I/O classes. In JDK 1.4, the nio classes (for “new I/O,” a name we’ll still be using years from now) were added for improved performance and functionality. As a result, there are a fair number of classes to learn before you understand enough of Java’s I/O picture that you can use it properly. In addition, it’s rather important to understand the evolution history of the I/O library, even if your first reaction is “don’t bother me with history, just show me how to use it!” The problem is that without the historical perspective, you will rapidly become confused with some of the classes and when you should and shouldn’t use them.

This chapter will give you an introduction to the variety of I/O classes in the standard Java library and how to use them.

The File class

Before getting into the classes that actually read and write data to streams, we’ll look at a utility provided with the library to assist you in handling file directory issues.

The File class has a deceiving name; you might think it refers to a file, but it doesn’t. It can represent either the name of a particular file or the names of a set of files in a directory. If it’s a set of files, you can ask for that set using the list( ) method, which returns an array of String. It makes sense to return an array rather than one of the flexible container classes, because the number of elements is fixed, and if you want a different directory listing, you just create a different File object. In fact, “FilePath” would have been a better name for the class. This section shows an example of the use of this class, including the associated FilenameFilter interface.

A directory lister

Suppose you’d like to see a directory listing. The File object can be listed in two ways. If you call list( ) with no arguments, you’ll get the full list that the File object contains. However, if you want a restricted list—for example, if you want all of the files with an extension of .java—then you use a “directory filter,” which is a class that tells how to select the File objects for display.

The DirFilter class “implements” the interface FilenameFilter. It’s useful to see how simple the FilenameFilter interface is:

public interface FilenameFilter {
  boolean accept(File dir, String name);
}
 

It says all that this type of object does is provide a method called accept( ). The whole reason behind the creation of this class is to provide the accept( ) method to the list( ) method so that list( ) can “call back” accept( ) to determine which file names should be included in the list. Thus, this structure is often referred to as a callback. More specifically, this is an example of the Strategy Pattern, because list( ) implements basic functionality, and you provide the Strategy in the form of a FilenameFilter in order to complete the algorithm necessary for list( ) to provide its service. Because list( ) takes a FilenameFilter object as its argument, it means that you can pass an object of any class that implements FilenameFilter to choose (even at run time) how the list( ) method will behave. The purpose of a callback is to provide flexibility in the behavior of code.

DirFilter shows that just because an interface contains only a set of methods, you’re not restricted to writing only those methods. (You must at least provide definitions for all the methods in an interface, however.) In this case, the DirFilter constructor is also created.

The accept( ) method must accept a File object representing the directory that a particular file is found in, and a String containing the name of that file. You might choose to use or ignore either of these arguments, but you will probably at least use the file name. Remember that the list( ) method is calling accept( ) for each of the file names in the directory object to see which one should be included; this is indicated by the boolean result returned by accept( ).

To make sure the element you’re working with is only the file name and contains no path information, all you have to do is take the String object and create a File object out of it, then call getName( ), which strips away all the path information (in a platform-independent way). Then accept( ) uses a regular expression matcher object to see if the regular expression regex matches the name of the file. Using accept( ), the list( ) method returns an array.

Input and output

I/O libraries often use the abstraction of a stream, which represents any data source or sink as an object capable of producing or receiving pieces of data. The stream hides the details of what happens to the data inside the actual I/O device.

The Java library classes for I/O are divided by input and output, as you can see by looking at the class hierarchy in the JDK documentation. By inheritance, everything derived from the InputStream or Reader classes have basic methods called read( ) for reading a single byte or array of bytes. Likewise, everything derived from OutputStream or Writer classes have basic methods called write( ) for writing a single byte or array of bytes. However, you won’t generally use these methods; they exist so that other classes can use them—these other classes provide a more useful interface. Thus, you’ll rarely create your stream object by using a single class, but instead will layer multiple objects together to provide your desired functionality. The fact that you create more than one object to create a single resulting stream is the primary reason that Java’s stream library is confusing.

It’s helpful to categorize the classes by their functionality. In Java 1.0, the library designers started by deciding that all classes that had anything to do with input would be inherited from InputStream, and all classes that were associated with output would be inherited from OutputStream.

Types of InputStream

InputStream’s job is to represent classes that produce input from different sources. These sources can be:

  1. An array of bytes.
  2. A String object.
  3. A file.
  4. A “pipe,” which works like a physical pipe: You put things in at one end and they come out the other.
  5. A sequence of other streams, so you can collect them together into a single stream.
  6. Other sources, such as an Internet connection. (This is covered in Thinking in Enterprise Java.)

Each of these has an associated subclass of InputStream. In addition, the FilterInputStream is also a type of InputStream, to provide a base class for "decorator" classes that attach attributes or useful interfaces to input streams. This is discussed later.

Types of OutputStream

This category includes the classes that decide where your output will go: an array of bytes (no String, however; presumably, you can create one using the array of bytes), a file, or a “pipe.”

In addition, the FilterOutputStream provides a base class for "decorator" classes that attach attributes or useful interfaces to output streams.

Adding attributes and useful interfaces

The use of layered objects to dynamically and transparently add responsibilities to individual objects is referred to as the Decorator pattern. (Patterns are the subject of Thinking in Patterns (with Java) at www.BruceEckel.com.) The decorator pattern specifies that all objects that wrap around your initial object have the same interface. This makes the basic use of the decorators transparent—you send the same message to an object whether it has been decorated or not. This is the reason for the existence of the “filter” classes in the Java I/O library: The abstract “filter” class is the base class for all the decorators. (A decorator must have the same interface as the object it decorates, but the decorator can also extend the interface, which occurs in several of the “filter” classes).

Decorators are often used when simple subclassing results in a large number of classes in order to satisfy every possible combination that is needed—so many classes that it becomes impractical. The Java I/O library requires many different combinations of features, and this is the justification for using the decorator pattern.There is a drawback to the decorator pattern, however. Decorators give you much more flexibility while you’re writing a program (since you can easily mix and match attributes), but they add complexity to your code. The reason that the Java I/O library is awkward to use is that you must create many classes—the “core” I/O type plus all the decorators—in order to get the single I/O object that you want.

The classes that provide the decorator interface to control a particular InputStream or OutputStream are the FilterInputStream and FilterOutputStream, which don’t have very intuitive names. FilterInputStream and FilterOutputStream are derived from the base classes of the I/O library, InputStream and OutputStream, which is the key requirement of the decorator (so that it provides the common interface to all the objects that are being decorated).

Reading from an InputStream with FilterInputStream

The FilterInputStream classes accomplish two significantly different things. DataInputStream allows you to read different types of primitive data as well as String objects. (All the methods start with “read,” such as readByte( ), readFloat( ), etc.) This, along with its companion DataOutputStream, allows you to move primitive data from one place to another via a stream. These “places” are determined by the classes in Table

The remaining classes modify the way an InputStream behaves internally: whether it’s buffered or unbuffered, if it keeps track of the lines it’s reading (allowing you to ask for line numbers or set the line number), and whether you can push back a single character. The last two classes look a lot like support for building a compiler (that is, they were probably added to support the construction of the Java compiler), so you probably won’t use them in general programming.

You’ll need to buffer your input almost every time, regardless of the I/O device you’re connecting to, so it would have made more sense for the I/O library to make a special case (or simply a method call) for unbuffered input rather than buffered input.

Writing to an OutputStream with FilterOutputStream

The complement to DataInputStream is DataOutputStream, which formats each of the primitive types and String objects onto a stream in such a way that any DataInputStream, on any machine, can read them. All the methods start with “write,” such as writeByte( ), writeFloat( ), etc.

The original intent of PrintStream was to print all of the primitive data types and String objects in a viewable format. This is different from DataOutputStream, whose goal is to put data elements on a stream in a way that DataInputStream can portably reconstruct them.

The two important methods in PrintStream are print( ) and println( ), which are overloaded to print all the various types. The difference between print( ) and println( ) is that the latter adds a newline when it’s done.

PrintStream can be problematic because it traps all IOExceptions (You must explicitly test the error status with checkError( ), which returns true if an error has occurred). Also, PrintStream doesn’t internationalize properly and doesn’t handle line breaks in a platform-independent way.

Readers & Writers

Java 1.1 made some significant modifications to the fundamental I/O stream library. When you see the Reader and Writer classes, your first thought (like mine) might be that these were meant to replace the InputStream and OutputStream classes. But that’s not the case. Although some aspects of the original streams library are deprecated (if you use them you will receive a warning from the compiler), the InputStream and OutputStream classes still provide valuable functionality in the form of byte-oriented I/O, whereas the Reader and Writer classes provide Unicode-compliant, character-based I/O. In addition:

  1. Java 1.1 added new classes into the InputStream and OutputStream hierarchy, so it’s obvious those hierarchies weren’t being replaced. 
  2. There are times when you must use classes from the “byte” hierarchy in combination with classes in the “character” hierarchy. To accomplish this, there are “adapter” classes:

InputStreamReader converts an InputStream to a Reader and OutputStreamWriter converts an   OutputStream to a  Writer.

The most important reason for the Reader and Writer hierarchies is for internationalization. The old I/O stream hierarchy supports only 8-bit byte streams and doesn’t handle the 16-bit Unicode characters well. Since Unicode is used for internationalization (and Java’s native char is 16-bit Unicode), the Reader and Writer hierarchies were added to support Unicode in all I/O operations. In addition, the new libraries are designed for faster operations than the old.

As is the practice in this book, I will attempt to provide an overview of the classes, but assume that you will use the JDK documentation to determine all the details, such as the exhaustive list of methods.

Sources and sinks of data

Almost all of the original Java I/O stream classes have corresponding Reader and Writer classes to provide native Unicode manipulation. However, there are some places where the byte-oriented InputStreams and OutputStreams are the correct solution; in particular, the java.util.zip libraries are byte-oriented rather than char-oriented. So the most sensible approach to take is to try to use the Reader and Writer classes whenever you can, and you’ll discover the situations when you have to use the byte-oriented libraries, because your code won’t compile.

Off by itself: RandomSQLServer2000File

RandomSQLServer2000File is used for files containing records of known size so that you can move from one record to another using seek( ), then read or change the records. The records don’t have to be the same size; you just have to be able to determine how big they are and where they are placed in the file.

At first it’s a little bit hard to believe that RandomSQLServer2000File is not part of the InputStream or OutputStream hierarchy. However, it has no association with those hierarchies other than that it happens to implement the DataInput and DataOutput interfaces (which are also implemented by DataInputStream and DataOutputStream). It doesn’t even use any of the functionality of the existing InputStream or OutputStream classes; it’s a completely separate class, written from scratch, with all of its own (mostly native) methods. The reason for this may be that RandomSQLServer2000File has essentially different behavior than the other I/O types, since you can move forward and backward within a file. In any event, it stands alone, as a direct descendant of Object.

Essentially, a RandomSQLServer2000File works like a DataInputStream pasted together with a DataOutputStream, along with the methods getFilePointer( ) to find out where you are in the file, seek( ) to move to a new point in the file, and length( ) to determine the maximum size of the file. In addition, the constructors require a second argument (identical to fopen( ) in C) indicating whether you are just randomly reading (“r”) or reading and writing (“rw”). There’s no support for write-only files, which could suggest that RandomSQLServer2000File might have worked well if it were inherited from DataInputStream.

The seeking methods are available only in RandomSQLServer2000File, which works for files only. BufferedInputStream does allow you to mark( ) a position (whose value is held in a single internal variable) and reset( ) to that position, but this is limited and not very useful.

Most, if not all, of the RandomSQLServer2000File functionality is superceded in JDK 1.4 with the nio memory-mapped files.

 

 

Input streams

1. Buffered input file

To open a file for character input, you use a FileInputReader with a String or a File object as the file name. For speed, you’ll want that file to be buffered so you give the resulting reference to the constructor for a BufferedReader. Since BufferedReader also provides the readLine( ) method, this is your final object and the interface you read from. When you reach the end of the file, readLine( ) returns null so that is used to break out of the while loop.

The String s2 is used to accumulate the entire contents of the file (including newlines that must be added since readLine( ) strips them off). s2 is then used in the later portions of this program. Finally, close( ) is called to close the file. Technically, close( ) will be called when finalize( ) runs, and this is supposed to happen (whether or not garbage collection occurs) as the program exits. However, this has been inconsistently implemented, so the only safe approach is to explicitly call close( ) for files.

Section 1b shows how you can wrap System.in for reading console input. System.in is an InputStream, and BufferedReader needs a Reader argument, so InputStreamReader is brought in to perform the adaptation.

2. Input from memory

This section takes the String s2 that now contains the entire contents of the file and uses it to create a StringReader. Then read( ) is used to read each character one at a time and send it out to the console. Note that read( ) returns the next byte as an int and thus it must be cast to a char to print properly.

3. Formatted memory input

To read “formatted” data, you use a DataInputStream, which is a byte-oriented I/O class (rather than char-oriented). Thus you must use all InputStream classes rather than Reader classes. Of course, you can read anything (such as a file) as bytes using InputStream classes, but here a String is used. To convert the String to an array of bytes, which is what is appropriate for a ByteArrayInputStream, String has a getBytes( ) method to do the job. At that point, you have an appropriate InputStream to hand to DataInputStream.

If you read the characters from a DataInputStream one byte at a time using readByte( ), any byte value is a legitimate result, so the return value cannot be used to detect the end of input. Instead, you can use the available( ) method to find out how many more characters are available. Here’s an example that shows how to read a file one byte at a time:

//: c12:TestEOF.java
// Testing for end of file while reading a byte at a time.
import java.io.*;
 
public class TestEOF {
  // Throw exceptions to console:
  public static void main(String[] args)
  throws IOException {
    DataInputStream in = new DataInputStream(
      new BufferedInputStream(
        new FileInputStream("TestEOF.java")));
    while(in.available() != 0)
      System.out.print((char)in.readByte());
  }
} ///:~

Note that available( ) works differently depending on what sort of medium you’re reading from; it’s literally “the number of bytes that can be read without blocking.” With a file, this means the whole file, but with a different kind of stream this might not be true, so use it thoughtfully.

You could also detect the end of input in cases like these by catching an exception. However, the use of exceptions for control flow is considered a misuse of that feature.

4. File output

This example also shows how to write data to a file. First, a FileWriter is created to connect to the file. You’ll virtually always want to buffer the output by wrapping it in a BufferedWriter (try removing this wrapping to see the impact on the performance—buffering tends to dramatically increase performance of I/O operations). Then for the formatting it’s turned into a PrintWriter. The data file created this way is readable as an ordinary text file.

As the lines are written to the file, line numbers are added. Note that LineNumberInputStream is not used, because it’s a silly class and you don’t need it. As shown here, it’s trivial to keep track of your own line numbers.

When the input stream is exhausted, readLine( ) returns null. You’ll see an explicit close( ) for out1, because if you don’t call close( ) for all your output files, you might discover that the buffers don’t get flushed, so they’re incomplete.

Output streams

The two primary kinds of output streams are separated by the way they write data; one writes it for human consumption, and the other writes it to be reacquired by a DataInputStream. The RandomSQLServer2000File stands alone, although its data format is compatible with the DataInputStream and DataOutputStream.

 

5. Storing and recovering data

A PrintWriter formats data so that it’s readable by a human. However, to output data for recovery by another stream, you use a DataOutputStream to write the data and a DataInputStream to recover the data. Of course, these streams could be anything, but here a file is used, buffered for both reading and writing. DataOutputStream and DataInputStream are byte-oriented and thus require the InputStreams and OutputStreams.

If you use a DataOutputStream to write the data, then Java guarantees that you can accurately recover the data using a DataInputStream—regardless of what different platforms write and read the data. This is incredibly valuable, as anyone knows who has spent time worrying about platform-specific data issues. That problem vanishes if you have Java on both platforms.

When using a DataOutputStream, the only reliable way to write a String so that it can be recovered by a DataInputStream is to use UTF-8 encoding, accomplished in section 5 of the example using writeUTF( ) and readUTF( ). UTF-8 is a variation on Unicode, which stores all characters in two bytes. If you’re working with ASCII or mostly ASCII characters (which occupy only seven bits), this is a tremendous waste of space and/or bandwidth, so UTF-8 encodes ASCII characters in a single byte, and non-ASCII characters in two or three bytes. In addition, the length of the string is stored in the first two bytes. However, writeUTF( ) and readUTF( ) use a special variation of UTF-8 for Java (which is completely described in the JDK documentation for those methods) , so if you read a string written with writeUTF( ) using a non-Java program, you must write special code in order to read the string properly.

With writeUTF( ) and readUTF( ), you can intermingle Strings and other types of data using a DataOutputStream with the knowledge that the Strings will be properly stored as Unicode, and will be easily recoverable with a DataInputStream.

The writeDouble( ) stores the double number to the stream and the complementary readDouble( ) recovers it (there are similar methods for reading and writing the other types). But for any of the reading methods to work correctly, you must know the exact placement of the data item in the stream, since it would be equally possible to read the stored double as a simple sequence of bytes, or as a char, etc. So you must either have a fixed format for the data in the file, or extra information must be stored in the file that you parse to determine where the data is located. Note that object serialization (described later in this chapter) may be an easier way to store and retrieve complex data structures.

6. Reading and writing random SQLServer2000 files

As previously noted, the RandomSQLServer2000File is almost totally isolated from the rest of the I/O hierarchy, save for the fact that it implements the DataInput and DataOutput interfaces. So you cannot combine it with any of the aspects of the InputStream and OutputStream subclasses. Even though it might make sense to treat a ByteArrayInputStream as a random-SQLServer2000 element, you can use RandomSQLServer2000File only to open a file. You must assume a RandomSQLServer2000File is properly buffered since you cannot add that.

The one option you have is in the second constructor argument: you can open a RandomSQLServer2000File to read (“r”) or read and write (“rw”).

Using a RandomSQLServer2000File is like using a combined DataInputStream and DataOutputStream (because it implements the equivalent interfaces). In addition, you can see that seek( ) is used to move about in the file and change one of the values.

With the advent of new I/O in JDK 1.4, you may want to consider using memory-mapped files instead of RandomSQLServer2000File.

Piped streams

The PipedInputStream, PipedOutputStream, PipedReader and PipedWriter have been mentioned only briefly in this chapter. This is not to suggest that they aren’t useful, but their value is not apparent until you begin to understand multithreading, since the piped streams are used to communicate between threads.

Standard I/O

The term standard I/O refers to the Unix concept (which is reproduced in some form in Windows and many other operating systems) of a single stream of information that is used by a program. All the program’s input can come from standard input, all its output can go to standard output, and all of its error messages can be sent to standard error. The value of standard I/O is that programs can easily be chained together, and one program’s standard output can become the standard input for another program. This is a powerful tool.

Reading from standard input

Following the standard I/O model, Java has System.in, System.out, and System.err. Throughout this book, you’ve seen how to write to standard output using System.out, which is already prewrapped as a PrintStream object. System.err is likewise a PrintStream, but System.in is a raw InputStream with no wrapping. This means that although you can use System.out and System.err right away, System.in must be wrapped before you can read from it.

 

译文:

Java I/O 系统

对编程语言的设计者来说,创建一套好的输入输出(I/O)系统,是一项难度极高的任务。

这一点可以从解决方案的数量之多上看出端倪。这个问题难就难在它要面对的可能性太多了。不仅是因为有那么多I/O的源和目地(文件,控制台,网络连接等等),而且还有很多方法(顺序的『sequential』,随机的『random-SQLServer2000』,缓存的『buffered』,二进制的『binary』,字符方式的『character』,行的『by lines』,字的『by words』,等等)。

Java类库的设计者们用"创建很多类"的办法来解决这个问题。坦率地说Java I/O系统的类实在是太多了,以至于初看起来会把人吓着(但是,具有讽刺意味的是,这种设计实际上是限制了类的爆炸性增长)。此外,Java1.0版之后又对其I/O类库作了重大的修改,原先是面向byte的,现在又补充了面向Unicode字符的类库。为了提高性能,完善功能,JDK 1.4又加了一个nio(意思是"new I/O"。这个名字会用上很多年)。这么以来,如果你想对JavaI/O类库有个全面了解,并且做到运用自如,你就得先学习大量的类。此外,了解I/O类库的演化的历史也是相当重要的。可能你的第一反应是"别拿什么历史来烦我了,告诉我怎么用就可以了!"但问题是,如果你对这段历史一无所知,很快就会被一些有用或是没用的类给搞糊涂了。

本章会介绍Java标准类库中的各种I/O类,及其使用方法。

File 类

在介绍直接从流里读写数据的类之前,我们先介绍一下处理文件和目录的类。

File类有一个极具欺骗性的名字;或许你会认为这是一个关于文件的类,但它不是。你可以用它来表示某个文件的名字,也可以用它来表示目录里一组文件的名字。如果它表示的是一组文件,那么你还可以用list( )方法来进行查询,让它会返回String数组。由于元素数量是固定的,因此数组会比容器更好一些。如果你想要获取另一个目录的清单,再建一个File对象就是了。实际上,叫它 "FilePath"可能会更好一些。下面我们举例说明怎样使用这个类及其相关的FilenameFilter接口。

目录列表器

假设你想看看这个目录。有两个办法。一是不带参数调用list( )。它返回的是File对象所含内容的完整清单。但是,如果你要的是一个"限制性列表(restricted list)"的话 —— 比方说,你想看看所有扩展名为.java的文件 —— 那么你就得使用"目录过滤器"了。这是一个专门负责挑选显示File对象的内容的类。

DirFilter实现了FilenameFilter接口。我们来看看FilenameFilter究竟有多简单: 
public interface FilenameFilter {
           boolean accept(File dir, String name);
}

也就是说,这类对象的任务就是提供一个accept( )的方法。之所以要创建这个类,就是要给list( )提供一个accept( )方法,这样当list( )判断该返回哪些文件名的时候,能够"回过头来调用"accept( )方法。因此,这种结构通常被称为回调(callback)。更准确地说,由于list( )实现了基本功能,而FilenameFilter提供了"对外服务所需的算法",因此这是一种"策略模式(Strategy Pattern)"。由于list( )FilenameFilter对象当参数,因此你可以将任何实现FilenameFilter接口的对象传给它,并以此(甚至是在运行时)控制list( )的工作方式。回调能提高程序的灵活性。

DirFilter还告诉我们,interface只是包含了一些方法,它没说你只能写这些方法。(但是,你至少要定义接口里有的方法。) 这里我们还定义了DirFilter的构造函数。

accept( )方法需要两个参数,一个是File对象,表示这个文件是在哪个目录里面的;另一个是String,表示文件名。虽然你可以忽略它们中的一个,甚至两个都不管,但是你大概总得用一下文件名吧。记住,list( )会对目录里的每个文件调用accept( ),并以此判断是不是把它包括到返回值里;这个判断依据就是accept( )的返回值。

切记,文件名里不能有路径信息。为此你只要用一个String对象来创建File对象,然后再调用这个File对象的getName( )就可以了。它会帮你剥离路径信息(以一种平台无关的方式)。然后再在accept( )里面用正则表达式(regular expression)的matcher对象判断,regex是否与文件名相匹配。兜完这个圈子,list( )方法返回了一个数组。

输入与输出

I/O类库常使用"流(stream)"这种抽象。所谓"流"是一种能生成或接受数据的,代表数据的源和目标的对象。流把I/O设备内部的具体操作给隐藏起来了。

正如JDK文档所显示的,JavaI/O类库分成输入和输出两大部分。所有InputStreamReader的派生类都有一个基本的,继承下来的,能读取单个或byte数组的read( )方法。同理,所有OutputStreamWriter的派生类都有一个基本的,能写入单个或byte数组的write( )方法。但通常情况下,你是不会去用这些方法的;它们是给其它类用的 —— 而后者会提供一些更实用的接口。因此,你很少会碰到只用一个类就能创建一个流的情形,实际上你得把多个对象叠起来,并以此来获取所需的功能。Java的流类库之所以会那么让人犯晕,最主要的原因就是"你必须为创建一个流而动用多个对象"。

我们最好还是根据其功能为这些class归个类。Java 1.0的类库设计者们是从决定"让所有与输入相关的类去继承InputStream"入手的。同理,所有与输出相关的类就该继承OutputStream了。

InputStream的种类

InputStream的任务就是代表那些能从各种输入源获取数据的类。这些源包括:

  1. byte数组
  2. String对象
  3. 文件
  4. 类似流水线的"管道(pipe)"。把东西从一头放进去,让它从另一头出来。
  5. 一个"流的序列(A sequence of other streams)",可以将它们组装成一个单独的流。
  6. 其它源,比如Internet的连接。(这部分内容在Thinking in Enterprise Java中讨论。)

这些数据源各自都有与之相对应的InputStream的子类。此外,FilterInputStream也是InputStream的子类,其作用是为基类提供"decorator(修饰)"类,而decorator又是为InputStream配置属性和接口的。这部分内容我们以后再谈。

OutputStream的种类

这部分都是些决定往哪里输出的类:是byte的数组(不能是String;不过你可以根据byte数组创建字符串)还是文件,或者是"管道"。

此外,FilterOutputStream还是decorator类的基类。它会为OutputStream安装属性和适用的接口。

添加属性与适用的接口

使用"分层对象(layered objects)",为单个对象动态地,透明地添加功能的做法,被称为Decorator Pattern。(模式是Thinking in Patterns (with Java)的主题。)Decorator模式要求所有包覆在原始对象之外的对象,都必须具有与之完全相同的接口。这使得decorator的用法变得非常的透明--无论对象是否被decorate过,传给它的消息总是相同的。这也是Java I/O类库要有"filter(过滤器)"类的原因:抽象的"filter"类是所有decorator的基类。(decorator必须具有与它要包装的对象的全部接口,但是decorator可以扩展这个接口,由此就衍生出了很多"filter"类)。

Decorator模式常用于如下的情形:如果用继承来解决各种需求的话,类的数量会多到不切实际的地步。Java的I/O类库需要提供很多功能的组合,于是decorator模式就有了用武之地。 但是decorator有个缺点,在提高编程的灵活性的同时(因为你能很容易地混合和匹配属性),也使代码变得更复杂了。Java的I/O类库之所以会这么怪,就是因为它"必须为一个I/O对象创建很多类",也就是为一个"核心"I/O类加上很多decorator。

InputStreamOutputStream定义decorator类接口的类,分别是FilterInputStreamFilterOutputStream。这两个名字都起得不怎么样。FilterInputStreamFilterOutputStream都继承自I/O类库的基类InputStreamOutputStream,这是decorator模式的关键(惟有这样decorator类的接口才能与它要服务的对象的完全相同)。

用FilterInputStream读取InputStream

FilterInputStream及其派生类有两项重要任务。DataInputStream可以读取各种primitive及String。(所有的方法都以"read"打头,比如readByte( )readFloat( ))。它,以及它的搭档DataOutputStream,能让你通过流将primitive数据从一个地方导到另一个地方。这些"地方"都列在表12-1里。

其它的类都是用来修改InputStream的内部行为的:是不是做缓冲,是不是知道它所读取的行信息(允许你读取行号或设定行号),是不是会弹出单个字符。后两个看上去更像是给编译器用的(也就是说,它们大概是为Java编译器设计的),所以通常情况下,你是不大会用到它们的。

不论你用哪种I/O设备,输入的时候,最好都做缓冲。所以对I/O类来说,比较明智的做法还是把不缓冲当特例(或者去直接调用方法),而不是像现在这样把缓冲当作特例。

用FilterOutputStream往OutputStream里面写东西

DataInputStream的另一半是DataOutputStream。它的任务是把primitive数据和String对象重新组织成流,这样其它机器就能用DataInputStream读取这个流了。DataOutputStream的方法都是以"write"开头的,比如writeByte( )writeFloat( )等等。

PrintStream的用意是要以一种大家都能看懂的方式把primitive数据和String对象打印出来。这一点同DataOutputStream不同,后者是要将数据装入一个流,然后再交给 DataInputStream处理。

PrintStream的两个最重要的方法是print( )println( )。这两个方法都已经作了重载,因此可以打印各种数据。print( )println( )的区别在于,后者会多打印一个换行符。

使用PrintStream的时候会比较麻烦,因为它会捕捉所有的IOException(所以你必须直接调用checkError( )来检查错误条件,因为这个方法会在碰到问题的时候返回true)。再加上,PrintStream的国际化做得也不好,而且还不能以与平台无关的方式处理换行。

Reader 和 Writer类系

Java 1.1对最底层的I/O流类库作了重大修改。第一次看到ReaderWriter的时候,你会觉得"它们大概是用来取代InputStreamOutputStream的" (和我一样)。但事实并非如此。虽然InputStreamOutputStream的某些功能已经淘汰了(如果你继续使用,编译器就会发警告),但它们仍然提供了很多很有价值的,面向byte的I/O功能,而ReaderWriter则提供了Unicode兼容的,面向字符的I/O功能。此外:

  1. Java 1.1还对InputStream和OutputStream作了新的补充,所以很明显这两个类系并没有被完全替代。
  2. 有时,你还必须同时使用"基于byte的类"和"基于字符的类"。为此,它还提供了两个"适配器(adapter)"类。InputStreamReader负责将InputStream转化成Reader,而OutputStreamWriter则将OutputStream转化成Writer。

ReaderWriter要解决的,最主要的问题就是国际化。原先的I/O类库只支持8位的字节流,因此不可能很好地处理16位的Unicode字符流。Unicode是国际化的字符集(更何况Java内置的char就是16位的Unicode字符),这样加了ReaderWriter之后,所有的I/O就都支持Unicode了。此外新类库的性能也比旧的好。

秉承本书的一贯风格,我这里只准备作一个简要的介绍,要想了解具体的细节,比如类都有哪些方法,请查阅JDK的文档。

数据源和目的

几乎所有的Java I/O流都有与之对应的,专门用来处理UnicodeReaderWriter。但有时,面向byte的InputStreamOutputStream才是正确的选择;特别是java.util.zip;它的类都是面向byte的。所以最明智的做法是,先用ReaderWriter,等到必须要用面向byte的类库时,你自然会知道的,因为程序编译不过去了。

自成一派: RandomSQLServer2000File

RandomSQLServer2000File是用来访问那些保存数据记录的文件的,这样你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。

首先,你可能会不太相信,RandomSQLServer2000File竟然会是不属于InputStreamOutputStream类系的。实际上,除了实现DataInputDataOutput接口之外(DataInputStreamDataOutputStream也实现了这两个接口),它和这两个类系毫不相干,甚至都没有用InputStreamOutputStream已经准备好的功能;它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是从零开始写的。这可能是因为RandomSQLServer2000File能在文件里面前后移动,所以它的行为与其它的I/O类有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。

基本上,RandomSQLServer2000File的工作方式是,把DataInputStreamDataOutputStream粘起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )。此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件,从这一点上看,假如RandomSQLServer2000File继承了DataInputStream,它也许会干得更好。

只有RandomSQLServer2000File才有seek方法,而这个方法也只适用于文件。BufferedInputStream有一个mark( )方法,你可以用它来设定标记(把结果保存在一个内部变量里),然后再调用reset( )返回这个位置,但是它的功能太弱了,而且也不怎么实用。

RandomSQLServer2000File的绝大多数功能,如果不是全部的话,已经被JDK 1.4nio的"内存映射文件(memory-mapped files)"给取代了。

输入流

1. 对输入文件作缓冲

要想打开打开文件读取字符,你得先用StringFile对象创建一个FileInputReader。为了提高速度,你应该对这个文件作缓冲,因此你得把FileInputReader的reference交给BufferedReader。由于BufferedReader也提供了readLine( )方法,因此它就成为你最终要使用的那个对象,而它的接口也成为你使用的接口了。当你读到了文件的末尾时,readLine( )会返回一个null,于是就退出while循环了。

String s2是用来累加文件内容的(再加一个换行符,因为readLine( )会把它们都剥掉)。后面的程序都要用到这个s2。最后,用close( )来关闭文件。单从技术角度上说,程序退出的时候(不管有没有垃圾要回收)都应该调用finalize( ),而finalize( )又会调用close( )。不过各种JVM的实现并不一致,所以最好还是明确地调用close( )

1b部分还演示了如何用System.in来生成一个能读取控制台输入的流。System.in是一个InputStream,而BufferedReader需要一个Reader作参数,所以要先通过InputStreamReader来转转手

2. 读取内存

现在String s2里面已经有一个完整的文件了。因此这部分程序要用它去创建一个StringReader。然后用(StringReader的)read( )方法把字符读出来,再送到控制台上去。注意,read( )会把读出来的byte当作int,所以要想正常打印的话,你得先把它们转换成char

3. 读取格式化的内存

要想读取"格式化"的数据,你就得用DataInputStream了,它是一个面向byte的I/O类 (不是面向char的),因此你只能从头到底一直用InputStream了。当然你可以把所有东西(比方说文件) 都当成byte,然后用InputStream读出来,但这里是String。要想把String变成成byte数组,可以用StringgetBytes( )方法,而ByteArrayInputStream是可以处理byte数组的。到了这一步,你就不用担心没有合适的InputStream来创建DataInputStream了。

如果你是用readByte( )逐字节地读取DataInputStream的话,那么无论byte的值是多少,都是合法的,所以你无法根据返回值来判断输入是否已经结束了。你只能用available( )来判断还有多少字符。下面我们来演示一下怎样逐字节地读取文件:

//: c12:TestEOF.java
// Testing for end of file while reading a byte at a time.
import java.io.*;
public class TestEOF {
  // Throw exceptions to console:
  public static void main(String[] args)
  throws IOException {
    DataInputStream in = new DataInputStream(
      new BufferedInputStream(
        new FileInputStream("TestEOF.java")));
    while(in.available() != 0)
      System.out.print((char)in.readByte());
  }
} ///:~

注意,available( )的工作方式会随读取介质的不同而不同;严格地讲,它的意思是"可以不被阻塞地读取的字节的数目。"对文件来说,它就是整个文件,但如果是其它流,情况就不一定了,所以用之前要多留一个心眼。

你也可以像这样,用异常来检查输入是不是完了。但不管怎么说,把异常当成控制流程来用总是对这种功能的滥用。

4. 读取文件

这段程序还演示了怎样往文件里面写数据。先创建一个FileWriterBufferedWriter总是免不掉的。(试试把BufferedWriter去掉,你就能看到它对性能的影响了—— 缓冲能大幅提高I/O的性能)。然后再让PrintWriter去排版。这样就能得出能够读得懂的,普通的文本文件了。

随着程序一行一行地写文件,我们就顺便加了一个行号。注意,我们没用LineNumberInputStream,因为这是一个傻乎乎的,没什么用的类。正如这里所看到的,要知道行号还不简单。

输入流用完之后,readLine( )会返回null。你会发现,程序显式地调用了out1close( ),因为如果写文件的时候不调用close( ),它是不会去清空缓冲区的,这样就有可能会落下一些东西了

输出流

根据写数据的方式不同,OutputStream主要分成两类;一类是写给人看的,一类是供DataInputStream用的。虽然RandomSQLServer2000File的数据格式同DataInputStreamDataOutputStream的相同,但它不属于OutputStream的。

5. 存储和恢复数据

PrintWriter会对数据进行格式化,这样人就能读懂了。但是如果数据输出之后,还要恢复出来供其它流用,那你就必须用DataOutputStream来写数据,再用DataInputStream来读数据了。当然,它们可以是任何流,不过我们这里用的是一个经缓冲的文件。DataOutputStreamDataInputStream是面向byte的,因此这些流必须都是InputStreamOutputStream

如果数据是用DataOutputStream写的,那么不管在哪个平台上,DataInputStream都能准确地把它还原出来。这一点真是太有用了,因为没人知道谁在为平台专属的数据操心。如果你在两个平台上都用Java,那这个问题就根本不存在了。

DataOutputStreamString的时候,要想确保将来能用DataInputStream恢复出来,唯一的办法就是使用UTF-8编码,也就是像例程第5部分那样,用writeUTF( )readUTF( )。UTF-8是Unicode的一种变形。Unicode用两个字节来表示一个字符。但是,如果你处理的全部,或主要是ASCII字符(只有7位),那么无论从存储空间还是从带宽上看,就都显得太浪费了,所以UTF-8 用一个字节表示ASCII字符,用两或三个字节表示非ASCII的字符。此外,字符串的长度信息存在(字符串)的头两个字节里。writeUTF( )readUTF( )用的是Java自己的UTF-8版本(JDK文档里有关于这个字符集的完整讲解,就在这两个方法的文档里),所以如果你要用一个Java程序读取writeUTF( )写的字符串的话,就必须进行一些特殊处理了。

有了writeUTF( )readUTF( ),你就能放心地把String和其它数据混在一起交给DataOutputStream了,因为你知道String是以Unicode的形式存储的,而且可以很方便地用DataOutputStream恢复出来。

writeDouble( )会往流里写double,而它"影子"readDouble( )则负责把它恢复出来(其它数据也有类似的读写方法)。但是要想让读取方法能正常工作,你就必须知道流的各个位置上都放了些什么数据。因为你完全可以把double读成bytechar,或其它什么东西。所以要么以固定的格式写文件,要么在文件里提供额外的解释信息,然后一边读数据一边找数据。先提一下,对于复杂数据的存储和恢复,对象的序列化可能会比较简单。

6. 读写随机文件

正如我们前面所讲的,如果不算它实现了DataInputDataOutput接口,RandomSQLServer2000File几乎是完全独立于其它I/O类库之外的,所以它不能与InputStreamOutputStream合起来用。虽然把ByteArrayInputStream当作"随机存取的元素(random-SQLServer2000 element)"是一件很合情合理的事,但你只能用RandomSQLServer2000File来打开文件。而且,你只能假定RandomSQLServer2000File已经做过缓冲了,因为即便没做你也无能为力。

构造函数的第二个参数的意思是:是以只读("r") 还是读写("rw")方式打开RandomSQLServer2000File

RandomSQLServer2000File的用法就像是DataInputStreamDataOutputStream的结合(因为它们的接口是等效的)。此外,你还能用seek( )在文件里上下移动,并进行修改。

随着JDK 1.4new I/O的问世,你该考虑一下是不是用"内存映射文件(memory-mapped file)"来代替RandomSQLServer2000File了。

管道流

这一章只会大致地提一下PipedInputStreamPipedOutputStreamPipedReaderPipedWriter。这并不是说它们不重要,只是因为管道流是用于线程间的通信的,所以除非你已经理解了多线程,否则是不会理解它的价值的。

标准I/O

"标准I/O"是Unix的概念,它的意思是,一个程序只使用一个信息流 (这种设计思想也以某种形式体现在Windows及其它很多操作系统上)。所有输入都是从"标准输入"进来的,输出都从"标准输出"出去,错误消息都送到"标准错误"里。标准I/O的优点是,它可以很容易地把程序串连起来,并且把一个程序的输出当作另一个程序的输入。这是一种非常强大的功能。

读取标准输入

Java遵循标准I/O的模型,提供了Syetem.in,System.out,以及System.err。本书一直都在用System.out往标准输出上写,而它(System.out)是一个已经预先处理过的,被包装成PrintStream的对象。和System.out一样,System.err也是一个PrintStream,但是System.in就不对了,它是一个未经处理的InputStream。也就是说,虽然你可以直接往System.out和System.err上写,但是要想读

System.in的话,就必须先做处理了。

你可能感兴趣的:(基于jsp网上书店设计与实现毕业设计论文)