﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-风花雪月</title><link>http://www.cnitblog.com/conish/</link><description /><language>zh-cn</language><lastBuildDate>Wed, 29 Apr 2026 07:11:54 GMT</lastBuildDate><pubDate>Wed, 29 Apr 2026 07:11:54 GMT</pubDate><ttl>60</ttl><item><title>JAVA 内部类的简单总结 </title><link>http://www.cnitblog.com/conish/archive/2009/11/27/62874.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Fri, 27 Nov 2009 07:52:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/27/62874.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62874.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/27/62874.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62874.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62874.html</trackback:ping><description><![CDATA[定义在一个类内部的类叫内部类，包含内部类的类称为外部类。内部类可以声明public、protected、private等访问限制，可以声明为abstract的供其他内部类或外部类继承与扩展，或者声明为static、final的，也可以实现特定的接口。static的内部类行为上象一个独立的类，非static在行为上类似类的属性或方法且禁止声明static的方法。内部类可以访问外部类的所有方法与属性，但static的内部类只能访问外部类的静态属性与方法。<br><br>外部类按常规的类访问方式使用内部类，唯一的差别是外部类可以访问内部类的所有方法与属性，包括私有方法与属性。如：<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pinner p = new pinner();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;p.index = 20;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;p.Print();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ---- 这种方式适合外部类的非static方法；<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pouter po = new pouter();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pinner pi = po.new pinner();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pi.index = 40;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pi.Print();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ---- 这种方式适合外部类的static方法；<br><br>内部类类似外部类的属性，因此访问内部类对象时总是需要一个创建好的外部类对象。内部类对象通过&#8216;外部类名.this.xxx&#8217;的形式访问外部类的属性与方法。如：<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;System.out.println("Print in inner Outer.index=" + pouter.this.index);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;System.out.println("Print in inner Inner.index=" + this.index);<br><br>如果需要在其他类中访问内部类，可以使用：<br>(1)外部类提供创建内部类的方法供其他类使用。如：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 外部类<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pinner getInner()<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return new pinner();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 其他类<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pouter.pinner pi = po.getInner();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pi.Print();<br><br>(2)直接创建内部类的对象。如：<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pouter po = new pouter();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pouter.pinner pi = po.new pinner();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pi.Print();<br><br>内部类可以声明在外部类的方法中或语句块中。如果内部类需要访问包含它的外部类方法或语句块的局部变量或参数，则该局部变量或参数必须是final的。外部类的其他方法、其他类无法访问声明在方法内部或块内部的内部类。<br><br>如果一个类继承内部类，则创建该类的对象时需提供一个外部类的对象作为构造方法的参数。如：<br>class Car<br>{<br>&nbsp;&nbsp; &nbsp;class Wheel<br>&nbsp;&nbsp; &nbsp;{<br><br>&nbsp;&nbsp; &nbsp;}<br>}<br><br>class SuperWheel extends Car.Wheel<br>{<br>&nbsp;&nbsp; &nbsp;SuperWheel(Car car)<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;car.super();<br>&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;public static void main(String [] args)<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Car car = new Car();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;SuperWheel wl = new SuperWheel(car);<br>&nbsp;&nbsp; &nbsp;} <br>}<br><br>如果创建命名的内部类没有多少实际意义时，可以创建匿名的内部类。比如使用内部类实现接口的功能(如事件处理器、适配器等)，而功能的差异较大，需要根据实际的情况创建相应的内部类时，可以使用匿名内部类。简单的示例如下：<br>interface WebView<br>{<br>&nbsp;&nbsp; &nbsp;void doGet();<br>}<br><br>class A<br>{<br>&nbsp;&nbsp; &nbsp;WebView ShowName()<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return new WebView()<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;void doGet()<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;System.out.println("Name");<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;};<br>&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;WebView ShowCode()<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return new WebView()<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;void doGet()<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;System.out.println("Code");<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;};<br>&nbsp;&nbsp; &nbsp;}<br>}<br><br>最后，JAVA 内部类还有一个作用，那就是实现JAVA的多继承。JAVA本身是不允许多继承的，如果我们想一个类继承多个基类，就可以使用内部类。通过内部类分别继承一个基类，外部类创建内部类的对象，并使用内部类的方法，变相地实现了多继承。
<img src ="http://www.cnitblog.com/conish/aggbug/62874.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-27 15:52 <a href="http://www.cnitblog.com/conish/archive/2009/11/27/62874.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入研究java.lang.Class类</title><link>http://www.cnitblog.com/conish/archive/2009/11/27/62869.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Fri, 27 Nov 2009 05:52:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/27/62869.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62869.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/27/62869.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62869.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62869.html</trackback:ping><description><![CDATA[&nbsp;
<p align=left><span>Java</span><span>程序在运行时，</span><span>Java</span><span>运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行，用来保存这些类型信息的类是</span><span>Class</span><span>类。</span><span>Class</span><span>类封装一个对象和接口运行时的状态，当装载类时，</span><span>Class</span><span>类型的对象自动创建。</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Class </span><span>没有公共构造方法。</span><span>Class </span><span>对象是在加载类时由</span><span> Java </span><span>虚拟机以及通过调用类加载器中的</span><span> defineClass </span><span>方法自动构造的，因此不能显式地声明一个</span><span>Class</span><span>对象。</span><span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>虚拟机为每种类型管理一个独一无二的</span><span>Class</span><span>对象。也就是说，每个类（型）都有一个</span><span>Class</span><span>对象。运行程序时，</span><span>Java</span><span>虚拟机</span><span>(JVM)</span><span>首先检查是否所要加载的类对应的</span><span>Class</span><span>对象是否已经加载。如果没有加载，</span><span>JVM</span><span>就会根据类名查找</span><span>.class</span><span>文件，并将其</span><span>Class</span><span>对象载入。</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>基本的</span><span> Java </span><span>类型（</span><span>boolean</span><span>、</span><span>byte</span><span>、</span><span>char</span><span>、</span><span>short</span><span>、</span><span>int</span><span>、</span><span>long</span><span>、</span><span>float </span><span>和</span><span> double</span><span>）和关键字</span><span> void </span><span>也都对应一个</span><span> Class </span><span>对象。</span><span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>每个数组属于被映射为</span><span> Class </span><span>对象的一个类，所有具有相同元素类型和维数的数组都共享该</span><span> Class </span><span>对象。</span><span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>一般某个类的</span><span>Class</span><span>对象被载入内存，它就用来创建这个类的所有对象。</span><span> </span></p>
<p align=left><strong><span>一、如何得到</span></strong><strong><span>Class</span></strong><strong><span>的对象呢？有三种方法可以的获取：</span></strong><strong><span><br></span></strong><span>&nbsp;&nbsp;&nbsp; </span><span>1</span><span>、调用</span><span>Object</span><span>类的</span><span>getClass()</span><span>方法来得到</span><span>Class</span><span>对象，这也是最常见的产生</span><span>Class</span><span>对象的方法。</span><span>例如：</span><span><br>&nbsp;&nbsp;&nbsp; MyObject x;<br>&nbsp;&nbsp;&nbsp; Class c1 = x.getClass();<br>&nbsp;&nbsp;&nbsp; </span><span>2</span><span>、使用</span><span>Class</span><span>类的中静态</span><span>forName()</span><span>方法获得与字符串对应的</span><span>Class</span><span>对象。</span><span>例如：</span><span> <br>&nbsp;&nbsp;&nbsp; Class c2=Class.forName("MyObject"),Employee</span><span>必须是接口或者类的名字。</span><span><br>&nbsp;&nbsp;&nbsp; </span><span>3</span><span>、获取</span><span>Class</span><span>类型对象的第三个方法非常简单。如果</span><span>T</span><span>是一个</span><span>Java</span><span>类型，那么</span><span>T.class</span><span>就代表了匹配的类对象。</span><span>例如</span><span><br>&nbsp;&nbsp;&nbsp; Class cl1 = Manager.class;<br>&nbsp;&nbsp;&nbsp; Class cl2 = int.class;<br>&nbsp;&nbsp;&nbsp; Class cl3 = Double[].class;<br>&nbsp;&nbsp;&nbsp; </span><span>注意：</span><span>Class</span><span>对象实际上描述的只是类型，而这类型未必是类或者接口。例如上面的</span><span>int.class</span><span>是一个</span><span>Class</span><span>类型的对象。由于历史原因，数组类型的</span><span>getName</span><span>方法会返回奇怪的名字。</span><span> </span></p>
<p align=left><strong><span>二、</span></strong><strong><span>Class</span></strong><strong><span>类的常用方法</span></strong><span><br>&nbsp;&nbsp;&nbsp; 1</span><span>、</span><span>getName() <br>&nbsp;&nbsp;&nbsp; </span><span>一个</span><span>Class</span><span>对象描述了一个特定类的属性，</span><span>Class</span><span>类中最常用的方法</span><span>getName</span><span>以</span><span> String </span><span>的形式返回此</span><span> Class </span><span>对象所表示的实体（类、接口、数组类、基本类型或</span><span> <span>void</span></span><span>）名称。</span><span> </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp; 2</span><span>、</span><span>newInstance()<br>&nbsp;&nbsp;&nbsp; Class</span><span>还有一个有用的方法可以为类创建一个实例，这个方法叫做</span><span>newInstance()</span><span>。例如：</span><span><br>&nbsp;&nbsp;&nbsp; x.getClass.newInstance()</span><span>，创建了一个同</span><span>x</span><span>一样类型的新实例。</span><span>newInstance()</span><span>方法调用默认构造器（无参数构造器）初始化新建对象。</span><span> </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp; 3</span><span>、</span><span>getClassLoader() <br>&nbsp;&nbsp;&nbsp; </span><span>返回该类的类加载器。</span><span> </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp; 4</span><span>、</span><span>getComponentType() <br>&nbsp;&nbsp;&nbsp; </span><span>返回表示数组组件类型的</span><span> Class</span><span>。</span><span> </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp; 5</span><span>、</span><span>getSuperclass() <br>&nbsp;&nbsp;&nbsp; </span><span>返回表示此</span><span> Class </span><span>所表示的实体（类、接口、基本类型或</span><span> void</span><span>）的超类的</span><span> Class</span><span>。</span><span> </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp; 6</span><span>、</span><span>isArray() <br>&nbsp;&nbsp;&nbsp; </span><span>判定此</span><span> Class </span><span>对象是否表示一个数组类。</span><span> </span></p>
<p align=left><strong><span>三、</span></strong><strong><span>Class</span></strong><strong><span>的一些使用技巧</span></strong><span><br>&nbsp;&nbsp;&nbsp; 1</span><span>、</span><span>forName</span><span>和</span><span>newInstance</span><span>结合起来使用，可以根据存储在字符串中的类名创建对象。例如</span><span><br>&nbsp;&nbsp;&nbsp; Object obj = Class.forName(s).newInstance(); </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp; 2</span><span>、虚拟机为每种类型管理一个独一无二的</span><span>Class</span><span>对象。因此可以使用</span><span>==</span><span>操作符来比较类对象。例如：</span><span><br>&nbsp;&nbsp;&nbsp; if(e.getClass() == Employee.class)... </span></p>
<p>&nbsp;</p>
<img src ="http://www.cnitblog.com/conish/aggbug/62869.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-27 13:52 <a href="http://www.cnitblog.com/conish/archive/2009/11/27/62869.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>自动装箱（autoboxing）与自动拆箱（unboxing）</title><link>http://www.cnitblog.com/conish/archive/2009/11/27/62868.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Fri, 27 Nov 2009 05:45:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/27/62868.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62868.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/27/62868.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62868.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62868.html</trackback:ping><description><![CDATA[<p>基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是J2SE 5.0提供的新功能。虽然为你打包基本数据类型提供了方便，但提供方便的同时表示隐藏了细节，建议在能够区分基本数据类型与对象的差别时再使用。 </p>
<p>1)　autoboxing和unboxing </p>
<p>在Java中，所有要处理的东西几乎都是对象(Object)。然而基本(Primitive)数据类型不是对象，也就是你使用int、double、boolean等定义的变量，以及你在程序中直接写下的字面常量。 </p>
<p>使用Java有一段时间的人都知道，有时需要将基本数据类型转换为对象。例如使用Map对象要操作put()方法时，需要传入的参数是对象而不是基本数据类型。 </p>
<p>要使用打包类型(Wrapper Types)才能将基本数据类型包装为对象，前一个小节中你已经知道在J2SE 5.0之前，要使用以下语句才能将int包装为一个Integer对象： </p>
<p>Integer integer = new Integer(10); </p>
<p>在 J2SE 5.0之后提供了自动装箱的功能，你可以直接使用以下语句来打包基本数据类型： </p>
<p>Integer integer = 10; </p>
<p>在进行编译时，编译器再自动根据你写下的语句，判断是否进行自动装箱动作。在上例中integer变量参考的会是Integer类的实例。同样的动作可以适用于 boolean、byte、short、char、long、float、double等基本数据类型，分别会使用对应的打包类型(Wrapper Types)Boolean、Byte、Short、Character、Long、Float或Double。</p>
<p>自动装箱运用的方法还可以如下： </p>
<p>int i = 10; Integer integer = i;也可以使用更一般化的java.lang.Number类来自动装箱。例如： </p>
<p>Number number = 3.14f;3.14f会先被自动装箱为Float，然后指定给number。 </p>
<p>J2SE 5.0中可以自动装箱，也可以自动拆箱(unboxing)，也就是将对象中的基本数据形态信息从对象中自动取出。例如下面这样写是可以的： </p>
<p>Integer fooInteger = 10;int fooPrimitive = fooInteger;fooInteger变量在自动装箱为Integer的实例后，如果被指定给一个int类型的变量fooPrimitive，则会自动变为int类型再指定给fooPrimitive。在运算时，也可以进行自动装箱与拆箱。例如： </p>
<p>Integer i = 10;System.out.println(i + 10);System.out.println(i++);上例中会显示20与10，编译器会自动进行自动装箱与拆箱，也就是10会先被装箱，然后在i + 10时会先拆箱，进行加法运算；i++该行也是先拆箱再进行递增运算。再来看一个例子： </p>
<p>Boolean boo = true;System.out.println(boo &amp;&amp; false);同样的boo原来是Boolean的实例，在进行AND运算时，会先将boo拆箱，再与false进行AND运算，结果会显示false。 </p>
<p>2)　小心使用 boxing </p>
<p>自动装箱与拆箱的功能事实上是编译器来帮你的忙，编译器在编译时期依你所编写的语法，决定是否进行装箱或拆箱动作。例如： </p>
<p>Integer i = 100;相当于编译器自动为你作以下的语法编译： </p>
<p>Integer i = new Integer(100);所以自动装箱与拆箱的功能是所谓的&#8220;编译器蜜糖&#8221;(Compiler Sugar)，虽然使用这个功能很方便，但在程序运行阶段你得了解Java的语义。例如下面的程序是可以通过编译的： </p>
<p>Integer i = null;int j = i;这样的语法在编译时期是合法的，但是在运行时期会有错误，因为这种写法相当于： </p>
<p>Integer i = null;int j = i.intValue();null表示i没有参考至任何的对象实体，它可以合法地指定给对象参考名称。由于实际上i并没有参考至任何的对象，所以也就不可能操作intValue()方法，这样上面的写法在运行时会出现NullPointerException错误。 </p>
<p>自动装箱、拆箱的功能提供了方便性，但隐藏了一些细节，所以必须小心。再来看&#8220;AutoBoxDemo1.java &#8221;，你认为结果是什么呢？ </p>
<p>AutoBoxDemo1.java：</p>
<p>view plaincopy to clipboardprint?<br>public class AutoBoxDemo1 {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; public static void main(String[] args) {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i1 = 127;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i2 = 127;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i1 == i2)&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 == i2");&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 != i2");&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; <br>}&nbsp; <br>public class AutoBoxDemo1 {<br>&nbsp;&nbsp;&nbsp; public static void main(String[] args) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i1 = 127;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i2 = 127;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i1 == i2) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 == i2");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 != i2");<br>&nbsp;&nbsp;&nbsp; }<br>}</p>
<p>从自动装箱与拆箱的机制来看，可能会觉得结果是显示i1 == i2，你是对的。</p>
<p>那么&#8220;AutoBoxDemo2.java &#8221;的这个程序，你觉得结果是什么？ </p>
<p>AutoBoxDemo2.java：</p>
<p>view plaincopy to clipboardprint?<br>public class AutoBoxDemo2 {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; public static void main(String[] args) {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i1 = 128;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i2 = 128;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i1 == i2)&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 == i2");&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 != i2");&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; <br>}&nbsp; <br>public class AutoBoxDemo2 {<br>&nbsp;&nbsp;&nbsp; public static void main(String[] args) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i1 = 128;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i2 = 128;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i1 == i2) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 == i2");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 != i2");<br>&nbsp;&nbsp;&nbsp; }<br>}</p>
<p>结果是显示i1 != i2，这有些令人惊讶，两个范例语法完全一样，只不过改个数值而已，结果却相反。 </p>
<p>其实这与==运算符的比较有关，==是用来比较两个基本数据类型的变量值是否相等，事实上==也用于判断两个对象变量名称是否参考至同一个对象。 </p>
<p>在自动装箱时对于值从&#8211;128到127之间的值，它们被装箱为Integer对象后，会存在内存中被重用，所以范例4.6中使用==进行比较时，i1 与 i2实际上参考至同一个对象。如果超过了从&#8211;128到127之间的值，被装箱后的Integer对象并不会被重用，即相当于每次装箱时都新建一个Integer对象，所以&#8220;AutoBoxDemo2.java&#8221;使用==进行比较时，i1与i2参考的是不同的对象。 </p>
<p>所以不要过分依赖自动装箱与拆箱，你还是必须知道基本数据类型与对象的差异。最好还是依正规的方式来写，而不是依赖编译器蜜糖(Compiler Sugar)。例如&#8220;AutoBoxDemo2.java&#8221;必须改写为&#8220;AutoBoxDemo3.java &#8221;才是正确的。 </p>
<p>AutoBoxDemo3.java：</p>
<p>view plaincopy to clipboardprint?<br>public class AutoBoxDemo3 {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; public static void main(String[] args) {&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i1 = 128;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i2 = 128;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i1.equals(i2))&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 == i2");&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 != i2");&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp; <br>}&nbsp; <br>public class AutoBoxDemo3 {<br>&nbsp;&nbsp;&nbsp; public static void main(String[] args) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i1 = 128;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer i2 = 128;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i1.equals(i2)) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 == i2");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("i1 != i2");<br>&nbsp;&nbsp;&nbsp; }<br>}</p>
<p>结果这次是显示i1 == i2。使用这样的写法，相信也会比较放心一些，对于这些方便但隐藏细节的功能到底要不要用呢？基本上只有一个原则：如果你不确定就不要用。</p>
<img src ="http://www.cnitblog.com/conish/aggbug/62868.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-27 13:45 <a href="http://www.cnitblog.com/conish/archive/2009/11/27/62868.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java下Class.forName的作用是什么，为什么要使用它</title><link>http://www.cnitblog.com/conish/archive/2009/11/27/62867.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Fri, 27 Nov 2009 05:40:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/27/62867.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62867.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/27/62867.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62867.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62867.html</trackback:ping><description><![CDATA[<p>Class.forName(xxx.xx.xx)&nbsp;返回的是一个类</p>
<p>首先你要明白在java里面任何class都要装载在虚拟机上才能运行。这句话就是装载类用的(和new&nbsp;不一样，要分清楚)。&nbsp;<br><br>至于什么时候用，你可以考虑一下这个问题，给你一个字符串变量，它代表一个类的包名和类名，你怎么实例化它？只有你提到的这个方法了，不过要再加一点。&nbsp;<br>A&nbsp;a&nbsp;=&nbsp;(A)Class.forName("pacage.A").newInstance();&nbsp;<br>这和你&nbsp;<br>A&nbsp;a&nbsp;=&nbsp;new&nbsp;A()；&nbsp;<br>是一样的效果。&nbsp;<br><br>关于补充的问题&nbsp;<br>答案是肯定的，jvm会执行静态代码段，你要记住一个概念，静态代码是和class绑定的，class装载成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。</p>
<p>Class.forName(xxx.xx.xx)&nbsp;返回的是一个类&nbsp;<br>Class.forName(xxx.xx.xx);的作用是要求JVM查找并加载指定的类，也就是说JVM会执行该类的静态代码段</p>
<p>动态加载和创建Class&nbsp;对象，比如想根据用户输入的字符串来创建对象&nbsp;<br>String&nbsp;str&nbsp;=&nbsp;用户输入的字符串&nbsp;<br>Class&nbsp;t&nbsp;=&nbsp;Class.forName(str);&nbsp;<br>t.newInstance();</p>
<p>&nbsp;在初始化一个类，生成一个实例的时候，newInstance()方法和new关键字除了一个是方法，一个是关键字外，最主要有什么区别？它们的区别在于创建对象的方式不一样，前者是使用类加载机制，后者是创建一个新类。<font color=#ff0000>那么为什么会有两种创建对象方式？这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想。</font>&nbsp;<br><br>Java中工厂模式经常使用newInstance()方法来创建对象，因此从为什么要使用工厂模式上可以找到具体答案。&nbsp;例如：&nbsp;<br>class&nbsp;c&nbsp;=&nbsp;Class.forName(&#8220;Example&#8221;);&nbsp;<br>factory&nbsp;=&nbsp;(ExampleInterface)c.newInstance();&nbsp;<br><br>其中ExampleInterface是Example的接口，可以写成如下形式：&nbsp;<br>String&nbsp;className&nbsp;=&nbsp;"Example";&nbsp;<br>class&nbsp;c&nbsp;=&nbsp;Class.forName(className);&nbsp;<br>factory&nbsp;=&nbsp;(ExampleInterface)c.newInstance();&nbsp;<br><br>进一步可以写成如下形式：&nbsp;<br>String&nbsp;className&nbsp;=&nbsp;readfromXMlConfig;//从xml&nbsp;配置文件中获得字符串&nbsp;<br>class&nbsp;c&nbsp;=&nbsp;Class.forName(className);&nbsp;<br>factory&nbsp;=&nbsp;(ExampleInterface)c.newInstance();&nbsp;<br><br>上面代码已经不存在Example的类名称，它的优点是，无论Example类怎么变化，上述代码不变，甚至可以更换Example的兄弟类Example2&nbsp;,&nbsp;Example3&nbsp;,&nbsp;Example4&#8230;&#8230;，只要他们继承ExampleInterface就可以。&nbsp;<br><br>从JVM的角度看，我们使用关键字new创建一个类的时候，这个类可以没有被加载。但是使用newInstance()方法的时候，就必须保证：1、这个类已经加载；2、这个类已经连接了。而完成上面两个步骤的正是Class的静态方法forName()所完成的，这个静态方法调用了启动类加载器，即加载java&nbsp;API的那个加载器。&nbsp;<br><br>现在可以看出，newInstance()实际上是把new这个方式分解为两步，即首先调用Class加载方法加载某个类，然后实例化。&nbsp;这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性，提供给了一种降耦的手段。&nbsp;<br><br>最后用最简单的描述来区分new关键字和newInstance()方法的区别：&nbsp;<br>newInstance:&nbsp;弱类型。低效率。只能调用无参构造。&nbsp;<br>new:&nbsp;强类型。相对高效。能调用任何public构造。<br></p>
<img src ="http://www.cnitblog.com/conish/aggbug/62867.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-27 13:40 <a href="http://www.cnitblog.com/conish/archive/2009/11/27/62867.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Oracle内存结构研究-SGA篇</title><link>http://www.cnitblog.com/conish/archive/2009/11/02/62363.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Mon, 02 Nov 2009 10:32:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/02/62363.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62363.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/02/62363.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62363.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62363.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 12pt">一、概述<br></p>
SGA，PGA，UGA都是Oracle管理的内存区。<br><br>SGA（System Global Area），即系统全局区，Oracle中最重要的内存区。<br>PGA（Process Global Area），即程序全局区，一个进程的专用的内存区。<br>UGA（User Global Area），即用户全局区，与特定的会话相关联。<br>专用服务器连接模式，UGA在PGA中分配。<br>共享服务器连接模式，UGA在SGA中的Large Pool中分配。<br><br>如果采用专用服务器连接模式，PGA中包含UGA，其他区域用来排序，散列和位图合并。<br>简单来讲，PGA=UGA+排序区+散列区+位图合并区。<br><br><br>二、PGA的<a onclick="javascript:tagshow(event, '%B9%DC%C0%ED');" href="javascript:;" target=_self><u><strong>管理</strong></u></a>模式。<br>PGA分两种管理模式：<br>1) 手动PGA内存管理，用户指定排序区和散列区所使用的内存，每个连接使用相同的内存。<br>2) 自动PGA内存管理，告诉Oracle可以使用的PGA的总量，由Oraclce根据系统负载决定具体分配。<br><br> 9iR1时默认为手动PGA内存管理，9iR2以后默认为自动PGA内存管理。<br> PGA内存可以动态扩大和回收。<br><br>PGA内存管理模式由WORKAREA_SIZE_POLICY控制。<br>1) 设为MANUAL，启用手动内存管理。<br>2) 设为AUTO，并且PGA_AGGREGATE_TARGET不为0时，启用自动内存管理。<br><br><br>三、手动PGA内存管理<br>有三个参数对PGA影响最大。<br>SORT_AREA_SIZE：对信息排序所用的内存总量<br>SORT_AREA_RETAINED_SIZE：排序后在内存中保存排序信息的内存总量。<br>HASH_AREA_SIZE：存储散列列表所用的内存量。<br><br>下面对这三个参数进行说明：<br>1) SORT_AREA_SIZE：<br>如果SORT_AREA_SIZE设为512KB，SORT_AREA_RETAINED_SIZE也为512KB，则Oracle使用512KB的内存进行排序，排序后所有数据都留在内存中。<br><br>2) SORT_AREA_RETAINED_SIZE：<br>如果SORT_AREA_SIZE设为512KB，SORT_AREA_RETAINED_SIZE设为384KB，则Oracle使用512KB的内存进行排序，然后保留384KB的已排序数据，另外512KB-384KB=128KB的已排序数据会写到临时表空间中。<br> 如果SORT_AREA_RETAINED_SIZE没有设置，则它的值为0，但是实际保留的排序数据和SORT_AREA_SIZE相同。<br><br>3) HASH_AREA_SIZE：<br>一个大集合和另个集合进行连接时，会用到HASH_AREA_SIZE参数。较小的 表会放到这部分内存中作为驱动表，然后大表进行探索（PROBE）操作进行连接。如果HASH_AREA_SIZE过小会影响两个集合（表）连接时的性能。<br><br>注意点：<br>1) 如果需要排序的数据量大于SORT_AREA_SIZE，Oracle会分批进行排序。把当前已排序的数据保存到临时表空间中，然后对剩余的数据进行排序。最后，还会对这些保存在临时表空间中的已排序数据再进行排序，因为每次保存到临时表空间中的已排序数据只是部分数据的排序，对整体需排序的数据来说只是部分局部有序。<br>2) *_AREA_SIZE只是对某个操作的限制，一个查询可能有多个操作，每个操作都有自己的内存区。如果SORT_AREA_SIZE设为5MB，一个查询可能会有10个排序操作，这样一个查询会占用50MB的排序内存。<br>3) 3，*_AREA_SIZE内存的分配是按需分配。如果一个查询需要5MB内存进行排序，就算分配1G的SORT_AREA_SIZE也不会全部使用，只会使用需要的5MB的内存量。<br><br><br>四、自动PGA内存管理<br>要启用自动PGA内存管理，设置下列参数：<br>1，WORKAREA_SIZE_POLICY=AUTO<br>2，PGA_AGGREGATE_TARGET=非零<br><br>有关PGA_AGGREGATE_TARGET：<br>1) PGA_AGGREGATE_TARGET是一个目标值。连接数少的时候实际分配PGA内存会比它要小。连接数多的时候实际分配的PGA内存会比它要大，但是Oracle会努力保持总PGA保持在PGA_AGGREGATE_TARGET值内。<br><br>例如，PGA_AGGREGATE_TARGET 设为300MB。5个用户连接时，每个用户可能分配10MB的PGA内存，共分配50MB的PGA内存。300个用户连接时每个用户可能分配1.3MB的PGA内存，共分配390MB的PGA内存。当用户连接多时，Oracle会降低每个用户的PGA内存使用量。<br>2) 一个串行查询（非并行查询）可能包括多个排序/散列操作，每个排序/散列操作最多使用5%的PGA内存。<br>3) 一个并行查询最多可用到30%的PGA内存，无论有多少并行进程。<br><br><br>五、手动PGA内存管理与自动PGA内存管理<br>自动PGA内存管理相对于手动PGA内存管理有很多优点<br>1， 当用户连接少时<br>a) 手动PGA内存管理不管有多少可用内存都按照预设值进行分配。比如当前空闲内存为300MB，连接需要10MB的内存进行排序，而我们设定的排序区大小为5MB，导致虽然有足够的空闲内存却无法分配给当前连接，造成执行效率低下。<br>b) 自动PGA内存管理会根据当前空闲内存来进行分配。当空闲内存为300MB，当前用户需要10MB内存进行排序，Oracle就会分配10MB内存给当前用户。<br>2， 当用户连接多时<br>a) 手动PGA内存管理会完全按照预设值分配内存。如果物理内存总量为1G，排序区设为5MB，当有300个用户连接时，Oracle会分配1.5G的内存，这已经超过了我们的实际物理内存！<br>b) 自动PGA内存管理会根据当前连接情况进行分配。如果物理内存总量为1G，PGA_AGGREGATE_TARGET为300MB，当用户数从10升到300时，每个用户连接的内存会从满足需要的10MB慢慢减少到1.3MB，虽然最后总量也会超过PGA_AGGREGATE_TARGET，但比起手动PGA内存管理要好很多了。<br><br>什么时候使用自动PGA内存管理？什么时候使用手动PGA内存管理？<br>白天系统正常运行时适合使用自动PGA内存管理，让Oracle根据当前负载自动管理、分配PGA内存。<br>夜里用户数少、进行维护的时候可以设定当前会话使用手动PGA内存管理，让当前的维护操作获得尽可能多的内存，加快执行速度。<br><br>如：服务器平时运行在自动PGA内存管理模式下，夜里有个任务要大表进行排序连接后更新，就可以在该操作session中临时更改为手动PGA内存管理，然后分配大的SORT_AREA_SIZE和HASH_AREA_SIZE（50%甚至80%内存，要确保无其他用户使用），这样能大大加快系统运行速度，又不影响白天高峰期对系统造成的影响。<br><br><br>六、操作命令<br>系统级更改：<br>ALTER SYSTEM SET WORKAREA_SIZE_POLICY = {AUTO | MANAUL}；<br>ALTER SYSTEM SET PGA_AGGREGATE_TARGET=100000000;<br>ALTER SYSTEM SET SORT_AREA_SIZE = 65536 SCOPE = SPFILE；<br>ALTER SYSTEM SET HASH_AREA_SIZE = 65536 SCOPE = SPFILE；<br><br>会话级更改<br>ALTER SESSION SET WORKAREA_SIZE_POLICY = {AUTO | MANAUL}；<br>ALTER SESSION SET SORT_AREA_SIZE = 65536；<br>ALTER SESSION SET HASH_AREA_SIZE = 65536；<br><br><br>七、学以致用<br>1，排序区：<br>pga_aggregate_target为100MB，单个查询能用到5%也就是5MB时排序所需时间<br><br>SQL&gt; create table sorttable as select * from all_objects;<br><br>表已创建。<br><br>SQL&gt; insert into sorttable (select * from sorttable);<br><br>已创建49735行。<br><br>SQL&gt; insert into sorttable (select * from sorttable);<br><br>已创建99470行。<br><br>SQL&gt; set timing on;<br>SQL&gt; set autotrace traceonly;<br>SQL&gt; select * from sorttable order by object_id;<br><br>已选择198940行。<br><br>已用时间: 00: 00: 50.49<br><br>Session级修改排序区为30mb所需时间<br>SQL&gt; ALTER SESSION SET WORKAREA_SIZE_POLICY = MANUAL;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.02<br>SQL&gt; ALTER SESSION SET SORT_AREA_SIZE = 30000000;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.01<br>SQL&gt; select * from sorttable order by object_id;<br><br>已选择198940行。<br><br>已用时间: 00: 00: 10.76<br><br>可以看到所需时间从50.49秒减少到10.31秒，速度提升很明显。<br><br>2，散列区：<br>pga_aggregate_target为100MB，单个查询能用到5%也就是5MB时表连接所需时间<br><br>SQL&gt; select /*+ use_hash(tb1 tb2)*/ * from sorttable tb1,sorttable tb2 where tb1.object_id=tb2.object_id;<br><br>已选择49735行。<br><br>已用时间: 00: 00: 40.50<br><br>Session级修改散列区为30mb所需时间<br>SQL&gt; ALTER SESSION SET WORKAREA_SIZE_POLICY = MANUAL;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.01<br>SQL&gt; ALTER SESSION SET HASH_AREA_SIZE = 30000000;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.01<br>SQL&gt; select /*+ use_hash(tb1 tb2)*/ * from sorttable tb1,sorttable tb2 where tb1.object_id=tb2.object_id;<br><br>已选择49735行。<br><br>已用时间: 00: 00: 04.47<br><br>所需时间由40.50秒提升到4.47秒，效果同样很明显。<br><br>备注：以上<a onclick="javascript:tagshow(event, '%CA%B5%D1%E9');" href="javascript:;" target=_self><u><strong>实验</strong></u></a>皆执行全表扫描保证相关表读入缓冲区中，避免因数据没读入缓存造成误差。<br><br><br>结论：在9iR2版以后，PGA不再像以前那样困扰DBA了，Oracle会帮我们做好PGA的分配。但这并不意味着DBA不需要深入了解PGA了，掌握PGA并根据适当应用会让工作如虎添翼。<br>
<img src ="http://www.cnitblog.com/conish/aggbug/62363.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-02 18:32 <a href="http://www.cnitblog.com/conish/archive/2009/11/02/62363.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>pctused（percent used）与pctfree（percent free）</title><link>http://www.cnitblog.com/conish/archive/2009/11/02/62362.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Mon, 02 Nov 2009 10:31:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/02/62362.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62362.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/02/62362.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62362.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62362.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 12pt">概念：<br><br>&nbsp; &nbsp; pctused：一个块的使用水位的百分比，这个水位将使该块返回到可用列表中去等待更多的插入操作。<br>&nbsp; &nbsp; pctfree：用来为一个块保留的空间百分比，以防止在今后的更新操作中增加一列或多列值的长度。<br>&nbsp; &nbsp; freelist：可用列表是表中的一组可插入数据的可用块。<br>&nbsp; &nbsp; 行连接：指一行存储在多个块中的情况，这是因为该行的长度超过了一个块的可用空间大小，即行链接是跨越多块的行。<br>&nbsp; &nbsp; 行迁移：指一个数据行不适合放入当前块而被重新定位到另一个块（那里有充足的空间）中，但在原始块中保留一个指针的情形。原始块中的指针是必需的，因为索引的ROWID项仍然指向原始位置。<br><br>&nbsp; &nbsp; 计算公式：<br><br>&nbsp; &nbsp; PCTFREE=(Average Row Size-Initial Row Size)*100/Average Row Size<br>&nbsp; &nbsp; PCTUSED=(100-PCTFREE) -Average Row Size * 100/Availabe Data Space<br><br>&nbsp; &nbsp; Oracle的其中一个优点时它可以</span><a onclick="javascript:tagshow(event, '%B9%DC%C0%ED');" href="javascript:;" target=_self><u><strong><span style="FONT-SIZE: 12pt">管理</span></strong></u></a><span style="FONT-SIZE: 12pt">每个表空间中的自由空间。Oracle负责处理表和索引的空间管理，这样就可以让我们无需懂得Oracle的表和索引的内部运作。不过，对于有经验的Oracle调优专家来说，他需要懂得Oracle是如何管理表的extent和空闲的数据块。对于调整拥有高的insert或者update的系统来说，这是非常重要的。<br><br>&nbsp; &nbsp; 要精通对象的调整，你需要懂得freelists和freelist组的行为，它们和pctfree及pctused参数的值有关。这些知识对于</span><a onclick="javascript:tagshow(event, '%C6%F3%D2%B5');" href="javascript:;" target=_self><u><strong><span style="FONT-SIZE: 12pt">企业</span></strong></u></a><span style="FONT-SIZE: 12pt">资源计划（ERP）的应用是特别重要的，因为在这些应用中，不正确的表设置通常是DML语句执行慢的原因。<br><br>&nbsp; &nbsp; 对于初学者来说，最常见的错误是认为默认的Oracle参数对于所有的对象都是最佳的。除非磁盘的消耗不是一个问题，否则在设置表的pctfree和pctused参数时，就必须考虑平均的行长和</span><a onclick="javascript:tagshow(event, '%CA%FD%BE%DD%BF%E2');" href="javascript:;" target=_self><u><strong><span style="FONT-SIZE: 12pt">数据库</span></strong></u></a><span style="FONT-SIZE: 12pt">的块大小，这样空的块才会被有效地放到freelists中。当这些设置不正确时，那些得到的freelists也是"dead"块，因为它们没有足够的空间来存储一行，这样将会导致明显的处理延迟。<br><br>&nbsp; &nbsp; Freelists对于有效地重新使用Oracle表空间中的空间是很重要的，它和pctfree及pctused这两个存储参数的设置直接相关。如果将pctused设置为一个高的值，这时数据库就会尽快地重新使用块。不过，高性能和有效地重新使用表的块是对立的。在调整Oracle的表格和索引时，需要认真考虑究竟需要高性能还是有效的空间重用，并且据此来设置表的参数。以下我们来看一下这些freelists是如何影响Oracle的性能的。<br><br>&nbsp; &nbsp; 当有一个请求需要插入一行到表格中时，Oracle就会到freelist中寻找一个有足够的空间来容纳一行的块。你也许知道，freelist串是放在表格或者索引的第一个块中，这个块也被称为段头（segment header）。pctfree和pctused 参数的唯一目的就是为了控制块如何在freelists中进出。虽然freelist link和 unlink是简单的Oracle功能，不过设置freelist link (pctused) 和unlink (pctfree) 对Oracle的性能确实有影响。<br><br>&nbsp; &nbsp; 由DBA的基本知识知道，pctfree参数是控制freelist un-links的（即将块由freelists中移除）。设置pctfree=10 意味着每个块都保留10%的空间用作行扩展。pctused参数是控制freelist re-links的。设置pctused=40意味着只有在块的使用低于40%时才会回到表格的freelists中。<br><br>&nbsp; &nbsp; 许多新手对于一个块重新回到freelists后的处理都有些误解。其实，一旦由于一个删除的操作而令块被重新加入到freelist中，它将会一直保留在freelist中即使空间的使用超过了60%，只有在到达pctfree时才会将数据块由freelist中移走。<br><br>&nbsp; &nbsp; 表格和索引存储参数设置的要求总结<br><br>&nbsp; &nbsp; 以下的一些规则是用来设置freelists, freelist groups, pctfree和pctused存储参数的。你也知道，pctused和pctfree的值是可以很容易地通过alter table命令修改的，一个好的DBA应该知道如何设置这些参数的最佳值。<br><br>&nbsp; &nbsp; 有效地使用空间和高性能之间是有矛盾的，而表格的存储参数就是控制这个方面的矛盾：<br><br>&nbsp; &nbsp; . 对于需要有效地重新使用空间，可以设置一个高的pctused值，不过副作用是需要额外的I/O。一个高的pctused值意味着相对满的块都会放到freelist中。因此，这些块在再次满之前只可以接受几行记录，从而导致更多的I/O。<br><br>&nbsp; &nbsp; . 追求高性能的话，可以将pctused设置为一个低的值，这意味着Oracle不会将数据块放到freelists中直到它几乎是空的。那么块将可以在满之前接收更多的行，因此可以减少插入操作的I/O。要记住Oracle扩展新块的性能要比重新使用现有的块高。对于Oracle来说，扩展一个表比管理freelists消耗更少的资源。<br><br>&nbsp; &nbsp; 让我们来回顾一下设置对象存储参数的一些常见规则：<br><br>&nbsp; &nbsp; ．经常将pctused设置为可以接收一条新行。对于不能接受一行的free blocks对于我们来说是没有用的。如果这样做，将会令Oracle的性能变慢，因为Oracle将在扩展表来得到一个空的块之前，企图读取5个"dead"的free block。<br><br>&nbsp; &nbsp; ．表格中chained rows的出现意味着pctfree太低或者是db_block_size太少。在很多情况下，RAW和LONG RAW列都很巨大，以至超过了Oracle的最大块的大小，这时chained rows是不可以避免的。<br><br>&nbsp; &nbsp; ．如果一个表有同时插入的</span><a onclick="javascript:tagshow(event, 'SQL');" href="javascript:;" target=_self><u><strong><span style="FONT-SIZE: 12pt">SQL</span></strong></u></a><span style="FONT-SIZE: 12pt">语句，那么它需要有同时删除的语句。运行单一个一个清除的工作将会把全部的空闲块放到一个freelist中，而没有其它包含有任何空闲块的freelists出现。<br><br>&nbsp; &nbsp; ．freelist参数应该设置为表格同时更新的最大值。例如，如果在任何时候，某个表最多有20个用户执行插入的操作，那么该表的参数应该设置为freelists=20。<br><br>&nbsp; &nbsp; 应记住的是freelist groups参数的值只是对于Oracle Parallel </span><a onclick="javascript:tagshow(event, 'Server');" href="javascript:;" target=_self><u><strong><span style="FONT-SIZE: 12pt">Server</span></strong></u></a><span style="FONT-SIZE: 12pt">和Real Application Clusters才是有用的。对于这类Oracle，freelist groups应该设置为访问该表格的Oracle Parallel Server实例的数目。<br></span>
<img src ="http://www.cnitblog.com/conish/aggbug/62362.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-02 18:31 <a href="http://www.cnitblog.com/conish/archive/2009/11/02/62362.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Oracle内存结构研究-PGA篇</title><link>http://www.cnitblog.com/conish/archive/2009/11/02/62360.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Mon, 02 Nov 2009 10:26:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/02/62360.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62360.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/02/62360.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62360.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62360.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;
<p style="FONT-SIZE: 12pt">一、概述<br></p>
SGA，PGA，UGA都是Oracle管理的内存区。<br><br>SGA（System Global Area），即系统全局区，Oracle中最重要的内存区。<br>PGA（Process Global Area），即程序全局区，一个进程的专用的内存区。<br>UGA（User Global Area），即用户全局区，与特定的会话相关联。<br>专用服务器连接模式，UGA在PGA中分配。<br>共享服务器连接模式，UGA在SGA中的Large Pool中分配。<br><br>如果采用专用服务器连接模式，PGA中包含UGA，其他区域用来排序，散列和位图合并。<br>简单来讲，PGA=UGA+排序区+散列区+位图合并区。<br><br><br>二、PGA的<a onclick="javascript:tagshow(event, '%B9%DC%C0%ED');" href="javascript:;" target=_self><u><strong>管理</strong></u></a>模式。<br>PGA分两种管理模式：<br>1) 手动PGA内存管理，用户指定排序区和散列区所使用的内存，每个连接使用相同的内存。<br>2) 自动PGA内存管理，告诉Oracle可以使用的PGA的总量，由Oraclce根据系统负载决定具体分配。<br><br> 9iR1时默认为手动PGA内存管理，9iR2以后默认为自动PGA内存管理。<br> PGA内存可以动态扩大和回收。<br><br>PGA内存管理模式由WORKAREA_SIZE_POLICY控制。<br>1) 设为MANUAL，启用手动内存管理。<br>2) 设为AUTO，并且PGA_AGGREGATE_TARGET不为0时，启用自动内存管理。<br><br><br>三、手动PGA内存管理<br>有三个参数对PGA影响最大。<br>SORT_AREA_SIZE：对信息排序所用的内存总量<br>SORT_AREA_RETAINED_SIZE：排序后在内存中保存排序信息的内存总量。<br>HASH_AREA_SIZE：存储散列列表所用的内存量。<br><br>下面对这三个参数进行说明：<br>1) SORT_AREA_SIZE：<br>如果SORT_AREA_SIZE设为512KB，SORT_AREA_RETAINED_SIZE也为512KB，则Oracle使用512KB的内存进行排序，排序后所有数据都留在内存中。<br><br>2) SORT_AREA_RETAINED_SIZE：<br>如果SORT_AREA_SIZE设为512KB，SORT_AREA_RETAINED_SIZE设为384KB，则Oracle使用512KB的内存进行排序，然后保留384KB的已排序数据，另外512KB-384KB=128KB的已排序数据会写到临时表空间中。<br> 如果SORT_AREA_RETAINED_SIZE没有设置，则它的值为0，但是实际保留的排序数据和SORT_AREA_SIZE相同。<br><br>3) HASH_AREA_SIZE：<br>一个大集合和另个集合进行连接时，会用到HASH_AREA_SIZE参数。较小的 表会放到这部分内存中作为驱动表，然后大表进行探索（PROBE）操作进行连接。如果HASH_AREA_SIZE过小会影响两个集合（表）连接时的性能。<br><br>注意点：<br>1) 如果需要排序的数据量大于SORT_AREA_SIZE，Oracle会分批进行排序。把当前已排序的数据保存到临时表空间中，然后对剩余的数据进行排序。最后，还会对这些保存在临时表空间中的已排序数据再进行排序，因为每次保存到临时表空间中的已排序数据只是部分数据的排序，对整体需排序的数据来说只是部分局部有序。<br>2) *_AREA_SIZE只是对某个操作的限制，一个查询可能有多个操作，每个操作都有自己的内存区。如果SORT_AREA_SIZE设为5MB，一个查询可能会有10个排序操作，这样一个查询会占用50MB的排序内存。<br>3) 3，*_AREA_SIZE内存的分配是按需分配。如果一个查询需要5MB内存进行排序，就算分配1G的SORT_AREA_SIZE也不会全部使用，只会使用需要的5MB的内存量。<br><br><br>四、自动PGA内存管理<br>要启用自动PGA内存管理，设置下列参数：<br>1，WORKAREA_SIZE_POLICY=AUTO<br>2，PGA_AGGREGATE_TARGET=非零<br><br>有关PGA_AGGREGATE_TARGET：<br>1) PGA_AGGREGATE_TARGET是一个目标值。连接数少的时候实际分配PGA内存会比它要小。连接数多的时候实际分配的PGA内存会比它要大，但是Oracle会努力保持总PGA保持在PGA_AGGREGATE_TARGET值内。<br><br>例如，PGA_AGGREGATE_TARGET 设为300MB。5个用户连接时，每个用户可能分配10MB的PGA内存，共分配50MB的PGA内存。300个用户连接时每个用户可能分配1.3MB的PGA内存，共分配390MB的PGA内存。当用户连接多时，Oracle会降低每个用户的PGA内存使用量。<br>2) 一个串行查询（非并行查询）可能包括多个排序/散列操作，每个排序/散列操作最多使用5%的PGA内存。<br>3) 一个并行查询最多可用到30%的PGA内存，无论有多少并行进程。<br><br><br>五、手动PGA内存管理与自动PGA内存管理<br>自动PGA内存管理相对于手动PGA内存管理有很多优点<br>1， 当用户连接少时<br>a) 手动PGA内存管理不管有多少可用内存都按照预设值进行分配。比如当前空闲内存为300MB，连接需要10MB的内存进行排序，而我们设定的排序区大小为5MB，导致虽然有足够的空闲内存却无法分配给当前连接，造成执行效率低下。<br>b) 自动PGA内存管理会根据当前空闲内存来进行分配。当空闲内存为300MB，当前用户需要10MB内存进行排序，Oracle就会分配10MB内存给当前用户。<br>2， 当用户连接多时<br>a) 手动PGA内存管理会完全按照预设值分配内存。如果物理内存总量为1G，排序区设为5MB，当有300个用户连接时，Oracle会分配1.5G的内存，这已经超过了我们的实际物理内存！<br>b) 自动PGA内存管理会根据当前连接情况进行分配。如果物理内存总量为1G，PGA_AGGREGATE_TARGET为300MB，当用户数从10升到300时，每个用户连接的内存会从满足需要的10MB慢慢减少到1.3MB，虽然最后总量也会超过PGA_AGGREGATE_TARGET，但比起手动PGA内存管理要好很多了。<br><br>什么时候使用自动PGA内存管理？什么时候使用手动PGA内存管理？<br>白天系统正常运行时适合使用自动PGA内存管理，让Oracle根据当前负载自动管理、分配PGA内存。<br>夜里用户数少、进行维护的时候可以设定当前会话使用手动PGA内存管理，让当前的维护操作获得尽可能多的内存，加快执行速度。<br><br>如：服务器平时运行在自动PGA内存管理模式下，夜里有个任务要大表进行排序连接后更新，就可以在该操作session中临时更改为手动PGA内存管理，然后分配大的SORT_AREA_SIZE和HASH_AREA_SIZE（50%甚至80%内存，要确保无其他用户使用），这样能大大加快系统运行速度，又不影响白天高峰期对系统造成的影响。<br><br><br>六、操作命令<br>系统级更改：<br>ALTER SYSTEM SET WORKAREA_SIZE_POLICY = {AUTO | MANAUL}；<br>ALTER SYSTEM SET PGA_AGGREGATE_TARGET=100000000;<br>ALTER SYSTEM SET SORT_AREA_SIZE = 65536 SCOPE = SPFILE；<br>ALTER SYSTEM SET HASH_AREA_SIZE = 65536 SCOPE = SPFILE；<br><br>会话级更改<br>ALTER SESSION SET WORKAREA_SIZE_POLICY = {AUTO | MANAUL}；<br>ALTER SESSION SET SORT_AREA_SIZE = 65536；<br>ALTER SESSION SET HASH_AREA_SIZE = 65536；<br><br><br>七、学以致用<br>1，排序区：<br>pga_aggregate_target为100MB，单个查询能用到5%也就是5MB时排序所需时间<br><br>SQL&gt; create table sorttable as select * from all_objects;<br><br>表已创建。<br><br>SQL&gt; insert into sorttable (select * from sorttable);<br><br>已创建49735行。<br><br>SQL&gt; insert into sorttable (select * from sorttable);<br><br>已创建99470行。<br><br>SQL&gt; set timing on;<br>SQL&gt; set autotrace traceonly;<br>SQL&gt; select * from sorttable order by object_id;<br><br>已选择198940行。<br><br>已用时间: 00: 00: 50.49<br><br>Session级修改排序区为30mb所需时间<br>SQL&gt; ALTER SESSION SET WORKAREA_SIZE_POLICY = MANUAL;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.02<br>SQL&gt; ALTER SESSION SET SORT_AREA_SIZE = 30000000;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.01<br>SQL&gt; select * from sorttable order by object_id;<br><br>已选择198940行。<br><br>已用时间: 00: 00: 10.76<br><br>可以看到所需时间从50.49秒减少到10.31秒，速度提升很明显。<br><br>2，散列区：<br>pga_aggregate_target为100MB，单个查询能用到5%也就是5MB时表连接所需时间<br><br>SQL&gt; select /*+ use_hash(tb1 tb2)*/ * from sorttable tb1,sorttable tb2 where tb1.object_id=tb2.object_id;<br><br>已选择49735行。<br><br>已用时间: 00: 00: 40.50<br><br>Session级修改散列区为30mb所需时间<br>SQL&gt; ALTER SESSION SET WORKAREA_SIZE_POLICY = MANUAL;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.01<br>SQL&gt; ALTER SESSION SET HASH_AREA_SIZE = 30000000;<br><br>会话已更改。<br><br>已用时间: 00: 00: 00.01<br>SQL&gt; select /*+ use_hash(tb1 tb2)*/ * from sorttable tb1,sorttable tb2 where tb1.object_id=tb2.object_id;<br><br>已选择49735行。<br><br>已用时间: 00: 00: 04.47<br><br>所需时间由40.50秒提升到4.47秒，效果同样很明显。<br><br>备注：以上<a onclick="javascript:tagshow(event, '%CA%B5%D1%E9');" href="javascript:;" target=_self><u><strong>实验</strong></u></a>皆执行全表扫描保证相关表读入缓冲区中，避免因数据没读入缓存造成误差。<br><br><br>结论：在9iR2版以后，PGA不再像以前那样困扰DBA了，Oracle会帮我们做好PGA的分配。但这并不意味着DBA不需要深入了解PGA了，掌握PGA并根据适当应用会让工作如虎添翼。<br>
<img src ="http://www.cnitblog.com/conish/aggbug/62360.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-02 18:26 <a href="http://www.cnitblog.com/conish/archive/2009/11/02/62360.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>检查点SCN</title><link>http://www.cnitblog.com/conish/archive/2009/11/02/62359.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Mon, 02 Nov 2009 10:25:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/11/02/62359.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62359.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/11/02/62359.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62359.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62359.html</trackback:ping><description><![CDATA[<div>一、检查点概述<br>大多数关系型<a onclick="javascript:tagshow(event, '%CA%FD%BE%DD%BF%E2');" href="javascript:;" target=_self><u><strong><font color=#0000ff>数据库</font></strong></u></a>都采用"在提交时并不强迫针对数据块的修改完成"而是"提交时保证修改记录（以重做日志的形式）写入日志文件"的机制，来获得性能的优势。这句话的另外一种描述是：当用户提交事务，写数据文件是"异步"的，写日志文件是"同步"的。这就可能导致数据库实例崩溃时，内存中的DB_Buffer 中的修改过的数据，可能没有写入到数据块中。数据库在重新打开时，需要进行恢复，来恢复DB Buffer 中的数据状态，并确保已经提交的数据被写入到数据块中。检查点是这个过程中的重要机制，通过它来确定，恢复时哪些重做日志应该被扫描并应用于恢复。<br>要了解这个检查点，首先要知道checkpoint queu概念，检查点发生后，触发dbwn，CKPT获取发生检查点时对应的SCN，通知DBWn要写到这个SCN为止，<br>dbwr 写dirty buffer 是根据 buffer 在被首次 modify的时候的时间的顺序写出，也就是 buffer被modify 的时候会进入一个queue （checkpoint queue），dbwr 就根据queue从其中批量地写到数据文件。 由于这里有一个顺序的关系，所以 dbwr的写的进度就是可衡量的，写到哪个buffer的时候该buffer的首次变化时候的scn就是当前所有数据文件block的最新scn，但是由于无法适时的将dbwr的进度记录下来，所以<a onclick="javascript:tagshow(event, 'oracle');" href="javascript:;" target=_self><u><strong><font color=#0000ff>oracle</font></strong></u></a> 选择了一些策略。 其中就包括ckpt进程的检查点和心跳。<br>检查点发生以后，CKPT进程检查checkpoint queue(也就是脏块链表)是否过长，如果是，则触发DBWn，将一部分脏块写入数据文件，从而缩短checkpoint queue。</div>
<div>checkpoint 发生时，一方面通知dbwr进行下一批写操作，（dbwr 写入的时候，一次写的块数是有一个批量写的隐藏参数控制的。）另一方面，oracle 采用了一个心跳的概念，以3秒的频率将dbwr 写的进度反应到控制文件中，也就是把dbwr当前刚写完的dirty buffer对应的scn 写入数据文件头和控制文件，这就是检查点scn。<br>这个3秒和增量检查点不是一个概念，3秒只是在控制文件中，ckpt 进程去更新当前 dbwr写到哪里了，这个对于 ckpt 进程来说叫 heartbeat ，heartbeat是3秒一次，3秒可以看作不停的检查并记录检查点执行情况（DBWR的写进度）。<br>检查点发生之后数据库的数据文件、控制文件处于一致状态的含义是不需要进行 介质恢复，只表示数据文件头一致，但是并不表示数据文件内容一致，因为数据文件内容可能在没有发生检查点的其他情况下的dbwr写数据文件，这样数据文件内容就不一致，若掉电需要进行崩溃恢复。<br>二、触发的条件<br>这里需要明白两个概念"完全检查点和增量检查点"的区别。<br>增量检查点（incremental checkpoint）<br>oracle8以后推出了incremental checkpoint的机制，在以前的版本里每checkpoint时都会做一个full thread checkpoint,这样的话所有脏数据会被写到磁盘，巨大的i/o对系统性能带来很大影响。为了解决这个问题，oracle引入了checkpoint queue机制，每一个脏块会被移到检查点队列里面去，按照low rdb（第一次对此块修改对应的redo block address）来排列，靠近检查点队列尾端的数据块的low rba值是最小的，而且如果这些赃块被再次修改后它在检查点队列里的顺序也不会改变，这样就保证了越早修改的块越早写入磁盘。每隔3秒钟ckpt会去更新控制文件和数据文件，记录checkpoint执行的情况。<br>在运行的Oracle 数据中，有很多事件、条件或者参数来触发检查点。比如<br>当已通过正常事务处理或者立即选项关闭例程时；（shutdown immediate或者Shutdown normal;）<br>当通过设置初始化参数LOG_CHECKPOINT_INTERVAL、LOG_CHECKPOINT_TIMEOUT 和FAST_START_IO_TARGET 强制时；<br>当数据库<a onclick="javascript:tagshow(event, '%B9%DC%C0%ED');" href="javascript:;" target=_self><u><strong><font color=#0000ff>管理</font></strong></u></a>员手动请求时；（ALter system checkpoint）<br>alter tablespace ... offline;<br>每次日志切换时;（alter system switch logfile）<br>需要说明的是，alter system switch logfile也将触发完全检查点的发生。<br>alter database datafile ... offline不会触发检查点进程。<br>如果是单纯的offline datafile，那么将不会触发文件检查点，只有针对offline tablespace的时候才会触发文件检查点，这也是为什么online datafile需要media recovery而online tablespace不需要。<br>对于表空间的offline后再online这种情况，最好做个强制的checkpoint比较好。<br>　　上面几种情况，将触发完全检查点，促使DBWR 将检查点时刻前所有的脏数据写入数据文件。<br>另外，一般正常运行期间的数据库不会产生完全检查点，下面很多事件将导致增量检查点，比如：<br>在联机热<a onclick="javascript:tagshow(event, '%B1%B8%B7%DD');" href="javascript:;" target=_self><u><strong><font color=#0000ff>备份</font></strong></u></a>数据文件前，要求该数据文件中被修改的块从DB_Buffer 写入数据文件中。所以，发出这样的命令：<br>ALTER TABLESPACE tablespace_name BIGEN BACKUP &amp; end backup; 也将触发和该表空间的数据文件有关的局部检查点；另外，<br>ALTER TABLESPACE tablespace_name READ ONLY;<br>ALTER TABLESPACE tablespace_name OFFLINE NORMAL;<br>等命令都会触发增量检查点。</div>
<div>关于检查点的一点具体应用讨论：<br>Commit成功后，数据还会丢失吗？<br>对于Oracle来说，用户所做的DML操作一旦被提交，则先是在database buffer cache 中进行修改，同时在修改之前会将数据的前镜像保存在回滚段中，然后将修改之前和修改之后的数据都写入到redo log buffer中，当接收到commit命令之后，则redo log buffer开始写redo log file ，并且记录此时的scn，当redo log file 写完了之后，表示这次事务提交操作已经确认被数据库记录了，只有当redo log file 写成功了，才会给用户 Commit completed 的成功字样。而对于Database buffer cache中的dirty buffer则会等待触发DBWn才写入，但是如果此时断电，则数据已经被记录到了redo log file中，系统在重新启动的时候，会自动进行嵌滚和回滚来保证数据的一致。所以，只要是commit成功的了，数据不会丢失！</div>
<div>数据库发生一次DBWn，是否将所有buffer cache 中的dirty buffer 都写入，还是先将脏队列中的数据写入？</div>
<div>这话看起来有道理，但实际上，dbwr在写的时候又不断地在产生dirty buffer ,所以说检查点发生的时候是期望把该时间点之前的所有脏缓冲区写入数据文件。<br>所有的buffer，不在LRU list上就在dirty list上, dbwr写入的时候，一次写的块数是有一个批量写的隐藏参数控制的。<br>所以说要是 dbwr将 dirty list也好， lru list上的也好，要实现全部写入，都是一个现实系统中很难存在的现象。dirty 总是在不断的产生，dbwr总是在不断地写,增量检查点发生的时候也并不意味着一定要更新数据文件头，检查点开始的时候只表示该次检查点结束的时候要更新数据文件头的话数据文件头具有该时间点的一致性。<br>关于检查点等待事件：<br>有些事件的产生必须等待检查点的完成，才能开始数据库的其它操作：<br>日志文件切换就是其中一个事件，日志切换必须等待检查点完成。<br>log file switch (checkpoint incomplete) 这个等待事件本身也说明，日志切换时产生的检查点是需要等待的，这个日志文件所对应脏块全部写完后，检查点进程更新控制文件和数据头，然后这个检查点才能算完成。<br>也就是说日志切换必须等待检查点完成，而检查点在等待DBWn完成。<br>这种等待DBWn完成的检查点，最后一步写入控制文件和数据文件头的SCN，肯定是DBWn完成的最后一块的SCN。<br>检查点为什么要等待dbwr完成后才进行切换（log switch）？<br>log switch时，是不能立即switch到active状态的，log group必须等待。active表示该log group还没有完成归档（归档模式下）或者该log group有进行instance recovery的需要用到的日志。所以当checkpoint发生时，如果dbwr还没有写完它该写完的dirty buffers（该checkpoint时间点以前产生的dirty buffers, 靠scn判断），则该log group处于active状态，不会进行日志切换，当然也不会发生日志文件被覆盖的问题了。<br>如果没有设置archive log ，在检查点发生后，发生log switch一个轮回，log file是否会被覆盖掉？<br>当检查点发生后，会触发lgwr，lgwr会把此时SCN以前在redo buffer中的所有操作写到redo log file，同时lgwr也会触发dbwr进程，dbwr也开始把此刻以前database buffer中的dirty buffer队列中的操作写入data file。<br>其实检查点发生后，就是lgwr和dbwr写buffer到磁盘文件的过程，但是两者的读写速度时不同的，dbwr写buffer到数据文件的过程相对较慢，因为dbwr写过程是一个随机读取存储的过程。Lgwr写buffer到redo文件的过程比dbwr要快很多，因为lgwr是顺序读取写入的。<br>由于以上lgwr和dbwr写操作的速度不同，就产生了一个等待问题。即当lgwr<br>轮循一圈后，要进行日志切换，覆盖redo log file，但是此时dbwr还没有写完，那么lgwr就会出现等待，oracle也会hang在那里，同时alter文件中会提示一个相应的提示checkpoint not complete。等检查点完成后数据库自动恢复正常。<br>当log switch时，会触发检查点，标记检查点时，DBWR要把log file中covered by the log being checkpointed的数据写入到数据文件上，如果Dbwr没有写完，那么前一个logfile是不能被覆盖的。因此必须等检查点完毕，也可以说是将 要被覆盖的日志相关的数据块全部写入数据文件后，该日志文件才能被覆盖！<br>如果这种情况出现，alert文件中会记录checkpoint not complete事件，这通常表明你的dbwr写入过慢或者logfile组数过少或log file太小。</div>
<div>检查点发生时，出现日志切换，但是dbwr还没有写完，是否会覆盖redo log file，如果此时掉电，dbwr挂起，会出现丢失数据吗？<br>答：不会覆盖redo文件，要等待dbwr写完才覆盖。或者说要等待检查点完成才覆盖。<br>如果DBWn正在写的脏块是指对应到即将被覆盖的那个日志文件的，那么，因为在这些操作完成之前，不可能有覆盖这件事发生，假设你预料的掉电发生了，不会有什么问题，实例恢复时的前滚从刚才准备覆盖的那个日志文件开始。</div>
<div>如果正在写的脏块是指对应到刚刚被写满的那个日志文件的，肯定一时半会也写不完，日志切换也不会等它，如果切换成功后dbwr挂了，后面的写脏块操作估计也还没完成，但是，刚写满的那个文件并没有被覆盖，也不会有任何问题。<br>因此不会出现数据丢失！<br>当一个很大的dml发生时，用户在commit后，需要将redo log buffer中的脏数据写入redo log file中。如果在写的过程中，发现一个redo log file写不下的话，需要写另外一个redo log file，这时应该触发checkpoint，接着触发DBWn，将脏数据写入datafile，这时发生掉电，导致DBWR失败。这时其实就可以说commit失败了。以上所述其实就是commit没有提交成功。</div>
<div>alter systen switch logfile会触发完全检查点;但是为什么，日志切换以后检查点只能记录到上一次归档日志产生的时间呢？而不是现在归档日志产生的时间呢？<br>因为这时的checkpoint enqueue队列中，也就是脏块队列中脏块还不够多，还不足以触发DBWn写脏块。所以，内存里需要写入的脏块所对应的redo还存在于上一个redo log里，这时你去看该redo log的status为active。<br>下面操作日志中出现当前redo log的status为current，而上一个redo log的status为active就说明了这一切。上一个redo log的status为active正是说明在上一个redo log中还存在未写入datafile的block所对应的redo。<br>SQL&gt; create table gaojf3 as select * from all_objects;<br>Table created<br>SQL&gt; insert into gaojf3 select * from gaojf3;<br>29630 rows inserted<br>SQL&gt; /<br>59260 rows inserted<br>SQL&gt; commit;<br>Commit complete<br>SQL&gt; select * from x$kccrt;<br>ADDR INDX INST_ID RTNUM RTSTA RTCKP_SCN RTCKP_TIM RTCKP_THR RTCKP_RBA_SEQ RTCKP_RBA_BNO RTCKP_RBA_BOF RTCKP_ETB RTOTF RTOTB RTNLF RTLFH RTLFT RTCLN RTSEQ RTENB RTETS RTDIS RTDIT RTLHP RTSID RTOTS<br>-------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------- ------------- ------------- ------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------------- -------------------- ---------- ---------------- --------------------<br>421A54D0 0 1 1 15 8796119126928 08/08/2006 00:28:38 1 14 2 16 0200000000000000 0 0 3 1 3 1 14 8796117861680 08/02/2006 02:29:36 0 13 cicro 08/02/2006 02:33:50<br>SQL&gt; alter system switch logfile;<br>System altered<br>SQL&gt; select * from x$kccrt;<br>ADDR INDX INST_ID RTNUM RTSTA RTCKP_SCN RTCKP_TIM RTCKP_THR RTCKP_RBA_SEQ RTCKP_RBA_BNO RTCKP_RBA_BOF RTCKP_ETB RTOTF RTOTB RTNLF RTLFH RTLFT RTCLN RTSEQ RTENB RTETS RTDIS RTDIT RTLHP RTSID RTOTS<br>-------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------- ------------- ------------- ------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------------- -------------------- ---------- ---------------- --------------------<br>421A54D0 0 1 1 15 8796119126928 08/08/2006 00:28:38 1 14 2 16 0200000000000000 0 0 3 1 3 2 15 8796117861680 08/02/2006 02:29:36 0 14 cicro 08/02/2006 02:33:50<br>SQL&gt; select * from x$kccrt;<br>ADDR INDX INST_ID RTNUM RTSTA RTCKP_SCN RTCKP_TIM RTCKP_THR RTCKP_RBA_SEQ RTCKP_RBA_BNO RTCKP_RBA_BOF RTCKP_ETB RTOTF RTOTB RTNLF RTLFH RTLFT RTCLN RTSEQ RTENB RTETS RTDIS RTDIT RTLHP RTSID RTOTS<br>-------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------- ------------- ------------- ------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------------- -------------------- ---------- ---------------- --------------------<br>421A54D0 0 1 1 15 8796119126928 08/08/2006 00:28:38 1 14 2 16 0200000000000000 0 0 3 1 3 2 15 8796117861680 08/02/2006 02:29:36 0 14 cicro 08/02/2006 02:33:50<br>SQL&gt; select * from x$kccrt;<br>ADDR INDX INST_ID RTNUM RTSTA RTCKP_SCN RTCKP_TIM RTCKP_THR RTCKP_RBA_SEQ RTCKP_RBA_BNO RTCKP_RBA_BOF RTCKP_ETB RTOTF RTOTB RTNLF RTLFH RTLFT RTCLN RTSEQ RTENB RTETS RTDIS RTDIT RTLHP RTSID RTOTS<br>-------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------- ------------- ------------- ------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------------- -------------------- ---------------- -------------------- ---------- ---------------- --------------------<br>421A54D0 0 1 1 15 8796119128114 08/08/2006 00:35:51 1 15 2 16 0200000000000000 0 0 3 1 3 3 16 8796117861680 08/02/2006 02:29:36 0 15 cicro 08/02/2006 02:33:50<br>从上面试验中可以看到，完全检查点正在执行，还没有执行完毕。因此，切换日志前后察看x$kccrt，看到的RTCKP_SCN, RTCKP_TIM没有发生变化。等检查点执行完毕后（也就是检查点触发的dbwr执行完写操作后），rtckp_scn, rtckp_tim才发生变化。</div>
<img src ="http://www.cnitblog.com/conish/aggbug/62359.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-11-02 18:25 <a href="http://www.cnitblog.com/conish/archive/2009/11/02/62359.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux 系统启动相关知识</title><link>http://www.cnitblog.com/conish/archive/2009/10/23/62097.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Fri, 23 Oct 2009 13:12:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/10/23/62097.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/62097.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/10/23/62097.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/62097.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/62097.html</trackback:ping><description><![CDATA[<p>引导扇区是每个分区（Partition）的第一扇区，而主引导扇区是硬盘的第一扇区。它由三个部分组成，主引导记录MBR、硬盘分区表DPT和硬盘有效标志。在总共512字节的主引导扇区里MBR占446个字节（偏移0--偏移1BDH），DPT占64个字节（偏移1BEH--偏移1FDH），最后两个字节&#8220;55AA&#8221;（偏移1FEH--偏移1FFH）是硬盘有效标志</p>
<p>&nbsp;<wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr> 系统启动阶段依赖于引导 Linux 系统上的硬件。在嵌入式平台中，当系统加电或重置时，会使用一个启动环境。这方面的例子包括 U-Boot、RedBoot 和 Lucent 的 MicroMonitor。嵌入式平台通常都是与引导监视器搭配销售的。这些程序位于目标硬件上的闪存中的某一段特殊区域，它们提供了将 Linux 内核映像下载到闪存并继续执行的方法。除了可以存储并引导 Linux 映像之外，这些引导监视器还执行一定级别的系统测试和硬件初始化过程。在嵌入式平台中，这些引导监视器通常会涉及第一阶段和第二阶段的引导加载程序。</p>
<p><br><font color=#ff0000>提取 MBR 的信息</font></p>
<p>要查看 MBR 的内容，请使用下面的命令：</p>
<p># dd if=/dev/hda of=mbr.bin bs=512 count=1 # od -xa mbr.bin<br>这个 dd 命令需要以 root 用户的身份运行，它从 /dev/hda（第一个 IDE 盘） 上读取前 512 个字节的内容，并将其写入 mbr.bin 文件中。od 命令会以十六进制和 ASCII 码格式打印这个二进制文件的内容。</p>
<p>&nbsp;<wbr>&nbsp;<wbr> 在 PC 中，引导 Linux 是从 BIOS 中的地址 0xFFFF0 处开始的。BIOS 的第一个步骤是加电自检（POST）。POST 的工作是对硬件进行检测。BIOS 的第二个步骤是进行本地设备的枚举和初始化。 中国给定 BIOS 功能的不同用法之后，BIOS 由两部分组成：POST 代码和运行时服务。当 POST 完成之后，它被从内存中清理了出来，但是 BIOS 运行时服务依然保留在内存中，目标操作系统可以使用这些服务。</p>
<p>&nbsp;<wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr> 要引导一个操作系统，BIOS 运行时会按照 CMOS 的设置定义的顺序来搜索处于活动状态并且可以引导的设备。引导设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备，甚至是 USB 闪存。</p>
<p>&nbsp;<wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr> 通常，Linux 都是从硬盘上引导的，其中主引导记录（MBR）中包含主引导加载程序。MBR 是一个 512 字节大小的扇区，位于磁盘上的第一个扇区中（0 道 0 柱面 1 扇区）。当 MBR 被加载到 RAM 中之后，BIOS 就会将控制权交给 MBR。</p>
<p><br><font color=#ff0000>第一阶段引导加载程序</font><br>&nbsp;<wbr>&nbsp;<wbr> MBR 中的主引导加载程序是一个 512 字节大小的映像，其中包含程序代码和一个小分区表（参见图 2）。前 446 个字节是主引导加载程序，其中包含可执行代码和错误消息文本。接下来的 64 个字节是分区表，其中包含 4 个分区的记录（每个记录的大小是 16 个字节）。MBR 以两个特殊数字的字节（0xAA55）结束。这个数字会用来进行 MBR 的有效性检查。</p>
<p>&nbsp;<wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr> 主引导加载程序的工作是查找并加载次引导加载程序（第二阶段）。它是通过在分区表中查找一个活动分区来实现这种功能的。当找到一个活动分区时，它会扫描分区表中的其他分区，以确保它们都不是活动的。当这个过程验证完成之后，就将活动分区的引导记录从这个设备中读入 RAM 中并执行它。</p>
<p>&nbsp;<wbr></p>
<p>&nbsp;<wbr></p>
<p><font color=#ff0000>第二阶段引导加载程序</font><br>&nbsp;<wbr>&nbsp;<wbr> 次引导加载程序（第二阶段引导加载程序）可以更形象地称为内核加载程序。这个阶段的任务是加载 Linux 内核和可选的初始 RAM 磁盘。</p>
<p>&nbsp;<wbr></p>
<p><font color=#ff0000>GRUB 阶段引导加载程序</font></p>
<p>&nbsp;<wbr>&nbsp;<wbr> /boot/grub 目录中包含了 stage1、stage1.5 和 stage2 引导加载程序，以及很多其他加载程序（例如，CR-ROM 使用的是 iso9660_stage_1_5）。</p>
<p>&nbsp;<wbr>&nbsp;<wbr> 在 x86 PC 环境中，第一阶段和第二阶段的引导加载程序一起称为 Linux Loader（LILO）或 GRand Unified Bootloader（GRUB）。由于 LILO 有一些缺点，而 GRUB 克服了这些缺点，因此下面让我们就来看一下 GRUB。</p>
<p>&nbsp;<wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr> 关于 GRUB，很好的一件事情是它包含了有关 Linux 文件系统的知识。GRUB 不像 LILO 一样使用裸扇区，而是可以从 ext2 或 ext3 文件系统中加载 Linux 内核。它是通过将两阶段的引导加载程序转换成三阶段的引导加载程序来实现这项功能的。阶段 1 （MBR）引导了一个阶段 1.5 的引导加载程序，它可以理解包含 Linux 内核映像的特殊文件系统。这方面的例子包括 reiserfs_stage1_5（要从 Reiser 日志文件系统上进行加载）或 e2fs_stage1_5（要从 ext2 或 ext3 文件系统上进行加载）。当阶段 1.5 的引导加载程序被加载并运行时，阶段 2 的引导加载程序就可以进行加载了。</p>
<p><br>&nbsp;<wbr>&nbsp;<wbr> 当阶段 2 加载之后，GRUB 就可以在请求时显示可用内核列表（在 /etc/grub.conf 中进行定义，同时还有几个软符号链接 /etc/grub/menu.lst 和 /etc/grub.conf）。我们可以选择内核甚至修改附加内核参数。另外，我们也可以使用一个命令行的 shell 对引导过程进行高级手工控制。</p>
<p>&nbsp;<wbr>&nbsp;<wbr> 将第二阶段的引导加载程序加载到内存中之后，就可以对文件系统进行查询了，并将默认的内核映像和 initrd 映像加载到内存中。当这些映像文件准备好之后，阶段 2 的引导加载程序就可以调用内核映像了。</p>
<p><br><font color=#ff00ff><strong>硬件读取引导扇区<br>加载LILO或者grub<br>加载内核<br>挂装根文件系统<br>启动/sbin/init ，一切进程的&#8220;祖父&#8221;<br>读取/etc/inittab文件<br>读取rc.sysinit文件<br>读取/etc/fstab文件<br>运行rcX.d下的文件（文件都是init.d下的符号链接）<br>其中有一个xinetd 的超级进程，调用/etc/xinetd.conf 配置文件，从配置文件中知道读/etc/xinetd.d 文件，结束后调用Miggetty<br>读取/etc/rc.d/rc.local文件</strong></font><br></p>
<img src ="http://www.cnitblog.com/conish/aggbug/62097.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-10-23 21:12 <a href="http://www.cnitblog.com/conish/archive/2009/10/23/62097.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解决windows下vim汉化菜单栏乱码问题</title><link>http://www.cnitblog.com/conish/archive/2009/06/04/59057.html</link><dc:creator>风花雪月</dc:creator><author>风花雪月</author><pubDate>Thu, 04 Jun 2009 10:40:00 GMT</pubDate><guid>http://www.cnitblog.com/conish/archive/2009/06/04/59057.html</guid><wfw:comment>http://www.cnitblog.com/conish/comments/59057.html</wfw:comment><comments>http://www.cnitblog.com/conish/archive/2009/06/04/59057.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/conish/comments/commentRss/59057.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/conish/services/trackbacks/59057.html</trackback:ping><description><![CDATA[1.到<a href="http://www.vim.org/download.php#pc">http://www.vim.org/download.php#pc</a>上下载gvim72.exe，安装<br>2.下载中文帮助稳定<a href="http://downloads.sourceforge.net/vimcdoc/vimcdoc-1.7.0-setup-unicode.exe">http://downloads.sourceforge.net/vimcdoc/vimcdoc-1.7.0-setup-unicode.exe</a><br>安装后运行vim菜单栏为乱码<br>解决办法为：到vim安装路径下C:\Program Files\Vim进入vimfiles的plugin打开vimdoc.vim 将set encoding设为set encoding=gbk <br>3.vim中文键盘图片<br><img height=723 alt="" src="http://www.cnitblog.com/images/cnitblog_com/conish/vim.png" width=1024 border=0>
<img src ="http://www.cnitblog.com/conish/aggbug/59057.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/conish/" target="_blank">风花雪月</a> 2009-06-04 18:40 <a href="http://www.cnitblog.com/conish/archive/2009/06/04/59057.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>