2008年7月25日
運行看結果,慢慢了解
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public class Testone {
public static void main(String args[]){
A a=new A();
//a.test();
//SoftReference sr = new SoftReference(a);
ReferenceQueue<A> rq = new ReferenceQueue<A>();
WeakReference<A> wr = new WeakReference<A>(a, rq);
a = null;
System.out.println(wr.get());
System.out.println(rq.poll());
System.gc();
System.runFinalization();
System.out.println(wr.get());
System.out.println(rq.poll());
if (wr != null) {
a = (A)wr.get();
System.out.println("asdasdas");
a.test();
}
else{
a = new A();
System.out.println("123123");
a.test();
a = null;
wr = new WeakReference<A>(a);
}
}
}
class A{
void test(){
System.out.println("A.test()");
}
}
posted @
2008-12-06 21:14 竹子 阅读(447) |
评论 (0) |
编辑 收藏
垃圾回收与强引用,软引用,弱引用,幻引用的关系(一)
Java 2 平台引入了 java.lang.ref 包,其中包括的类可以让您引用对象,而不将它们留在内存中。这些类还提供了与垃圾收集器(garbage collector)之间有限的交互。Peter Haggar 在本文中分析了 SoftReference、WeakReference 和 PhantomReference 类的功能和行为,并就这些类的使用给出了一些编程风格上的建议。
当在 Java 2 平台中首次引入 java.lang.ref 包(其中包含 SoftReference、WeakReference 和 PhantomReference 类)时,它的实用性显然被过分夸大了。它包含的类可能是有用的,但这些类具有的某些局限性会使它们显得不是很有吸引力,而且其应用程序也将特别局限于解决一类特定的问题。
垃圾收集概述
引用类的主要功能就是能够引用仍可以被垃圾收集器回收的对象。在引入引用类之前,我们只能使用强引用(strong reference)。举例来说,下面一行代码显示的就是强引用 obj:
Object obj = new Object();
obj 这个引用将引用堆中存储的一个对象。只要 obj 引用还存在,垃圾收集器就永远不会释放用来容纳该对象的存储空间。
当 obj 超出范围或被显式地指定为 null 时,垃圾收集器就认为没有对这个对象的其它引用,也就可以收集它了。然而您还需要注意一个重要的细节:仅凭对象可以被收集并不意味着垃圾收集器的一次指定运行就能够回收它。由于各种垃圾收集算法有所不同,某些算法会更频繁地分析生存期较短的对象,而不是较老、生存期较长的对象。因此,一个可供收集的对象可能永远也不会被回收。如果程序在垃圾收集器释放对象之前结束,这种情况就可能会出现。因此,概括地说,您永远无法保证可供收集的对象总是会被垃圾收集器收集。
这些信息对于您分析引用类是很重要的。由于垃圾收集有着特定的性质,所以引用类实际上可能没有您原来想像的那么有用,尽管如此,它们对于特定问题来说还是很有用的类。软引用(soft reference)、弱引用(weak reference)和虚引用(phantom reference)对象提供了三种不同的方式来在不妨碍收集的情况下引用堆对象。每种引用对象都有不同的行为,而且它们与垃圾收集器之间的交互也有所不同。此外,这几个新的引用类都表现出比典型的强引用“更弱”的引用形式。而且,内存中的一个对象可以被多个引用(可以是强引用、软引用、弱引用或虚引用)引用。在进一步往下讨论之前,让我们来看看一些术语:
强可及对象(strongly reachable):可以通过强引用访问的对象。
软可及对象(softly reachable):不是强可及对象,并且能够通过软引用访问的对象。
弱可及对象(weakly reachable):不是强可及对象也不是软可及对象,并且能够通过弱引用访问的对象。
虚可及对象(phantomly reachable):不是强可及对象、软可及对象,也不是弱可及对象,已经结束的,可以通过虚引用访问的对象。
清除:将引用对象的 referent 域设置为 null,并将引用类在堆中引用的对象声明为可结束的。
SoftReference 类
SoftReference 类的一个典型用途就是用于内存敏感的高速缓存。SoftReference 的原理是:在保持对对象的引用时保证在 JVM 报告内存不足情况之前将清除所有的软引用。关键之处在于,垃圾收集器在运行时可能会(也可能不会)释放软可及对象。对象是否被释放取决于垃圾收集器的算法以及垃圾收集器运行时可用的内存数量。
WeakReference 类
WeakReference 类的一个典型用途就是规范化映射(canonicalized mapping)。另外,对于那些生存期相对较长而且重新创建的开销也不高的对象来说,弱引用也比较有用。关键之处在于,垃圾收集器运行时如果碰到了弱可及对象,将释放 WeakReference 引用的对象。然而,请注意,垃圾收集器可能要运行多次才能找到并释放弱可及对象。
PhantomReference 类
PhantomReference 类只能用于跟踪对被引用对象即将进行的收集。同样,它还能用于执行 pre-mortem 清除操作。PhantomReference 必须与 ReferenceQueue 类一起使用。需要 ReferenceQueue 是因为它能够充当通知机制。当垃圾收集器确定了某个对象是虚可及对象时,PhantomReference 对象就被放在它的 ReferenceQueue 上。将 PhantomReference 对象放在 ReferenceQueue 上也就是一个通知,表明 PhantomReference 对象引用的对象已经结束,可供收集了。这使您能够刚好在对象占用的内存被回收之前采取行动。
垃圾收集器和引用交互
垃圾收集器每次运行时都可以随意地释放不再是强可及的对象占用的内存。如果垃圾收集器发现了软可及对象,就会出现下列情况:
SoftReference 对象的 referent 域被设置为 null,从而使该对象不再引用 heap 对象。
SoftReference 引用过的 heap 对象被声明为 finalizable。
当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放,SoftReference 对象就被添加到它的 ReferenceQueue(如果后者存在的话)。
如果垃圾收集器发现了弱可及对象,就会出现下列情况:
WeakReference 对象的 referent 域被设置为 null,从而使该对象不再引用 heap 对象。
WeakReference 引用过的 heap 对象被声明为 finalizable。
当 heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放时,WeakReference 对象就被添加到它的 ReferenceQueue(如果后者存在的话)。
如果垃圾收集器发现了虚可及对象,就会出现下列情况:
PhantomReference 引用过的 heap 对象被声明为 finalizable。
与软引用和弱引用有所不同,PhantomReference 在堆对象被释放之前就被添加到它的 ReferenceQueue。(请记住,所有的 PhantomReference 对象都必须用经过关联的 ReferenceQueue 来创建。)这使您能够在堆对象被回收之前采取行动。
请考虑清单 1 中的代码。
清单 1. 使用 WeakReference 及 ReferenceQueue 的示例代码
//Create a strong reference to an object
MyObject obj = new MyObject(); //1
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue(); //2
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq); //3
行 //1 创建 MyObject 对象,而行 //2 则创建 ReferenceQueue 对象。行 //3 创建引用其引用对象 MyObject 的 WeakReference 对象,还创建它的 ReferenceQueue。请注意,每个对象引用(obj、rq 及 wr)都是强引用。要利用这些引用类,您必须取消对 MyObject 对象的强引用,方法是将 obj 设置为 null。前面说过,如果不这样做,对象 MyObject 永远都不会被回收,引用类的任何优点都会被削弱。
每个引用类都有一个 get() 方法,而 ReferenceQueue 类有一个 poll() 方法。get() 方法返回对被引用对象的引用。在 PhantomReference 上调用 get() 总是会返回 null。这是因为 PhantomReference 只用于跟踪收集。poll() 方法返回已被添加到队列中的引用对象,如果队列中没有任何对象,它就返回 null。因此,执行清单 1 之后再调用 get() 和 poll() 的结果可能是:
wr.get(); //returns reference to MyObject
rq.poll(); //returns null
现在我们假定垃圾收集器开始运行。由于 MyObject 对象没有被释放,所以 get() 和 poll() 方法将返回同样的值;obj 仍然保持对该对象进行强引用。实际上,对象布局还是没有改变,和图 1 所示的差不多。然而,请考虑下面的代码:
obj = null;
System.gc(); //run the collector
现在,调用 get() 和 poll() 将产生与前面不同的结果:
wr.get(); //returns null
rq.poll(); //returns a reference to the WeakReference object
这种情况表明,MyObject 对象(对它的引用原来是由 WeakReference 对象进行的)不再可用。这意味着垃圾收集器释放了 MyObject 占用的内存,从而使 WeakReference 对象可以被放在它的 ReferenceQueue 上。这样,您就可以知道当 WeakReference 或 SoftReference 类的 get() 方法返回 null 时,就有一个对象被声明为 finalizable,而且可能(不过不一定)被收集。只有当 heap 对象完全结束而且其内存被回收后,WeakReference 或 SoftReference 才会被放到与其关联的 ReferenceQueue 上。清单 2 显示了一个完整的可运行程序,它展示了这些原理中的一部分。这段代码本身就颇具说明性,它含有很多注释和打印语句,可以帮助您理解。
清单 2. 展示引用类原理的完整程序
import java.lang.ref.*;
class MyObject
{
protected void finalize() throws Throwable
{
System.out.println("In finalize method for this object: " +
this);
}
}
class ReferenceUsage
{
public static void main(String args[])
{
hold();
release();
}
public static void hold()
{
System.out.println("Example of incorrectly holding a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);
System.out.println("The weak reference is " + wr);
//Check to see if it´s on the ref queue yet
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
System.out.println("Calling GC");
System.gc();
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}
public static void release()
{
System.out.println("");
System.out.println("Example of correctly releasing a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);
System.out.println("The weak reference is " + wr);
//Check to see if it´s on the ref queue yet
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
System.out.println("Set the obj reference to null and call GC");
obj = null;
System.gc();
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}
}
用途和风格
这些类背后的原理就是避免在应用程序执行期间将对象留在内存中。相反,您以软引用、弱引用或虚引用的方式引用对象,这样垃圾收集器就能够随意地释放对象。当您希望尽可能减小应用程序在其生命周期中使用的堆内存大小时,这种用途就很有好处。您必须记住,要使用这些类,您就不能保留对对象的强引用。如果您这么做了,那就会浪费这些类所提供的任何好处。
另外,您必须使用正确的编程风格以检查收集器在使用对象之前是否已经回收了它,如果已经回收了,您首先必须重新创建该对象。这个过程可以用不同的编程风格来完成。选择错误的风格会导致出问题。请考虑清单 3 中从 WeakReference 检索被引用对象的代码风格:
清单 3. 检索被引用对象的风格
obj = wr.get();
if (obj == null)
{
wr = new WeakReference(recreateIt()); //1
obj = wr.get(); //2
}
//code that works with obj
研究了这段代码之后,请看看清单 4 中从 WeakReference 检索被引用对象的另一种代码风格:
清单 4. 检索被引用对象的另一种风格
obj = wr.get();
if (obj == null)
{
obj = recreateIt(); //1
wr = new WeakReference(obj); //2
}
//code that works with obj
请比较这两种风格,看看您能否确定哪种风格一定可行,哪一种不一定可行。清单 3 中体现出的风格不一定在所有情况下都可行,但清单 4 的风格就可以。清单 3 中的风格不够好的原因在于,if 块的主体结束之后 obj 不一定是非空值。请考虑一下,如果垃圾收集器在清单 3 的行 //1 之后但在行 //2 执行之前运行会怎样。recreateIt() 方法将重新创建该对象,但它会被 WeakReference 引用,而不是强引用。因此,如果收集器在行 //2 在重新创建的对象上施加一个强引用之前运行,对象就会丢失,wr.get() 则返回 null。
清单 4 不会出现这种问题,因为行 //1 重新创建了对象并为其指定了一个强引用。因此,如果垃圾收集器在该行之后(但在行 //2 之前)运行,该对象就不会被回收。然后,行 //2 将创建对 obj 的 WeakReference。在使用这个 if 块之后的 obj 之后,您应该将 obj 设置为 null,从而让垃圾收集器能够回收这个对象以充分利用弱引用。清单 5 显示了一个完整的程序,它将展示刚才我们描述的风格之间的差异。(要运行该程序,其运行目录中必须有一个“temp.fil”文件。
清单 5. 展示正确的和不正确的编程风格的完整程序。
import java.io.*;
import java.lang.ref.*;
class ReferenceIdiom
{
public static void main(String args[]) throws FileNotFoundException
{
broken();
correct();
}
public static FileReader recreateIt() throws FileNotFoundException
{
return new FileReader("temp.fil");
}
public static void broken() throws FileNotFoundException
{
System.out.println("Executing method broken");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);
System.out.println("wr refers to object " + wr.get());
System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj.
obj = null;
System.gc();
System.out.println("wr refers to object " + wr.get());
//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it
in a WeakReference");
wr = new WeakReference(recreateIt());
System.gc(); //FileReader object is NOT pinned...there is no
//strong reference to it. Therefore, the next
//line can return null.
obj = (FileReader)wr.get();
}
System.out.println("wr refers to object " + wr.get());
}
public static void correct() throws FileNotFoundException
{
System.out.println("");
System.out.println("Executing method correct");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);
System.out.println("wr refers to object " + wr.get());
System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj
obj = null;
System.gc();
System.out.println("wr refers to object " + wr.get());
//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it
in a WeakReference");
obj = recreateIt();
System.gc(); //FileReader is pinned, this will not affect
//anything.
wr = new WeakReference(obj);
}
System.out.println("wr refers to object " + wr.get());
}
}
总结
如果使用得当,引用类还是很有用的。然而,由于它们所依赖的垃圾收集器行为有时候无法预知,所以其实用性就会受到影响。能否有效地使用它们还取决于是否应用了正确的编程风格;关键在于您要理解这些类是如何实现的以及如何对它们进行编程。
=================================================================================
Java 对象的状态有:
* 已创建(created)
* 强可达(strong reachable)
* 不可见(invisible)
* 不可达(unreachable)
* 已收集(collected)
* 终化(finalized)
* 已回收(deallocated)
Java对象生命周期的状态转换: {image:img=objectstatus.jpg|width=400} 引用对象
三种新的引用类型:
* 软引用(soft reference)
* 弱引用(weak reference)
* 幻引用(phantom reference)
强可达(Strong Reachable)
定义: ~An object is strong reachable if it can be reached by some thread without traversing any reference objects. A newly-created object is strong reachable by the thread that created it.~
处于强可达状态的对象, 在任何情况下都不会被回收掉. 软可达(Softly Reachable)
定义:~An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.~
含义是:当对象不处于强可达状态, 并且可以通过软引用进行访问时, 即处于软可达状态.
当程序申请内存的时候, 垃圾收集器会判断是否开始回收处于软可达状态的对象, 如果决定回收某个对象, 那么垃圾收集器会清除所有指向该对象的软引用, 如果任何处于其它软可达状态的对象可以通过强引用访问该对象, 那么指向这些对象的软引用也会被清除掉. 垃圾收集器在决定哪些软可达状态的对象被收集时, 采用"最久未被使用"原则, 或称"最不常使用"原则. 垃圾收集器也保证在OutOfMemeryError产生以前, 所有的软引用都被清除.
* 产生和使用一个软引用
// createSoftReference sr = new SoftReference(new SomeObject());// getSomeObject o = (SomeObject) sf.get();// create in a reference queue;ReferenceQueue queue = new ReferenceQueue();SoftReference sr = new SoftReference(new SomeObject(), queue);
弱可达(Weakly Reachable)
定义:~An Object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference.~
垃圾收集器会一次清除所有弱引用. 幻可达(Phantomly Reachable)
定义:~An object is phantomly reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.~
幻引用不能直接创建. 必须通过向引用队列等级的途径来创建:
ReferenceQueue queue = new ReferenceQueue();PhantomReference pr = new PhantomReference (new SomeObject(), queue);
你不可能从幻引用再次得到对象, pr.get()永远返回null. 另外, 必须调用Reference.clear()手工清除幻引用. All About ReferenceObjects No InterWiki reference defined in properties for Wiki called '[http'!)]
Reference Objects No InterWiki reference defined in properties for Wiki called '[http'!)]
Reference Objects and Garbage Collection No InterWiki reference defined in properties for Wiki called '[http'!)]
\[Jike Thread\?Soft, Weak, and Phantom References|http://www-124.ibm.com/pipermail/jikesrvm-core/2003-May/000365.html]
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1492810
posted @
2008-12-06 21:13 竹子 阅读(476) |
评论 (0) |
编辑 收藏
T-SQL 编码标准
发布日期: 4/15/2005 | 更新日期: 4/15/2005
Brian Walker
可能让人觉得很奇怪,但好像的确没有什么“正式的”T-SQL 编码标准。早在 1999 年末的时候,我惊喜地发现 John Hindmarsh 提出的 SQL Server 7.0 标准,我在 2000 年 2 月的社论中对他的某些建议进行了总结。(2000 年 2 月以及本月的“下载”中都包括了 John 原来的标准。)后来,Ron Talmage 撰写了一系列专栏文章,提出了他对各种“最佳方法”的建议,当然,SQL Server 小组也已正式发布了 SQL Server 最佳方法分析器 (SQLBPA)。现在,一位具有超过 25 年经验的数据库管理员和应用程序开发员 Brian Walker 又提出了他的建议和提示。
进行 T-SQL 编程时常常会忽略编码标准,但这些标准却是开发小组顺利开展工作的关键工具。这里介绍的编码标准是我多年的开发成果。它们当然还没有得到普遍接受,而且不可否认,有些标准带有主观色彩。我的目的实际上更多的是为了提高大家的意识,而不是吹捧自己是 T-SQL 样式方面的仲裁者:最重要的是要建立某些合理的编码标准并遵循这些标准。您在这篇文章中会发现有关 T-SQL 编程的一系列不同的编码标准、技巧和提示。它们并未以任何特定的优先级或重要性顺序列出。
让我们从格式开始。表面上,T-SQL 代码的格式似乎并不重要,但一致的格式可以使您的同事(不论是同一小组的成员还是更大范围的 T-SQL 开发团队的成员)更轻松地浏览和理解您的代码。T-SQL 语句有一个结构,遵循一目了然的结构使您可以更轻松地查找和确认语句的不同部分。统一的格式还使您可以更轻松地在复杂 T-SQL 语句中增删代码段,使调试工作变得更容易。下面是 SELECT 语句的格式示例:
SELECT C.Name
, E.NameLast
, E.NameFirst
, E.Number
, ISNULL(I.Description,'NA') AS Description
FROM tblCompany AS C
JOIN tblEmployee AS E
ON C.CompanyID = E.CompanyID
LEFT JOIN tblCoverage AS V
ON E.EmployeeID = V.EmployeeID
LEFT JOIN tblInsurance AS I
ON V.InsuranceID = I.InsuranceID
WHERE C.Name LIKE @Name
AND V.CreateDate > CONVERT(smalldatetime,
'01/01/2000')
ORDER BY C.Name
, E.NameLast
, E.NameFirst
, E.Number
, ISNULL(I.Description,'NA')
SELECT @Retain = @@ERROR, @Rows = @@ROWCOUNT
IF @Status = 0 SET @Status = @Retain
►一个嵌套代码块中的语句使用四个空格的缩进。(上述代码中的多行 SELECT 语句是一个 SQL 语句。)在同一语句中开始新行时,使 SQL 关键字右对齐。将代码编辑器配置为使用空格,而不是使用制表符。这样,不管使用何种程序查看代码,格式都是一致的。
►大写所有的 T-SQL 关键字,包括 T-SQL 函数。变量名称及光标名称使用混和大小写。数据类型使用小写。
►表名别名要简短,但意义要尽量明确。通常,使用大写的表名作为别名,使用 AS 关键字指定表或字段的别名。
►当一个 T-SQL 语句中涉及到多个表时,始终使用表名别名来限定字段名。这使其他人阅读起来更清楚,避免了含义模糊的引用。
►当相关数字出现在连续的代码行中时(例如一系列 SUBSTRING 函数调用),将它们排成列。这样容易浏览数字列表。
►使用一个(而不是两个)空行分隔 T-SQL 代码的逻辑块,只要需要就可以使用。
►声明 T-SQL 局部变量(例如 @lngTableID)时,使用适当的数据类型声明和一致的大写。
►始终指定字符数据类型的长度,并确保允许用户可能需要的最大字符数,因为超出最大长度的字符会丢失。
►始终指定十进制数据类型的精度和范围,否则,将默认为未指定精度和整数范围。
►使用错误处理程序,但要记住行首 (BOL) 中的错误检查示例不会象介绍的那样起作用。用来检查 @@ERROR 系统函数的 T-SQL 语句 (IF) 实际上在进程中清除了 @@ERROR 值,无法再捕获除零之外的任何值。(即使示例起作用,它们也只能捕获最后发生的一个错误,而不是您更想捕获的第一个错误。)必须使用 SET 或 SELECT 立即捕获错误代码,如前面示例所示。如果状态变量仍然为零,应转换到状态变量。
►避免使用“未声明的”功能,例如系统表中未声明的列、T-SQL 语句中未声明的功能或者未声明的系统存储过程或扩展的存储过程。
►不要依赖任何隐式的数据类型转换。例如,不能为数字变量赋予字符值,而假定 T-SQL 会进行必要的转换。相反,在为变量赋值或比较值之前,应使用适当的 CONVERT 函数使数据类型相匹配。另一个示例:虽然 T-SQL 会在进行比较之前对字符表达式进行隐式且自动的 RTRIM,但不能依赖此行为,因为兼容性级别设置非字符表达式会使情况复杂化。
►不要将空的变量值直接与比较运算符(符号)比较。如果变量可能为空,应使用 IS NULL 或 IS NOT NULL 进行比较,或者使用 ISNULL 函数。
►不要使用 STR 函数进行舍入,此函数只能用于整数。如果需要十进制值的字符串形式,应先使用 CONVERT 函数(转至不同的范围)或 ROUND 函数,然后将其转换为字符串。也可以使用 CEILING 和 FLOOR 函数。
►使用数学公式时要小心,因为 T-SQL 可能会将表达式强制理解为一个不需要的数据类型。如果需要十进制结果,应在整数常量后加点和零 (.0)。
►决不要依赖 SELECT 语句会按任何特定顺序返回行,除非在 ORDER BY 子句中指定了顺序。
►通常,应将 ORDER BY 子句与 SELECT 语句一起使用。可预知的顺序(即使不是最方便的)比不可预知的顺序强,尤其是在开发或调试过程中。(部署到生产环境中之前,可能需要删除 ORDER BY 子句。)在返回行的顺序无关紧要的情况下,可以忽略 ORDER BY 的开销。
►不要在 T-SQL 代码中使用双引号。应为字符常量使用单引号。如果没有必要限定对象名称,可以使用(非 ANSI SQL 标准)括号将名称括起来。
►在 SQL Server 2000 中,尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
►先在例程中创建临时表,最后再显式删除临时表。将 DDL 与 DML 语句混合使用有助于处理额外的重新编译活动。
►要认识到临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
►使用表值 UDF 时要小心,因为在变量(而不是常量)中传递某个参数时,如果在 WHERE 子句中使用该参数,会导致表扫描。还要避免在一个查询中多次使用相同的表值 UDF。但是,表值 UDF 确实具有某些非常方便的动态编译功能。[相关资料:参阅 Tom Moreau 在 2003 年 11 月份“生成序列号”专栏中的“使用 UDF 填充表变量”。-编者按]
►几乎所有的存储过程都应在开始时设置 SET NOCOUNT ON,而在结束时设置 SET NOCOUNT OFF。[SET NOCOUNT ON 使 SQL Server 无需在执行存储过程的每个语句后向客户端发送 DONE_IN_PROC 消息。- 编者按] 此标准同样适用于触发器。
►只要在例程中使用多个数据库修改语句,包括在一个循环中多次执行一个语句,就应考虑声明显式事务。
►使用基于光标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题。基于集的方法通常更有效。
►与临时表一样,光标并不是不可使用。对小型数据集使用 FAST_FORWARD 光标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用光标执行的速度快。如果开发时间允许,基于光标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
►使用包含序号(从 1 到 N)的表很方便。
►理解 CROSS JOIN 的工作原理并加以利用。例如,您可以在工作数据表和序号表之间有效地使用 CROSS JOIN,结果集中将包含每个工作数据与序号组合的记录。
►我的结束语是:T-SQL 代码往往很简洁,因此如果某个代码块看起来很难处理或重复内容较多,那么可能存在一种更简单,更好的方法。
结论
如果您对我的建议有任何看法,欢迎随时向我发送电子邮件进行讨论,也可以就其他问题提出您的建议。我希望您将此作为谈话的开场白。
其他信息:摘自 Karen 2000 年 2 月份的社论
在标准开发的前沿阵地上,有一股以 SQL Server 数据库管理员 John Hindmarsh 为首的独立的新生力量。MCT、MCSE 和 MCDBA 都是最值得您花时间去研究的。John 的贡献是撰写了一份详细的白皮书,概述了他对各种 SQL Server 相关标准提出的建议。我所知道的其他唯一提出类似建议的文章是 Andrew Zanevsky 的《Transact-SQL Programming》(ISBN 1-56592-401-0) 中的“Format and Style”一章。Andrew、SQL Server Professional 的投稿人 Tom Moreau 和 Paul Munkenbeck 以及 John 的朋友兼同事 Stephen James 都为 John 的白皮书做出过贡献。下面是 John 为编写存储过程提供的建议示例:
• |
使用 SQL-92 标准连接句法。
|
• |
为了提高性能,应优先使用连接,然后使用子查询或嵌套查询。
|
• |
确保变量和参数的类型和大小与表数据列相匹配。
|
• |
确保使用所有变量和参数,或者全部删除。
|
• |
尽可能将临时对象放置在本地。
|
• |
只使用在存储过程中创建的临时表。
|
• |
检查输入参数的有效性。
|
• |
优先使用 SELECT...INTO,然后使用 INSERT...SELECT,以避免大量死锁。
|
• |
维护工作需要的逻辑单元;在可以缩短的情况下,不要创建大量或长时间运行的进程。
|
• |
不要在任何代码中使用 SELECT *。
|
• |
在过程中使用缩进、块、制表符和空格(参阅示例脚本)。
|
• |
T-SQL 语句要大写。
|
• |
在过程中添加大量注释,确保可以识别进程。在有助于澄清处理步骤的地方使用行注释。
|
• |
包括事务管理,除非要从 MTS 进程中调用过程。(为 MTS 进程编写独立的过程。)
|
• |
监视 @@TRANCOUNT 以确定事务的责任级别。
|
• |
避免使用 GOTO,错误处理程序中除外。
|
• |
避免使用嵌套过程。
|
• |
避免隐式解析对象名称,确保所有对象都归 dbo 所有。
|
下载 412BRIAN.ZIP
链接至www.microsoft.com/downloads/details.aspx?displayla%20ng=en&familyid=B352EB1F-D3CA-44EE-893E-9E07339C1F22&displaylang=en
有关 SQL Server Professional 和 Pinnacle Publishing 的详细信息,请访问其 Web 站点 http://www.pinpub.com/
注意:这不是 Microsoft Corporation 的 Web 站点。Microsoft 对该 Web 站点上的内容不承担任何责任。
本文转载自 2004 年 12 月份的 SQL Server Professional。除非另行说明,否则版权所有 2004 Pinnacle Publishing, Inc.。保留所有权利。SQL Server Professional 是 Pinnacle Publishing 独立发行的刊物。未经 Pinnacle Publishing, Inc. 事先同意,不得以任何方式使用或复制本文的任何部分(评论文章中的简短引用除外)。如需与 Pinnacle Publishing, Inc. 联系,请拨打 1-800-788-1900。
© 2005 Microsoft Corporation 版权所有。保留所有权利。使用规定。
原文出处:http://www.microsoft.com/china/msdn/library/data/sqlserver/sp04l9.mspx?mfr=true
posted @
2008-12-02 10:50 竹子 阅读(349) |
评论 (0) |
编辑 收藏
(1) 选择最有效率的表名顺序(只在基于规则的优化器中有效):
ORACLE 的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表.
(2) WHERE子句中的连接顺序.:
ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾.
(3) SELECT子句中避免使用 ‘ * ‘:
ORACLE在解析的过程中, 会将'*' 依次转换成所有的列名, 这个工作是通过查询数据字典完成的, 这意味着将耗费更多的时间
(4) 减少访问数据库的次数:
ORACLE在内部执行了许多工作: 解析SQL语句, 估算索引的利用率, 绑定变量 , 读数据块等;
(5) 在SQL*Plus , SQL*Forms和Pro*C中重新设置ARRAYSIZE参数, 可以增加每次数据库访问的检索数据量 ,建议值为200
(6) 使用DECODE函数来减少处理时间:
使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.
(7) 整合简单,无关联的数据库访问:
如果你有几个简单的数据库查询语句,你可以把它们整合到一个查询中(即使它们之间没有关系)
(8) 删除重复记录:
最高效的删除重复记录方法 ( 因为使用了ROWID)例子:
DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID)
FROM EMP X WHERE X.EMP_NO = E.EMP_NO);
(9) 用TRUNCATE替代DELETE:
当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息. 如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况) 而当运用TRUNCATE时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因此很少的资源被调用,执行时间也会很短. (译者按: TRUNCATE只在删除全表适用,TRUNCATE是DDL不是DML)
(10) 尽量多使用COMMIT:
只要有可能,在程序中尽量多使用COMMIT, 这样程序的性能得到提高,需求也会因为COMMIT所释放的资源而减少:
COMMIT所释放的资源:
a. 回滚段上用于恢复数据的信息.
b. 被程序语句获得的锁
c. redo log buffer 中的空间
d. ORACLE为管理上述3种资源中的内部花费
(11) 用Where子句替换HAVING子句:
避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销. (非oracle中)on、where、having这三个都可以加条件的子句中,on是最先执行,where次之,having最后,因为on是先把不符合条件的记录过滤后才进行统计,它就可以减少中间运算要处理的数据,按理说应该速度是最快的,where也应该比having快点的,因为它过滤数据后才进行sum,在两个表联接时才用on的,所以在一个表的时候,就剩下where跟having比较了。在这单表查询统计的情况下,如果要过滤的条件没有涉及到要计算字段,那它们的结果是一样的,只是where可以使用rushmore技术,而having就不能,在速度上后者要慢如果要涉及到计算的字段,就表示在没计算之前,这个字段的值是不确定的,根据上篇写的工作流程,where的作用时间是在计算之前就完成的,而having就是在计算后才起作用的,所以在这种情况下,两者的结果会不同。在多表联接查询时,on比where更早起作用。系统首先根据各个表之间的联接条件,把多个表合成一个临时表后,再由where进行过滤,然后再计算,计算完后再由having进行过滤。由此可见,要想过滤条件起到正确的作用,首先要明白这个条件应该在什么时候起作用,然后再决定放在那里
(12) 减少对表的查询:
在含有子查询的SQL语句中,要特别注意减少对表的查询.例子:
SELECT TAB_NAME FROM TABLES WHERE (TAB_NAME,DB_VER) = ( SELECT
TAB_NAME,DB_VER FROM TAB_COLUMNS WHERE VERSION = 604)
(13) 通过内部函数提高SQL效率.:
复杂的SQL往往牺牲了执行效率. 能够掌握上面的运用函数解决问题的方法在实际工作中是非常有意义的
(14) 使用表的别名(Alias):
当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误.
(15) 用EXISTS替代IN、用NOT EXISTS替代NOT IN:
在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接.在这种情况下, 使用EXISTS(或NOT EXISTS)通常将提高查询的效率. 在子查询中,NOT IN子句将执行一个内部的排序和合并. 无论在哪种情况下,NOT IN都是最低效的 (因为它对子查询中的表执行了一个全表遍历). 为了避免使用NOT IN ,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS.
例子:
(高效)SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND EXISTS (SELECT ‘X' FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB')
(低效)SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC = ‘MELB')
(16) 识别'低效执行'的SQL语句:
虽然目前各种关于SQL优化的图形化工具层出不穷,但是写出自己的SQL工具来解决问题始终是一个最好的方法:
SELECT EXECUTIONS , DISK_READS, BUFFER_GETS,
ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2) Hit_radio,
ROUND(DISK_READS/EXECUTIONS,2) Reads_per_run,
SQL_TEXT
FROM V$SQLAREA
WHERE EXECUTIONS>0
AND BUFFER_GETS > 0
AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8
ORDER BY 4 DESC;
(17) 用索引提高效率:
索引是表的一个概念部分,用来提高检索数据的效率,ORACLE使用了一个复杂的自平衡B-tree结构. 通常,通过索引查询数据比全表扫描要快. 当ORACLE找出执行查询和Update语句的最佳路径时, ORACLE优化器将使用索引. 同样在联结多个表时使用索引也可以提高效率. 另一个使用索引的好处是,它提供了主键(primary key)的唯一性验证.。那些LONG或LONG RAW数据类型, 你可以索引几乎所有的列. 通常, 在大型表中使用索引特别有效. 当然,你也会发现, 在扫描小表时,使用索引同样能提高效率. 虽然使用索引能得到查询效率的提高,但是我们也必须注意到它的代价. 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时, 索引本身也会被修改. 这意味着每条记录的INSERT , DELETE , UPDATE将为此多付出4 , 5 次的磁盘I/O . 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢.。定期的重构索引是有必要的.:
ALTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>
(18) 用EXISTS替换DISTINCT:
当提交一个包含一对多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXIST替换, EXISTS 使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果. 例子:
(低效):
SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D , EMP E
WHERE D.DEPT_NO = E.DEPT_NO
(高效):
SELECT DEPT_NO,DEPT_NAME FROM DEPT D WHERE EXISTS ( SELECT ‘X'
FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO);
(19) sql语句用大写的;因为oracle总是先解析sql语句,把小写的字母转换成大写的再执行
(20) 在java代码中尽量少用连接符“+”连接字符串!
(21) 避免在索引列上使用NOT 通常,
我们要避免在索引列上使用NOT, NOT会产生在和在索引列上使用函数相同的影响. 当ORACLE”遇到”NOT,他就会停止使用索引转而执行全表扫描.
(22) 避免在索引列上使用计算.
WHERE子句中,如果索引列是函数的一部分.优化器将不使用索引而使用全表扫描.
举例:
低效:
SELECT … FROM DEPT WHERE SAL * 12 > 25000;
高效:
SELECT … FROM DEPT WHERE SAL > 25000/12;
(23) 用>=替代>
高效:
SELECT * FROM EMP WHERE DEPTNO >=4
低效:
SELECT * FROM EMP WHERE DEPTNO >3
两者的区别在于, 前者DBMS将直接跳到第一个DEPT等于4的记录而后者将首先定位到DEPTNO=3的记录并且向前扫描到第一个DEPT大于3的记录.
(24) 用UNION替换OR (适用于索引列)
通常情况下, 用UNION替换WHERE子句中的OR将会起到较好的效果. 对索引列使用OR将造成全表扫描. 注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低. 在下面的例子中, LOC_ID 和REGION上都建有索引.
高效:
SELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE LOC_ID = 10
UNION
SELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE REGION = “MELBOURNE”
低效:
SELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE LOC_ID = 10 OR REGION = “MELBOURNE”
如果你坚持要用OR, 那就需要返回记录最少的索引列写在最前面.
(25) 用IN来替换OR
这是一条简单易记的规则,但是实际的执行效果还须检验,在ORACLE8i下,两者的执行路径似乎是相同的.
低效:
SELECT…. FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30
高效
SELECT… FROM LOCATION WHERE LOC_IN IN (10,20,30);
(26) 避免在索引列上使用IS NULL和IS NOT NULL
避免在索引中使用任何可以为空的列,ORACLE将无法使用该索引.对于单列索引,如果列包含空值,索引中将不存在此记录. 对于复合索引,如果每个列都为空,索引中同样不存在此记录. 如果至少有一个列不为空,则记录存在于索引中.举例: 如果唯一性索引建立在表的A列和B列上, 并且表中存在一条记录的A,B值为(123,null) , ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入). 然而如果所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空. 因此你可以插入1000 条具有相同键值的记录,当然它们都是空! 因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引.
低效: (索引失效)
SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL;
高效: (索引有效)
SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0;
(27) 总是使用索引的第一个列:
如果索引是建立在多个列上, 只有在它的第一个列(leading column)被where子句引用时,优化器才会选择使用该索引. 这也是一条简单而重要的规则,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引
(28) 用UNION-ALL 替换UNION ( 如果有可能的话):
当SQL 语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并, 然后在输出最终结果前进行排序. 如果用UNION ALL替代UNION, 这样排序就不是必要了. 效率就会因此得到提高. 需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录. 因此各位还是要从业务需求分析使用UNION ALL的可行性. UNION 将对结果集合排序,这个操作会使用到SORT_AREA_SIZE这块内存. 对于这块内存的优化也是相当重要的. 下面的SQL可以用来查询排序的消耗量
低效:
SELECT ACCT_NUM, BALANCE_AMT
FROM DEBIT_TRANSACTIONS
WHERE TRAN_DATE = '31-DEC-95'
UNION
SELECT ACCT_NUM, BALANCE_AMT
FROM DEBIT_TRANSACTIONS
WHERE TRAN_DATE = '31-DEC-95'
高效:
SELECT ACCT_NUM, BALANCE_AMT
FROM DEBIT_TRANSACTIONS
WHERE TRAN_DATE = '31-DEC-95'
UNION ALL
SELECT ACCT_NUM, BALANCE_AMT
FROM DEBIT_TRANSACTIONS
WHERE TRAN_DATE = '31-DEC-95'
(29) 用WHERE替代ORDER BY:
ORDER BY 子句只在两种严格的条件下使用索引.
ORDER BY中所有的列必须包含在相同的索引中并保持在索引中的排列顺序.
ORDER BY中所有的列必须定义为非空.
WHERE子句使用的索引和ORDER BY子句中所使用的索引不能并列.
例如:
表DEPT包含以下列:
DEPT_CODE PK NOT NULL
DEPT_DESC NOT NULL
DEPT_TYPE NULL
低效: (索引不被使用)
SELECT DEPT_CODE FROM DEPT ORDER BY DEPT_TYPE
高效: (使用索引)
SELECT DEPT_CODE FROM DEPT WHERE DEPT_TYPE > 0
(30) 避免改变索引列的类型.:
当比较不同数据类型的数据时, ORACLE自动对列进行简单的类型转换.
假设 EMPNO是一个数值类型的索引列.
SELECT … FROM EMP WHERE EMPNO = ‘123'
实际上,经过ORACLE类型转换, 语句转化为:
SELECT … FROM EMP WHERE EMPNO = TO_NUMBER(‘123')
幸运的是,类型转换没有发生在索引列上,索引的用途没有被改变.
现在,假设EMP_TYPE是一个字符类型的索引列.
SELECT … FROM EMP WHERE EMP_TYPE = 123
这个语句被ORACLE转换为:
SELECT … FROM EMP WHERETO_NUMBER(EMP_TYPE)=123
因为内部发生的类型转换, 这个索引将不会被用到! 为了避免ORACLE对你的SQL进行隐式的类型转换, 最好把类型转换用显式表现出来. 注意当字符和数值比较时, ORACLE会优先转换数值类型到字符类型
(31) 需要当心的WHERE子句:
某些SELECT 语句中的WHERE子句不使用索引. 这里有一些例子.
在下面的例子里, (1)‘!=' 将不使用索引. 记住, 索引只能告诉你什么存在于表中, 而不能告诉你什么不存在于表中. (2) ‘||'是字符连接函数. 就象其他函数那样, 停用了索引. (3) ‘+'是数学函数. 就象其他数学函数那样, 停用了索引. (4)相同的索引列不能互相比较,这将会启用全表扫描.
(32) a. 如果检索数据量超过30%的表中记录数.使用索引将没有显著的效率提高.
b. 在特定情况下, 使用索引也许会比全表扫描慢, 但这是同一个数量级上的区别. 而通常情况下,使用索引比全表扫描要块几倍乃至几千倍!
(33) 避免使用耗费资源的操作:
带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL引擎
执行耗费资源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序. 通常, 带有UNION, MINUS , INTERSECT的SQL语句都可以用其他方式重写. 如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是可以考虑的, 毕竟它们的可读性很强
(34) 优化GROUP BY:
提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉.下面两个查询返回相同结果但第二个明显就快了许多.
低效:
SELECT JOB , AVG(SAL)
FROM EMP
GROUP JOB
HAVING JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
高效:
SELECT JOB , AVG(SAL)
FROM EMP
WHERE JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
GROUP JOB
posted @
2008-12-02 10:42 竹子 阅读(200) |
评论 (0) |
编辑 收藏
l l 数据库回滚段是否足够?
l l 是否需要建立ORACLE数据库索引、聚集、散列?
l l 系统全局区(SGA)大小是否足够?
l l SQL语句是否高效?
2、2、数据仓库系统(Data Warehousing),这种信息系统的主要任务是从ORACLE的海量数据中进行查询,得到数据之间的某些规律。数据库管理员需要为这种类型的ORACLE数据库着重考虑下述参数:
l l 是否采用B*-索引或者bitmap索引?
l l 是否采用并行SQL查询以提高查询效率?
l l 是否采用PL/SQL函数编写存储过程?
l l 有必要的话,需要建立并行数据库提高数据库的查询效率
SQL语句的调整原则
SQL语言是一种灵活的语言,相同的功能可以使用不同的语句来实现,但是语句的执行效率是很不相同的。程序员可以使用EXPLAIN PLAN语句来比较各种实现方案,并选出最优的实现方案。总得来讲,程序员写SQL语句需要满足考虑如下规则:
1、1、尽量使用索引。试比较下面两条SQL语句:
语句A:SELECT dname, deptno FROM dept WHERE deptno NOT IN
(SELECT deptno FROM emp);
语句B:SELECT dname, deptno FROM dept WHERE NOT EXISTS
(SELECT deptno FROM emp WHERE dept.deptno = emp.deptno);
这两条查询语句实现的结果是相同的,但是执行语句A的时候,ORACLE会对整个emp表进行扫描,没有使用建立在emp表上的deptno索引,执行语句B的时候,由于在子查询中使用了联合查询,ORACLE只是对emp表进行的部分数据扫描,并利用了deptno列的索引,所以语句B的效率要比语句A的效率高一些。
2、2、选择联合查询的联合次序。考虑下面的例子:
SELECT stuff FROM taba a, tabb b, tabc c
WHERE a.acol between :alow and :ahigh
AND b.bcol between :blow and :bhigh
AND c.ccol between :clow and :chigh
AND a.key1 = b.key1
AMD a.key2 = c.key2;
这个SQL例子中,程序员首先需要选择要查询的主表,因为主表要进行整个表数据的扫描,所以主表应该数据量最小,所以例子中表A的acol列的范围应该比表B和表C相应列的范围小。
3、3、在子查询中慎重使用IN或者NOT IN语句,使用where (NOT) exists的效果要好的多。
4、4、慎重使用视图的联合查询,尤其是比较复杂的视图之间的联合查询。一般对视图的查询最好都分解为对数据表的直接查询效果要好一些。
5、5、可以在参数文件中设置SHARED_POOL_RESERVED_SIZE参数,这个参数在SGA共享池中保留一个连续的内存空间,连续的内存空间有益于存放大的SQL程序包。
6、6、ORACLE公司提供的DBMS_SHARED_POOL程序可以帮助程序员将某些经常使用的存储过程“钉”在SQL区中而不被换出内存,程序员对于经常使用并且占用内存很多的存储过程“钉”到内存中有利于提高最终用户的响应时间。
CPU参数的调整
CPU是服务器的一项重要资源,服务器良好的工作状态是在工作高峰时CPU的使用率在90%以上。如果空闲时间CPU使用率就在90%以上,说明服务器缺乏CPU资源,如果工作高峰时CPU使用率仍然很低,说明服务器CPU资源还比较富余。
使用操作相同命令可以看到CPU的使用情况,一般UNIX操作系统的服务器,可以使用sar –u命令查看CPU的使用率,NT操作系统的服务器,可以使用NT的性能管理器来查看CPU的使用率。
数据库管理员可以通过查看v$sysstat数据字典中“CPU used by this session”统计项得知ORACLE数据库使用的CPU时间,查看“OS User level CPU time”统计项得知操作系统用户态下的CPU时间,查看“OS System call CPU time”统计项得知操作系统系统态下的CPU时间,操作系统总的CPU时间就是用户态和系统态时间之和,如果ORACLE数据库使用的CPU时间占操作系统总的CPU时间90%以上,说明服务器CPU基本上被ORACLE数据库使用着,这是合理,反之,说明服务器CPU被其它程序占用过多,ORACLE数据库无法得到更多的CPU时间。
数据库管理员还可以通过查看v$sesstat数据字典来获得当前连接ORACLE数据库各个会话占用的CPU时间,从而得知什么会话耗用服务器CPU比较多。
出现CPU资源不足的情况是很多的:SQL语句的重解析、低效率的SQL语句、锁冲突都会引起CPU资源不足。
1、数据库管理员可以执行下述语句来查看SQL语句的解析情况:
SELECT * FROM V$SYSSTAT
WHERE NAME IN
('parse time cpu', 'parse time elapsed', 'parse count (hard)');
这里parse time cpu是系统服务时间,parse time elapsed是响应时间,用户等待时间
waite time = parse time elapsed – parse time cpu
由此可以得到用户SQL语句平均解析等待时间=waite time / parse count。这个平均等待时间应该接近于0,如果平均解析等待时间过长,数据库管理员可以通过下述语句
SELECT SQL_TEXT, PARSE_CALLS, EXECUTIONS FROM V$SQLAREA
ORDER BY PARSE_CALLS;
来发现是什么SQL语句解析效率比较低。程序员可以优化这些语句,或者增加ORACLE参数SESSION_CACHED_CURSORS的值。
2、数据库管理员还可以通过下述语句:
SELECT BUFFER_GETS, EXECUTIONS, SQL_TEXT FROM V$SQLAREA;
查看低效率的SQL语句,优化这些语句也有助于提高CPU的利用率。
3、3、数据库管理员可以通过v$system_event数据字典中的“latch free”统计项查看ORACLE数据库的冲突情况,如果没有冲突的话,latch free查询出来没有结果。如果冲突太大的话,数据库管理员可以降低spin_count参数值,来消除高的CPU使用率。
内存参数的调整
内存参数的调整主要是指ORACLE数据库的系统全局区(SGA)的调整。SGA主要由三部分构成:共享池、数据缓冲区、日志缓冲区。
1、 1、 共享池由两部分构成:共享SQL区和数据字典缓冲区,共享SQL区是存放用户SQL命令的区域,数据字典缓冲区存放数据库运行的动态信息。数据库管理员通过执行下述语句:
select (sum(pins - reloads)) / sum(pins) "Lib Cache" from v$librarycache;
来查看共享SQL区的使用率。这个使用率应该在90%以上,否则需要增加共享池的大小。数据库管理员还可以执行下述语句:
select (sum(gets - getmisses - usage - fixed)) / sum(gets) "Row Cache" from v$rowcache;
查看数据字典缓冲区的使用率,这个使用率也应该在90%以上,否则需要增加共享池的大小。
2、 2、 数据缓冲区。数据库管理员可以通过下述语句:
SELECT name, value FROM v$sysstat WHERE name IN ('db block gets', 'consistent gets','physical reads');
来查看数据库数据缓冲区的使用情况。查询出来的结果可以计算出来数据缓冲区的使用命中率=1 - ( physical reads / (db block gets + consistent gets) )。
这个命中率应该在90%以上,否则需要增加数据缓冲区的大小。
3、 3、 日志缓冲区。数据库管理员可以通过执行下述语句:
select name,value from v$sysstat where name in ('redo entries','redo log space requests');查看日志缓冲区的使用情况。查询出的结果可以计算出日志缓冲区的申请失败率:
申请失败率=requests/entries,申请失败率应该接近于0,否则说明日志缓冲区开设太小,需要增加ORACLE数据库的日志缓冲区。
posted @
2008-11-20 09:47 竹子 阅读(359) |
评论 (0) |
编辑 收藏
本文描述了Oracle 的查询优化程序,它是数据库的关键组件,能让Oracle 的用户获得极佳的执行性能。Oracle 的查询优化技术在功能上无与伦比,本文详细讨论了查询优化的所有重要领域。
简介
什么是查询优化程序?
查询优化对于关系数据库的性能,特别是对于执行复杂SQL 语句的性能而言至关重要。查询优化程序确定执行每一次查询的最佳策略。
例如,查询优化程序选择对于指定的查询是否使用索引,以及在联接多个表时采用哪一种联接技术。这类决策对SQL 语句的执行性能有很大的影响,查询优化对于每一种应用程序都是关键技术,应用程序涉及的范围从操作系统到数据仓库,从分析系统到内容管理系统。查询优化程序对于应用程序和最终用户是完全透明的。
由于应用程序可能生成非常复杂的SQL 语句, 查询优化程序必须精心构建、功能强大,以保障良好的执行性能。例如,查询优化程序可转换SQL 语句,使复杂的语句转换成为等价的但执行性能更好的SQL 语句。查询优化程序的典型特征是基于开销。在基于开销的优化策略中,对于给定查询生成多个执行计划,然后对每个计划估算开销。查询优化程序选用估算开销最低的计划。
Oracle 在查询优化方面提供了什么?
Oracle 的优化程序可称是业界最成功的优化程序。基于开销的优化程序自1992 年随Oracle7 推出后,通过10 年的丰富的实际用户经验,不断得到提高和改进。好的查询优化程序不是基于纯粹的理论假设及谓词在实验室中开发出来的,而是通过适合实际用户需求开发和磨合出来的。
Oracle 的查询优化程序比任何其他查询优化程序在数据库应用程序的应用都要多,而且Oracle 的优化程序一直由于实际应用的反馈而得到改进。
Oracle 的优化程序包含4 大主要部分(本文将在以下章节详细讨论这些部分):
SQL 语句转换:在查询优化中Oracle 使用一系列精深技术对SQL 语句进行转换。查询优化的这一步骤的目的是将原有的SQL 语句转换成为语义相同而处理效率更高的SQL 语句。
执行计划选择:对于每个SQL 语句, 优化程序选择一个执行计划(可使用Oracle 的EXPLAIN PLAN 工具或通过Oracle 的“v$sql_plan” 视图查看)。执行计划描述了执行SQL 时的所有步骤,如访问表的顺序;如何将这些表联接在一起;以及是否通过索引来访问这些表。优化程序为每个SQL 语句设计许多可能的执行计划,并选出最好的一个。
开销模型与统计:Oracle 的优化程序依赖于执行SQL 语句的所有单个操作的开销估算。想要优化程序能选出最好的执行计划,需要最好的开销估算方法。开销估算需要详细了解某些知识,这些知识包括:明白每个查询所需的I/O、CPU 和内存资源以及数据库对象相关的统计信息(表、索引和物化视图),还有有关硬件服务器平台的性能信息。收集这些统计和性能信息的过程应高效并且高度自动化。
动态运行时间优化:并不是SQL 执行的每个方面都可以事先进行优化。Oracle 因此要根据当前数据库负载对查询处理策略进行动态调整。该动态优化的目标是获得优化的执行性能,即使每个查询可能不能够获得理想的CPU 或内存资源。Oracle 另有一个原来的优化程序,即基于规则的优化程序。该优化程序仅向后兼容,在Oracle 的下个版本将不再得到支持。绝大多数Oracle 用户目前使用基于开销的优化程序。所有主要的应用程序供应商(如Oracle 应用程序、SAP 和Peoplesoft,仅列出这几家)以及大量近来开发的客户应用程序都使用基于开销的优化程序来获得优良的执行性能,故本文仅讲述基于开销的优化程序。
SQL 语句转换
使用SQL 语句表示复杂查询可以有多种方式。提交到数据库的SQL 语句类型通常是最终用户或应用程序可以最简单的方式生成的SQL 类型。但是这些人工编写或机器生成的查询公式不一定是执行查询最高效的SQL 语句。例如,由应用程序生成的查询通常含有一些无关紧要的条件,这些条件可以去掉。或者,有些从某查询谓词出的附加条件应当添加到该SQL 语句中。SQL 转换语句的目的是将给定的SQL 语句转换成语义相同(即返回相同结果的SQL 语句)并且性能更好的SQL 语句。
所有的这些转换对应用程序及最终用户完全透明。SQL 语句转换在查询优化过程中自动实现。
Oracle 实现了多种SQL 语句转换。这些转换大概可分成两类:
试探查询转换:在可能的情况下对进来的SQL 语句都会进行这种转换。这种转换能够提供相同或较好的查询性能,所以Oracle 知道实施这种转换不会降低执行性能。 基于开销的查询转换:Oracle 使用基于开销的方法进行几类查询转换。借助这种方法,转换后的查询会与原查询相比较,然后Oracle 的优化程序从中选出最佳执行策略。
以下部分将讨论Oracle 转换技术的几个示例。这些示例并非是权威的,仅用于帮助读者理解该关键转换技术及其益处。
试探查询转换
简单视图合并
可能最简单的查询转换是视图合并。对于包含视图的查询,通常可以通过把视图定义与查询“合并”来将视图从查询中去掉。例如,请看下面的非常简单的视图及查询。
CREATE VIEW TEST_VIEW AS SELECT ENAME, DNAME, SAL FROM EMP E, DEPT D WHERE E.DEPTNO = D.DEPTNO;
SELECT ENAME, DNAME FROM TEST_VIEW WHERE SAL > 10000;
如果不加任何转换,处理该查询的唯一方法是将EMP 的所有行联接到DEPT 表的所有行,然后筛选有适当的SAL 的值的那些行。
如果使用视图合并,上述查询可以转换为:
SELECT ENAME, DNAME FROM EMP E, DEPT D WHERE E.DEPTNO = D.DEPTNO AND E.SAL > 10000;
处理该转换后的查询时,可以在联接EMP 和DEPT 表前使用谓词‘SAL>10000’。这一转换由于减少了联接的数据量而大大提高了查询的执行性能。即便在这样一个非常简单的示例里,查询转换的益处和重要性也显而易见。
复杂视图合并
许多视图合并操作都是直截了当的,如以上示例。但是,较复杂的视图,如包含GROUP BY 或DISTINCT 操作符的视图合并起来就不那么容易了。Oracle 为合并这类复杂视图提供了一些高级技术。
请看以下带有GROUP BY 语句的视图。在该示例中,视图计算每个部门的平均工资。
CREATE VIEW AVG_SAL_VIEW AS SELECT DEPTNO, AVG(SAL) AVG_SAL_DEPT FROM EMP GROUP BY DEPTNO
查询的目的是要找出Oakland 每个部门的平均工资:
SELECT DEPT.NAME, AVG_SAL_DEPT FROM DEPT, AVG_SAL_VIEW WHERE DEPT.DEPTNO = AVG_SAL_VIEW.DEPTNO AND DEPT.LOC = 'OAKLAND'
可以转换为:
SELECT DEPT.NAME, AVG(SAL) FROM DEPT, EMP WHERE DEPT.DEPTNO = EMP.DEPTNO AND DEPT.LOC = 'OAKLAND' GROUP BY DEPT.ROWID, DEPT.NAME
该特殊转换的执行性能优点立即显现:该转换把EMP 数据在分组聚合前进行联接和筛选,而不是在联接前将EMP 表的所有数据分组聚合。
子查询“展平”
Oracle 有一些转换能将不同类型的子查询转变为联接、半联接或反联接。作为该领域内的技术示例,我们来看下面这个查询,找出有工资超过10000 的员工的那些部门:
SELECT D.DNAME FROM DEPT D WHERE D.DEPTNO IN (SELECT E.DEPTNO FROM EMP E WHERE E.SAL > 10000)
存在一系列可以优化本查询的执行计划。Oracle 会考虑这些可能的不同转换,基于开销选出最佳计划。
如果不进行任何转换,这一查询的执行计划如下:
OPERATION OBJECT_NAME OPTIONS
SELECT STATEMENT
FILTER
TABLE ACCESS DEPT FULL
TABLE ACCESS EMP FULL
按照该执行计划,将扫描DEPT 表的每一行查找所有满足子查询条件的EMP 记录。通常,这不是一种高效的执行策略。然而,查询转换可以实现效率更高的计划。
该查询的可能计划之一是将查询作为“半联接”来执行。“半联接”是一种特殊类型的联接,它消除了联接中来自内表的冗余值(这实际上就是该子查询的原本的语义)。在该示例中,优化程序选择了一个散列半联接,尽管Oracle 也支持排序-合并以及嵌套-循环半联接:
OPERATION OBJECT_NAME OPTIONS
SELECT STATEMENT
HASH JOIN SEMI
TABLE ACCESS DEPT FULL
TABLE ACCESS EMP FULL
由于SQL 没有用于半联接的直接语法,此转换过的查询不能使用标准的SQL 来表示。但是,转换后的伪SQL 将是:
SELECT DNAME FROM EMP E, DEPT D WHERE D.DEPTNO E.DEPTNO AND E.SAL > 10000;
另一个可能的计划是优化程序可以决定将DEPT 表作为联接的内表。在这种情况下,查询作为通常的联接来执行,但对EMP 表进行特别排序,以消除冗余的部门号:
OPERATION OBJECT_NAME OPTIONS
SELECT STATEMENT
HASH JOIN
SORT UNIQUE
TABLE ACCESS EMP FULL
TABLE ACCESS DEPT FULL
转换后的SQL 语句为:
SELECT D.DNAME FROM (SELECT DISTINCT DEPTNO FROM EMP) E, DEPT D WHERE E.DEPTNO = D.DEPTNO AND E.SAL > 10000;
与视图合并一样,子查询展平也是获得良好查询执行性能的基本优化办法。
传递谓词生成
在某些查询中,由于表间的联接关系,一个表中的谓词可以转化为另一个表中的谓词。Oracle 会以这种方式演绎出新的谓词,这类谓词被称为传递谓词。例如,来看一个查询,找出定货当天运出的所有商品:
SELECT COUNT(DISTINCT O_ORDERKEY) FROM ORDER, LINEITEM WHERE O_ORDERKEY = L_ORDERKEY AND O_ORDERDATE = L_SHIPDATE AND O_ORDERDATE BETWEEN '1-JAN-2002' AND '31-JAN-2002'
利用传递性,该ORDER 表中的谓词也可以用于LINEITEM 表:
SELECT COUNT(DISTINCT O_ORDERKEY) FROM ORDER, LINEITEM WHERE
posted @
2008-11-20 09:46 竹子 阅读(428) |
评论 (0) |
编辑 收藏
1、父子二人经过五星级饭店门口,看到一辆十分豪华的进口轿车。儿子不屑地对他的父亲说:「坐这种车的人,肚子里一定没有学问!」父亲则轻描淡写地回答:「说这种话的人,口袋里一定没有钱!」
(注:你对事情的看法,是不是也反映出你内心真正的态度?)
2、晚饭后,母亲和女儿一块儿洗碗盘,父亲和儿子在客厅看电视。突然,厨房里传来打破盘子的响声,然后一片沉寂。是儿子望着他父亲,说道:「一定是妈妈打破的。」「你怎么知道?」「她没有骂人。」
(注:我们习惯以不同的标准来看人看己,以致往往是责人以严,待己以宽。)
3、有两个台湾观光团到日本伊豆半岛旅游,路况很坏,到处都是坑洞。其中一位导游连声抱歉,说路面简直像麻子一样。说而另一个导游却诗意盎然地对游客说:诸位先生女士,我们现在走的这条道路,正是赫赫有名的伊豆迷人酒窝大道。」
(注:虽是同样的情况,然而不同的意念,就会产生不同的态度。思想是何等奇妙的事,如何去想,决定权在你。)
4、同样是小学三年级的学生,在作文中说他们将来的志愿是当小丑。中国的老师斥之为:「胸无大志,孺子不可教也!」而外国的老师则会说:「愿你把欢笑带给全世界!」
(注:身为长辈的我们,不但容易要求多于鼓励,更狭窄的界定了成功的定义。)
5、在故宫博物院中,有一个太太不耐烦地对她先生说:「我说你为甚么走得这么慢。原来你老是停下来看这些东西。」
(注:有人只知道在人生的道路上狂奔,结果失去了观看两旁美丽花朵的机会。)
6、妻子正在厨房炒菜。丈夫在她旁边一直唠叨不停:慢些。小心!火太大了。赶快把鱼翻过来。快铲起来,油放太多了!把豆腐整平一下!「哎呀!」妻子脱口而出,「我懂得怎样炒菜。」「你当然懂,太太,」丈夫平静地答道:「我只是要让你知道,我在开车时,你在旁边喋喋不休,我的感觉如何。」
(注:学会体谅他人并不困难,只要你愿意认真地站在对方的角度和立场看问题。)
7、一辆载满乘客的公共汽车沿着下坡路快速前进着,有一个人後面紧紧地追赶着这辆车子。一个乘客从车窗中伸出头来对追车子的人说:“老兄!算啦,你追不上的!”“我必须追上它,”这人气喘吁吁地说:“我是这辆车的司机!”
(注:有些人必须非常认真努力,因为不这样的话,後果就十分悲惨了!然而也正因为必须全力以赴,潜在的本能和不为人知的特质终将充份展现出来。)
8、甲:「新搬来的邻居好可恶,昨天晚上三更半夜、夜深人静之时然跑来猛按我家的门铃。」
乙:「的确可恶!你有没有马上报警?」
甲:「没有。我当他们是疯子,继续吹我的小喇叭。」
(事出必有因,如果能先看到自己的不是,答案就会不一样在你面对冲突和争执时,先想一想是否心中有亏,或许很快就能释怀了)
9、某日,张三在山间小路开车,正当他悠哉地欣赏美丽风景时,突然迎面开来一辆货车,而且满囗黑牙的司机还摇下窗户对他大骂一声:“猪!”
张三越想越纳闷,也越想越气,於是他也摇下车窗回头大骂:“你才是猪!”
才刚骂完,他便迎头撞上一群过马路的猪。
(不要错误的诠释别人的好意,那只会让自己吃亏,并且使别人受辱。在不明所以之前,先学会按捺情绪,耐心观察,以免事後生发悔意。)
10、小男孩问爸爸:“是不是做父亲的总比做儿子的知道得多?”
爸爸回答:“当然啦!”
小男孩问:“电灯是谁发明的?”
爸爸:“是爱迪生。”
小男孩又问:“那爱迪生的爸爸怎麽没有发明电灯?”
(很奇怪,喜欢倚老卖老的人,特别容易栽跟斗。权威往往只是一个经不起考验的空壳子,尤其在现今这个多元开放的时代。)
11、小明洗澡时不小心吞下一小块肥皂,他的妈妈慌慌张张地打电话向家庭医生求助。医生说:“我现在还有几个病人在,可能要半小时後才能赶过去。”
小明妈妈说:“在你来之前,我该做甚麽?”
医生说:“给小明喝一杯白开水,然後用力跳一跳,你就可以让小明用嘴巴吹泡泡消磨时间了。”
(take it easy,放轻松放轻松些,生活何必太紧张?事情既然已经发生了,何不坦然自在的面对。担心不如宽心,穷紧张不如穷开心。)
12、一把坚实的大锁挂在大门上,一根铁杆费了九牛二虎之力,还是无法将它撬开。钥匙来了,他瘦小的身子钻进锁孔,只轻轻一转,大锁就“啪”地一声打开了。
铁杆奇怪地问:“为什麽我费了那麽大力气也打不开,而你却轻而易举地就把它打开了呢?”
钥匙说:“因为我最了解他的心。”
问题一
如果你家附近有一家餐厅,东西又贵又难吃,桌上还爬着蟑螂,你会因为它很近很方便,就一而再、再而三地光临吗?
回答:你一定会说,这是什么烂问题,谁那么笨,花钱买罪受?
可同样的情况换个场合,自己或许就做类似的蠢事。不少男女都曾经抱怨过他们的情人或配偶品性不端,三心二意,不负责任。明知在一起没什么好的结果,怨恨已经比爱还多,但却“不知道为什么”还是要和他搅和下去,分不了手。说穿了,只是为了不甘,为了习惯,这不也和光临餐厅一样?
――做人,为什么要过于执著?!
问题二
如果你不小心丢掉100块钱,只知道它好像丢在某个你走过的地方,你会花200块钱的车费去把那100块找回来吗?
回答:一个超级愚蠢的问题。
可是,相似的事情却在人生中不断发生。做错了一件事,明知自己有问题,却死也不肯认错,反而花加倍的时间来找藉口,让别人对自己的印象大打折扣。被人骂了一句话,却花了无数时间难过,道理相同。为一件事情发火,不惜损人不利已,不惜血本,不惜时间,只为报复,不也一样无聊?
失去一个人的感情,明知一切已无法挽回,却还是那么伤心,而且一伤心就是好几年,还要借酒浇愁,形销骨立。其实这样一点用也没有,只是损失更多。
――做人,干吗为难自己?!
问题三
你会因为打开报纸发现每天都有车祸,就不敢出门吗?
回答:这是个什么烂问题?当然不会,那叫因噎废食。
然而,有不少人却曾说:现在的离婚率那么高,让我都不敢谈恋爱了。说得还挺理所当然。也有不少女人看到有关的诸多报道,就对自己的另一半忧心忡忡,这不也是类似的反应?
所谓乐观,就是得相信:虽然道路多艰险,我还是那个会平安过马路的人,只要我小心一点,不必害怕过马路。
――做人,先要相信自己。
问题四
你相信每个人随便都可以成功立业吗?
回答:当然不会相信。
但据观察,有人总是在听完成功人士绞尽脑汁的建议,比如说,多读书,多练习之后,问了另一个问题?那不是很难?
我们都想在3分钟内学好英文,在5分钟内解决所有难题,难道成功是那么容易的吗?改变当然是难的。成功只因不怕困难,所以才能出类拔萃。
有一次坐在出租车上,听见司机看到自己前后都是高档车,兀自感叹:“唉,为什么别人那么有钱,我的钱这么难赚?” 我心血来潮,问他:“你认为世上有什么钱是好赚的?”他答不出来,过了半晌才说:好像都是别人的钱比较好赚。
其实任何一个成功者都是艰辛取得。我们实在不该抱怨命运。
――做人,依靠自己!
问题五
你认为完全没有打过篮球的人,可以当很好的篮球教练吗?
回答:当然不可能,外行不可能领导内行。
可是,有许多人,对某个行业完全不了解,只听到那个行业好赚钱,就马上开起业来了。
我看过对穿着没有任何口味、或根本不在乎穿着的人,梦想却是开间服装店;不知道电脑怎么开机的人,却想在网上赚钱,结果道听途说,却不反省自己是否专业能力不足,只抱怨时不我与。
――做人,量力而行。
问题六
相似但不相同的问题:你是否认为,篮球教练不上篮球场,闭着眼睛也可以主导一场完美的利?
回答:有病啊,当然是不可能的。
可是却有不少朋友,自己没有时间打理,却拼命投资去开咖啡馆,开餐厅,开自己根本不懂的公司,火烧一样急着把辛苦积攒的积蓄花掉,去当一个稀里糊涂的投资人。亏的总是比赚的多,却觉得自己是因为运气不好,而不是想法出了问题。
――做人,记得反省自己。
问题七
你宁可永远后悔,也不愿意试一试自己能否转败为胜?
解答:恐怕没有人会说:“对,我就是这样的孬种”吧。
然而,我们却常常在不该打退堂鼓时拼命打退堂鼓,为了恐惧失败而不敢尝试成功。
以关颖珊赢得2000年世界花样滑冰冠军时的精彩表现为例:她一心想赢得第一名,然而在最后一场比赛前,她的总积分只排名第三位,在最后的自选曲项目上,她选择了突破,而不是少出错。在4分钟的长曲中,结合了最高难度的三周跳,并且还大胆地连跳了两次。她也可能会败得很难看,但是她毕竟成功了。
她说:“因为我不想等到失败,才后悔自己还有潜力没发挥。”
一个中国伟人曾说;胜利的希望和有利情况的恢复,往往产生于再坚持一下的努力之中。
――做人,何妨放手一搏。
问题八
你的时间无限,长生不老,所以最想做的事,应该无限延期?
回答:不,傻瓜才会这样认为。
然而我们却常说,等我老了,要去环游世界;等我退休,就要去做想做的事情;等孩子长大了,我就可以……
我们都以为自己有无限的时间与精力。其实我们可以一步一步实现理想,不必在等待中徒耗生命。如果现在就能一步一步努力接近,我们就不会活了半生,却出现自己最不想看到的结局。
――做人,要活在当下。
posted @
2008-09-11 21:01 竹子 阅读(175) |
评论 (0) |
编辑 收藏
做为一名大四的学生,我面试过不少的单位,有成功的也有失败的,但是对我来说所有的失败在某种意义上都是一种成功,特别是我下面写的这些,写这篇文章的时候,我已经签了南京的一家软件公司,但是想起今年2月21日我面试苏州TW的IT公司的经历联想到我们现在学习编程的一些情况我真的深有感触,这次面试使我深深的体会到了失败但也收获了很多。我要说的将分成三部分,1.是我面试的具体经过2.是由面试想到的3.现今我应该做的。当然这些话很大程度上是我个人的意见,不可能完全得到大家的赞同,所以在某些观点上如果哪位朋友觉得跟我的有很大出入,请不要介意,也不要对我攻击,就当我没有说过,欢迎和我联系共同探讨这些问题!我的E-Mail:fvifnmmmm@126.com
1.面试经过
大约在年前我接到了TW瑞晟(Realtek)苏州公司的面试通知,通知我2月21日到苏州工业园区面试,接到面试后的几天我把一些专业课温习了一遍,特别是C++和数据结构,由于大学几年里,我一直专研这些方面,加上通过了高级程序员的考试,对于一些常用的算法我差不多也达到了烂熟于胸的地步,当时的感觉是如果问了我这些方面的问题我应该是没有问题的!
21日那天我被安排在4:30面试,由一位技术人员单独给我面试,在问了一些简单的问题之后他给我出了一道编程题目,题目是这样的:
(由于具体面试的题目比较烦琐,我将其核心思想提取出来分解成了两个独立的简单的问题,有可能问题分解的不当,请大家见谅,实际面试了一个的问题但比其复杂很多,而且涉及一些高等数学变换)
1) 写一个函数计算当参数为n(n很大)时的值 1-2+3-4+5-6+7......+n
哼,我的心里冷笑一声!没想到这么简单,我有点紧张的心情顿时放松起来!
于是很快我给出我的解法:
long fn(long n)
{
long temp=0;
int i,flag=1;
if(n<=0)
{
printf("error: n must > 0);
exit(1);
}
for(i=1;i<=n;i++)
{
temp=temp+flag*i;
flag=(-1)*flag;
}
return temp;
}
搞定!当我用期待的目光看着面试官的时候,他微笑着跟我说,执行结果肯定是没有问题!但当n很大的时候我这个程序执行效率很低, 在嵌入式系统的开发中,程序的运行效率很重要,能让CPU少执行一条指令都是好的,他让我看看这个程序还有什么可以修改的地方,把程序优化一下!听了这些话,我的心情当时变的有点沉重,没想到他的要求很严格,之后我对程序进行了严格的分析,给出了改进了的方案!
long fn(long n)
{
long temp=0;
int j=1,i=1,flag=1;
if(n<=0)
{
printf("error: n must > 0);
exit(1);
}
while(j<=n)
{
temp=temp+i;
i=-i;
i>0?i++:i--;
j++;
}
return temp;
}
虽然我不敢保证我这个算法是最优的,但是比起上一个程序,我将所有涉及到乘法指令的语句改为执行加法指令,既达到要题目的要求而且运算时间上缩短了很多!而代价仅仅是增加了一个整型变量!但是我现在的信心已经受了一点打击,我将信将疑的看者面试官,他还是微笑着跟我说:“不错,这个程序确实在效率上有的很大的提高!”我心里一阵暗喜!但他接着说这个程序仍然不能达到他的要求,要我给出更优的方案!天啊!还有优化!我当时真的有点崩溃了,想了一会后,我请求他给出他的方案!然后他很爽快的给出了他的程序!
long fn(long n)
{
if(n<=0)
{
printf("error: n must > 0);
exit(1);
}
if(0==n%2)
return (n/2)*(-1);
else
return (n/2)*(-1)+n;
}
搞笑,当时我目瞪口呆,没想到他是这个意思,这么简单的代码我真的不会写吗,但是我为什么没有往那方面上想呢!他说的没有错,在n很大很大的时候这三个程序运行时间的差别简直是天壤之别!当我刚想开口说点什么的时候,他却先开口了:“不要认为CPU运算速度快就把所有的问题都推给它去做,程序员应该将代码优化再优化,我们自己能做的决不要让CPU做,因为CPU是为用户服务的,不是为我们程序员服务的!”多么精辟的语言,我已经不想再说什么了!接着是第二个问题:
2),他要求我用一种技巧性的编程方法来用一个函数实现两个函数的功能n为如:
fn1(n)=n/2!+n/3!+n/4!+n/5!+n/6!
fn2(n)=n/5!+n/6!+n/7!+n/8!+n/9!
现在用一个函数fn(int n,int flag)实现,当flag为0时,实现fn1功能,如果flag为1时实现fn2功能!
他的要求还是效率,效率,效率!说实在话,如果我心情好的话我应该能给出一种比较好的算法,但我那时真的没有什么心思再想了,我在纸上胡乱画了一些诸如6!=6*5!的公式后直截了当的跟他说要他给出他的答案!面试官也没有说什么,给出了他的思路:
定义一个二维数组 float t[2][5]存入[2!,3!,4!,5!,6!},{5!,6!,7!,8!,9!]然后给出一个循环:
for(i=0;i<6;i++)
{
temp=temp+n/t[flag];
}
最后得到计算值!呵呵,典型的空间换时间的算法!
这些总共花了50分钟的时间,还有十分钟我就跟他很随意的聊聊天,聊了一些编程以及生活的问题,那时的我已经很放松了,因为我知道这次面试结果只有一个:失败。5:30的时候面试官要我等通知,于是我离开了他们公司。这就是面试的整个经过!
.由面试想到的
真的是很失败啊!我记得那天下好大的雨,气温也很低,我边走边想,从5:30一直走到7:30,全身都湿透了,又冷又饿,但是我只是一直走,脑子里面充满了疑惑,我也想让雨把自己淋醒!看到这里有些朋友可能觉得那些面试题目不算什么如果让自己做的话肯定能全部答对,我肯定相信你,因为我从未怀疑过中国程序员的能力,我认为中国有世界上最好的程序员,我也从未认为自己是高手,所以我做不出来不代表中国程序员比TW或者别的地方的程序员差,所以我就从我的角度,我的所见所想来谈一些感想:
不错全世界都有优秀的程序员,中国也不例外,但是我疑惑的是:到底中国和TW或者国外的优秀的程序员的比例到底是多少?TW我不知道,中国100个程序员里有几个是优秀的呢?我根本算不上,从上面的表现就足以说明一切了!是1个?5个?10个?50个?这个数字我不敢乱猜,恐遭网友一顿痛骂,那么我们国内有多少人学习计算机呢?拿我们学校来说,计算机97级4个班,98级5个班,99级10个班,2000级17个班,人多了,老师怎么办?我们学校的做法是让研究生上课,然后呢?补考一抓一大把,大把大把的补考费落入了学校的口袋,还说现在的学生素质低!真是好笑,我都不知道学校这么做是为了什么,为国内培养大量的程序员吗?学生们能真正学到计算机知识吗?好了,我敢讲,在我们学校学习编程学生和优秀程序员(注意我指的是优秀,只会编几个糟烂程序的人算不上)的比例应该是100:0.1 。在这种比例下虽然我们中国学习编程的人铺天盖地,但是想想有多少个人能真正为中国软件业发展作出贡献,有多少人能真正写出优秀的程序名扬海外!
我从学习编程以来,不管是自学还是老师指导,从来都是解决问题就好,编出程序来就行,我的疑惑是:我们有真正的强调过程序的效率,程序的质量吗?我们有仔细分析过我们写的东西,看看有没有可以改进的地方,看看有没有简单的方法来达到同样的目的呢?我问心自问,我发现,我从来没有对我写出来的程序进行过优化,最多就是进行详细的测试,然后Debug,但是这就足够了吗?这些天我偶尔发现我曾经写过的一个游戏,那是一年前我刚加入www.vcroad.net做为其中一员时候,感觉应该拿点东西出来,然后花了一个星期的时间写出来的!程序不算复杂,但是用到了不少数据结构的东西,也用到了一些精彩的算法,加上windows的界面和游戏的可玩性,写完后受到了不少好评,我当时真的很佩服自己!但是现在看呢:没有一句注释,好多丑陋的函数名比如:void chushihua(),好多没有必要的变量,可以用简单语句完成工作的我使用华丽的算法,大量使用全局变量.....,说不好听的话,六百多行的程序除了能运行之外就是一陀屎!如果一年前我能听到一些反面意见的话,大概我能早一点觉悟,但是自从原代码在网站发布以来听到的都是赞美之词,没有一个人向我提出程序改进的意见,这又说明了一个什么问题呢?很值得思考啊!
还有一个疑惑是:我们说的和做的真的一样吗?我在学校的时候曾经受学院指派承办过一个计算机大赛,请了一个老师出决赛的题目,主要是一些算法题目,这个老师可能是我上大学以来唯一敬佩的老师了,从程序调试到打分,对于每个程序都仔细分析其时间效率和空间效率,然后综合打分,四十个人的卷子,老师从下午三点一直调试到晚上十点,在有些写的精彩的语句后还加上批注。我真是高兴很遇到这样的老师并且和他做深入的交流,但在事后,却发生了一件不愉快的事,在比赛中获得第二名的学生找到我,说他程序全部调试成功应该给他满分,并且应该得第一,我说不过他,最后调出了他的原程序和第一名的原程序对比,不错,两个程序都运行的很好,这时,那个同学开口了:“我的程序写的十分简捷明了,仅仅数行就完成了题目要求,而他的却写了一大堆,为什么给他的分多过给我的分。”我当时很是气愤,如果不是老师负责的话,那么现在第一名和第二名的位置真的要互调了,拜托,不是程序的行数越少程序的质量就越高,我记得我跟他大谈这方面的道理,最后说服他了!哈哈,但是我,只能说说而已,我不知道还有多少人一样,说起来头头是道,但心里却压根就从未重视过它!
.我打算做的!
其实那天我想到的远不止上面那么多,但是我不想再说了,因为我猜想看这篇文章的网友大概都有一肚子的感想,一肚子的抱怨,借用这篇文章发泄可不是我想达到的目的,在上面我把自己骂的一文不值也不是妄自菲薄,但是在某些方面我真的做错了,或者说是偏离了正确方向,现在是矫正方向和重整旗鼓的时候了,就象我前面说过的,我相信中国有世界上最好的程序员,我也相信我的水平不会一直保持现状,我现在就收拾起牢骚真正的实干起来!
真的很巧,就写到这里的时候我在网上偶尔发现了这篇手册,我不知道这预示着什么,但是我想如果我照下面这个基本原则一直踏实做下去,我一定会实现我的理想---一名优秀的软件设计师!
(下面这些文字不是我的原创,是我偶尔在网上发现的,我真的很幸运能看到这些,这篇文章也随着下面的文字而结束,我真心的希望您能从这篇文章中得到启发,这篇文章欢迎大家随意转载,您可以不写作者是谁,但是请您写上www.vcroad.net原创,谢谢您的支持)
作者:金蝶中间件公司CTO袁红岗
不知不觉做软件已经做了十年,有成功的喜悦,也有失败的痛苦,但总不敢称自己是高手,因为和我心目中真正的高手们比起来,还差的太远。世界上并没有成为高手的捷径,但一些基本原则是可以遵循的。
1. 扎实的基础。数据结构、离散数学、编译原理,这些是所有计算机科学的基础,如果不掌握他们,很难写出高水平的程序。据我的观察,学计算机专业的人比学其他专业的人更能写出高质量的软件。程序人人都会写,但当你发现写到一定程度很难再提高的时候,就应该想想是不是要回过头来学学这些最基本的理论。不要一开始就去学OOP,即使你再精通OOP,遇到一些基本算法的时候可能也会束手无策。
2. 丰富的想象力。不要拘泥于固定的思维方式,遇到问题的时候要多想几种解决问题的方案,试试别人从没想过的方法。丰富的想象力是建立在丰富的知识的基础上,除计算机以外,多涉猎其他的学科,比如天文、物理、数学等等。另外,多看科幻电影也是一个很好的途径。
3. 最简单的是最好的。这也许是所有科学都遵循的一条准则,如此复杂的质能互换原理在爱因斯坦眼里不过是一个简单得不能再简单的公式:E=mc2。简单的方法更容易被人理解,更容易实现,也更容易维护。遇到问题时要优先考虑最简单的方案,只有简单方案不能满足要求时再考虑复杂的方案。
4. 不钻牛角尖。当你遇到障碍的时候,不妨暂时远离电脑,看看窗外的风景,听听轻音乐,和朋友聊聊天。当我遇到难题的时候会去玩游戏,而且是那种极暴力的打斗类游戏,当负责游戏的那部分大脑细胞极度亢奋的时候,负责编程的那部分大脑细胞就得到了充分的休息。当重新开始工作的时候,我会发现那些难题现在竟然可以迎刃而解。
5. 对答案的渴求。人类自然科学的发展史就是一个渴求得到答案的过程,即使只能知道答案的一小部分也值得我们去付出。只要你坚定信念,一定要找到问题的答案,你才会付出精力去探索,即使最后没有得到答案,在过程中你也会学到很多东西。
6. 多与别人交流。三人行必有我师,也许在一次和别人不经意的谈话中,就可以迸出灵感的火花。多上上网,看看别人对同一问题的看法,会给你很大的启发。
7. 良好的编程风格。注意养成良好的习惯,代码的缩进编排,变量的命名规则要始终保持一致。大家都知道如何排除代码中错误,却往往忽视了对注释的排错。注释是程序的一个重要组成部分,它可以使你的代码更容易理解,而如果代码已经清楚地表达了你的思想,就不必再加注释了,如果注释和代码不一致,那就更加糟糕。
8. 韧性和毅力。这也许是"高手"和一般程序员最大的区别。A good programming is 99 weat and 1?ffee。高手们并不是天才,他们是在无数个日日夜夜中磨练出来的。成功能给我们带来无比的喜悦,但过程却是无比的枯燥乏味。你不妨做个测试,找个10000以内的素数表,把它们全都抄下来,然后再检查三遍,如果能够不间断地完成这一工作,你就可以满足这一条。
posted @
2008-09-08 13:32 竹子 阅读(180) |
评论 (0) |
编辑 收藏
一、引言
Java虚拟机(JVM)的类装载就是指将包含在类文件中的字节码装载到JVM中, 并使其成为JVM一部分的过程。JVM的类动态装载技术能够在运行时刻动态地加载或者替换系统的某些功能模块, 而不影响系统其他功能模块的正常运行。本文将分析JVM中的类装载系统,探讨JVM中类装载的原理、实现以及应用。
二、Java虚拟机的类装载实现与应用
2.1 装载过程简介
所谓装载就是寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的class对象的过程,其中类或接口的名称是给定了的。当然名称也可以通过计算得到,但是更常见的是通过搜索源代码经过编译器编译后所得到的二进制形式来构造。
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
装载:查找和导入类或接口的二进制数据;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
至于在类装载和虚拟机启动的过程中的具体细节和可能会抛出的错误,请参看《Java虚拟机规范》以及《深入Java虚拟机》,它们在网络上面的资源地址是:
http://java.sun.com/docs/books/vmspec/2nd-edition/html/Preface.doc.html
http://www.artima.com/insidejvm/ed2/index.html
由于本文的讨论重点不在此就不再多叙述。
2.2 装载的实现
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
在Java中,ClassLoader是一个抽象类,它在包java.lang中,可以这样说,只要了解了在ClassLoader中的一些重要的方法,再结合上面所介绍的JVM中类装载的具体的过程,对动态装载类这项技术就有了一个比较大概的掌握,这些重要的方法包括以下几个:
①loadCass方法 loadClass(String name ,boolean resolve)其中name参数指定了JVM需要的类的名称,该名称以包表示法表示,如Java.lang.Object;resolve参数告诉方法是否需要解析类,在初始化类之前,应考虑类解析,并不是所有的类都需要解析,如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要解析。这个方法是ClassLoader 的入口点。
②defineClass方法 这个方法接受类文件的字节数组并把它转换成Class对象。字节数组可以是从本地文件系统或网络装入的数据。它把字节码分析成运行时数据结构、校验有效性等等。
③findSystemClass方法 findSystemClass方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用defineClass将字节数组转换成Class对象,以将该文件转换成类。当运行Java应用程序时,这是JVM 正常装入类的缺省机制。
④resolveClass方法 resolveClass(Class c)方法解析装入的类,如果该类已经被解析过那么将不做处理。当调用loadClass方法时,通过它的resolve 参数决定是否要进行解析。
⑤findLoadedClass方法 当调用loadClass方法装入类时,调用findLoadedClass 方法来查看ClassLoader是否已装入这个类,如果已装入,那么返回Class对象,否则返回NULL。如果强行装载已存在的类,将会抛出链接错误。
2.3 装载的应用
一般来说,我们使用虚拟机的类装载时需要继承抽象类java.lang.ClassLoader,其中必须实现的方法是loadClass(),对于这个方法需要实现如下操作:(1) 确认类的名称;(2) 检查请求要装载的类是否已经被装载;(3) 检查请求加载的类是否是系统类;(4) 尝试从类装载器的存储区获取所请求的类;(5) 在虚拟机中定义所请求的类;(6) 解析所请求的类;(7) 返回所请求的类。
所有的Java 虚拟机都包括一个内置的类装载器,这个内置的类库装载器被称为根装载器(bootstrap ClassLoader)。根装载器的特殊之处是它只能够装载在设计时刻已知的类,因此虚拟机假定由根装载器所装载的类都是安全的、可信任的,可以不经过安全认证而直接运行。当应用程序需要加载并不是设计时就知道的类时,必须使用用户自定义的装载器(user-defined ClassLoader)。下面我们举例说明它的应用。
public abstract class MultiClassLoader extends ClassLoader{
...
public synchronized Class loadClass(String s, boolean flag)
throws ClassNotFoundException
{
/* 检查类s是否已经在本地内存*/
Class class1 = (Class)classes.get(s);
/* 类s已经在本地内存*/
if(class1 != null) return class1;
try/*用默认的ClassLoader 装入类*/ {
class1 = super.findSystemClass(s);
return class1;
}
catch(ClassNotFoundException _ex) {
System.out.println(">> Not a system class.");
}
/* 取得类s的字节数组*/
byte abyte0[] = loadClassBytes(s);
if(abyte0 == null) throw new ClassNotFoundException();
/* 将类字节数组转换为类*/
class1 = defineClass(null, abyte0, 0, abyte0.length);
if(class1 == null) throw new ClassFormatError();
if(flag) resolveClass(class1); /*解析类*/
/* 将新加载的类放入本地内存*/
classes.put(s, class1);
System.out.println(">> Returning newly loaded class.");
/* 返回已装载、解析的类*/
return class1;
}
...
}
三、Java虚拟机的类装载原理
前面我们已经知道,一个Java应用程序使用两种类型的类装载器:根装载器(bootstrap)和用户定义的装载器(user-defined)。根装载器是Java虚拟机实现的一部分,举个例子来说,如果一个Java虚拟机是在现在已经存在并且正在被使用的操作系统的顶部用C程序来实现的,那么根装载器将是那些C程序的一部分。根装载器以某种默认的方式将类装入,包括那些Java API的类。在运行期间一个Java程序能安装用户自己定义的类装载器。根装载器是虚拟机固有的一部分,而用户定义的类装载器则不是,它是用Java语言写的,被编译成class文件之后然后再被装入到虚拟机,并像其它的任何对象一样可以被实例化。 Java类装载器的体系结构如下所示:
Java的类装载模型是一种代理(delegation)模型。当JVM 要求类装载器CL(ClassLoader)装载一个类时,CL首先将这个类装载请求转发给他的父装载器。只有当父装载器没有装载并无法装载这个类时,CL才获得装载这个类的机会。这样, 所有类装载器的代理关系构成了一种树状的关系。树的根是类的根装载器(bootstrap ClassLoader) , 在JVM 中它以"null"表示。除根装载器以外的类装载器有且仅有一个父装载器。在创建一个装载器时, 如果没有显式地给出父装载器, 那么JVM将默认系统装载器为其父装载器。Java的基本类装载器代理结构如图2所示:
下面针对各种类装载器分别进行详细的说明。
根(Bootstrap) 装载器:该装载器没有父装载器,它是JVM实现的一部分,从sun.boot.class.path装载运行时库的核心代码。
扩展(Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯Java代码实现的,它从java.ext.dirs (扩展目录)中装载代码。
系统(System or Application) 装载器:装载器为扩展装载器,我们都知道在安装JDK的时候要设置环境变量(CLASSPATH ),这个类装载器就是从java.class.path(CLASSPATH 环境变量)中装载代码的,它也是用纯Java代码实现的,同时还是用户自定义类装载器的缺省父装载器。
小应用程序(Applet) 装载器: 装载器为系统装载器,它从用户指定的网络上的特定目录装载小应用程序代码。
在设计一个类装载器的时候,应该满足以下两个条件:
对于相同的类名,类装载器所返回的对象应该是同一个类对象
如果类装载器CL1将装载类C的请求转给类装载器CL2,那么对于以下的类或接口,CL1和CL2应该返回同一个类对象:a)S为C的直接超类;b)S为C的直接超接口;c)S为C的成员变量的类型;d)S为C的成员方法或构建器的参数类型;e)S为C的成员方法的返回类型。
每个已经装载到JVM中的类都隐式含有装载它的类装载器的信息。类方法getClassLoader 可以得到装载这个类的类装载器。一个类装载器认识的类包括它的父装载器认识的类和它自己装载的类,可见类装载器认识的类是它自己装载的类的超集。注意我们可以得到类装载器的有关的信息,但是已经装载到JVM中的类是不能更改它的类装载器的。
Java中的类的装载过程也就是代理装载的过程。比如:Web浏览器中的JVM需要装载一个小应用程序TestApplet。JVM调用小应用程序装载器ACL(Applet ClassLoader)来完成装载。ACL首先请求它的父装载器, 即系统装载器装载TestApplet是否装载了这个类, 由于TestApplet不在系统装载器的装载路径中, 所以系统装载器没有找到这个类, 也就没有装载成功。接着ACL自己装载TestApplet。ACL通过网络成功地找到了TestApplet.class 文件并将它导入到了JVM中。在装载过程中, JVM发现TestAppet是从超类java.applet.Applet继承的。所以JVM再次调用ACL来装载java.applet.Applet类。ACL又再次按上面的顺序装载Applet类, 结果ACL发现他的父装载器已经装载了这个类, 所以ACL就直接将这个已经装载的类返回给了JVM , 完成了Applet类的装载。接下来,Applet类的超类也一样处理。最后, TestApplet及所有有关的类都装载到了JVM中。
四、结论
类的动态装载机制是JVM的一项核心技术, 也是容易被忽视而引起很多误解的地方。本文介绍了JVM中类装载的原理、实现以及应用,尤其分析了ClassLoader的结构、用途以及如何利用自定义的ClassLoader装载并执行Java类,希望能使读者对JVM中的类装载有一个比较深入的理解
posted @
2008-08-21 20:45 竹子 阅读(167) |
评论 (0) |
编辑 收藏
-------------------
Tomcat 5中文问题
author:kiss__sky@163.com
-------------------
问题描述:
1 表单提交的数据,用request.getParameter(“xxx”)返回的字符串为乱码或者??
2 直接通过url如http://localhost/a.jsp?name=中国,这样的get请求在服务端用request. getParameter(“name”)时返回的是乱码;按tomcat4的做法设置Filter也没有用或者用request.setCharacterEncoding("GBK");也不管用
原因:
1 tomcat的j2ee实现对表单提交即post方式提示时处理参数采用缺省的iso-8859-1来处理
2 tomcat对get方式提交的请求对query-string 处理时采用了和post方法不一样的处理方式。(与tomcat4不一样,所以设置setCharacterEncoding(“gbk”))不起作用。
解决办法:
首先所有的jsp文件都加上:
1 实现一个Filter.设置处理字符集为GBK。(在tomcat的webapps/servlet-examples目录有一个完整的例子。请参考web.xml和SetCharacterEncodingFilter的配置。)
1)只要把%TOMCAT安装目录%/ webapps\servlets-examples\WEB-INF\classes\filters\SetCharacterEncodingFilter.class文件拷到你的webapp目录/filters下,如果没有filters目录,就创建一个。
2)在你的web.xml里加入如下几行:
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3)完成.
2 get方式的解决办法
1) 打开tomcat的server.xml文件,找到区块,加入如下一行:
URIEncoding=”GBK”
完整的应如下:
<Connector
port="80" maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
debug="0" connectionTimeout="20000"
disableUploadTimeout="true"
URIEncoding="GBK"
/>
2)重启tomcat,一切OK。
执行如下jsp页页测试是否成功
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="java.util.*"%>
<%
String q=request.getParameter("q");
q = q == null? "没有值" : q;
%>
<HTML>
<HEAD><TITLE>新闻列表显示</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb2312">
<META http-equiv=pragma content=no-cache>
<body>
你提交了:
<%=q%>
<br>
<form action="tcnchar.jsp" method="post">
输入中文:<input type="text" name="q"><input type="submit" value="确定">
<br>
<a href="tcnchar.jsp?q=中国">通过get方式提交</a>
</form>
</BODY></HTML>
测试结果如果你输入文本框或者点超链都会显示:你提交了”中国”,说明成功!!!!!
特别感谢下面这篇帖子,帮我解决了中文问题.最后祝大家好运!!!
参考网址:
http://www.javaworld.com.tw/jute/post/view?bid=9&id=44042&sty=1&tpg=1&age=0
posted @
2008-08-16 18:29 竹子 阅读(145) |
评论 (0) |
编辑 收藏
step into就是单步执行,遇到子函数就进入并且继续单步执行;
step over是在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子函数整个执行完再停止,也就是把子函数整个作为一步。有一点,经过我们简单的调试,在不存在子函数的情况下是和step into效果一样的。
step out就是但单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函
数。
step into:进入子函数
step over:越过子函数,但子函数会执行
step out:跳出子函数
posted @
2008-08-13 18:36 竹子 阅读(10966) |
评论 (0) |
编辑 收藏
如何计算某一天是星期几?
—— 蔡勒(Zeller)公式
历史上的某一天是星期几?未来的某一天是星期几?关于这个问题,有很多计算公式(两个通用计算公式和一些分段计算公式),其中最著名的是蔡勒(Zeller)公式。即w=y+[y/4]+[c/4]-2c+[26(m+1)/10]+d-1
公式中的符号含义如下,w:星期;c:世纪-1;y:年(两位数);m:月(m大于等于3,小于等于14,即在蔡勒公式中,某年的1、2月要看作上一年的13、14月来计算,比如2003年1月1日要看作2002年的13月1日来计算);d:日;[ ]代表取整,即只要整数部分。(C是世纪数减一,y是年份后两位,M是月份,d是日数。1月和2月要按上一年的13月和 14月来算,这时C和y均按上一年取值。)
算出来的W除以7,余数是几就是星期几。如果余数是0,则为星期日。
以2049年10月1日(100周年国庆)为例,用蔡勒(Zeller)公式进行计算,过程如下:
蔡勒(Zeller)公式:w=y+[y/4]+[c/4]-2c+[26(m+1)/10]+d-1
=49+[49/4]+[20/4]-2×20+[26× (10+1)/10]+1-1
=49+[12.25]+5-40+[28.6]
=49+12+5-40+28
=54 (除以7余5)
即2049年10月1日(100周年国庆)是星期5。
你的生日(出生时、今年、明年)是星期几?不妨试一试。
不过,以上公式只适合于1582年10月15日之后的情形(当时的罗马教皇将恺撒大帝制订的儒略历修改成格里历,即今天使用的公历)。
过程的推导:(对推理不感兴趣的可略过不看)
星期制度是一种有古老传统的制度。据说因为《圣经·创世纪》中规定上帝用了六
天时间创世纪,第七天休息,所以人们也就以七天为一个周期来安排自己的工作和生
活,而星期日是休息日。从实际的角度来讲,以七天为一个周期,长短也比较合适。所
以尽管中国的传统工作周期是十天(比如王勃《滕王阁序》中说的“十旬休暇”,即是
指官员的工作每十日为一个周期,第十日休假),但后来也采取了西方的星期制度。
在日常生活中,我们常常遇到要知道某一天是星期几的问题。有时候,我们还想知
道历史上某一天是星期几。通常,解决这个方法的有效办法是看日历,但是我们总不会
随时随身带着日历,更不可能随时随身带着几千年的万年历。假如是想在计算机编程中
计算某一天是星期几,预先把一本万年历存进去就更不现实了。这时候是不是有办法通
过什么公式,从年月日推出这一天是星期几呢?
答案是肯定的。其实我们也常常在这样做。我们先举一个简单的例子。比如,知道
了2004年5月1日是星期六,那么2004年5月31日“世界无烟日”是星期几就不难推算出
来。我们可以掰着指头从1日数到31日,同时数星期,最后可以数出5月31日是星期一。
其实运用数学计算,可以不用掰指头。我们知道星期是七天一轮回的,所以5月1日是星
期六,七天之后的5月8日也是星期六。在日期上,8-1=7,正是7的倍数。同样,5月15
日、5月22日和5月29日也是星期六,它们的日期和5月1日的差值分别是14、21和28,也
都是7的倍数。那么5月31日呢?31-1=30,虽然不是7的倍数,但是31除以7,余数为2,
这就是说,5月31日的星期,是在5月1日的星期之后两天。星期六之后两天正是星期一。
这个简单的计算告诉我们计算星期的一个基本思路:首先,先要知道在想算的日子
之前的一个确定的日子是星期几,拿这一天做为推算的标准,也就是相当于一个计算的
“原点”。其次,知道想算的日子和这个确定的日子之间相差多少天,用7除这个日期
的差值,余数就表示想算的日子的星期在确定的日子的星期之后多少天。如果余数是
0,就表示这两天的星期相同。显然,如果把这个作为“原点”的日子选为星期日,那
么余数正好就等于星期几,这样计算就更方便了。
但是直接计算两天之间的天数,还是不免繁琐。比如1982年7月29日和2004年5月
1日之间相隔7947天,就不是一下子能算出来的。它包括三段时间:一,1982年7月29
日以后这一年的剩余天数;二,1983-2003这二十一个整年的全部天数;三,从2004年
元旦到5月1日经过的天数。第二段比较好算,它等于21*365+5=7670天,之所以要加
5,是因为这段时间内有5个闰年。第一段和第三段就比较麻烦了,比如第三段,需要把
5月之前的四个月的天数累加起来,再加上日期值,即31+29+31+30+1=122天。同理,第
一段需要把7月之后的五个月的天数累加起来,再加上7月剩下的天数,一共是155天。
所以总共的相隔天数是122+7670+155=7947天。
仔细想想,如果把“原点”日子的日期选为12月31日,那么第一段时间也就是一个
整年,这样一来,第一段时间和第二段时间就可以合并计算,整年的总数正好相当于两
个日子的年份差值减一。如果进一步把“原点”日子选为公元前1年12月31日(或者天文
学家所使用的公元0年12月31日),这个整年的总数就正好是想算的日子的年份减一。这
样简化之后,就只须计算两段时间:一,这么多整年的总天数;二,想算的日子是这一
年的第几天。巧的是,按照公历的年月设置,这样反推回去,公元前1年12月31日正好是
星期日,也就是说,这样算出来的总天数除以7的余数正好是星期几。那么现在的问题就
只有一个:这么多整年里面有多少闰年。这就需要了解公历的置闰规则了。
我们知道,公历的平年是365天,闰年是366天。置闰的方法是能被4整除的年份在
2月加一天,但能被100整除的不闰,能被400整除的又闰。因此,像1600、2000、2400
年都是闰年,而1700、1800、1900、2100年都是平年。公元前1年,按公历也是闰年。
因此,对于从公元前1年(或公元0年)12月31日到某一日子的年份Y之间的所有整年
中的闰年数,就等于
[(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400],
[...]表示只取整数部分。第一项表示需要加上被4整除的年份数,第二项表示需要去掉
被100整除的年份数,第三项表示需要再加上被400整除的年份数。之所以Y要减一,这
样,我们就得到了第一个计算某一天是星期几的公式:
W = (Y-1)*365 + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + D. (1)
其中D是这个日子在这一年中的累积天数。算出来的W就是公元前1年(或公元0年)12月
31日到这一天之间的间隔日数。把W用7除,余数是几,这一天就是星期几。比如我们来
算2004年5月1日:
W = (2004-1)*365 + [(2004-1)/4] - [(2004-1)/100] + [(2004-1)/400] +
(31+29+31+30+1)
= 731702,
731702 / 7 = 104528……6,余数为六,说明这一天是星期六。这和事实是符合的。
上面的公式(1)虽然很准确,但是计算出来的数字太大了,使用起来很不方便。仔
细想想,其实这个间隔天数W的用数仅仅是为了得到它除以7之后的余数。这启发我们是
不是可以简化这个W值,只要找一个和它余数相同的较小的数来代替,用数论上的术语
来说,就是找一个和它同余的较小的正整数,照样可以计算出准确的星期数。
显然,W这么大的原因是因为公式中的第一项(Y-1)*365太大了。其实,
(Y-1)*365 = (Y-1) * (364+1)
= (Y-1) * (7*52+1)
= 52 * (Y-1) * 7 + (Y-1),
这个结果的第一项是一个7的倍数,除以7余数为0,因此(Y-1)*365除以7的余数其实就
等于Y-1除以7的余数。这个关系可以表示为:
(Y-1)*365 ≡ Y-1 (mod 7).
其中,≡是数论中表示同余的符号,mod 7的意思是指在用7作模数(也就是除数)的情
况下≡号两边的数是同余的。因此,完全可以用(Y-1)代替(Y-1)*365,这样我们就得到
了那个著名的、也是最常见到的计算星期几的公式:
W = (Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + D. (2)
这个公式虽然好用多了,但还不是最好用的公式,因为累积天数D的计算也比较麻
烦。是不是可以用月份数和日期直接计算呢?答案也是肯定的。我们不妨来观察一下各
个月的日数,列表如下:
月 份:1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
--------------------------------------------------------------------------
天 数: 31 28(29) 31 30 31 30 31 31 30 31 30 31
如果把这个天数都减去28(=4*7),不影响W除以7的余数值。这样我们就得到另一张
表:
月 份:1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
------------------------------------------------------------------------
剩余天数: 3 0(1) 3 2 3 2 3 3 2 3 2 3
平年累积: 3 3 6 8 11 13 16 19 21 24 26 29
闰年累积: 3 4 7 9 12 14 17 20 22 25 27 30
仔细观察的话,我们会发现除去1月和2月,3月到7月这五个月的剩余天数值是3,2,3,2,
3;8月到12月这五个月的天数值也是3,2,3,2,3,正好是一个重复。相应的累积天数中,
后一月的累积天数和前一月的累积天数之差减去28就是这个重复。正是因为这种规律的
存在,平年和闰年的累积天数可以用数学公式很方便地表达:
╭ d; (当M=1)
D = { 31 + d; (当M=2) (3)
╰ [ 13 * (M+1) / 5 ] - 7 + (M-1) * 28 + d + i. (当M≥3)
其中[...]仍表示只取整数部分;M和d分别是想算的日子的月份和日数;平年i=0,闰年
i=1。对于M≥3的表达式需要说明一下:[13*(M+1)/5]-7算出来的就是上面第二个表中的
平年累积值,再加上(M-1)*28就是想算的日子的月份之前的所有月份的总天数。这是一
个很巧妙的办法,利用取整运算来实现3,2,3,2,3的循环。比如,对2004年5月1日,有:
D = [ 13 * (5+1) / 5 ] - 7 + (5-1) * 28 + 1 + 1
= 122,
这正是5月1日在2004年的累积天数。
假如,我们再变通一下,把1月和2月当成是上一年的“13月”和“14月”,不仅仍
然符合这个公式,而且因为这样一来,闰日成了上一“年”(一共有14个月)的最后一
天,成了d的一部分,于是平闰年的影响也去掉了,公式就简化成:
D = [ 13 * (M+1) / 5 ] - 7 + (M-1) * 28 + d. (3≤M≤14) (4)
上面计算星期几的公式,也就可以进一步简化成:
W = (Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + [ 13 * (M+1) / 5 ] - 7
+ (M-1) * 28 + d.
因为其中的-7和(M-1)*28两项都可以被7整除,所以去掉这两项,W除以7的余数不变,
公式变成:
W = (Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] + [ 13 * (M+1) / 5 ] + d.
(5)
当然,要注意1月和2月已经被当成了上一年的13月和14月,因此在计算1月和2月的日子
的星期时,除了M要按13或14算,年份Y也要减一。比如,2004年1月1日是星期四,用这
个公式来算,有:
W = (2003-1) + [(2003-1)/4] - [(2003-1)/100] + [(2003-1)/400] + [13*(13+1)/5]
+ 1
= 2002 + 500 - 20 + 5 + 36 + 1
= 2524;
2524 / 7 = 360……4.这和实际是一致的。
公式(5)已经是从年、月、日来算星期几的公式了,但它还不是最简练的,对于年
份的处理还有改进的方法。我们先来用这个公式算出每个世纪第一年3月1日的星期,列
表如下:
年份: 1(401,801,…,2001) 101(501,901,…,2101)
--------------------------------------------------------------------
星期: 4 2
====================================================================
年份:201(601,1001,…,2201) 301(701,1101,…,2301)
--------------------------------------------------------------------
星期: 0 5
可以看出,每隔四个世纪,这个星期就重复一次。假如我们把301(701,1101,…,2301)
年3月1日的星期数看成是-2(按数论中对余数的定义,-2和5除以7的余数相同,所以可
以做这样的变换),那么这个重复序列正好就是一个4,2,0,-2的等差数列。据此,我们
可以得到下面的计算每个世纪第一年3月1日的星期的公式:
W = (4 - C mod 4) * 2 - 4. (6)
式中,C是该世纪的世纪数减一,mod表示取模运算,即求余数。比如,对于2001年3月
1日,C=20,则:
W = (4 - 20 mod 4) * 2 - 4
= 8 - 4
= 4.
把公式(6)代入公式(5),经过变换,可得:
(Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400] ≡ (4 - C mod 4) * 2 - 1
(mod 7). (7)
因此,公式(5)中的(Y-1) + [(Y-1)/4] - [(Y-1)/100] + [(Y-1)/400]这四项,在计算
每个世纪第一年的日期的星期时,可以用(4 - C mod 4) * 2 - 1来代替。这个公式写
出来就是:
W = (4 - C mod 4) * 2 - 1 + [13 * (M+1) / 5] + d. (8)
有了计算每个世纪第一年的日期星期的公式,计算这个世纪其他各年的日期星期的公式
就很容易得到了。因为在一个世纪里,末尾为00的年份是最后一年,因此就用不着再考
虑“一百年不闰,四百年又闰”的规则,只须考虑“四年一闰”的规则。仿照由公式(1)
简化为公式(2)的方法,我们很容易就可以从式(8)得到一个比公式(5)更简单的计算任意
一天是星期几的公式:
W = (4 - C mod 4) * 2 - 1 + (y-1) + [y/4] + [13 * (M+1) / 5] + d. (9)
式中,y是年份的后两位数字。
如果再考虑到取模运算不是四则运算,我们还可以把(4 - C mod 4) * 2进一步改写
成只含四则运算的表达式。因为世纪数减一C除以4的商数q和余数r之间有如下关系:
4q + r = C,
其中r即是 C mod 4,因此,有:
r = C - 4q
= C - 4 * [C/4]. (10)
则
(4 - C mod 4) * 2 = (4 - C + 4 * [C/4]) * 2
= 8 - 2C + 8 * [C/4]
≡ [C/4] - 2C + 1 (mod 7). (11)
把式(11)代入(9),得到:
W = [C/4] - 2C + y + [y/4] + [13 * (M+1) / 5] + d - 1. (12)
这个公式由世纪数减一、年份末两位、月份和日数即可算出W,再除以7,得到的余数是
几就表示这一天是星期几,唯一需要变通的是要把1月和2月当成上一年的13月和14月,
C和y都按上一年的年份取值。因此,人们普遍认为这是计算任意一天是星期几的最好的
公式。这个公式最早是由德国数学家克里斯蒂安·蔡勒(Christian Zeller, 1822-
1899)在1886年推导出的,因此通称为蔡勒公式(Zeller’s Formula)。为方便口算,
式中的[13 * (M+1) / 5]也往往写成[26 * (M+1) / 10]。
现在仍然让我们来算2004年5月1日的星期,显然C=20,y=4,M=5,d=1,代入蔡勒
公式,有:
W = [20/4] - 40 + 4 + 1 + [13 * (5+1) / 5] + 1 - 1
= -15.
注意负数不能按习惯的余数的概念求余数,只能按数论中的余数的定义求余。为了方便
计算,我们可以给它加上一个7的整数倍,使它变为一个正数,比如加上70,得到55。
再除以7,余6,说明这一天是星期六。这和实际是一致的,也和公式(2)计算所得的结
果一致。
最后需要说明的是,上面的公式都是基于公历(格里高利历)的置闰规则来考虑
的。对于儒略历,蔡勒也推出了相应的公式是:
W = 5 - C + y + [y/4] + [13 * (M+1) / 5] + d - 1. (13)
这样,我们终于一劳永逸地解决了不查日历计算任何一天是星期几的问题。
posted @
2008-08-13 13:46 竹子 阅读(507) |
评论 (0) |
编辑 收藏
import java.security.MessageDigest;
/**
* MD5加密类
* @author zhang
*/
public class MD5Encoding
{
/**
*
*
*/
private MD5Encoding()
{
}
/**
* 加密算法MD5
*
* @param text 明文
* @return String 密文
*/
public final static String encoding(String text)
{
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
String encodingStr = null;
try
{
byte[] strTemp = text.getBytes();
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(strTemp);
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++)
{
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
encodingStr = new String(str);
}
catch (Exception e)
{
}
return encodingStr;
}
public static void main(String[] areg)
{
MD5Encoding md5 = new MD5Encoding();
md5.encoding("admin");
}
}
posted @
2008-08-12 22:55 竹子 阅读(175) |
评论 (0) |
编辑 收藏
1, Tomcat内存参数配置
如果是Win与Linux系统启动Tomcat服务器,需要在tomcat/bin/catalina.sh与/tomcat/bin/catalina.bat两个文件:在两个文件里面加如:
SET CATALINA_OPTS= -Xms64m -Xmx128m
2, Tomcat连接数配置
配置Tomcat连接数.需要在Server.xml文件里面加如:
maxThreads="150" 表示最多同时处理150个连接
minSpareThreads="25" 表示即使没有人使用也开这么多空线程等待
maxSpareThreads="75" 表示如果最多可以空75个线程,例如某时刻有80人访问,之后没有人访问了,则tomcat不会保留80个空线程,而是关闭5个空的。
acceptCount="100" 当同时连接的人数达到maxThreads时,还可以接收排队的连接,超过这个连接的则直接返回拒绝连接。
connectionTimeout="20000" maxThreads="150"
minSpareThreads="25"
maxSpareThreads="75"
enableLookups="false"
acceptCount="100" debug="0"
disableUploadTimeout="true"
redirectPort="8443" URIEncoding="UTF-8"/>
URIEncoding="UTF-8" 是设定JSP编码格式.
posted @
2008-08-12 22:46 竹子 阅读(1324) |
评论 (0) |
编辑 收藏
我倡议的是奥运会如有幸能到奥运会现场的朋友在巴基斯坦运动员入场时能起立给他们十二分的掌声.全世界只有他们在我们的地震灾害是搬空了自己的战略储备帐篷来支援我们.在全世界要制裁中国时,只有巴基斯坦和古巴投了反对票,而我们用几十万志愿军的生命却只换来了朝鲜的弃权票!在那以后巴基斯坦和古巴也遭了了美国长时间疯狂的制裁!在那一刻中国才知道什么是患难与共的好兄弟!虽然那是很早以前的事情了!但是中国人不能忘记!
许多人都向地震中的汶川伸出援手,其中也有我们的邻邦。“动用国家全部的运输机”、“搬光国家战略储备的帐篷”这种倾囊相助的国家,就是我们坚定的盟友-巴基斯坦。
巴基斯坦在汶川中的感动还不仅仅是这些。当巴基斯坦的首批医疗队赶赴灾区的时候,我方的接待人员想给医疗队员做出妥善的生活安排的时候,他们的领队回道:不用操心医疗队的吃饭、接待问题,他们是去救人的,有菜叶吃就行。
奥运会在北京召开,届时世界各国的领导和世界各国的运动员都会到场.这是中国人像外展示自己的时候!我们要让世界知道中国是懂得感恩的民族.让巴基斯坦的民众知道中国的民众也是把他们看做是自己的兄弟!我们要用这种方式像他们表示最诚挚的谢意!让他们感觉到不管他们会遇到什么样的困难,中国人永远会和他们站在一起!虽然我们和他们的长相和人种不同,但我们的心在一起!也让美国人知道什么才是和谐世界,和谐中国.让中国和巴基斯坦的友谊成为世界的典范!也让巴铁的兄弟们知道在中国遇到灾难时他们能搬空自己的战略储备来支援中国,中国也能在他们遇到灾难时付出的更多!
古人云:"滴水之恩,当涌泉相报"奥运会就是中国报答巴基斯坦的最佳时机。患难见真情。巴基斯坦的朋友在我们需要帮助的时候,给与我们最及时的帮助,这些援助对他们而言就是倾其所有。
同时我们应像社会招集一些口号之类的东西在奥运会开幕式时展示出来.让那些亡我中国之心不死的人,感受到中国人团结的力量.也让那些和中国人一起的人们感受一下中国人的团结让他们更坚定的和中国站在一起.
巴基斯坦,中国欢迎你!北京欢迎你!虽去不了北京在电视屏幕前的我,也会起立,鼓掌!
希望广大网友转帖!
posted @
2008-07-25 13:52 竹子 阅读(100) |
评论 (0) |
编辑 收藏