Google宣布在7月1号关闭Reader

真尼玛草蛋。这次真心是打击。之前在ifttt做了个pipe来把Reader的加星的文章自动发布到BLOG的功能,这么快就废了。这些还不是最重要的,重要的是已经习惯了Google Reader了,其他的阅读器如何能够吸引到我?

发表在 未分类 | 留下评论

我的微博被封

人生完整了。我了个擦。不过是转了同事的村里发生的事情,还调侃了一下敏感的PARTY一下而已。

发表在 未分类 | 留下评论

[GR Starred Item]实例分析Java Class的文件结构

【感谢网友 @Krq_Tiger 投稿】

今天把之前在Evernote中的笔记重新整理了一下,发上来供对java class 文件结构的有兴趣的同学参考一下。

学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号,说“一次编写,到处运行”,其实说到无关性,Java平台还有另外一个无关 性那就是语言无关性,要实现语言无关性,那么Java体系中的class的文件结构或者说是字节码就显得相当重要了,其实Java从刚开始的时候就有两套 规范,一个是Java语言规范,另外一个是Java虚拟机规范,Java语言规范只是规定了Java语言相关的约束以及规则,而虚拟机规范则才是真正从跨 平台的角度去设计的。今天我们就以一个实际的例子来看看,到底Java中一个Class文件对应的字节码应该是什么样子。 这篇文章将首先总体上阐述一下Class到底由哪些内容构成,然后再用一个实际的Java类入手去分析class的文件结构。

在继续之前,我们首先需要明确如下几点:

1)Class文件是有8个字节为基础的字节流构成的,这些字节流之间都严格按照规定的顺序排列,并且字节之间不存在任何空隙,对于超过8个字节的数据,将按 照Big-Endian的顺序存储的,也就是说高位字节存储在低的地址上面,而低位字节存储到高地址上面,其实这也是class文件要跨平台的关键,因为 PowerPC架构的处理采用Big-Endian的存储顺序,而x86系列的处理器则采用Little-Endian的存储顺序,因此为了Class文 件在各中处理器架构下保持统一的存储顺序,虚拟机规范必须对起进行统一。

2) Class文件结构采用类似C语言的结构体来存储数据的,主要有两类数据项,无符号数和表,无符号数用来表述数字,索引引用以及字符串等,比如 u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数,而表是有多个无符号数以及其它的表组成的复合结构。可能大家看到这里 对无符号数和表到底是上面也不是很清楚,不过不要紧,等下面实例的时候,我会再以实例来解释。

明确了上面的两点以后,我们接下来后来看看Class文件中按照严格的顺序排列的字节流都具体包含些什么数据:

(上图来自The Java Virtual Machine Specification Java SE 7 Edition)

在看上图的时候,有一点我们需要注意,比如cp_info,cp_info表示常量池,上图中用 constant_pool[constant_pool_count-1]的方式来表示常量池有constant_pool_count-1个常量,它 这里是采用数组的表现形式,但是大家不要误以为所有的常量池的常量长度都是一样的,其实这个地方只是为了方便描述采用了数组的方式,但是这里并不像编程语 言那里,一个int型的数组,每个int长度都一样。明确了这一点以后,我们在回过头来看看上图中每一项都具体代表了什么含义。

1)u4 magic 表示魔数,并且魔数占用了4个字节,魔数到底是做什么的呢?它其实就是表示一下这个文件的类型是一个Class文件,而不是一张JPG图片,或者AVI的电影。而Class文件对应的魔数是0xCAFEBABE.

2)u2 minor_version 表示Class文件的次版本号,并且此版本号是u2类型的无符号数表示。

3) u2 major_version 表示Class文件的主版本号,并且主版本号是u2类型的无符号数表示。major_version和minor_version主要用来表示当前的虚拟 机是否接受当前这种版本的Class文件。不同版本的Java编译器编译的Class文件对应的版本是不一样的。高版本的虚拟机支持低版本的编译器编译的 Class文件结构。比如Java SE 6.0对应的虚拟机支持Java SE 5.0的编译器编译的Class文件结构,反之则不行。

4) u2 constant_pool_count 表示常量池的数量。这里我们需要重点来说一下常量池是什么东西,请大家不要与Jvm内存模型中的运行时常量池混淆了,Class文件中常量池主要存储了字 面量以及符号引用,其中字面量主要包括字符串,final常量的值或者某个属性的初始值等等,而符号引用主要存储类和接口的全限定名称,字段的名称以及描 述符,方法的名称以及描述符,这里名称可能大家都容易理解,至于描述符的概念,放到下面说字段表以及方法表的时候再说。另外大家都知道Jvm的内存模型中 有堆,栈,方法区,程序计数器构成,而方法区中又存在一块区域叫运行时常量池,运行时常量池中存放的东西其实也就是编译器长生的各种字面量以及符号引用, 只不过运行时常量池具有动态性,它可以在运行的时候向其中增加其它的常量进去,最具代表性的就是String的intern方法。

5)cp_info 表示常量池,这里面就存在了上面说的各种各样的字面量和符号引用。放到常量池的中数据项在The Java Virtual Machine Specification Java SE 7 Edition 中一共有14个常量,每一种常量都是一个表,并且每种常量都用一个公共的部分tag来表示是哪种类型的常量。

下面分别简单描述一下具体细节等到后面的实例 中我们再细化。

  • CONSTANT_Utf8_info      tag标志位为1,   UTF-8编码的字符串
  • CONSTANT_Integer_info  tag标志位为3, 整形字面量
  • CONSTANT_Float_info     tag标志位为4, 浮点型字面量
  • CONSTANT_Long_info     tag标志位为5, 长整形字面量
  • CONSTANT_Double_info  tag标志位为6, 双精度字面量
  • CONSTANT_Class_info    tag标志位为7, 类或接口的符号引用
  • CONSTANT_String_info    tag标志位为8,字符串类型的字面量
  • CONSTANT_Fieldref_info  tag标志位为9,  字段的符号引用
  • CONSTANT_Methodref_info  tag标志位为10,类中方法的符号引用
  • CONSTANT_InterfaceMethodref_info tag标志位为11, 接口中方法的符号引用
  • CONSTANT_NameAndType_info tag 标志位为12,字段和方法的名称以及类型的符号引用

6) u2 access_flags 表示类或者接口的访问信息,具体如下图所示:

7)u2 this_class 表示类的常量池索引,指向常量池中CONSTANT_Class_info的常量

8)u2 super_class 表示超类的索引,指向常量池中CONSTANT_Class_info的常量

9)u2 interface_counts 表示接口的数量

10)u2 interface[interface_counts]表示接口表,它里面每一项都指向常量池中CONSTANT_Class_info常量

11)u2 fields_count 表示类的实例变量和类变量的数量

12) field_info fields[fields_count]表示字段表的信息,其中字段表的结构如下图所示:

上图中access_flags表示字段的访问表示,比如字段是public,private,protect 等,name_index表示字段名 称,指向常量池中类型是CONSTANT_UTF8_info的常量,descriptor_index表示字段的描述符,它也指向常量池中类型为 CONSTANT_UTF8_info的常量,attributes_count表示字段表中的属性表的数量,而属性表是则是一种用与描述字段,方法以及 类的属性的可扩展的结构,不同版本的Java虚拟机所支持的属性表的数量是不同的。

13) u2 methods_count表示方法表的数量

14)method_info 表示方法表,方法表的具体结构如下图所示:


其中access_flags表示方法的访问表示,name_index表示名称的索引,descriptor_index表示方法的描述 符,attributes_count以及attribute_info类似字段表中的属性表,只不过字段表和方法表中属性表中的属性是不同的,比如方法 表中就Code属性,表示方法的代码,而字段表中就没有Code属性。其中具体Class中到底有多少种属性,等到Class文件结构中的属性表的时候再 说说。

15) attribute_count表示属性表的数量,说到属性表,我们需要明确以下几点:

  • 属性表存在于Class文件结构的最后,字段表,方法表以及Code属性中,也就是说属性表中也可以存在属性表
  • 属性表的长度是不固定的,不同的属性,属性表的长度是不同的

上面说完了Class文件结构中每一项的构成以后,我们以一个实际的例子来解释以下上面所说的内容。

package com.ejushang.TestClass;

public class TestClass implements Super{

private static final int staticVar = 0;

private int instanceVar=0;

public int instanceMethod(int param){
 return param+1;
 }

}

interface Super{ }

通过jdk1.6.0_37的javac 编译后的TestClass.java对应的TestClass.class的二进制结构如下图所示:

下面我们就根据前面所说的Class的文件结构来解析以下上图中字节流。

1)魔数
从Class的文件结构我们知道,刚开始的4个字节是魔数,上图中从地址00000000h-00000003h的内容就是魔数,从上图可知Class的文件的魔数是0xCAFEBABE。

2)主次版本号
接下来的4个字节是主次版本号,有上图可知从00000004h-00000005h对应的是0×0000,因此Class的minor_version 为0×0000,从00000006h-00000007h对应的内容为0×0032,因此Class文件的major_version版本为 0×0032,这正好就是jdk1.6.0不带target参数编译后的Class对应的主次版本。

3)常量池的数量
接下来的2个字节从00000008h-00000009h表示常量池的数量,由上图可以知道其值为0×0018,十进制为24个,但是对于常量池的数量 需要明确一点,常量池的数量是constant_pool_count-1,为什么减一,是因为索引0表示class中的数据项不引用任何常量池中的常 量。

4)常量池
我们上面说了常量池中有不同类型的常量,下面就来看看TestClass.class的第一个常量,我们知道每个常量都有一个u1类型的tag标识来表示 常量的类型,上图中0000000ah处的内容为0x0A,转换成二级制是10,有上面的关于常量类型的描述可知tag为10的常量是Constant_Methodref_info,而Constant_Methodref_info的结够如下图所示:

其中class_index指向常量池中类型为CONSTANT_Class_info的常量,从TestClass的二进制文件结构中可以看出 class_index的值为0×0004(地址为0000000bh-0000000ch),也就是说指向第四个常量。

name_and_type_index指向常量池中类型为CONSTANT_NameAndType_info常量。从上图可以看出name_and_type_index的值为0×0013,表示指向常量池中的第19个常量。

接下来又可以通过同样的方法来找到常量池中的所有常量。不过JDK提供了一个方便的工具可以让我们查看常量池中所包含的常量。通过javap -verbose TestClass 即可得到所有常量池中的常量,截图如下:

从上图我们可以清楚的看到,TestClass中常量池有24个常量,不要忘记了第0个常量,因为第0个常量被用来表示 Class中的数据项不引用任何常量池中的常量。从上面的分析中我们得知TestClass的第一个常量表示方法,其中class_index指向的第四 个常量为java/lang/Object,name_and_type_index指向的第19个常量值为<init>:()V,从这里可 以看出第一个表示方法的常量表示的是java编译器生成的实例构造器方法。通过同样的方法可以分析常量池的其它常量。OK,分析完常量池,我们接下来再分 析下access_flags。
5)u2 access_flags 表示类或者接口方面的访问信息,比如Class表示的是类还是接口,是否为public,static,final等。具体访问标示的含义之前已经说过 了,下面我们就来看看TestClass的访问标示。Class的访问标示是从0000010dh-0000010e,期值为0×0021,根据前面说的 各种访问标示的标志位,我们可以知道:0×0021=0×0001|0×0020 也即ACC_PUBLIC 和 ACC_SUPER为真,其中ACC_PUBLIC大家好理解,ACC_SUPER是jdk1.2之后编译的类都会带有的标志。

6)u2 this_class 表示类的索引值,用来表示类的全限定名称,类的索引值如下图所示:

从上图可以清楚到看到,类索引值为0×0003,对应常量池的第三个常量,通过javap的结果,我们知道第三个常量为 CONSTANT_Class_info类型的常量,通过它可以知道类的全限定名称为:com/ejushang/TestClass /TestClass

7)u2 super_class 表示当前类的父类的索引值,索引值所指向的常量池中类型为CONSTANT_Class_info的常量,父类的索引值如下图所示,其值为0×0004, 查看常量池的第四个常量,可知TestClass的父类的全限定名称为:java/lang/Object

8)interfaces_count和  interfaces[interfaces_count]表示接口数量以及具体的每一个接口,TestClass的接口数量以及接口如下图所示,其中 0×0001表示接口数量为1,而0×0005表示接口在常量池的索引值,找到常量池的第五个常量,其类型为CONSTANT_Class_info,其 值为:com/ejushang/TestClass/Super

9)fields_count 和 field_info, fields_count表示类中field_info表的数量,而field_info表示类的实例变量和类变量,这里需要注意的是 field_info不包含从父类继承过来的字段,field_info的结构如下图所示:

其中access_flags表示字段的访问标示,比如public,private,protected,static,final等,access_flags的取值如下图所示:

其中name_index 和 descriptor_index都是常量池的索引值,分别表示字段的名称和字段的描述符,字段的名称容易理解,但是字段的描述符如何理解呢?其实在JVM 规范中,对于字段的描述符规定如下图所示:

其中大家需要关注一下上图最后一行,它表示的是对一维数组的描述符,对于String[][]的描述符将是[[ Ljava/lang/String,而对于int[][]的描述符为[[I。接下来的attributes_count以及 attribute_info分别表示属性表的数量以及属性表。下面我们还是以上面的TestClass为例,来看看TestClass的字段表吧。

首先我们来看一下字段的数量,TestClass的字段的数量如下图所示:

从上图中可以看出TestClass有两个字段,查看TestClass的源代码可知,确实也只有两个字段,接下来我们看看第一个字段,我们知道第一个字段应该为private int staticVar,它在Class文件中的二进制表示如下图所示:


其中0x001A表示访问标示,通过查看access_flags表可知,其为ACC_PRIVATE,ACC_STATIC,ACC_FINAL,接下 来0×0006和0×0007分别表示常量池中第6和第7个常量,通过查看常量池可知,其值分别为:staticVar和I,其中staticVar为字 段名称,而I为字段的描述符,通过上面对描述符的解释,I所描述的是int类型的变量,接下来0×0001表示staticVar这个字段表中的属性表的 数量,从上图可以staticVar字段对应的属性表有1个,0×0008表示常量池中的第8个常量,查看常量池可以得知此属性为 ConstantValue属性,而ConstantValue属性的格式如下图所示:

其中attribute_name_index表述属性名的常量池索引,本例中为ConstantValue,而ConstantValue的 attribute_length固定长度为2,而constantValue_index表示常量池中的引用,本例中,其中为0×0009,查看第9个 常量可以知道,它表示一个类型为CONSTANT_Integer_info的常量,其值为0。

上面说完了private static final int staticVar=0,下面我们接着说一下TestClass的private int instanceVar=0,在本例中对instanceVar的二进制表示如下图所示:


其中0×0002表示访问标示为ACC_PRIVATE,0x000A表示字段的名称,它指向常量池中的第10个常量,查看常量池可以知道字段名称为 instanceVar,而0×0007表示字段的描述符,它指向常量池中的第7个常量,查看常量池可以知道第7个常量为I,表示类型为 instanceVar的类型为I,最后0×0000表示属性表的数量为0.

10)methods_count 和 method_info ,其中methods_count表示方法的数量,而method_info表示的方法表,其中方法表的结构如下图所示:

从上图可以看出method_info和field_info的结构是很类似的,方法表的access_flag的所有标志位以及取值如下图所示:

其中name_index和descriptor_index表示的是方法的名称和描述符,他们分别是指向常量池的索引。这里需要结解释一下方法的描述 符,方法的描述符的结构为:(参数列表)返回值,比如public int instanceMethod(int param)的描述符为:(I)I,表示带有一个int类型参数且返回值也为int类型的方法,接下来就是属性数量以及属性表了,方法表和字段表虽然都有 属性数量和属性表,但是他们里面所包含的属性是不同。接下来我们就以TestClass来看一下方法表的二进制表示。首先来看一下方法表数量,截图如下:


从上图可以看出方法表的数量为0×0002表示有两个方法,接下来我们来分析第一个方法,我们首先来看一下TestClass的第一个方法的access_flag,name_index,descriptor_index,截图如下:


从上图可以知道access_flags为0×0001,从上面对access_flags标志位的描述,可知方法的access_flags的取值为 ACC_PUBLIC,name_index为0x000B,查看常量池中的第11个常量,知道方法的名称为<init>,0x000C表示 descriptor_index表示常量池中的第12常量,其值为()V,表示<init>方法没有参数和返回值,其实这是编译器自动生成 的实例构造器方法。接下来的0×0001表示<init>方法的方法表有1个属性,属性截图如下:

从上图可以看出0x000D对应的常量池中的常量为Code,表示的方法的Code属性,所以到这里大家应该明白方法的那些代码是存储在Class文件方法表中的属性表中的Code属性中。接下来我们在分析一下Code属性,Code属性的结构如下图所示:

其中attribute_name_index指向常量池中值为Code的常量,attribute_length的长度表示Code属性表的长度(这里 需要注意的时候长度不包括attribute_name_index和attribute_length的6个字节的长度)。

max_stack表示最大栈深度,虚拟机在运行时根据这个值来分配栈帧中操作数的深度,而max_locals代表了局部变量表的存储空间。

max_locals的单位为slot,slot是虚拟机为局部变量分配内存的最小单元,在运行时,对于不超过32位类型的数据类型,比如 byte,char,int等占用1个slot,而double和Long这种64位的数据类型则需要分配2个slot,另外max_locals的值并 不是所有局部变量所需要的内存数量之和,因为slot是可以重用的,当局部变量超过了它的作用域以后,局部变量所占用的slot就会被重用。

code_length代表了字节码指令的数量,而code表示的时候字节码指令,从上图可以知道code的类型为u1,一个u1类型的取值为0×00-0xFF,对应的十进制为0-255,目前虚拟机规范已经定义了200多条指令。

exception_table_length以及exception_table分别代表方法对应的异常信息。

attributes_count和attribute_info分别表示了Code属性中的属性数量和属性表,从这里可以看出Class的文件结构中,属性表是很灵活的,它可以存在于Class文件,方法表,字段表以及Code属性中。

接下来我们继续以上面的例子来分析一下,从上面init方法的Code属性的截图中可以看出,属性表的长度为0×00000026,max_stack的 值为0×0002,max_locals的取值为0×0001,code_length的长度为0x0000000A,那么00000149h- 00000152h为字节码,接下来exception_table_length的长度为0×0000,而attribute_count的值为 0×0001,00000157h-00000158h的值为0x000E,它表示常量池中属性的名称,查看常量池得知第14个常量的值为 LineNumberTable,LineNumberTable用于描述java源代码的行号和字节码行号的对应关系,它不是运行时必需的属性,如果通 过-g:none的编译器参数来取消生成这项信息的话,最大的影响就是异常发生的时候,堆栈中不能显示出出错的行号,调试的时候也不能按照源代码来设置断 点,接下来我们再看一下LineNumberTable的结构如下图所示:

其中attribute_name_index上面已经提到过,表示常量池的索引,attribute_length表示属性长度,而start_pc和 line_number分表表示字节码的行号和源代码的行号。本例中LineNumberTable属性的字节流如下图所示:

上面分析完了TestClass的第一个方法,通过同样的方式我们可以分析出TestClass的第二个方法,截图如下:

其中access_flags为0×0001,name_index为0x000F,descriptor_index为0×0010,通过查看常量池可 以知道此方法为public int instanceMethod(int param)方法。通过和上面类似的方法我们可以知道instanceMethod的Code属性为下图所示:

最后我们来分析一下,Class文件的属性,从00000191h-00000199h为Class文件中的属性表,其中0×0011表示属性的名称,查看常量池可以知道属性名称为SourceFile,我们再来看看SourceFile的结构如下图所示:

其中attribute_length为属性的长度,sourcefile_index指向常量池中值为源代码文件名称的常量,在本例中SourceFile属性截图如下:


其中attribute_length为0×00000002表示长度为2个字节,而soucefile_index的值为0×0012,查看常量池的第18个常量可以知道源代码文件的名称为TestClass.java

最后,希望对技术感兴趣的朋友多交流。个人微博:(http://weibo.com/xmuzyq)

(全文完)

(转载本站文章请注明作者和出处 酷壳 – CoolShell.cn ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 以支持公益事业 ===——

相关文章

==============华丽的分割线==============
via 酷壳 – CoolShell.cn 链接

发表在 starred | 留下评论

[GR Starred Item]《蒋介石与现代中国的奋斗》读后感

去年暑假,我读完了美国历史学家陶涵(Jay Taylor)的《蒋经国传》。这一周,又读完了他的后一部作品《蒋介石与现代中国的奋斗》

我读的是台湾2010年的版本,但是该书已经于2012年由中信出版社引进了(书名改为《蒋介石与现代中国》)。

这本书能够在大陆出版(不管删节了多少字),是我们国家的一大进步。因为它全面地为蒋介石辩护,完全把蒋介石写成了一个正面人物,与官方的历史观截然相反。

当然,它不是那种歌功颂德式的文字,而是以各种历史资料为依据,试图做出尽可能客观的结论。请先看我做的笔记,该书对一些历史问题是怎么解释的。

=================================

Q:为什么国民党内部从一开始就派系林立?

北伐时期,蒋介石对投降的军阀来者不拒,把后续的政治问题留到以后再头痛。日后这将使他吃尽苦头,但在当时,他觉得自己别无他计。

另一方面,各路军阀佯称接受编遣,可是实际上牢牢控制自己的地盘不放。蒋介石觉悟到,他唯一的办法就是挑拨军阀互斗。

Q:为什么中共能建立自己的武装?

(1)蒋介石专注于北伐以及后来与军阀的冲突,无暇顾及中共;(2)军阀战争创造出成群游荡的逃兵溃卒,这些人极易被中共吸收;(3)国民党派系内讧消耗掉中央政府的力量,使中共在苏联金钱及武器的援助下有足够的时间去扩张。

Q:为什么抗战初期,国军不用游击战打击日军?

从一开始就打游击战或机动战,就长期而言或许是较好的策略,但中国初期可能会丢掉更多城市,而且必然会引发蒋介石避免与入侵者奋战的指控。

Q:为什么抗战对中共有利,对国民党不利?

1940年1月,周恩来给斯大林的秘密报告提到,中国有百万以上的士兵阵亡或受伤。他说,在这个数字当中,八路军伤亡仅有3万人、新四军则为1000人。对日作战大量消耗了蒋介石的兵力,而共产党则一直在积极扩充实力,苏联更是用各种手段支持共产党。

Q:为什么发生皖南事变?

项英领导的新四军违反国共协议,跑到长江北岸扩张地盘,日军打得少,国军打得多。屡次命令无效,蒋介石迫于无奈,下令歼灭新四军。

Q:为什么美军司令史迪威看不起蒋介石?

61岁的史迪威,4年前还只是个任职多年、没有实战经验的上校,现在却幻想成为中国实质上最有权力的人,僭取蒋介石之大位。他过度简化复杂的问题,低估中国人性格的复杂,在评断别人时严厉无情,甚至是恶毒的。在史迪威心目中,蒋根本没有价值、没有治国技能、没有将才、除了为个人集权,对中国的现代化和福祉毫无兴趣。

Q:为什么国共在重庆无法达成和平协议?

中共要求与国民党平等地位,且可以不受限制地在控制区内扩张或垄断政治权力。而国民党绝不接受与任何一个武装政党组成联合政府,要求中共的军队必须先编入政府军,然后再建立新政府。

Q:为什么国民党发动内战?

1946年1月18日,尽管规定国共双方停火,共军仍在东北抢占两座城市,其中之一是重要的港口营口。

Q:为什么国民党无法在东北获胜?

共产党在东北的兵力占优,控制了约90%的乡村地区,而且有苏联的支持,所以东北无法守住。

Q:为什么国民党守不住华北?

失去了东北,华北就没有了屏障,山东的港口也很快会失守。而且,国军在东北投入庞大兵力,全力求胜,导致没有力量在华北防卫。

Q:为什么国民党没有早一点退回关内?

多年之后,蒋说他未能早点退回关内,是因为”将领反对、民意、政治、经济、社会、与外交影响,尤其是东北与华北之民意代表请愿与哭诉,更使无法实行”。

Q:为什么国民党失去民心?

(1)发生恶性通货膨胀。法币的兑换价,从1945年8月的1500元法币兑换1美元,巨幅降到1948年1月的18万元法币兑换1美元。对外贸易已几乎停摆。(2)民间已意识到国民政府快要撑不下去了,大洪水将至,人人都想起码再抓住点什么,贪腐一发不可收拾。(3)那些保守的中国商人,在1945年仍是国民政府的死忠支持者,但是现在却感觉在中共清廉的集权统治下生活,相较之下,不会比国民党无能的贪污统治差。

Q:为什么说蒋介石没有镇压学潮?

警察打杀游行的学生后,蒋介石立刻退却,放弃”强硬”政策。他发表个人谈话,谴责警方的行动;当被捕学生的家长申请人身保护令时,他几乎把他们全都释放。此外,武汉卫戍司令部侦查队队长被发现在长江淹死,显然是自杀。

Q:为什么蒋介石容忍国民党的严重腐败?

认真矫正高级官员的腐败问题,恐怕会产生复杂的政治问题。蒋介石认为,革除腐败还可以继续搁置一段时间,等到真正的政治团结达成之后再来处理也不迟。

Q:为什么蒋介石不进行改革?

有一次,蒋介石告诉美国大使司徒雷登,他真的希望改革,但是不知如何着手。

Q:为什么蒋介石不放过张学良和杨虎城?

蒋介石逃离重庆的时候,下令枪决杨虎城父子、一个女儿、一个秘书及这个秘书的妻子。因为张学良让蒋介石误信,杨虎城是西安事变的主谋。蒋介石至死都相信若没发生西安事变,中国历史将会完全不同。

Q:为什么国民党能在台湾站住脚跟?

1949年夏季,国民党在台湾开展大规模土地改革,将日本人占据的土地,以低价卖给台湾贫农。同时,政府亦实施大幅减租。很快的,这些初步改革替国民党争取到台湾农民真正的政治支持,也替下一阶段的加强工作奠定了基础。这是国民党首次在农村地区赢得群众的支持。

Q:为什么蒋介石信仰基督教不值得批评?

这带来的问题绝对不比毛泽东拥抱外来意识形态带来的问题多。

Q:为什么国民党在台湾的表现好于在大陆的表现?

过去40年让蒋介石头痛的老军阀,到了台湾几乎都不再掌握部队,在党、政部门也完全没有权力。一度权势如日中天的宋氏家族,也都移居了美国。蒋介石终于第一次摆脱了国民党内的派系,对党政军有了完全的控制。

1959年,蒋介石在接见美国众议员周以德时说,从历史的角度来看,失去大陆或许是天意。在台湾的20年,使他有机会在不受中共的干扰下,向全世界展示,一个未开发国家真正迈向进步的正确途径。他说,在台湾的一切成绩,绝对无法在大陆完成,因为有来自中共的压力干扰。

Q:蒋介石做错了什么?

他对多党制的民主制度并不相信,对真诚和道德的信念也互相矛盾。他批准过非常可怕的极端行动,使大量的人民丧生。

Q:蒋介石怎么评价自己?

蒋介石从来没有替自己的极端行动辩解过。如果被逼问,他或许会指说这是时代的不幸,数百万人丧生、数亿人受苦,套用他的话:中国文明存亡绝续系于此一大战。当然,他的那些最无情的决定也帮助他掌握权位;对此,他可能会声称:他受民之托,必须竭尽所能,领导国家为生存和统一而奋斗。

Q:我们应该怎么评价蒋介石?

蒋介石在今人难以想像的艰困条件下,对付军阀,完成统一;内抗中共、外御日本。漫长的抗战,初期仰赖苏联援助,后期则仰承史迪威鼻息而勉强顶住大局。虽然丢失了大陆,但是最终为台湾的经济繁荣打下基础,在他的统治时期,台湾人的生活水平有了长足的提高。

=======================================

读完上面这些内容,我陷入了深深的困惑。

从小我受到的教育就是,蒋介石是一个镇压人民、贪污腐化、愚蠢无能、刚愎自用的独裁者。可是这本书却把蒋介石写成了一个为了民族利益,在困境中不懈奋斗、以妥协求发展的爱国者。到底哪一个才是真实的蒋介石?

我不敢下结论。各方出于自己的目的,都对历史进行歪曲和编造,我不知道信任谁。

历史学家黄仁宇在回忆录中曾经说,他相信对蒋介石评价的最低点已经过去了,以后会逐步上升。而周恩来被美国记者问到,如何评价法国革命(French Revolution)时,曾经说过一句有名的话,”现在下结论还太早”(too early to say)。

好在蒋介石有写日记的习惯,他的1915年到1972年的日记,现在都保存在美国胡佛图书馆。等到日记完全公开出版的那一天,也许才是可以对他进行公正评价的时候。

(完)

文档信息

==============华丽的分割线==============
via 阮一峰的网络日志 链接

发表在 starred | 留下评论

[GR Starred Item]sed 简明教程

awk于1977年出生,今年36岁本命年,sed比awk大2-3岁,awk就像林妹妹,sed就是宝玉哥哥了。所以 林妹妹跳了个Topless,他的哥哥sed坐不住了,也一定要出来抖一抖。

sed全名叫stream editor,流编辑器,用程序的方式来编辑文本,相当的hacker啊。sed基本上就是玩正则模式匹配,所以,玩sed的人,正则表达式一般都比较强。

同样,本篇文章不会说sed的全部东西,你可以参看sed的手册,我这里主要还是想和大家竞争一下那些从手机指缝间或马桶里流走的时间,用这些时间来学习一些东西。当然,接下来的还是要靠大家自己双手。

用s命令替换

我使用下面的这段文本做演示:

$ cat pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam

把其中的my字符串替换成Hao Chen’s,下面的语句应该很好理解(s表示替换命令,/my/表示匹配my,/Hao Chen’s/表示把匹配替换成Hao Chen’s,/g 表示一行上的替换所有的匹配):

$ sed "s/my/Hao Chen's/g" pets.txt
This is Hao Chen's cat
  Hao Chen's cat's name is betty
This is Hao Chen's dog
  Hao Chen's dog's name is frank
This is Hao Chen's fish
  Hao Chen's fish's name is george
This is Hao Chen's goat
  Hao Chen's goat's name is adam

注意:如果你要使用单引号,那么你没办法通过\’这样来转义,就有双引号就可以了,在双引号内可以用\”来转义。

再注意:上面的sed并没有对文件的内容改变,只是把处理过后的内容输出,如果你要写回文件,你可以使用重定向,如:

$ sed "s/my/Hao Chen's/g" pets.txt > hao_pets.txt

或使用 -i 参数直接修改文件内容:

$ sed -i "s/my/Hao Chen's/g" pets.txt

在每一行最前面加点东西:

$ sed 's/^/#/g' pets.txt
#This is my cat
#  my cat's name is betty
#This is my dog
#  my dog's name is frank
#This is my fish
#  my fish's name is george
#This is my goat
#  my goat's name is adam

在每一行最后面加点东西:

$ sed 's/$/ --- /g' pets.txt
This is my cat ---
  my cat's name is betty ---
This is my dog ---
  my dog's name is frank ---
This is my fish ---
  my fish's name is george ---
This is my goat ---
  my goat's name is adam ---

顺手介绍一下正则表达式的一些最基本的东西:

  • ^ 表示一行的开头。如:/^#/ 以#开头的匹配。
  • $ 表示一行的结尾。如:/}$/ 以}结尾的匹配
  • \< 表示词首。 如 \ 表示词尾。 如 abc\> 表示以 abc 結尾的詞.
  • . 表示任何单个字符。
  • * 表示某个字符出现了0次或多次。
  • [ ] 字符集合。 如:[abc]表示匹配a或b或c,还有[a-zA-Z]表示匹配所有的26个字符。如果其中有^表示反,如[^a]表示非a的字符

正规则表达式是一些很牛的事,比如我们要去掉某html中的tags:


<b>This</b> is what <span style="text-decoration: underline;">I</span> meant. Understand?

看看我们的sed命令

# 如果你这样搞的话,就会有问题
$ sed 's/<.*>//g' html.txt
 Understand?

# 要解决上面的那个问题,就得像下面这样。
# 其中的'[^>]' 指定了除了>的字符重复0次或多次。
$ sed 's/<[^>]*>//g' html.txt
This is what I meant. Understand?

我们再来看看指定需要替换的内容:

$ sed "3s/my/your/g" pets.txt
This is my cat
  my cat's name is betty
This is your dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam

下面的命令只替换第3到第6行的文本。

$ sed "3,6s/my/your/g" pets.txt
This is my cat
  my cat's name is betty
This is your dog
  your dog's name is frank
This is your fish
  your fish's name is george
This is my goat
  my goat's name is adam
$ cat my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

只替换每一行的第一个s:

$ sed 's/s/S/1' my.txt
ThiS is my cat, my cat's name is betty
ThiS is my dog, my dog's name is frank
ThiS is my fish, my fish's name is george
ThiS is my goat, my goat's name is adam

只替换每一行的第二个s:

$ sed 's/s/S/2' my.txt
This iS my cat, my cat's name is betty
This iS my dog, my dog's name is frank
This iS my fish, my fish's name is george
This iS my goat, my goat's name is adam

只替换第一行的第3个以后的s:

$ sed 's/s/S/3g' my.txt
This is my cat, my cat'S name iS betty
This is my dog, my dog'S name iS frank
This is my fiSh, my fiSh'S name iS george
This is my goat, my goat'S name iS adam

多个匹配

如果我们需要一次替换多个模式,可参看下面的示例:(第一个模式把第一行到第三行的my替换成your,第二个则把第3行以后的This替换成了That)

$ sed '1,3s/my/your/g; 3,$s/This/That/g' my.txt
This is your cat, your cat's name is betty
This is your dog, your dog's name is frank
That is your fish, your fish's name is george
That is my goat, my goat's name is adam

上面的命令等价于:(注:下面使用的是sed的-e命令行参数)

sed -e '1,3s/my/your/g' -e '3,$s/This/That/g' my.txt

我们可以使用&来当做被匹配的变量,然后可以在基本左右加点东西。如下所示:

$ sed 's/my/[&]/g' my.txt
This is [my] cat, [my] cat's name is betty
This is [my] dog, [my] dog's name is frank
This is [my] fish, [my] fish's name is george
This is [my] goat, [my] goat's name is adam

圆括号匹配

使用圆括号匹配的示例:(圆括号括起来的正则表达式所匹配的字符串会可以当成变量来使用,sed中使用的是\1,\2…)

$ sed 's/This is my \([^,]*\),.*is \(.*\)/\1:\2/g' my.txt
cat:betty
dog:frank
fish:george
goat:adam

上面这个例子中的正则表达式有点复杂,解开如下(去掉转义字符):

正则为:This is my ([^,]*),.*is (.*)
匹配为:This is my (cat),……….is (betty)

然后:\1就是cat,\2就是betty

sed的命令

让我们回到最一开始的例子pets.txt,让我们来看几个命令:

N命令

先来看N命令 —— 把下一行的内容纳入当成缓冲区做匹配。

下面的的示例会把原文本中的偶数行纳入奇数行匹配,而s只匹配并替换一次,所以,就成了下面的结果:

$ sed 'N;s/my/your/' pets.txt
This is your cat
  my cat's name is betty
This is your dog
  my dog's name is frank
This is your fish
  my fish's name is george
This is your goat
  my goat's name is adam

也就是说,原来的文件成了:

This is my cat\n  my cat's name is betty
This is my dog\n  my dog's name is frank
This is my fish\n  my fish's name is george
This is my goat\n  my goat's name is adam

这样一来,下面的例子你就明白了,

$ sed 'N;s/\n/,/' pets.txt
This is my cat,  my cat's name is betty
This is my dog,  my dog's name is frank
This is my fish,  my fish's name is george
This is my goat,  my goat's name is adam
a命令和i命令

a命令就是append, i命令就是insert,它们是用来添加行的。如:

# 其中的1i表明,其要在第1行前插入一行(insert)
$ sed "1 i This is my monkey, my monkey's name is wukong" my.txt
This is my monkey, my monkey's name is wukong
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

# 其中的1a表明,其要在最后一行后追加一行(append)
$ sed "$ a This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my monkey, my monkey's name is wukong
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

我们可以运用匹配来添加文本:

# 注意其中的/fish/a,这意思是匹配到/fish/后就追加一行
$ sed "/fish/a This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my monkey, my monkey's name is wukong
This is my goat, my goat's name is adam

下面这个例子是对每一行都挺插入:

$ sed "/my/a ----" my.txt
This is my cat, my cat's name is betty
----
This is my dog, my dog's name is frank
----
This is my fish, my fish's name is george
----
This is my goat, my goat's name is adam
----
c命令

c 命令是替换匹配行

$ sed "2 c This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my monkey, my monkey's name is wukong
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

$ sed "/fish/c This is my monkey, my monkey's name is wukong" my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my monkey, my monkey's name is wukong
This is my goat, my goat's name is adam
d命令

删除匹配行

$ sed '/fish/d' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my goat, my goat's name is adam

$ sed '2d' my.txt
This is my cat, my cat's name is betty
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

$ sed '2,$d' my.txt
This is my cat, my cat's name is betty
p命令

打印命令

你可以把这个命令当成grep式的命令

# 匹配fish并输出,可以看到fish的那一行被打了两遍,
# 这是因为sed处理时会把处理的信息输出
$ sed '/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george
This is my fish, my fish's name is george
This is my goat, my goat's name is adam

# 使用n参数就好了
$ sed -n '/fish/p' my.txt
This is my fish, my fish's name is george

# 从一个模式到另一个模式
$ sed -n '/dog/,/fish/p' my.txt
This is my dog, my dog's name is frank
This is my fish, my fish's name is george

#从第一行打印到匹配fish成功的那一行
$ sed -n '1,/fish/p' my.txt
This is my cat, my cat's name is betty
This is my dog, my dog's name is frank
This is my fish, my fish's name is george

几个知识点

好了,下面我们要介绍四个sed的基本知识点:

Pattern Space

第零个是关于-n参数的,大家也许没看懂,没关系,我们来看一下sed处理文本的伪代码,并了解一下Pattern Space的概念:

foreach line in file {
    //放入把行Pattern_Space
    Pattern_Space <= line;

    // 对每个pattern space执行sed命令
    Pattern_Space <= EXEC(sed_cmd, Pattern_Space);

    // 如果没有指定 -n 则输出处理后的Pattern_Space
    if (sed option hasn't "-n")  {
       print Pattern_Space
    }
}
Address

第一个是关于address,几乎上述所有的命令都是这样的(注:其中的!表示匹配成功后是否执行命令)

[address[,address]][!]{cmd}

address可以是一个数字,也可以是一个模式,你可以通过逗号要分隔两个address 表示两个address的区间,参执行命令cmd,伪代码如下:

bool bexec = false
foreach line in file {
    if ( match(address1) ){
        bexec = true;
    }

    if ( bexec == true) {
        EXEC(sed_cmd);
    }

    if ( match (address2) ) {
        bexec = false;
    }
}

关于address可以使用相对位置,如:

# 其中的+3表示后面连续3行
$ sed '/dog/,+3s/^/# /g' pets.txt
This is my cat
  my cat's name is betty
# This is my dog
#   my dog's name is frank
# This is my fish
#   my fish's name is george
This is my goat
  my goat's name is adam
命令打包

第二个是cmd可以是多个,它们可以用分号分开,可以用大括号括起来作为嵌套命令。下面是几个例子:

$ cat pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
This is my fish
  my fish's name is george
This is my goat
  my goat's name is adam

# 对3行到第6行,执行命令/This/d
$ sed '3,6 {/This/d}' pets.txt
This is my cat
  my cat's name is betty
  my dog's name is frank
  my fish's name is george
This is my goat
  my goat's name is adam

# 对3行到第6行,匹配/This/成功后,再匹配/fish/,成功后执行d命令
$ sed '3,6 {/This/{/fish/d}}' pets.txt
This is my cat
  my cat's name is betty
This is my dog
  my dog's name is frank
  my fish's name is george
This is my goat
  my goat's name is adam

# 从第一行到最后一行,如果匹配到This,则删除之;如果前面有空格,则去除空格
$ sed '1,${/This/d;s/^ *//g}' pets.txt
my cat's name is betty
my dog's name is frank
my fish's name is george
my goat's name is adam 
Hold Space

第三个我们再来看一下 Hold Space

接下来,我们需要了解一下Hold Space的概念,我们先来看四个命令:

g: 将hold space中的内容拷贝到pattern space中,原来pattern space里的内容清除
G: 将hold space中的内容append到pattern space\n后
h: 将pattern space中的内容拷贝到hold space中,原来的hold space里的内容被清除
H: 将pattern space中的内容append到hold space\n后
x: 交换pattern space和hold space的内容

这些命令有什么用?我们来看两个示例吧,用到的示例文件是:

$ cat t.txt
one
two
three

第一个示例:

$ sed 'H;g' t.txt
one

one
two

one
two
three

是不是有点没看懂,我作个图你就看懂了。

第二个示例,反序了一个文件的行:

$ sed '1!G;h;$!d' t.txt
three
two
one

其中的 ’1!G;h;$!d’ 可拆解为三个命令

  • 1!G —— 只有第一行不执行G命令,将hold space中的内容append回到pattern space
  • h —— 第一行都执行h命令,将pattern space中的内容拷贝到hold space中
  • $!d —— 除了最后一行不执行d命令,其它行都执行d命令,删除当前行

这个执行序列很难理解,做个图如下大家就明白了:

就先说这么多吧,希望对大家有用。

(全文完)

(转载本站文章请注明作者和出处 酷壳 – CoolShell.cn ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 以支持公益事业 ===——

相关文章

==============华丽的分割线==============
via 酷壳 – CoolShell.cn 链接

发表在 starred | 留下评论