分享
 
 
 

Java入门(13) More Java!

王朝java/jsp·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

摘要

在本章中,我们将了解更多的关于Java的知识,包括用于访问数据库的JDBC、以及Java的网络编程、以及JavaBeans等Java高级特性。通过本章的学习,大家应该能够了解这些知识的概念,以便今后更好地学习Java语言。

(2002-09-02 13:38:18)

--------------------------------------------------------------------------------

By Wing, 出处:fjxufeng

本章目标:

在本章中,我们将了解更多的关于Java的知识,包括用于访问数据库的JDBC、以及Java的网络编程、以及JavaBeans等Java高级特性。通过本章的学习,大家应该能够了解这些知识的概念,以便今后更好地学习Java语言。

13.1 什么是JDBC

传授新知

人类已经走进信息时代,信息的获取、整理工作越来越重要,从传统业务电子化、自动化到决策支持等方方面面都离不开信息的处理。

为了能够更好地处理信息,就需要更好地收集、存储信息。这也是计算机领域中一个十分重要的分支学科:数据库系统。由于其巨大的市场空间,有许许多多的计算机软件公司致力于研究、开发各种功能强劲的数据库系统。从适合于桌型应用的FoXPro、DBASE,到大型数据库系统MicroSoft SQL Server、Oracle、Sybase、IBM DB2、Infomix等,它们虽然都是遵造SQL语言打造而成的,但是由于不同的开发厂商、不同产品特点、不同的市场针对性,使得它们的接口、使用方法等都有许多差别。这使得我们不得不研究这些不同的地方,以便能够在应用中灵活使用。而假如我们在开发计算机应用系统时需要根据不同的数据库系统进行专门的设计,必将使得程序的通用性、灵活性、可维护性大大降低。

13.1.1 ODBC

为了使数据库系统的应用开发能够更加轻松,微软公司提供了一套数据库接口规范ODBC(对,你没看错是ODBC,为了大家能够更加清楚地熟悉JDBC,我们先给大家介绍一下ODBC)。正如下图所示,它为编程语言访问数据库提供了一个标准接口:

图13-1 ODBC结构示意图

我们现在一起来看一下这张图,当应用程序(属于应用程序层)需要对数据库进行操作时,我们就通过ODBC接口来实现与数据库进行打交道。由于ODBC接口是统一的、是标准的,所以所有使用ODBC接口访问、操作数据库的程序的写法都是一样的。

那么ODBC接口又是如何完成实际的数据库访问和操作的呢!它是通过各种数据库系统所提供的ODBC驱动程序来完成了!

由于支持ODBC的数据库十分多,所以一个系统中ODBC驱动程序有许多个,因此ODBC需要一个治理这些ODBC驱动程序的治理器。

整个过程就是,应用程序通过ODBC接口来访问数据库,并且无需关心是什么数据库;而当ODBC接口收到这个数据库操作请求时,就通过驱动程序治理器找到相应的数据库ODBC驱动程序;最后ODBC驱动程序连接数据库完成操作。

ODBC的出现使得应用程序访问、操作数据库更加轻易,迅速成为了一个通用的标准。而Java做为了一种网络化的编程语言,也少不了要与数据库打交道,要让Java程序访问数据库更加轻易,也必须提供相应的机制把不同数据库系统所带来的差异屏蔽掉。因此,就导致了JDBC(Java DataBase Connectivity)的诞生。

13.1.2 JDBC

看到这里,大家也许会问既然有了ODBC,为什么要有JDBC呢?其实原因很简单,ODBC主要是对数据库系统的C语言接口,不太轻易被Java使用;再者它是Microsoft的标准,Microsoft何许人也,Java缔造者Sun公司的死对头呀!

因此,Sun公司重新设计了一个适合于Java使用的数据库操作引擎:JDBC,并于1996年3月份发布。它沿袭了ODBC的设计思想,并且采用了与ODBC一样的设计基础:X/Open SQL Call Level Interface(简称CLI)。为了使JDBC更加Power,还开发了一个“JDBC-ODBC的翻译器”(也被称为桥接器),通过它,还可以使用数据库的ODBC驱动程序。

JDBC定义了一套API对象和方法,用来访问、操作数据库系统。在一个使用JDBC的Java程序中,首先将打开一个数据库的连接,生成一个语句(Statement)对象,使用这个对象将SQL语句传送给数据库系统,然后用它检索结果。

JDBC类包含在java.sql包中,我们可以使用这个包中的对象和方法从数据库读取数据或写入数据。正如下图所示,JDBC提供了两种不同的访问数据库的机器:

1)

图13-2 通过JDBC-ODBC桥接器访问数据库

在这种情况下,JDBC通过访问一个本地模块JDBC-ODBC桥接器(JDBCODBC.DLL)来与数据库交互。这个数据库可以在本地,也可以在远程。

一些说明:

由于ODBC在ISV(Independent Software Vendor,独立软件开发商)和用户中非常流行,有许多数据库都提供了ODBC驱动程序,因此Sun公司为了利用这些可用的ODBC驱动程序,就开发了一个桥接器。这是一种很成功的市场行为。

2)

图13-3 通过HTTP侦听程序访问数据库

在这种情况下,Java程序通过使用一些RPC或一个HTTP发送者-侦听者协议与网络上的数据库服务器交谈,实现数据库的访问与操作。

一般来说,JDBC类文件和Java小应用程序是存放在客户端,用来访问位于远程服务器上的数据库。

3)

图13-4 JDBC示意图(3)

还有一种结构JDBC通过数据库的JDBC驱动程序来访问数据库,当然就需要数据库的支持,例如Oracle就有JDBC驱动程序。

随着JDBC的使用越来越广泛,对JDBC提供支持也会成为数据库厂商参与市场竞争的一个重要手段,所以今后会有越来越多的数据库会提供JDBC驱动程序。

一些提示:

关于JDBC的更多内容可以参考四个与JDBC规范有关的重要文档:JDBC规范、JDBC API文档I-JDBC接口、JDBC API文档II-类和异常以及JDK1.1文档的JDBC手册。

你可以在Sun的www.javasoft.com中找到。文件名分别为:jdbc.spec-0122.pdf、jdbc.api.1-0122.pdf、jdbc.api.2-0122.pdf、jdbc.pdf。

要想成为JDBC方面的专家,或想更深入地研究它们,请下载并刻苦研读它们。

自测练习

1) Java语言提供的访问、操作数据库的统一接口称为__________。

a.ODBC b.JDBC c.DBAP

2) Java语言中提供的这个数据库接口包含在__________包中。

a.java.odbc b.java.jdbc c.java.sql d.java.dbap

3) Java数据库程序通常是在网络上运行的,那么JDBC类一般位于_________。

a.客户端 b.服务器端 c.两端都有其中一部分

4) JDBC可以通过____________来使用数据库的ODBC驱动程序。

a.jdbcodbc.dll b.ODBC模拟器 c.没有条件,可以直接使用

5) ODBC与JDBC有一个共同的设计基础,它是______________。

a.SQL标准 b.CLI

6)__________发布时间更早。

a.JDBC b.ODBC c.DBAP

7)使用ODBC访问数据库,数据库只能够在主机上。__________

a.对 b.不对

8)在ODBC模型中,最终是通过________来操作数据库的。

a.ODBC接口 b.驱动程序治理器 c.数据库的ODBC驱动程序

9)而在JDBC模型中,在应用程序中,我们是通过_________来使用数据库的。

a.JDBC-ODBC桥接器 b.JDBC接口 c.ODBC驱动程序

练习答案

1)b 当然是JDBC,Java DataBase Connectivity。另外,并不存在一种名为DBAP的数据库接口API。

2)c 首先不可能会是java.odbc,也不会是java.dbap,也许你最想选择的可能会是java.jdbc,可是Java类的命名者却使用了数据库圣经的名字(SQL)命名了。

3)a 通常是在客户端。

4)a 使用JDBC-ODBC的转换器,这个转换器的文件名就是jdbc-odbc.dll。

5)b 这两个数据库接口使用了同一个设计基础,X/Open SQL Call Level Interface,它简称为CLI。

6)b ODBC比JDBC早很长一段时间。

7)b 当然不对,ODBC也是答应访问远程服务器上的数据库的。

8)c 是通过真正能够了解数据库结构的,由数据库厂商提供的,专门的ODBC驱动程序来完成的。

9)b 对于应用程序而言,它只需直接调用提供的接口就可以了,无须去关心具体的数据库实现。

13.2 用JDBC连接数据库

传授新知

所有的JDBC类和方法都包含在java.sql包中:

类别 类

驱动程序 java.sql.Driver

java.sql.DriverManager

java.sql.DrivePropertyInfo

连接 java.sql.Connection

语句 java.sql.Statement

java.sql.PreparedStatement

java.sql.CallableStatement

结果集 java.sql.ResultSet

错误/警告 java.sql..SQLException

java.sql.SQLWarning

元数据 java.sql.DatabaseMetaData

java.sql.ResultSetMetaData

日期/时间 java.sql.Date

java.sql.Time

java.sql.Timestamp

其它 java.sql.Types

java.sql.DataTruncation

表13-1 JDBC类一览表

在使用JDBC时,每一个驱动程序要被加载之前,都要使用DriverManager进行注册。当需要打开一个连接时,DriverManager根据JDBC的URL选择相应的驱动程序。

同样的,JDBC也体现了Java这一网络语言的特点,它也是使用URL进行识别一个数据库的。它的语法格式是:

jdbc:<子协议>:<与DBMS/Protocol相关的子名字>

JDBC的URL分三个部分组成:

1)指示部分:jdbc。请大家回忆一下,一个常见的WEB页面的URL是以什么开头的?对,http://,这个指示部分用来说明这个URL是什么类别的。

2)子协议部分,这个部分用来指出JDBC的子协议,如odbc;

3)子名字:数据源的名字,假如数据库是在网络上的,那么就用URL//hostname:port//来表示。

例如:jdbc:odbc:;User=<用户名>;PW=<口令>

使用JDBC的整个流程如下图所示:

图13-5 JDBC调用流程图

正如上图所示,使用JDBC访问数据库需要经过以下几个步骤:

1) 首先,程序调用getConnection()方法,获得一个Connection对象;

2) 然后程序创建一个Statement对象并预备一个SQL语句;这个SQL语句可以是:

a. 被立即执行的语句:Statement对象;

b.被编译的语句:PreparedStatement对象;

c.存储过程的调用:CallableStatement对象。

3) 接着我们就可以执行这个语句(如executeQuery( )、execute( ))等,将得到一个存储结果的ResultSet对象。

注重:

也有一些语句是完成一些事务工作,如executeUpdate(),更新。它们将不会返回ResultSet对象,因为它们并没有结果。

4) 最后,我们就可以使用类似next()等方法对返回的ResultSet对象进行相应的处理。

实例说明

下面,我们来看一个简单的例子。由于JDBC是用来访问数据库的,所以我们首先来创建一个数据库。为了大家都能够完成这个实验,我们就用Office套件中的Access数据库来完成这个例子。

1. 创建Access数据库student.mdb,在这个库中创建一个表student:

图13-6 数据库student的表结构

这个表student的内容如下图所示:

图13-7 表student的内容

2. 当我们建好这个示例数据库student后,我们要为其设置ODBC:

一些说明:

读到这里,可能有些读者会感到希奇,我们不是在使用JDBC吗?为什么要设置ODBC呢?这是因为有带JDBC驱动程序的数据库没有ODBC那样多?我们在这里使用的数据库系统Access就没有相应的JDBC驱动程序。

不过没关系,大家应该记得在JDBC中有一个JDBC-ODBC的桥接器,通过它就可以使用ODBC驱动程序啦。

现在应该明白为什么为什么要先设置ODBC了吧!

设置ODBC的方法很简单:

1) 首先,我们打开控制面板,双击“ODBC数据源(32位)”按钮,然后选择“系统DSN页”,将出现如下界面:

图13-8 ODBC设置(1)

2) 然后,我们单击“添加”按钮,选择“Microsoft Access Driver”,然后单击“完成”按钮:

图13-9 ODBC设置(2)

3) 接着,就会出现一个如下图所示的对话框,我们首先填上 “数据源名”:student,然后单击“选取”按钮,选择我们刚才创建的数据库student.mdb。然后单击“确定“按钮,至此ODBC设置完成:

13-10 ODBC设置(3)

3. 然后,创建一个源程序文件testjdbc.java,其内容如下:

源程序:testjdbc.java

import java.sql.*;

public class testjdbc

{

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

{

try

{

String name,sex,age,chinese,maths,output;

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

Connection con=DriverManager.getConnection

("jdbc:odbc:student");

Statement sta=con.createStatement();

ResultSet rs=sta.executeQuery

("SELECT Name,Sex,Age,Chinese,Maths FROM student");

System.out.println("Name Sex Age Chinese Maths");

while(rs.next())

{

name=rs.getString(1);

sex=rs.getString(2);

age=rs.getString(3);

chinese=rs.getString(4);

maths=rs.getString(5);

output=name+" "+sex+" "+age+" "+chinese+" "+maths;

System.out.println(output);

}

}

catch(java.lang.Exception ex)

{

ex.printStackTrace();

}

}

}

4. 最后,我们使用javac编译,然后执行java testjdbc,程序输出如下所示:

图13-11 程序输出图

传授新知

下面,我们就一起来学习一下这个程序:

1)

import java.sql.*;

我们知道,JDBC的所有类和方法都在java.sql包中实现的,所以当我们的程序中要使用到JDBC时,就要把这个包import进来。否则就会出错的哟。

2)

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

这个语句,我想大家应该可以看懂一半,也就是直到throws之前的部分,这是定义了main方法。而后面的throws SQLException则是说明假如程序碰到SQLException错误时就忽略错误。

3)

try

{

……

}

catch(java.lang.Exception ex)

{

ex.printStackTrace();

}

这个语句,我们曾经在前面一章中做过介绍,也就是在try程序段中,假如碰到catch语句中定义的错误(在这里是java.lang.Exception ex),就执行catch程序段(在这里就是执行ex.printStackTrace(),打印出相关的错误信息)。

4)

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

这条语句,大家虽然没有见过,但我想大家都能够猜出它的用途来!对,就是初始化并加载JDBC-ODBC驱动程序。

在JDBC的程序中使用JDBC-ODBC桥接器,就一定要在最前面加上这么一句。

5)

Connection con=DriverManager.getConnection

("jdbc:odbc:student");

这条语句中,我们定义了一个Connection类的对象con,用来存放getConnection方法返回的数据库连接。

大家回想一下图13-5,在最上层,我们是通过“驱动程序治理器”发出一个getConnection方法,以获得一个Connection(数据库连接)。

getConnection方法的语法格式是:

Connection getConnection(String url,String user,String passWord);

它可以带上三个参数:

<1> url:数据库的URL。我们知道JDBC的URL由三个部分组成:

图13-12 URL分析

<2> user:用户名。象SQL Server、Oracle…等大型数据库中,均采用了用户治理,而在Access中并未使用,所以在本例中当然就不用指出相应的用户了;

<3> password:相应用户的口令,它是与用户名对应的。

6)Statement sta=con.createStatement();

我们继续参考图13-5,获得了一个Connection(与数据库的连接)后,我们应该创建一个语句(有三种方法),在我们的程序中使用了最常使用的语句创建方法:createStatement。

注重:

创建语句所引用的方法createStatement是Connection对象的方法!

这样,我们就得到了一个Statement(语句)对象sta。

7)

ResultSet rs=sta.executeQuery

("SELECT Name,Sex,Age,Chinese,Maths FROM student");

根据图13-5的指示,接下来,我们要使用Statement对象的方法来执行SQL语句,在本例中,我们使用了一个最常用的executeQuery方法。

这个方法所带的参数是一个SQL语句。

小知识:SQL语言

SQL用来组织、治理和检索存储在计算机数据库中的数据。它的英文全称是:“StrUCtured Query Language”。它是所有的关系型数据库均采用的标准。是一种专门用于数据库操作的语言。它可嵌入到其它语言中(如这里)来实现数据库操作。

例如,这里的SELECT Name,Sex,Age,Chinese,Maths FROM student的意思就是从student表中选择出Name,Sex,Age,Chinese,Maths五个字段。

SQL语言博大精深,而且十分有用,有爱好的读者可以去阅读《SQL完全参考手册》(上、下)。在此限于篇幅就不带赘述了。

执行了这条SQL语句后,将产生许多输出,这些都将存放在一个ResultSet类的对象rs中。它对应与图13-5就是“结果集”。

8)

while(rs.next())

{

name=rs.getString(1);

sex=rs.getString(2);

age=rs.getString(3);

chinese=rs.getString(4);

maths=rs.getString(5);

output=name+" "+sex+" "+age+" "+chinese+" "+maths;

System.out.println(output);

}

根据图13-5的指示,有了结果集,我们就可以使用next方法、getString方法获取它们。其中next方法是指取下一个记录(一个记录由多个字段组成,在这个例子中共有5个字段:Name,Sex,Age,Chinese,Maths)。而getString则是从当前记录中取出某个字段,它所带的参数就是字段的序号。

一些提示:

刚返回的结果集ResultSet中,记录指针是指向TOP,并未指向任何记录。

在这个while循环中,首先将执行rs.next(),这样,就指向了第一条记录。(假如没有第一条记录,就会返回false,使得循环结束)。然后我们就可以使用getString(字段号)来获取这条记录中的字段。

当5个字段都取出来后,在程序中将它们组合在一起,然后将其打印出来。 接下来,就会再次执行rs.next()获取下一条记录,直到没有记录为止。

好了,到此你应该能够理解这个程序了吧!

JDBC是一个十分强大、有用的工具,而且也内容很多,足以成册,因此本书无法面面俱到说明,有爱好的话可以再去看一些Java的高级参考书。

自测练习

1) 使用JDBC,可分为四个步骤,请按顺序将它们排列出来:

________、________、________、________。

a.释放对象 b.语句处理 c.结果处理 d.创建连接

2) 在使用JDBC中,可能会用到许多方法,请将下列方法按照调用顺序排列出来:

________、________、________、________。

a.getString() b.executeQuery()

c.getConnection() d.createStatement()

3) 在JDBC中,使用________说明JDBC数据源。

a.数据库名 b.数据源名 c.JDBC URL d.JDBC对象名

4) 在jdbc:odbc:student中,odbc是___________。

a.协议名 b.驱动程序名 c.子协议名 d.桥接器

5) 在示例程序testjdbc.java中,我们选择出了所有的五个部分,假如我不想选择出Chinese、Maths字段,你认为程序应该做些什么修改?

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

6) SQL语句SELECT的语法格式是:

SELECT 字段名 FROM 表名 WHERE 条件语名

其中WHERE子句可以使SELECT只选择符合WHERE中说明的条件的记录。

请您编写一个程序,输出年纪为9岁的所有学生的Name、Sex、Chinese。

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

练习答案

1)d b c a

2)c d b a

3)c 在JDBC中,我们应该使用JDBC URL来说明数据源;

4)c 在这里odbc是子协议名;

5)共需要修改3处:

ResultSet rs=sta.executeQuery

("SELECT Name,Sex,Age,Chinese,Maths FROM student");

System.out.println("Name Sex Age Chinese Maths");

改为:

ResultSet rs=sta.executeQuery

("SELECT Name,Sex,Age FROM student");

System.out.println("Name Sex Age");

将以下两行删掉:

chinese=rs.getString(4);

maths=rs.getString(5);

将output=name+" "+sex+" "+age+" "+chinese+" "+maths;

改为:output=name+" "+sex+" "+age;

7) 以下是一个程序实例:

源程序:lianxi1301.java

import java.sql.*;

public class lianxi1301

{

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

{

try

{

String name,sex,chinese,output;

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

Connection con=DriverManager.getConnection

("jdbc:odbc:student");

Statement sta=con.createStatement();

ResultSet rs=sta.executeQuery("SELECT

Name,Sex,Chinese FROM student Where Age=9");

System.out.println("Name Sex Chinese");

while(rs.next())

{

name=rs.getString(1);

sex=rs.getString(2);

chinese=rs.getString(3);

output=name+" "+sex+" "+chinese;

System.out.println(output);

}

} catch(java.lang.Exception ex) {

ex.printStackTrace();

}

}

}

程序的输出如下图所示:

图13-13 lianxi1301的输出

13.3 编写多线程Java程序

传授新知

在今天,是否支持编写多线程的程序,已经成为一个程序设计语言的重要方面。那么什么是多线程,什么又是线程呢?

要搞懂什么是线程,其实就是要能够清楚地区分三个概念:“程序”、“进程”、“线程”。程序嘛,大家可能够很轻易理解。比如说,“写字板”、“Word”,还有我们前面写过的程序。那么什么是“进程”呢?

在许多计算机理论书籍中的定义十分拗口,对于您来说,只需记住:“进程就是一个运行着的程序”。看到这里,可能有人会说:“唉,说了半天,进程就是程序呀!一回事嘛!很简单,我懂了”。很遗憾,我得告诉你,这样的理解是错误的。请看下图:

图13-14 进程示意图

我们启动了两次notepad,就出现了两个记事本!而且,我们还可以在这两个记事本上写上不同的内容!

它们不是一个程序?它们可都是notepad呀,我们把这两个运行中的程序称为“进程”,不同的进程是完全不相关的。它们都有各地的内存块,用来存放运行时的临时数据(例如,在两个记事本上的写的字都是存放在各自进程的内存块中的)。

不知大家是否有过使用DOS操作系统的经验,在DOS中一次只答应执行一个程序,用刚学会的术语来说,就是只有一个进程。这种操作系统被称为“单任务系统”。

而在Windows中,我们可以答应执行多个程序,也可以将一个程序执行多次,这种操作系统就是“多任务系统”。操作系统负责协调这些运行着的程序---进程。

正如下图所示,在这种情况下,存在着互相不干拢的多个进程:

图13-15进程示意图

每一个圈代表一个进程,圆圈是这个进程的地址空间、寄存器……,中间的长方形是进程中的程序段,而黑色箭头则是程序计数器。

这个进程获得执行权时(CPU时间分片,轮到它),就从程序计数器开始执行。在程序时所有的数据都存放在自己的地址空间里(圆圈中)。

一些提示:

操作系统其中一个职责就是治理进程,为它们分配不同的存储区域,假如让这些“圆圈”相交,程序就会崩溃,Windows就蓝屏了!!

这个机制看起来十分的合理,不过碰到有些问题时,就会感觉到它并不是那么尽善尽美了。例如,我们有一个程序用来完成从远方接收信息,然后将其打印出来!

这个程序在工作时发现有这样的问题:当打印机忙的时候,这个程序无法打印,它就被“阻塞”了,一直在等待打印机空闲。在这个等待的时间里,也无法接收信息了。

这时,我们就可以使用“线程”来解决。

图13-16 线程示意图

上图中,圆圈仍然代表一个进程,但我们在这个进程中有两个不同的代码段,它们共享进程的地址空间、寄存器……,一个负责接收远方的信息,另一个负责打印出来!

这样,假如碰到打印机忙的时候,也只会将负责打印的那个部分“阻塞”,接收信息还是照常运行的。

这两个不同的代码段,就是线程,也称之为轻量进程。我们发现在一个程序中使用多个线程将会使我们的程序更Power!

注重:

线程的实现是要操作系统支持的,要写多线程的程序则需要编程语言的支持。Window操作系统是支持多线程的。因此我们只要使用能够支持多线程的编程语言就可以写多线程的程序。Java就是一种能够支持多线程编程的程序语言。

多线程的程序设计是比较高级的内容,而本书是一本Java的入门书籍,因此我们只对其做一个简单的介绍,使得大家通过本书的学习具备自学编写多线程的Java程序的能力。

在Java语言中,可以采用两种方法来实现多线程:

1.继续Thread类

也就是说,创建一个Thread类的子类,如:

public class testThread extends Thread

由于这种方法显得较为死板,所以我们一般很少使用这种方法。

2.使用Runnable接口

正如前面所说的,通常我们希望我们的类扩展其它类,而在Java中又不支持多重继续(也就是同时继续A、B两个类)。我们必须解决这个问题:

一些提示:

例如,我们想在一个Java的Applet程序中使用多线程,但我们要编写Applet

程序就必须继续java的Applet或Japplet类,那我们就无法再继续Thread类。因 为Java是不答应一个类同时继续两个类的。

Java提供了一个Runnable接口来实现这个问题。

1) 使用Runnable接口,首先在类的定义后面加上implements Runnable,如:

public class testThread extends Applet implements Runnable

2) 然后,再重写定义一个run()方法,定义线程要做的事情。

实例说明

下面,我们就来编写一个多线程的程序。首先创建两个源程序:testThread.java和Threader.java。

源程序:testThread.java

import java.awt.*;

import java.applet.*;

import Threader;

public class testThread extends Applet implements Runnable

{

Threader theRacers[];

static int racerCount = 3;

Thread theThreads[];

Thread thisThread;

static boolean inApplet=true;

int numberofThreadsAtStart;

public void init()

{

numberofThreadsAtStart = Thread.activeCount();

setLayout(new GridLayout(racerCount,1));

theRacers = new Threader [racerCount];

theThreads = new Thread[racerCount];

for (int x=0;xnumberofThreadsAtStart+2)

{

try

{

thisThread.sleep(100);

}

catch (InterruptedException e)

{

System.out.println("thisThread was interrupted");

}

}

if (inApplet)

{

stop();

destroy();

}

else

System.exit(0);

}

public static void main (String argv[])

{

inApplet=false;

if (argv.length>0)

racerCount = Integer.parseInt(argv[0]);

Frame theFrame = new Frame("The Great Thread Race");

testThread theRace = new testThread();

theFrame.setSize(400,200);

theFrame.add ("Center",theRace);

theFrame.show();

theRace.init();

theFrame.pack();

theRace.start();

}

}

源程序:Threader.java

import java.awt.*;

public class Threader extends Canvas implements Runnable

{

int myPosition =0;

String myName;

int numberofSteps=600;

boolean keepRunning = true;

public Threader (String inName)

{

myName=new String (inName);

}

public synchronized void paint(Graphics g)

{

g.setColor (Color.black);

g.drawLine 0,getSize().height/2,

getSize().width,getSize().height/2);

g.setColor (Color.yellow);

g.fillOval((myPosition*getSize().width/numberofSteps),0,

15,getSize().height);

}

public void stop()

{

keepRunning = false;

}

public void run()

{

while ((myPosition

输入完成以后,执行以下命令编译:

javac testThread.java Threader.java

然后,我们再执行命令:

java testThread

程序的输出如下图所示:

图13-17 程序testThread.java的输出

你看,三个人在赛跑!看谁快,他们太接近了,谁是第一名呢?所以我们来看看在DOS窗口的输出:

图13-18 MS-DOS下的输出

我们看到,程序的输出给出了公正的判决。第一次赛跑(第一次运行这个程序),冠军是第三道(Racer #2),第一道(Racer #0)屈居第二,第二道(Racer #1)是最后一名。第二次赛跑时,第二道反而成为了第一名。我们再运行几次程序,就会发现每一次的成绩都可能不同。你来可以使用以下命令来让更多的“人”参加赛跑:

java testThread 5

传授新知

这个程序太庞大了,也许会把大家吓倒!我们来看一下这个程序结构。这个示例由两个文件组成:testThread.java和Threader.java。

其中,Threader中定义了竞赛者对象,我们先一起来看一下这个类:

1)

public Threader (String inName)

{

myName=new String (inName);

}

这是类Threader中构造器方法,它为Threader设置对象名。

2)

public synchronized void paint(Graphics g)

{

g.setColor (Color.black);

g.drawLine (0,getSize().height/2,getSize().width,

getSize().height/2);

g.setColor (Color.yellow);

g.fillOval((myPosition*getSize().width/numberofSteps),0,

15,getSize().height);

}

我们为Threader类定义了一个paint方法,我们看一下这个方法做了些什么:

前两条语句用来画出这个竞赛者(Threader)的赛道,一条黑色的赛道!这条赛道用一根线来表示:它从(0,getSize().height/2)到(getSize().width,getSize().height/2)。也就是一个从最左边到最右边的,位于中心的黑线。

然后,我们画出这个竞赛者,它用一个黄色的椭圆来表示。这个椭圆宽15,高为整个格子,位置由变量myPosition决定。

一些提示:

我们将myPosition的初值设置为0.将整个赛道分成numberofSteps步,即600步。MyPosition每加1,就走过一步。

2)

public void run() {

while ((myPosition

这是一个while循环,当myposition小于numberofSteps,就将myposition加1(往前走一步),然后重画(这样这一步就会显示出来)。

假如myposition=numberofSteps时,意味着什么?对,意味着走完了赛程,因此,打印出完成信息。

大家看到,我们重画后,我们使用了这样一条语句:

Thread.currentThread().sleep(10);

这是让当前线程进入休眠状态一小会(10个时间周期)。这是为什么呢?要理解这个问题,我们需要学习一下CPU是如何治理这些线程的。线程与进程一样有三种状态:

§ 运行态:线程正在运行;

§ 就绪态:线程一切就绪,可以运行,正在等待CPU运行;

§ 阻塞态:线程未预备就绪,正在等待某个条件。

其间的关系,如下图所示:

图13-19 线程状态转换图

由于在单处理器的系统中,一个时间内CPU只能运行一个线程。所以假如我们在每个竞赛者跑一步时,就让它休眠(进入阻塞态,等待休眠时间到),这样就不会让一个线程一直占用CPU,以免不公平嘛!

由于我们让线程的休眠时间比较短,所以一会就回到了,这时线程就进入就绪态,等待CPU有时间的时候运行。CPU呢一有时间,就从就绪的线程中选择一个来运行。

大家看到这里,可能早已昏头转向了,下面我们举一个生活中的例子来模拟一下这个情况:有三位职员(对应程序中的三个竞赛者、即三个线程)要向经理(对应运行线程的CPU)汇报工作。但这个经理采用了一个十分公平的方法(当然在现实生活中是不可能的)来接受三个职员的汇报。也就是每一个职员一次只说一句话(每一个竞赛者跑一步),然后就呆一边休息一下(休眠10个时间周期),然后从另两个职员中任选一个来说(选择就绪的线程)。也是说一句话,就让他休息。这样周而复始,直到他们都汇报完毕。

情况如下图所示:

图13-20 竞赛者线程示意图

一些提示:

以上所述的关于线程的描述,是基于支持多线程的系统。假如不支持多线程的话,这是不成立的。在不支持线程的系统中,则将线程改成进程就行了。

接下来,我们看一下testThread.java程序,这才是我们执行时的主体部分:

3)

public static void main (String argv[])

{

inApplet=false;

if (argv.length>0)

racerCount = Integer.parseInt(argv[0]);

Frame theFrame = new Frame("The Great Thread Race");

testThread theRace = new testThread();

theFrame.setSize(400,200);

theFrame.add ("Center",theRace);

theFrame.show();

theRace.init();

theFrame.pack();

theRace.start();

}

这个程序执行时,首先将运行这个main方法。然后通过判定argv.length是否大于0,假如大于0,就将执行时所带的参数赋给racerCount(参赛人数),否则就为3(在程序的开始处有定义。

然后创建一个Frame,然后调用init()和start()方法。

一些提示:

注重,在这里我们创建一个testThread类的对象theRace,通过它来调用。

4)

for (int x=0;x

在theRace的init()方法中,最主要的部分就是这个for循环。通过这个循环我们创建了racerCount个Threader(竞赛者),并将它们命名为“Racer #x”,然后设置它的高度(整个Frame的高度/竞赛者数)。最后调用Frame的add方法将它们显示出来。

然后为每一个竞赛者(Threader)创建一个线程Thread。也就是说为每一个竞赛者创建一个线程,用这个线程来控制它。

一切就绪之后,我们就要开始“赛跑”了。

5)

public void start()

{

for (int x=0;x

接下来,我们调用了theRace的start()方法,我们看到在这个方法中,我将启动了“控制”所有参赛者的线程。(调用Thread的start方法)

多线程编程是相当复杂的,本书中仅给大家整理一下思路,做一些简要的介绍,大家可以阅读一些更高级的Java书籍,来更深入地学习。

自测练习

1) 以下关于进程的说法中,____________是正确的。

a.就是程序 b.一个运行中的程序 c.由线程组成

2) 以下关于线程的说法中,____________是正确的。

a.是一种轻量线进程 b.一个进程中只能有一个线程

c.每个线程都有自己的地址空间

3) Java语言中有两种方法实现多线程,它们是__________、 ____________。

a.继续Thread类 b.继续Threader类

c.使用Runnable接口 d.使用Thread接口

4) 最常使用的多线程实现方法是__________。

a.继续Thread类 b.继续Threader类

c.使用Runnable接口 d.使用Thread接口

请说明理由:

____________________________________________________________________

____________________________________________________________________

5) ___________不属于线程的状态:

a.运行态 b.完成态 c.就绪态 d.阻塞态

6) 请描述出使用Runnable接口实现多线程的步骤:

____________________________________________________________________

____________________________________________________________________

____________________________________________________________________

7) 假如我们使用java testThread a来执行这个程序的话,就会出错,请问这是为什么?请说明:

____________________________________________________________________

____________________________________________________________________

练习答案

1)b 并不是所有的进程都是由多个线程组成的。进程是程序的一个运行实例,它与程序是不等价的。

2)a 在支持线程的系统中,我们可以在一个进程中细分出多个线程,但并不是说每一个进程中都有多个线程。在一个进程中的线程是共享地址空间的。线程,也称为轻量级进程。

3)a、c 有两种方法,分别是继续Thread类,以及使用Runnable接口。

4)c 最常用的方法是使用Runnable接口。

这是因为,我们经常需要继续其它类,而Java又不支持多重继续,所以使用继续Thread类有很大的局限性。

5)b 当一个线程完成后,就释放了,并不存在一个完成态。

6)第一步:在类的定义后加上implements Runnable,以使用Runnable接口;第二步:重写定义一个run()方法,定义线程要做的事情。

7)将会出错。这是因为我们希望参数是竞赛者的人数,也就是需要整型数,而不是字符型。假如输入字符型就会造成程序出错。

13.4 JavaBeans

13.4.1 什么是组件(部件)技术

传授新知

通常,我们开发一个应用软件时,都是事先经过需求分析、设计,然后开发出所需的软件。在这个软件系统中,各种功能、特性用固定的方法联系在一起。但是,软件的组成部分大多无法独立地删除、升级。

这样造成的结果就是,不同的应用程序即使用同一种编程语言,在同一台机器上使用,也无法互相利用,它们之间就象是陌路人一样。

另外,由于操作系统不同,使得在不同的操作系统上完成一个相同的工作,往往实现的方法也就不同。尽管,国际上有许多标准化组织制定了许多规范来减少各个操作系统的开发接口的不同,但是这也只能够是一个权宜之计。

但是随着应用的不断扩展,经常需要让那些基于不同设计的软件交互,甚至一起工作。传统的方法是通过一组系统服务API(Application Programe Interface,应用编程接口)来实现。其工作原理如下图所示:

图13-21 通过API来利用原来的程序功能

通常会提供API的,都是一些系统软件,它们本身在设计时就充分考虑到让其它用户程序通过API来调用自身的“功能”模块。而假如一个软件本身在设计时并未考虑到API的话,就无法使用这种方法来实现,因此这种模式有极大的局限性。

矛盾与需求必将促进技术的发展。在20世纪90年代,出现了一种基于面向对象技术的软件工程技术---组件技术。这是一个新一代软件技术发展的里程碑。

一些提示:

面向对象技术是出现在20世纪80年代,但它并未完全解决我们碰到的问题。因为经缺乏一种让不同开发商提供的软件对象在同一地址空间里相互合作的机制。

这些有用的对象就象是“千军万马”,但我们还缺少一个能够“统帅三军”的人物。而组件技术就是这个“统帅”。

组件技术的基本思想就是:创建和利用可复用的软件组件来解决应用软件的开发问题。组件是一种可复用的一小段软件(可以是二进制的,而不是源代码)。组件可以有多种多类,小到图形界面上的一个按钮,大到一个复杂的软件。

只要开发者、应用商将它们的软件作品组件化,那么程序员们就可以在这些组件的基础上,根据自己的需要,将不同语言、不同厂商的组件组合在一起,编制自己的应用程序。通过复用这些组件,就能够使应用程序开发变得更加简单、更加快速,而且成本更加低廉。

组件技术的出现,使得:

1) 大大提高开发速度:由于许多软件模块都是通过复用这些已用的软件组件而成,因此将大大减少开发量,缩短开发周期。

2) 降低开发成本:开发量的减少,开发周期的缩短,都会节约大量的成本。

3) 增加应用软件的灵活性:当软件中的某个组件升级了,功能增强了!我们只需简单地换掉这个组件,马止就可以升级整个应用软件。而且我们还可以根据自己需要将它们个性化,这大大提高了应用软件的灵活性。

4) 降低维护费用:由于采用组件技术开发的应用软件,可以通过局部修改达到优化、修改软件的目的,而不必重新做全局修改,这将大大降低软件的维护费用。

最后,我们通过一个通俗、简单的例子,来说明使用组件技术开发应用软件前后的情形,希望这个例子能够帮助您理解组件技术的真谛。

我们要开发一个具有收音、放音、录音三种功能的“三用机”。

一些提示:

这其实是一个无线电技术方面的开发,我们在此就把它做一个类比。

1) 使用组件技术之前,我们开发应用软件就象:

我们做这个“三用机”时,我们放着现成的实现“收音”功能的集成电路、实现“放音”功能的集成电路、实现“录音”功能的集成电路不用,非得推倒一切,重新设计。由于时间有限,他可能做出了这些功能,但是整体的设计就可能无法让用户接受,最后可能得到一个不好的产品。而且要维护、维修这个产品,将会受到极大的困难。

这样做新产品开发的硬件工程师,肯定会受到各方面的批评吧!我想你也会觉得这种不可思义的工作方法是十分可笑的吧!

可是,很遗憾的是,我们的软件开发工作却一直使用这种“令人可笑”的方法来做的。但这也是因为软件产业尚未成熟,还没有整理出“收音集成电路”、“放音集成电路”、“录音集成电路”这些可复用的模块。只好使用这种“没有办法的办法”来做这样的工作。这种现状,也就是“软件危机”。

2) 使用组件技术之后,我们开发应用软件就完成不一样了:

我们做这个“三用机”时,我们认真设计,将实现“收音”、“放音”、“录音”功能的集成电路组合在一起,然后认真设计用户界面,外观、其它功能。结果我们得到了一个十分新潮的“三用机”

我想,这种方法在硬件工程设计上早已不是什么新技术了,正是这种不断地站在巨人的基础向前发展的经历,使得我们的CPU越来越快(你现在知道,为什么摩尔定律为什么会实现了吧!)。

因此,想让我们的软件开发工作能够象硬件开发一样有效率的话,也得总结出各种有效的“组件”,以后应用软件的开发变得象“搭积木”一样简单有效。

13.4.2 主流组件技术

传授新知

但是真正有效的软件组件,并不能够随意构造。因此,软件界就开始了一场组件软件的体系结构和组件间的接口方式的研究。并且许多软件厂商、组织制定出了许多这方面的方案与规范,比较有代表性的有三种:CORBA、COM/DCOM/OLE/ActiveX、JavaBeans。

1.CORBA

历史最悠久的是CORBA组件技术。它是由OMG(对象治理组织)制定的。OMG组织有许多大名鼎鼎的IT公司:HP、3COM、SUN、CANON、PHILIPS等。它使得异构系统中的部件能够很轻易地通信,就像是在本地进行通信一样。

2.COM/DCOM/OLE/ActiveX

最有市场竞争力的是Microsoft公司推出的COM/DOCM/OLE/ActiveX组件技术。它性能优越,但它仅局限于Windows平台上使用。

3.JavaBeans

由于组件技术规范具有不可小视的重要性,因此Sun公司为了与Microsoft竞争,在不久前发布了基于Java的组件技术标准:JavaBeans。它的目标是实现一种与平台无关的组件技术。不过,由于Java的地位不断的提高,使得对手Microsoft公司也对其提供了支持。

小知识:

第一个JavaBeans规格书是在1996年9月公布。这个规格书是多家公司合作的结果。它包括Apple、Borland、IBM、JustSystem、Microsoft、Netscape、RogueWave、SunSoft和Symantec等等。

到现在,又有许多新的公司公布对JavaBeans提供支持,这些公司包括:Corel、EnterpriseSoft、Gemstone、Jscape、K&A Software、KL Group、Lotus Development、NOVELL、ProtoView、Development、Rogue Wave和Stingray Software等。

关于JavaBeans的可多的内容,我们会在下一小节中讲述。

那么在这些组件技术,哪一个能够“浪沙淘尽,谁是英雄”呢?从现在的发展趋势来看,可能答案是“群雄逐鹿”。这是因为这三种组件技术各有优缺点,并不存在一种技术有明显的优势。而且它们之间还在不断地融合,互相提供接口桥,因此,这些组件技术均可能占据一定的市场。

13.4.3 组件技术:JavaBeans

传授新知

在组件技术JavaBeans中,每一个组件就是一个Bean,Bean可以结合起来,开发出应用程序。一个组件Bean就是使用Java语言编写的一个组件。

JavaBeans是一个十分完整的组件模型,它能够支持标准组件技术中的各种特性:属性(properties)、事件(events)、方法(method)和持续性(persistence)。

1) 组件的属性:

组件要能够被使用,那么就必须有一组属性来定义他的状态。例如,一个图形组件可以有属性:前景色、背景色。而一个计算储蓄的组件的属性可能就有本金、利率等。

大家想一下,它象不象一个对象的属性。

其实组件就是由一个或多个对象组件的,是一个更大些的软件组成部分。

2) 组件的方法:

与一个对象一样,组件提供了一系列的方法(也可以理解为函数),其它部分或其它软件通过调用这些方法来使用组件,或修改组件的状态。

3) 组件的事件:

由于组件技术是基于面向对象技术实现的,所以一样的,也是一种消息驱动机制,组件间是通过消息进行通讯的。这些消息就是事件。

例如,有一个“按钮”组件,当我们单击这个“按钮”时就会产生一个事件。

设计一个JavaBeans的步骤是:

1) 指定Beans的属性;

2) 指定Beans所产生或响应的事件;

3) 定义Beans应公开给其它Bean的属性、方法和事件。

JavaBeans是一个比较复杂的课题,更多的内容你可以参考《Using JavaBeans》一书。

自测练习

1) 组件技术的基础是_______________。

a.JavaBeans技术 b.传统的软件开发方法

c.面向对象技术

2) 最早的组件技术规范是____________。

a.JavaBeans b.CORBA c.COM/DCOM

3) 与平台相关的组件技术规范是____________。

a.JavaBeans b.CORBA c.COM/DCOM

4) OMG组织制订的组件技术规范是_________。

a.JavaBeans b.CORBA c.COM/DCOM

5) 以下关于组件的说法,正确的是__________。

a. 组件就是一个可复用的“对象”

b.组件技术是一个软件开发技术的里程碑

c.使用组件技术开发,会耗费更高的成本

练习答案

1)c 组件技术的基础是面向对象技术。JavaBeans是一种组件技术,它是为了解决传统的软件开发方法提出来的一种改进。

2)b 最早的组件技术规范是OMG组织开发的CORBA技术规范。

3)c COM/DCOM组件规范是Microsoft公司制定的,它只能够在Windows平台上使用,它是一种平台相关的技术。

4)b CORBA规范是OMG组织开发的。

5)b 组件技术是软件开发技术的一个里程碑。它比对象的粒度要大,它可以由多个对象组成。使用它能够更加高效地进行组件开发。

13.5 Java的学习资源

传授新知

善用Java的学习资源,能够给你学习Java插上腾飞的双翼!

13.5.1 最好的学习资源JDKDOC

学习Java语言,最好的资源就是JDKDOC(Java Development Kit Documentation),你可以在sun公司网站上下载。下图就是JDKDOC文档的主页:

图13-22 JDKDOC

13.5.2 WEB站点

Java语言更新迅速,要把握最新的内容,最好的方法就是通过Java相关的WEB站点来学习。下面就是一些闻名在Java站点,不过可惜的是大多都是英文站点。

1. Java的老家:

链接:www.javasoft.com java.sun.com

简介:这是查找各种Java官方资源的好地方,我们可以在这里下载到最新的JDK、Java工具、以及JDKDOC。

2. Earthweb的开发网站:

链接:www.gamelan.com

简介:它是所有Java资源站点的始祖。

3. Java Focus网站:

链接:www.miningco.com

简介:在这里你能够找到一大批关于Java的非凡信息。

4. Java Inside网站:

链接:www.inside-java.com

简介:这时Java程序员的一个很极好的信息源。这里我们可以找到许多关于Java语言的论文,以及许多Java的最新动态。

5. Java小应用程序评级服务网站:

链接:www.jars.com

简介:这是一个十分有趣的网站,它提供了一个Java小应用程序的排名。

6. Java开发者杂志

链接:www.javadevelopersjournal.com/java/

简介:在这里有免费的Java讲座,有免费的软件,可惜杂志并不是免费的,你只能免费试读3期。

7. Java Lobby

链接:www.javalobby.org

简介:这是一个由Java开发人员组成的群体。

8. Java世界

链接:www.javaworld.com

简介:这是一个关于Java的在线月刊。它是IDC公司面向Java一族的经典杂志。

9. 微软公司的Java主页:

链接:www.microsoft.com/java

简介:假如你想了解在Microsoft window平台上的Java的信息,你可以到这里看一看。注重它的Java可是不“纯”的哟。

10. Java团队

链接:www.teamjava.com

简介:这里的目的是通过提供关于Java的工作机会、新闻、教育材料及其它有用的Java资源,以向Java爱好者服务。

13.5.3 新闻组

以下是最经典的Java新闻组列表:

1) comp.lang.java:Java语言和编译;

2) comp.lang.java.advocacy:Java支持者的论点;

3) comp.lang.java.announce:Java产品及其它服务公报;

4) comp.lang.java.beans:JavaBeans讨论与编程;

5) comp.lang.java.databases:Java数据库编程;

6) comp.lang.java.gui:图形用户界面提示及帮助;

7) comp.lang.java.help:关于Java编程语言的一般性帮助;

8) comp.lang.java.machine:Java虚拟机讨论;

9) comp.lang.java.programmer:Java程序员帮助;

10)comp.lang.java.security:Java安全性讨论;

11)comp.lang.java.softwaretools:Java工具讨论组;

好,到此相信各位读者已经走进了Java的殿堂,可谓是“师傅”(笔者冒昧当一回师傅)带进门,修行靠个人。希望这些广泛的资源能够帮助你遨游Java世界。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有