精华内容
下载资源
问答
  • 关系模式规范化

    万次阅读 多人点赞 2016-09-29 13:27:42
    原文路径:...了解关系模式规范化的作用 掌握第一范式-重点 掌握第二范式-重点 掌握第三范式-重点 回顾关系

    原文路径:http://wenku.baidu.com/link?url=KzN3v3voao4ovz_izsiujMN8vMZMiArzgYbrue8tVL5L_ydc1B_6_1eEy7vfoSiO2-ORTXAXzVBxzWAaXJxiR68z6g4GMD_1AlZWzx46MSG

     

    了解关系模式规范化的作用

    掌握第一范式-重点

    掌握第二范式-重点

    掌握第三范式-重点

    回顾关系模式

    关系模式:关系模式相当于一张二维表的框架,在这个框架下填入数据,称为关系模式的一个实例,或者叫关系(R)

    R(A1,A2,A3..Ai):R是关系名,Ai是关系的属性名。一个关系名对应一张表,关系名对应表名,属性对应表中的列名。

    关系模型的简化表示法: R<U,F>

     

    关系模式规范化的作用

    关系型数据库的设计主要是关系模式的设计。关系模式设计的好坏直接影响关系型数据库设计的成败。将关系模式规范化是设计好关系型数据库的唯一途径。

    关系模式的规范化主要有范式来完成。

     

    关系模式

    所谓范式(Normal Form, NF)是指规范化的关系模式。由规范化程度不同而产生不同的范式。根据满足条件不同,经常称某一关系模式R为“第几模式”。

     

    为什么要设计规范化的数据库?

    未经规范化的数据库一般都有下述缺点:

    较大的数据冗余,数据一致性差,数据修改复杂,对表进行更新,插入,删除是会报异常。规范化的作用就在于尽量去除冗余,使数据保持一致,使数据修改简单,除去在表中进行插入、删除时产生的异常,规范化后的表一般都较小。

     

    第一范式(1NF)

    在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系型数据库。

    定义:在关系模型中的每一个具体关系R中,如果每个属性都是不可再分的,则称R属于第一范式(1NF),记作R属于1NF。

    第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。

    e.g.如下的数据库表是符合第一范式的:

    字段1   字段2   字段3   字段4

    而这样的数据库表是不符合第一范式的:

    字段1   字段2   字段3(字段3.1,字段3.2)   字段4

    如职工号,姓名,电话号码组成一个表(一个人可能有一个办公室电话和一个家里的电话号码)规范成为1NF。

    总结:不能有重复的列,列不可再分。

    不满足第一范式条件的关系为非范式关系,在关系数据库中,凡非范式关系必须要化成范式关系。

     

    第二范式(2NF)

    第二范式是在第一范式的基础上建立起来的,即满足第二范式必须先满足第一范式(1NF)。

    定义:如果关系模式R属于1NF,且每一个非主属性都完全依赖于主码,则称关系R是属于第二范式的,记作R属于2NF。

    第二范式(2NF)说明:要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖于主关键字的一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。

    e.g.

    假定选修课关系表为SelectCourse(学号,姓名,年龄,课程名称,成绩,学分),关键字为组合关键字(学号,课程名称),因为存在如下决定关系:

    (学号,课程名称)->(姓名,年龄,成绩,学分)

    这个数据库表不满足第二范式,因为存在如下决定关系:

    (课程名称)->(学分)

    (学号)->(姓名,年龄)

    即存在学分和姓名,年龄部分依赖于主关键字。

    由于不符合2NF,这个选课关系表会存在如下问题:

    (1)数据冗余:

    同一门课程会有N个学生选修,“学分”就会重复N-1次;同一个学生选修了M门课程,那姓名和年龄会重复M-1次。

    (2)更新异常:

    若课程的学分更新,那必须把表中所有的学分值都更新,不然会出现同一课程出现不同的学分。

    (3)插入异常:

    假设要开设一门新的课程,但是目前还没有学生选修这门课程,由于没有学号导致数据无法录入到数据库中。

    (4)删除异常:

    假设一批学生已经完成课程的选修,这些选修记录就应该从数据库中删除,但是,同时,课程名称和学分信息也被删除了。很显然,这也会导致插入异常。

    所以我们将设计修改了一下,把选课关系表SelectCourse改为如下三个表:

    学生:Student(学号,姓名,年龄)

    课程:Course(课程名称,学分)

    选课关系:SelectCourse(学号,课程名称,成绩)

    这样的数据库表是符合第二范式的,消除了数据冗余,更新,插入,删除异常。

    注:所有的单关键字的数据库表都符合第二范式,因为不可能存在组合关键字,也就不可能存在非主属性部分依赖于主关键字了。

     

    第三范式

    定义:如果关系模式R属于2NF,并且R中的非主属性不传递依赖与R的主码,则称关系R是属于第三范式的。(个人总结,非主属性必须直接依赖于主码,不能存在通过其他非主属性传递依赖于主码)

    所谓传递依赖,就是A依赖于B,B依赖于C,则A传递依赖于C。

    因此,满足第三范式的数据库表应该不存在如下依赖关系:

    关键字段->非关键字段x->非关键字段y

    e.g.

    假定学生关系表为Student(学号,姓名,年龄,所在学院,学院地点,学院电话),关键字为单一的学号,所以肯定符合第二范式,但是因为存在非关键字学院地点和学院电话依赖于所在学院,即传递依赖于学号,所以此关系表不符合第三范式。同样会导致数据冗余,DDL操作异常等问题。

    所以我们可以对其进行修改:

    学生:(学号,姓名,年龄,所在学院)

    学院:(学院,地点,电话)

    这样的数据库表就符合第三范式了。

     

    总结:

    a. 规范化目的是使结构更合理,消除存储异常,减少数据冗余,便于插入,删除,更新。

    b. 原则:遵从概念单一化“一事一地”原则,即一个关系模式描述一个实体或实体建的一种联系。

    c. 方法:将关系模式投影,分解成两个或两个以上的关系模式。

    d. 分解后的关系模式集合应当与原关系模式保持等价关系,即通过自然联接可以恢复原关系而不丢失信息,并保持属性间合理的联系。

    注意:一个关系模式结合分解可以得到不同关系模式集合,也就是说分解方法不是唯一的。最小冗余的要求必须以分解后的数据库能够表达原来数据库所有信息为前提来实现的。其根本膜表是节省存储空间,避免数据不一致性,提供对关系的操作效率,同事满足应用需求。实际上,并不一定要求全部模式都达到BCNF不可。有时候故意保留部分冗余可能更方便数据查询。尤其对于那些更新频度不高,查询频率极高的数据库系统更是如此。在关系数据库中,除了函数依赖之外还有多值依赖,联接依赖的问题,从而提出了第四范式,第五范式等更高一级的规范化要求。

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 数据库关系模式规范化

    千次阅读 2021-04-03 15:23:11
    在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即...

    前言:

    在关系型数据库中,规范化理论成为范式。范式是符合某一级别的关系模式集合。关系数据库中的关系必须满足一定的要求。即满足不同的范式。在关系数据库原理中规定了以下几种范式:第一范式,第二范式,第三范式,Boyee-Codd范式,第四范式,第五范式和第六范式,在进行关系数据库设计时,至少要符合1NF的要求,在1NF的基础上进一步满足就是2NF,一般来说,只需满足第三范式就行了。

    1、第一范式(1NF)

    在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。

    2、第二范式(2NF)

    **第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。
    第二范式要求数据库表中的每个实例或行必须能被唯一地区分。在第二范式中,要求实体的属性完全依赖于主关键字。所谓完全依赖,是指不能存在仅依赖主关键字一部分的属性,如果存在依赖关键字一部分的属性,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。简单地说,第二范式就是属性完全依赖于主关键字。

    3、第三范式(3NF)
    **满足第三范式(3NF)必须先满足第二范式(2NF)。
    第三范式就是在第二范式的基础上建立起来的,第三范式要求关系表中不存在非关键字对任一候选关键字的传递函数依赖。传递函数依赖是指如果存在“A-B-C”的决定关系,则C传递函数依赖于A。也就是说,第三范式要求关系表不包含其他表中已包含的非主关键字段信息。

    书上都是这样的长篇大论,反正我看晕了
    根据关系数据库规范范理论,关系数据库中的关系要满足第一范式,
    在部门关系中,因哪个属性而使它不满足第一范式?()
    A. 部门号
    B. 部门名
    C. 部门总经理
    D. 部门成员

    展开全文
  • 关系模式设计中的问题关系数据库设计要解决的主要问题 什么样的数据库模式才合理? 怎么分解才能满足要求? 衡量的标准是什么? 理论基础是什么? 如何进行实现? 关于好的数据库模式好的数据库模式是不会发生插入...

    关系模式设计中的问题

    关系数据库设计要解决的主要问题

    • 什么样的数据库模式才合理?
    • 怎么分解才能满足要求?
    • 衡量的标准是什么?
    • 理论基础是什么?
    • 如何进行实现?

    关于好的数据库模式

    好的数据库模式是不会发生插入异常、删除异常、更新异常,同时数据冗余尽可能少的模式。

    产生不好模式的根本原因是数据之间存在着某些数据依赖

    解决方法是通过分解关系来消除其中不合适的数据依赖。

    数据依赖

    数据依赖的定义

    数据依赖是数据之间的相互制约关系,是一种语义体现。

    数据依赖的类型

    • 函数依赖(FD)
    • 多值依赖(MVD)
    • 连接依赖(JD)

    函数依赖的定义

    两个实例化的属性集X,Y,如果属性集X中的两个元组取值相同,必有对应的另外一个属性集Y中元组取值相同,则称Y函数依赖于X函数。

    特别值得注意的是,如果属性集X中不存在两个取值相同的元组集合,则Y必定依赖于函数X,且函数X的属性集为超键。

    平凡函数依赖和非平凡函数依赖

    平凡函数依赖

    如果Y依赖于X,同时Y是X的子集,那么称X -> Y 为平凡函数依赖

    非平凡函数依赖

    Y不是X的子集

    对于任意关系模式而言,平凡函数依赖是必然成立的,其并不反映新的语义特征,因此我们一般不讨论平凡函数依赖。

    完全函数依赖和部分函数依赖、

    完全函数依赖表示的就是函数X的属性集构成了候选键。其中形式化的表示就是如果对于X的任何一个子集Z,都有Y不依赖于X,则称Y完全函数依赖于X。
    如果Y不完全函数依赖于X,则称Y部分函数依赖于X。

    完全函数依赖的左部构成主键,不包含冗余的属性。

    传递函数依赖和直接函数依赖

    如果Y函数依赖于X,Z函数依赖于Y,其Y不是X的子集,X不依赖于Y,则称Z**传递依赖于X,否则称Z直接函数依赖**于X。

    函数依赖的逻辑蕴涵

    {X→Y,Y→Z} ⊨ X→Z

    即对于关系模式上的函数依赖集合F,只要X→Y是一个函数依赖,那么必然可以推导认为F逻辑蕴涵X→Y。、

    函数依赖集合的闭包

    由函数依赖集合F所逻辑蕴涵的全部函数依赖所构成的集合称之为F的闭包。
    F+={ X→Y|F|=X→Y}

    闭包的性质

    • F 属于 F+,这是因为根据闭包的定义F中的每个函数依赖必定也在中F+;
    • (F+)+=F+,该性质说明闭包运算是幂等的,即F经过任意多次的闭包运算后其结果仍然等于F+
    • 如果F=F+,则称F是完备的

    实际上函数依赖集合的闭包是NP问题,因此我们需要使用其他闭包。

    函数依赖的推理规则(Armstrong公理)

    设U是关系模式R的属性集,F是R上的函数依赖集,则有:
    A1(自反律):如果Y X U,则X→Y成立;
    A2(增广律):如果X→Y成立,且Z U,则XZ→YZ成立
    A3(传递律):如果X→Y,Y→Z成立,则X→Z成立

    以上三条合起来成为Armstrong公理。

    由A1~A3易推出下面的三条推理规则也是正确的:
    - 合并规则:若X→Y,X→Z成立,则X→YZ成立;
    - 伪传递规则:若X→Y,WY→Z成立,则WX→Z成立;
    - 分解规则:若X→Y,且Z 属于 Y,则有X→Z;

    函数依赖的逻辑导出

    如果给定的关系模式中,函数依赖集合F由Armstrong公理能够推导出X→Y,则称X→Y是F的逻辑导出。记为F=>X→Y。

    值得注意的是,逻辑蕴涵是显式存在的关系,而逻辑导出是隐含存在的关系。

    属性集合的闭包

    X+F ={A|A∈U,F=> X→A}

    值得注意的是,求解属性集合的闭包的计算量远远小于求解函数依赖集合的闭包,因为属性集合的闭包是可数的,最多就是属性全集本身。

    X→Y可由Armstrong公理推出的充分必要条件是Y 属于 X属性结合的闭包

    算法:求解属性集X关于U上的函数依赖集F的闭包

    关于数据库系统原理的数据依赖部分的所有算法,只需要能够将其应用于解决实际问题即可。

    如:
    属性集U为ABCD,FD集为{ A→B,B→C,D→B }, 则A+=ABC; AD+=ABCD; BD+=BCD

    Armstrong公理的正确性和完备性

    • Armstrong公理的正确性是指“使用推理规则从FD集F推出的FD必定在F+中”即,如果F=>X→Y 则 F|=X→Y
    • Armstrong公理的完备性是指“F+中的FD都能从F集使用推理规则集导出”
      即,如果F|=X→Y 则 F=>X→Y
      Armstrong公理的正确性和完备性保证了FD推导过程的有效性和可靠性

    Armstrong公理的正确性和完备性说明逻辑导出逻辑蕴涵是两个等价的概念。

    函数依赖集的等价覆盖

    设F,G是两个函数依赖集合,如果F+=G+ ,则称F等价于G,或者F与G互相覆盖
    F与G等价的充分必要条件是F属于G+ 并且G属于F+。

    要判定F 属于 G+,只须逐一对F中的函数依赖X→Y,考查Y是否属于XG+ +
    关于等价覆盖的掌握需要到哪一个程度?

    最小函数依赖集

    1. 右部均为单属性
    2. 左部没有对于属性
    3. F中没有对于的函数依赖

    一个函数依赖集F均等价于一个最小函数依赖集Fmin

    例:

    R(A,B,C,D,E,H,I),F = {A→BE, AH→D, B→C, BD→E, C→D, H→I, I→H, H→BE},试求F的最小依赖集Fmin。

    :⑴右部拆成单属性
    F={A→B, A→E ,AH→D, B→C, BD→E, C→D, H→I,I→H, H→B, H→E}
    ⑵考察左部不是单属性的函数依赖,消除多余属性
    AH→D ∵((AH)-H)F+=ABECD,D∈((AH)-H)F+
    ∴以A→D取代AH→D
    BD→E ∵((BD)-D)F+=BCDE,E∈((BD)-D)F+
    ∴以B→E取代BD→E
    则 F={A→B, A→E ,A→D, B→C, B→E, C→D, H→I,I→H, H→B, H→E}

    ⑶消除多余的函数依赖
    A→B ∵AG+=AED,B∉ AG+(G=F-{A→B}) ∴保留该函数依赖
    A→E ∵AG+=ABCDE,E∈ AG+(G=F-{A→E}) ∴不保留该函数依赖
    A→D ∵(A)G+=ABCDE,D∈ (A)G+(G=F-{A→D}) ∴不保留该函数依赖
    B→C ∵BG+=B,C ∉ BG+(G=F-{B→C}) ∴保留该函数依赖
    B→E ∵(B)G+=BCD,E ∉ (B)G+(G=F-{B→E}) ∴保留该函数依赖
    C→D ∵CG+=AE,D∉CG+(G=F-{C→D})∴保留该函数依赖
    H→I ∵HG+=HBECD,I ∉HG+(G=F-{H→I}) ∴保留该函数依赖
    I→H ∵IG+=I,H ∉ IG+(G=F-{I→H}) ∴保留该函数依赖
    H→B ∵HG+=HIE,B∉ HG+(G=F-{H→B})∴保留该函数依赖
    H→E ∵HG+=HBCDE,E∈HG+(G=F-{H→E}) ∴不保留该函数依赖
    最后剩下的F是最小函数依赖集:
    Fmin= {A→B, B→C, B→E,C→D, H→I, I→H, H→B}

    F的最小依赖集Fmin不一定唯一,它和我们对各函数依赖FDi 及X→A中X各属性的处置顺序有关

    多值依赖

    多值依赖的定义

    对于某个关系上的三个属性A, B, C。如果属性B,C的取值都不单一,同时B的取值与C无关,也就是B依赖于A,随着A取值的变化可以取不同的值。

    形式化描述

    设R(U)是属性集U上的一个关系模式。X,Y,Z是的U的子集,并且Z=U-X-Y,如果对R(U)的任一关系r,都有如下性质:如果r中存在2个元组s、t,使得:
    s[X]=t[X]
    则r中必存在元组u,使得:
    (1) s[X]=t[X]=u[X]
    (2) u[Y]=t[Y] 且 u[Z]=s[Z]
    (即交换s、t在Y上的值得到的2个元组必在r中)
    则称关系模式R满足多值依赖X→→Y

    多值依赖的注意事项

    值得注意的是,多值依赖会导致数据冗余和更新异常,因此我们在进行数据模式设计的时候,要消除多值依赖。一般使用的方法是建立两个关系,让每个关系只存储一个多值属性的数据。

    多值依赖的推导规则

    与函数依赖一样,多值依赖也有一组推导规则:
    A4:互补律(MVD)
    如果X→→Y,则X→→(U-XY)
    以后如果需要,可用X→→Y|(U-XY)表示多值依赖,以强调其互补关系
    A5:扩展律(MVD)
    如果X→→Y且VW,则WX→→VY
    A6:传递律(MVD)
    如果X→→Y且Y→→Z,则X→→(Z-Y)
    下面两条为(FD+MVD)公理:
    A7:如果X→Y,则X→→Y,即FD是MVD的特例
    A8:如果X→→Y、ZY且对某个与Y不相交的W有:W→Z,则X→Z

    由上述公理,还可以得出下列四个有关MVD的推导规则:
    MVD合并规则
    如果X→→Y、X→→Z,则X→→YZ
    MVD伪传递规则
    如果X→→Y、WY→→Z,则WX→→(Z-WY)
    混合伪传递规则
    如果X→→Y、XY→→Z,则X→(Z-Y)
    MVD分解规则
    如果X→→Y、X→→Z,则X→→(Y∩Z)、X→→(Y-Z) X→→(Z-Y)均成立。

    关于多值依赖的具体内容,在学习深化的过程中去进行深入理解与记忆。

    多值依赖与函数依赖

    函数依赖规定某些元组不能够出现在关系中,也被称之为相等产生依赖
    多值依赖要求某种形式的其他元组必须在关系中,称为元组产生依赖

    有效性范围

    对于函数依赖
    X→Y的有效性仅决定于X、Y属性集上的值,它在任何属性集W(XY 属于 W 属于 U)上都成立。
    若X→Y在R(U)上成立,则对于任何Y′ 属于 Y,均有X→Y ′成立。

    对于多值依赖
    X→→Y在属性集W(XY 属于 W 属于 U)上成立,但在U上不一定成立
    若X→→Y在R(U)上成立,则不能断言对于Y′ 属于 Y,是否有X→→Y ′成立

    嵌入式多值依赖

    嵌入式多值依赖是指函数依赖X→→Y在模式R上不成立,但是在R的子模式W上成立,则称X→→Y为R上的嵌入型多值依赖。

    关系模式分解

    关系模式的分解以及关系模式的规范化相关的内容,关于公式等的记忆学习,最好参照课件。

    范式

    范式就是关系数据库中满足不同规范化程度的关系模式的类。
    第一范式是规范化约束范式,而其他范式根据下图进行依次类推。

    关系模式规范化

    值得注意的是,3NF可以在保证无损连接的同时保持函数依赖,而BCNF和4NF则可能不会保持函数依赖了。因此,一般而言进行关系模式设计时,满足3NF的标准即可。

    关系模式规范化

    规范化

    关系模式的规范化过程实际上就是按照不同级别范式的要求条件对模式进行逐渐分解的过程。

    展开全文
  • 深入理解Java对象的创建过程:类的初始实例化

    万次阅读 多人点赞 2017-05-18 14:17:45
    在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Java对象的创建过程。

    摘要:

      在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Java对象的创建过程。


    版权声明:

    本文原创作者:书呆子Rico
    作者博客地址:http://blog.csdn.net/justloveyou_/


    友情提示:

      一个Java对象的创建过程往往包括 类初始化 类实例化 两个阶段。本文的姊妹篇《 JVM类加载机制概述:加载时机与加载过程》主要介绍了类的初始化时机和初始化过程,本文在此基础上,进一步阐述了一个Java对象创建的真实过程。


    一、Java对象创建时机

      我们知道,一个对象在可以被使用之前必须要被正确地实例化。在Java代码中,有很多行为可以引起对象的创建,最为直观的一种就是使用new关键字来调用一个类的构造函数显式地创建对象,这种方式在Java规范中被称为 : 由执行类实例创建表达式而引起的对象创建。除此之外,我们还可以使用反射机制(Class类的newInstance方法、使用Constructor类的newInstance方法)、使用Clone方法、使用反序列化等方式创建对象。下面笔者分别对此进行一一介绍:


    1). 使用new关键字创建对象

      这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们可以调用任意的构造函数(无参的和有参的)去创建对象。比如:

      Student student = new Student();

    2). 使用Class类的newInstance方法(反射机制)

      我们也可以通过Java的反射机制使用Class类的newInstance方法来创建对象,事实上,这个newInstance方法调用无参的构造器创建对象,比如:

      Student student2 = (Student)Class.forName("Student类全限定名").newInstance(); 
    或者:
      Student stu = Student.class.newInstance();

    3). 使用Constructor类的newInstance方法(反射机制)

      java.lang.relect.Constructor类里也有一个newInstance方法可以创建对象,该方法和Class类中的newInstance方法很像,但是相比之下,Constructor类的newInstance方法更加强大些,我们可以通过这个newInstance方法调用有参数的和私有的构造函数,比如:

    public class Student {
    
        private int id;
    
        public Student(Integer id) {
            this.id = id;
        }
    
        public static void main(String[] args) throws Exception {
    
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);
            Student stu3 = constructor.newInstance(123);
        }
    }

      使用newInstance方法的这两种方式创建对象使用的就是Java的反射机制,事实上Class的newInstance方法内部调用的也是Constructor的newInstance方法。


    4). 使用Clone方法创建对象

      无论何时我们调用一个对象的clone方法,JVM都会帮我们创建一个新的、一样的对象,特别需要说明的是,用clone方法创建对象的过程中并不会调用任何构造函数。关于如何使用clone方法以及浅克隆/深克隆机制,笔者已经在博文《 Java String 综述(下篇)》做了详细的说明。简单而言,要想使用clone方法,我们就必须先实现Cloneable接口并实现其定义的clone方法,这也是原型模式的应用。比如:

    public class Student implements Cloneable{
    
        private int id;
    
        public Student(Integer id) {
            this.id = id;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            // TODO Auto-generated method stub
            return super.clone();
        }
    
        public static void main(String[] args) throws Exception {
    
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);
            Student stu3 = constructor.newInstance(123);
            Student stu4 = (Student) stu3.clone();
        }
    }

    5). 使用(反)序列化机制创建对象

      当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口,比如:

    public class Student implements Cloneable, Serializable {
    
        private int id;
    
        public Student(Integer id) {
            this.id = id;
        }
    
        @Override
        public String toString() {
            return "Student [id=" + id + "]";
        }
    
        public static void main(String[] args) throws Exception {
    
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);
            Student stu3 = constructor.newInstance(123);
    
            // 写对象
            ObjectOutputStream output = new ObjectOutputStream(
                    new FileOutputStream("student.bin"));
            output.writeObject(stu3);
            output.close();
    
            // 读对象
            ObjectInputStream input = new ObjectInputStream(new FileInputStream(
                    "student.bin"));
            Student stu5 = (Student) input.readObject();
            System.out.println(stu5);
        }
    }

    6). 完整实例

    public class Student implements Cloneable, Serializable {
    
        private int id;
    
        public Student() {
    
        }
    
        public Student(Integer id) {
            this.id = id;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            // TODO Auto-generated method stub
            return super.clone();
        }
    
        @Override
        public String toString() {
            return "Student [id=" + id + "]";
        }
    
        public static void main(String[] args) throws Exception {
    
            System.out.println("使用new关键字创建对象:");
            Student stu1 = new Student(123);
            System.out.println(stu1);
            System.out.println("\n---------------------------\n");
    
    
            System.out.println("使用Class类的newInstance方法创建对象:");
            Student stu2 = Student.class.newInstance();    //对应类必须具有无参构造方法,且只有这一种创建方式
            System.out.println(stu2);
            System.out.println("\n---------------------------\n");
    
            System.out.println("使用Constructor类的newInstance方法创建对象:");
            Constructor<Student> constructor = Student.class
                    .getConstructor(Integer.class);   // 调用有参构造方法
            Student stu3 = constructor.newInstance(123);   
            System.out.println(stu3);
            System.out.println("\n---------------------------\n");
    
            System.out.println("使用Clone方法创建对象:");
            Student stu4 = (Student) stu3.clone();
            System.out.println(stu4);
            System.out.println("\n---------------------------\n");
    
            System.out.println("使用(反)序列化机制创建对象:");
            // 写对象
            ObjectOutputStream output = new ObjectOutputStream(
                    new FileOutputStream("student.bin"));
            output.writeObject(stu4);
            output.close();
    
            // 读取对象
            ObjectInputStream input = new ObjectInputStream(new FileInputStream(
                    "student.bin"));
            Student stu5 = (Student) input.readObject();
            System.out.println(stu5);
    
        }
    }/* Output: 
            使用new关键字创建对象:
            Student [id=123]
    
            ---------------------------
    
            使用Class类的newInstance方法创建对象:
            Student [id=0]
    
            ---------------------------
    
            使用Constructor类的newInstance方法创建对象:
            Student [id=123]
    
            ---------------------------
    
            使用Clone方法创建对象:
            Student [id=123]
    
            ---------------------------
    
            使用(反)序列化机制创建对象:
            Student [id=123]
    *///:~

      从Java虚拟机层面看,除了使用new关键字创建对象的方式外,其他方式全部都是通过转变为invokevirtual指令直接创建对象的。


    二. Java 对象的创建过程

      当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序猿的意志进行初始化。在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是 实例变量初始化实例代码块初始化 以及 构造函数初始化


    1、实例变量初始化与实例代码块初始化

      我们在定义(声明)实例变量的同时,还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果我们以这两种方式为实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。实际上,如果我们对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后(还记得吗?Java要求构造函数的第一条语句必须是超类构造函数的调用语句),构造函数本身的代码之前。例如:

    public class InstanceVariableInitializer {  
    
        private int i = 1;  
        private int j = i + 1;  
    
        public InstanceVariableInitializer(int var){
            System.out.println(i);
            System.out.println(j);
            this.i = var;
            System.out.println(i);
            System.out.println(j);
        }
    
        {               // 实例代码块
            j += 3; 
    
        }
    
        public static void main(String[] args) {
            new InstanceVariableInitializer(8);
        }
    }/* Output: 
                1
                5
                8
                5
     *///:~

      上面的例子正好印证了上面的结论。特别需要注意的是,Java是按照编程顺序来执行实例变量初始化器和实例初始化器中的代码的,并且不允许顺序靠前的实例代码块初始化在其后面定义的实例变量,比如:

    public class InstanceInitializer {  
        {  
            j = i;  
        }  
    
        private int i = 1;  
        private int j;  
    }  
    
    public class InstanceInitializer {  
        private int j = i;  
        private int i = 1;  
    }  

      上面的这些代码都是无法通过编译的,编译器会抱怨说我们使用了一个未经定义的变量。之所以要这么做是为了保证一个变量在被使用之前已经被正确地初始化。但是我们仍然有办法绕过这种检查,比如:

    public class InstanceInitializer {  
        private int j = getI();  
        private int i = 1;  
    
        public InstanceInitializer() {  
            i = 2;  
        }  
    
        private int getI() {  
            return i;  
        }  
    
        public static void main(String[] args) {  
            InstanceInitializer ii = new InstanceInitializer();  
            System.out.println(ii.j);  
        }  
    }  

      如果我们执行上面这段代码,那么会发现打印的结果是0。因此我们可以确信,变量j被赋予了i的默认值0,这一动作发生在实例变量i初始化之前和构造函数调用之前。


    2、构造函数初始化

      我们可以从上文知道,实例变量初始化与实例代码块初始化总是发生在构造函数初始化之前,那么我们下面着重看看构造函数初始化过程。众所周知,每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成<init>()方法,参数列表与Java语言书写的构造函数的参数列表相同。

      我们知道,Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用,比如:

    public class ConstructorExample {  
    
    } 

      对于上面代码中定义的类,我们观察编译之后的字节码,我们会发现编译器为我们生成一个构造函数,如下,

    aload_0  
    invokespecial   #8; //Method java/lang/Object."<init>":()V  
    return  

      上面代码的第二行就是调用Object类的默认构造函数的指令。也就是说,如果我们显式调用超类的构造函数,那么该调用必须放在构造函数所有代码的最前面,也就是必须是构造函数的第一条指令。正因为如此,Java才可以使得一个对象在初始化之前其所有的超类都被初始化完成,并保证创建一个完整的对象出来。


      特别地,如果我们在一个构造函数中调用另外一个构造函数,如下所示,

    public class ConstructorExample {  
        private int i;  
    
        ConstructorExample() {  
            this(1);  
            ....  
        }  
    
        ConstructorExample(int i) {  
            ....  
            this.i = i;  
            ....  
        }  
    }  

      对于这种情况,Java只允许在ConstructorExample(int i)内调用超类的构造函数,也就是说,下面两种情形的代码编译是无法通过的:

    public class ConstructorExample {  
        private int i;  
    
        ConstructorExample() {  
            super();  
            this(1);  // Error:Constructor call must be the first statement in a constructor
            ....  
        }  
    
        ConstructorExample(int i) {  
            ....  
            this.i = i;  
            ....  
        }  
    }  

    或者,

    public class ConstructorExample {  
        private int i;  
    
        ConstructorExample() {  
            this(1);  
            super();  //Error: Constructor call must be the first statement in a constructor
            ....  
        }  
    
        ConstructorExample(int i) {  
            this.i = i;  
        }  
    }   

      Java通过对构造函数作出这种限制以便保证一个类的实例能够在被使用之前正确地初始化。


    3、 小结

      总而言之,实例化一个类的对象的过程是一个典型的递归过程,如下图所示。进一步地说,在实例化一个类的对象时,具体过程是这样的:

      在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类。此时,首先实例化Object类,再依次对以下各类进行实例化,直到完成对目标类的实例化。具体而言,在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始化,再执行构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。

                 这里写图片描述

                    
      Ps: 关于递归的思想与内涵的介绍,请参见我的博文《 算法设计方法:递归的内涵与经典应用》


    4、实例变量初始化、实例代码块初始化以及构造函数初始化综合实例

      笔者在《 JVM类加载机制概述:加载时机与加载过程》一文中详细阐述了类初始化时机和初始化过程,并在文章的最后留了一个悬念给各位,这里来揭开这个悬念。建议读者先看完《 JVM类加载机制概述:加载时机与加载过程》这篇再来看这个,印象会比较深刻,如若不然,也没什么关系~~
      

    //父类
    class Foo {
        int i = 1;
    
        Foo() {
            System.out.println(i);             -----------(1)
            int x = getValue();
            System.out.println(x);             -----------(2)
        }
    
        {
            i = 2;
        }
    
        protected int getValue() {
            return i;
        }
    }
    
    //子类
    class Bar extends Foo {
        int j = 1;
    
        Bar() {
            j = 2;
        }
    
        {
            j = 3;
        }
    
        @Override
        protected int getValue() {
            return j;
        }
    }
    
    public class ConstructorExample {
        public static void main(String... args) {
            Bar bar = new Bar();
            System.out.println(bar.getValue());             -----------(3)
        }
    }/* Output: 
                2
                0
                2
     *///:~

      根据上文所述的类实例化过程,我们可以将Foo类的构造函数和Bar类的构造函数等价地分别变为如下形式:

        //Foo类构造函数的等价变换:
        Foo() {
            i = 1;
            i = 2;
            System.out.println(i);
            int x = getValue();
            System.out.println(x);
        }
        //Bar类构造函数的等价变换
        Bar() {
            Foo();
            j = 1;
            j = 3;
            j = 2
        }

      这样程序就好看多了,我们一眼就可以观察出程序的输出结果。在通过使用Bar类的构造方法new一个Bar类的实例时,首先会调用Foo类构造函数,因此(1)处输出是2,这从Foo类构造函数的等价变换中可以直接看出。(2)处输出是0,为什么呢?因为在执行Foo的构造函数的过程中,由于Bar重载了Foo中的getValue方法,所以根据Java的多态特性可以知道,其调用的getValue方法是被Bar重载的那个getValue方法。但由于这时Bar的构造函数还没有被执行,因此此时j的值还是默认值0,因此(2)处输出是0。最后,在执行(3)处的代码时,由于bar对象已经创建完成,所以此时再访问j的值时,就得到了其初始化后的值2,这一点可以从Bar类构造函数的等价变换中直接看出。


    三. 类的初始化时机与过程

      关于类的初始化时机,笔者在博文《 JVM类加载机制概述:加载时机与加载过程》已经介绍的很清楚了,此处不再赘述。简单地说,在类加载过程中,准备阶段是正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,而初始化阶段是真正开始执行类中定义的java程序代码(字节码)并按程序猿的意图去初始化类变量的过程。更直接地说,初始化阶段就是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态代码块static{}中的语句合并产生的,其中编译器收集的顺序是由语句在源文件中出现的顺序所决定。

      类构造器<clinit>()与实例构造器<init>()不同,它不需要程序员进行显式调用,虚拟机会保证在子类类构造器<clinit>()执行之前,父类的类构造<clinit>()执行完毕。由于父类的构造器<clinit>()先执行,也就意味着父类中定义的静态代码块/静态变量的初始化要优先于子类的静态代码块/静态变量的初始化执行。特别地,类构造器<clinit>()对于类或者接口来说并不是必需的,如果一个类中没有静态代码块,也没有对类变量的赋值操作,那么编译器可以不为这个类生产类构造器<clinit>()。此外,在同一个类加载器下,一个类只会被初始化一次,但是一个类可以任意地实例化对象。也就是说,在一个类的生命周期中,类构造器<clinit>()最多会被虚拟机调用一次,而实例构造器<init>()则会被虚拟机调用多次,只要程序员还在创建对象。

      注意,这里所谓的实例构造器<init>()是指收集类中的所有实例变量的赋值动作、实例代码块和构造函数合并产生的,类似于上文对Foo类的构造函数和Bar类的构造函数做的等价变换。


    四. 总结

      1、一个实例变量在对象初始化的过程中会被赋值几次?

      我们知道,JVM在为一个对象分配完内存之后,会给每一个实例变量赋予默认值,这个时候实例变量被第一次赋值,这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作,那么这个时候,这个实例变量就被第二次赋值了。如果我们在实例代码块中,又对变量x做了初始化操作,那么这个时候,这个实例变量就被第三次赋值了。如果我们在构造函数中,也对变量x做了初始化操作,那么这个时候,变量x就被第四次赋值。也就是说,在Java的对象初始化过程中,一个实例变量最多可以被初始化4次。


      2、类的初始化过程与类的实例化过程的异同?

      类的初始化是指类加载过程中的初始化阶段对类变量按照程序猿的意图进行赋值的过程;而类的实例化是指在类完全加载到内存中后创建对象的过程。


      3、假如一个类还未加载到内存中,那么在创建一个该类的实例时,具体过程是怎样的?

      我们知道,要想创建一个类的实例,必须先将该类加载到内存并进行初始化,也就是说,类初始化操作是在类实例化操作之前进行的,但并不意味着:只有类初始化操作结束后才能进行类实例化操作。例如,笔者在博文《 JVM类加载机制概述:加载时机与加载过程》中所提到的下面这个经典案例:

    public class StaticTest {
        public static void main(String[] args) {
            staticFunction();
        }
    
        static StaticTest st = new StaticTest();
    
        static {   //静态代码块
            System.out.println("1");
        }
    
        {       // 实例代码块
            System.out.println("2");
        }
    
        StaticTest() {    // 实例构造器
            System.out.println("3");
            System.out.println("a=" + a + ",b=" + b);
        }
    
        public static void staticFunction() {   // 静态方法
            System.out.println("4");
        }
    
        int a = 110;    // 实例变量
        static int b = 112;     // 静态变量
    }/* Output: 
            2
            3
            a=110,b=0
            1
            4
     *///:~

      大家能得到正确答案吗?笔者已经在博文《 JVM类加载机制概述:加载时机与加载过程》中解释过这个问题了,此不赘述。
      
      总的来说,类实例化的一般过程是:父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数。


    五. 更多

      更多关于类初始化时机和初始化过程的介绍,请参见我的博文《 JVM类加载机制概述:加载时机与加载过程》

      更多关于类加载器等方面的内容,包括JVM预定义的类加载器、双亲委派模型等知识点,请参见我的转载博文《深入理解Java类加载器(一):Java类加载原理解析》

      关于递归的思想与内涵的介绍,请参见我的博文《 算法设计方法:递归的内涵与经典应用》


    引用:

    Java对象初始化详解
    Java中创建对象的几种方式

    展开全文
  • 数据库设计 范式 规范化实例

    千次阅读 2008-07-29 10:07:00
    设计范式(范式,数据库设计范式,数据库的设计范式)是符合某一种级别的关系模式的集合。构造数据库必须遵循一定的规则。在关系数据库中,这种规则就是范式。关系数据库中的关系必须满足一定的要求,即满足不同的范式...
  • 关系数据库规范化理论之范式

    千次阅读 2017-11-09 22:27:30
    因为在写项目时与同伴关于数据库到底建多少张表,每张表应包含哪些属性产生分歧,所以又好好研究了一下关系型数据库在设计时应该遵守怎样的规则以提高数据库性能。 在阅读本篇文章前读者须掌握关系数据库结构基础及...
  • 关系模式分解

    千次阅读 2020-01-02 12:14:10
    模式分解 模式S-C-M (S 学号,C 班级,M 班主任) 该模式设计不好,存在数据冗余、插入异常、删除异常和更新异常 ...规范化理论: 检测是否在一个表中聚集了过多的属性的过程 模式分解来消除违反范式规则而带来...
  • 简单工厂方法模式--水果实例

    千次阅读 2014-04-04 15:07:36
    一、简单工厂和工厂方法定义: 简单工厂模式是由一个...工厂方法模式通过调用不同的方法返回需要的类,而不是去实例化具体的类。 对实例创建进行了包装。 工厂方法是一组方法, 他们针对不同条件返回不同的类实
  • 文章目录0.思维导图1.为什么要学习关系数据库规范化理论?(1)基本概念回顾(2)...规范化---改造关系模式,解决插入异常、删除异常、更新异常和数据冗余问题。(1)规范化研究什么?(2)函数依赖① 函数依赖② 平
  • 数据库复习11——关系模式与范式

    千次阅读 2015-06-30 16:53:34
    数据库复习CH11 数据库模式(Schema)是数据库中全体数据的逻辑结构和特征的描述,关系型数据库的模式又叫关系模式,我所理解的关系模式就是数据库中表结构的定义以及多张表之间的逻辑联系关系模式的设计就是根据一...
  • 数据库关系模式的范式总结

    千次阅读 2019-04-25 21:21:01
    目录 什么是关系模式的范式 第一范式(1NF) 第二范式(2NF) ...关系模式的范式是衡量关系模式好坏的标准。范式的种类与数据依赖有着直接联系,满足不同程度要求的关系称为不同的范式等级。其中,...
  • 函数依赖与关系模式分解的一些技巧整理

    万次阅读 多人点赞 2018-01-21 19:58:27
    关系数据库设计理论的核心是数据间的函数依赖,衡量的标准是关系规范化的程度及分解的无损连接性和保持函 数依赖性。 数据依赖是通过一个关系中属性间值的相同与否体现出来的数据间的相互关系 函数依赖(FD)是...
  • BPMN2.0,Activiti,jbpm5学习资料 上世纪九十年代以后,随着WfMC联盟...2011年BPMN2.0新规范的发布为各工作流产品互容互通提供了统一的标准,结束了各工作流厂商各自为政相互抵斥的局面。 什么是BPMN、Workflow?
  • 基于最新Spring 5.x,详细介绍了finishBeanFactoryInitialization方法的整体流程和部分源码解析,比如DependsOn依赖校验、FactoryBean的特殊获取模式,以及SmartInitializingSingleton、FactoryBean 、...
  • 数据库的规范化——让你读懂什么是范式

    千次阅读 多人点赞 2019-11-18 14:36:23
    推荐个在线SQL网站:http://sqlfiddle.com/,满足个人即时学习需要,避免安装数据库实例的麻烦。 参考资料:Wiki百科、百度百科、Google、博客园等,定义性的内容,直接引用了官方介绍。...模式分解存在的问题...
  • 设计模式

    千次阅读 多人点赞 2019-07-22 09:33:22
    设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、...
  • C#之三十八 简单工厂设计模式

    千次阅读 2016-06-01 20:22:54
    设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人...简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例
  • 1NF 2NF 3NF BCNF 模式分解
  • Java设计模式之创建型模式

    千次阅读 2016-05-12 16:11:07
    单例模式 应用场合: 在一个JVM中,该对象只需有一个实例存在 特点: 对于创建频繁的大型对象可以降低系统开销 减轻GC压力(Garbage collection) 安全(核心交易引擎) 实现方式: 加载类时直接创建类的实例...
  • 设计模式(二)单例模式

    千次阅读 2015-11-26 09:47:18
    概念 单例模式也被称作单件模式(单体模式),主要作用...该类只有一个实例该类自行创建实例(改类内部创建自身的实例对象)想整个系统公开实例接口(类构造方法私有) 使用范围:目前java里面实现的单例是一个Clas
  •  函数依赖定义 设:R(U)是属性集U 上的关系模式,X,Y⊆U。如果对于R(U)的任意一个关系r,以及r 的任意两个元组t1,t2,不存在:t1[x]=t2[x],而t1[Y]≠t2[Y],则称X函数决定Y,或者说Y函数依赖于X。记为:X→Y。1...
  • 一什么是设计模式?  设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类...设计模式使代码编制真正工程;设计模式是软件工程的基石脉络,如同大厦的结构一样。  二为什么要使用设计模式?  
  • 【23种设计模式专题】二 工厂模式

    千次阅读 多人点赞 2020-04-04 19:47:55
    通过两个人对话的方式,来说一说简单工厂、工厂方法模式、抽象工厂模式
  • 常用设计模式总结

    万次阅读 多人点赞 2019-07-31 19:13:12
    毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中...
  • 秒懂Java代理与动态代理模式

    万次阅读 多人点赞 2018-06-30 17:08:23
    二者什么关系?具体如何实现?什么原理?如何改进?这即为我们学习一项新知识的正确打开方式,我们接下来会以此展开,让你秒懂。 概念 什么是代理模式 定义:为其他对象提供一种代理以控制对这个对象的访问 ...
  • 关于Repository和UnitOfWork模式关系

    千次阅读 2018-02-04 10:57:04
    3 , 二者常用关系 一,repository模式 描述和作用: 按照最初提出者的介绍,它是衔接数据映射层和域之间的一个纽带,作用相当于一个在内存中的域对象集合。客户端对象把查询的一些实体进行组合,并把它们提交...
  • 设计模式总结

    万次阅读 多人点赞 2014-01-18 10:49:06
    1、认识了这么多设计模式。刚刚接触java没多久就在学长那里听过设计模式的大名,但是由于能力有限,一直不敢触碰。而今有幸将其都认识了。  2、开始有设计的理论了。在接触设计模式之前没有怎么想过设计方面东东,...
  • 一文彻底搞懂前端模块:exports、module.exports、export、import、export default CommonJS规范、ES Module规范

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 96,712
精华内容 38,684
关键字:

关系模式的规范化过程实例