tree 订阅
Tree
信息
操作系统
未知
开发语言
开源协议
未知
Tree
Tree Structure that you can easily create by entering the values in text box and add in tree formate. It just uses the simple js file and small html file thats it. You can easily add and remove the nodes. The expanding and collapsing fecilties are provided in this tree structure.. You please look at it.
收起全文
精华内容
参与话题
问答
  • ARM Linux 3.x的设备树(Device Tree

    万次阅读 多人点赞 2013-01-01 17:32:36
    本文部分案例和文字英文原版来源于http://devicetree.org/Device_Tree_Usage 更多精华文章请扫描下方二维码关注Linux阅码场 1. ARM Device Tree起源 Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称...

    本文部分案例和文字英文原版来源于 http://devicetree.org/Device_Tree_Usage

    更多精华文章请扫描下方二维码关注Linux阅码场

    1.    ARM Device Tree起源

    Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发ARM Linux社区的地震,随后ARM社区进行了一系列的重大修正。在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。读者有兴趣可以统计下常见的s3c2410、s3c6410等板级目录,代码量在数万行。
    社区必须改变这种局面,于是PowerPC等其他体系架构下已经使用的Flattened Device Tree(FDT)进入ARM社区的视野。Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
    Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

    • CPU的数量和类别
    • 内存基地址和大小
    • 总线和桥
    • 外设连接
    • 中断控制器和中断使用情况
    • GPIO控制器和GPIO使用情况
    • Clock控制器和Clock使用情况

    它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

    2.    Device Tree组成和结构

    整个Device Tree牵涉面比较广,即增加了新的用于描述设备硬件信息的文本格式,又增加了编译这一文本的工具,同时Bootloader也需要支持将编译后的Device Tree传递给Linux内核。

    DTS (device tree source)

    .dts文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM Linux在,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dts就include这个.dtsi。譬如,对于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用, vexpress-v2p-ca9.dts有如下一行:
    /include/ "vexpress-v2m.dtsi"
    当然,和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。
    .dts(或者其include的.dtsi)基本元素即为前文所述的结点和属性:

    / {
        node1 {
            a-string-property = "A string";
            a-string-list-property = "first string", "second string";
            a-byte-data-property = [0x01 0x23 0x34 0x56];
            child-node1 {
                first-child-property;
                second-child-property = <1>;
                a-string-property = "Hello, world";
            };
            child-node2 {
            };
        };
        node2 {
            an-empty-property;
            a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
            child-node1 {
            };
        };
    };

    上述.dts文件并没有什么真实的用途,但它基本表征了一个Device Tree源文件的结构:
    1个root结点"/";
    root结点下面含一系列子结点,本例中为"node1" 和 "node2";
    结点"node1"下又含有一系列子结点,本例中为"child-node1" 和 "child-node2";
    各结点都有一系列属性。这些属性可能为空,如" an-empty-property";可能为字符串,如"a-string-property";可能为字符串数组,如"a-string-list-property";可能为Cells(由u32整数组成),如"second-child-property",可能为二进制数,如"a-byte-data-property"。
    下面以一个最简单的machine为例来看如何写一个.dts文件。假设此machine的配置如下:
    1个双核ARM Cortex-A9 32位处理器;
    ARM的local bus上的内存映射区域分布了2个串口(分别位于0x101F1000 和 0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10170000)、中断控制器(位于0x10140000)和一个external bus桥;
    External bus桥上又连接了SMC SMC91111 Ethernet(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR Flash(位于0x30000000);
    External bus桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58)。
    其对应的.dts文件为:

    / {
        compatible = "acme,coyotes-revenge";
        #address-cells = <1>;
        #size-cells = <1>;
        interrupt-parent = <&intc>;
    
        cpus {
            #address-cells = <1>;
            #size-cells = <0>;
            cpu@0 {
                compatible = "arm,cortex-a9";
                reg = <0>;
            };
            cpu@1 {
                compatible = "arm,cortex-a9";
                reg = <1>;
            };
        };
    
        serial@101f0000 {
            compatible = "arm,pl011";
            reg = <0x101f0000 0x1000 >;
            interrupts = < 1 0 >;
        };
    
        serial@101f2000 {
            compatible = "arm,pl011";
            reg = <0x101f2000 0x1000 >;
            interrupts = < 2 0 >;
        };
    
        gpio@101f3000 {
            compatible = "arm,pl061";
            reg = <0x101f3000 0x1000
                   0x101f4000 0x0010>;
            interrupts = < 3 0 >;
        };
    
        intc: interrupt-controller@10140000 {
            compatible = "arm,pl190";
            reg = <0x10140000 0x1000 >;
            interrupt-controller;
            #interrupt-cells = <2>;
        };
    
        spi@10115000 {
            compatible = "arm,pl022";
            reg = <0x10115000 0x1000 >;
            interrupts = < 4 0 >;
        };
    
        external-bus {
            #address-cells = <2>
            #size-cells = <1>;
            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                      1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                      2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash
    
            ethernet@0,0 {
                compatible = "smc,smc91c111";
                reg = <0 0 0x1000>;
                interrupts = < 5 2 >;
            };
    
            i2c@1,0 {
                compatible = "acme,a1234-i2c-bus";
                #address-cells = <1>;
                #size-cells = <0>;
                reg = <1 0 0x1000>;
                interrupts = < 6 2 >;
                rtc@58 {
                    compatible = "maxim,ds1338";
                    reg = <58>;
                    interrupts = < 7 3 >;
                };
            };
    
            flash@2,0 {
                compatible = "samsung,k8f1315ebm", "cfi-flash";
                reg = <2 0 0x4000000>;
            };
        };
    };

    上述.dts文件中,root结点"/"的compatible 属性compatible = "acme,coyotes-revenge";定义了系统的名称,它的组织形式为:<manufacturer>,<model>。Linux内核透过root结点"/"的compatible 属性即可判断它启动的是什么machine。
    在.dts文件的每个设备,都有一个compatible 属性,compatible属性用户驱动和设备的绑定。compatible 属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确切设备,形式为"<manufacturer>,<model>",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。如在arch/arm/boot/dts/vexpress-v2m.dtsi中的Flash结点:

    flash@0,00000000 {
         compatible = "arm,vexpress-flash", "cfi-flash";
         reg = <0 0x00000000 0x04000000>,
         <1 0x00000000 0x04000000>;
         bank-width = <4>;
     };

    compatible属性的第2个字符串"cfi-flash"明显比第1个字符串"arm,vexpress-flash"涵盖的范围更广。
    再比如,Freescale MPC8349 SoC含一个串口设备,它实现了国家半导体(National Semiconductor)的ns16550 寄存器接口。则MPC8349串口设备的compatible属性为compatible = "fsl,mpc8349-uart", "ns16550"。其中,fsl,mpc8349-uart指代了确切的设备, ns16550代表该设备与National Semiconductor 的16550 UART保持了寄存器兼容。
    接下来root结点"/"的cpus子结点下面又包含2个cpu子结点,描述了此machine上的2个CPU,并且二者的compatible 属性为"arm,cortex-a9"。
    注意cpus和cpus的2个cpu子结点的命名,它们遵循的组织形式为:<name>[@<unit-address>],<>中的内容是必选项,[]中的则为可选项。name是一个ASCII字符串,用于描述结点对应的设备类型,如3com Ethernet适配器对应的结点name宜为ethernet,而不是3com509。如果一个结点描述的设备有地址,则应该给出@unit-address。多个相同类型设备结点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0、cpu@1以及serial@101f0000与serial@101f2000这样的同名结点。设备的unit-address地址也经常在其对应结点的reg属性中给出。ePAPR标准给出了结点命名的规范。
    可寻址的设备使用如下信息来在Device Tree中编码地址信息:

    •     reg
    •     #address-cells
    •     #size-cells

    其中reg的组织形式为reg = <address1 length1 [address2 length2] [address3 length3] ... >,其中的每一组address length表明了设备使用的一个地址范围。address为1个或多个32位的整型(即cell),而length则为cell的列表或者为空(若#size-cells = 0)。address 和 length 字段是可变长的,父结点的#address-cells和#size-cells分别决定了子结点的reg属性的address和length字段的长度。在本例中,root结点的#address-cells = <1>;和#size-cells = <1>;决定了serial、gpio、spi等结点的address和length字段的长度分别为1。cpus 结点的#address-cells = <1>;和#size-cells = <0>;决定了2个cpu子结点的address为1,而length为空,于是形成了2个cpu的reg = <0>;和reg = <1>;。external-bus结点的#address-cells = <2>和#size-cells = <1>;决定了其下的ethernet、i2c、flash的reg字段形如reg = <0 0 0x1000>;、reg = <1 0 0x1000>;和reg = <2 0 0x4000000>;。其中,address字段长度为0,开始的第一个cell(0、1、2)是对应的片选,第2个cell(0,0,0)是相对该片选的基地址,第3个cell(0x1000、0x1000、0x4000000)为length。特别要留意的是i2c结点中定义的 #address-cells = <1>;和#size-cells = <0>;又作用到了I2C总线上连接的RTC,它的address字段为0x58,是设备的I2C地址。
    root结点的子结点描述的是CPU的视图,因此root子结点的address区域就直接位于CPU的memory区域。但是,经过总线桥后的address往往需要经过转换才能对应的CPU的memory映射。external-bus的ranges属性定义了经过external-bus桥后的地址范围如何映射到CPU的memory区域。

            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                      1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                      2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash

    ranges是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。映射表中的子地址、父地址分别采用子地址空间的#address-cells和父地址空间的#address-cells大小。对于本例而言,子地址空间的#address-cells为2,父地址空间的#address-cells值为1,因此0 0  0x10100000   0x10000的前2个cell为external-bus后片选0上偏移0,第3个cell表示external-bus后片选0上偏移0的地址空间被映射到CPU的0x10100000位置,第4个cell表示映射的大小为0x10000。ranges的后面2个项目的含义可以类推。
    Device Tree中还可以中断连接信息,对于中断控制器而言,它提供如下属性:
    interrupt-controller – 这个属性为空,中断控制器应该加上此属性表明自己的身份;
    #interrupt-cells – 与#address-cells 和 #size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小。
    在整个Device Tree中,与中断相关的属性还包括:
    interrupt-parent – 设备结点透过它来指定它所依附的中断控制器的phandle,当结点没有指定interrupt-parent 时,则从父级结点继承。对于本例而言,root结点指定了interrupt-parent = <&intc>;其对应于intc: interrupt-controller@10140000,而root结点的子结点并未指定interrupt-parent,因此它们都继承了intc,即位于0x10140000的中断控制器。
    interrupts – 用到了中断的设备结点透过它指定中断号、触发方法等,具体这个属性含有多少个cell,由它依附的中断控制器结点的#interrupt-cells属性决定。而具体每个cell又是什么含义,一般由驱动的实现决定,而且也会在Device Tree的binding文档中说明。譬如,对于ARM GIC中断控制器而言,#interrupt-cells为3,它3个cell的具体含义Documentation/devicetree/bindings/arm/gic.txt就有如下文字说明:

    01   The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
    02   interrupts.
    03
    04   The 2nd cell contains the interrupt number for the interrupt type.
    05   SPI interrupts are in the range [0-987].  PPI interrupts are in the
    06   range [0-15].
    07
    08   The 3rd cell is the flags, encoded as follows:
    09         bits[3:0] trigger type and level flags.
    10                 1 = low-to-high edge triggered
    11                 2 = high-to-low edge triggered
    12                 4 = active high level-sensitive
    13                 8 = active low level-sensitive
    14         bits[15:8] PPI interrupt cpu mask.  Each bit corresponds to each of
    15         the 8 possible cpus attached to the GIC.  A bit set to '1' indicated
    16         the interrupt is wired to that CPU.  Only valid for PPI interrupts.

    另外,值得注意的是,一个设备还可能用到多个中断号。对于ARM GIC而言,若某设备使用了SPI的168、169号2个中断,而言都是高电平触发,则该设备结点的interrupts属性可定义为:interrupts = <0 168 4>, <0 169 4>;
    除了中断以外,在ARM Linux中clock、GPIO、pinmux都可以透过.dts中的结点和属性进行描述。

    DTC (device tree compiler)

    将.dts编译为.dtb的工具。DTC的源代码位于内核的scripts/dtc目录,在Linux内核使能了Device Tree的情况下,编译内核的时候主机工具dtc会被编译出来,对应scripts/dtc/Makefile中的“hostprogs-y := dtc”这一hostprogs编译target。
    在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与VEXPRESS对应的.dtb包括:

    dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \
            vexpress-v2p-ca9.dtb \
            vexpress-v2p-ca15-tc1.dtb \
            vexpress-v2p-ca15_a7.dtb \
            xenvm-4.2.dtb

    在Linux下,我们可以单独编译Device Tree文件。当我们在Linux内核下运行make dtbs时,若我们之前选择了ARCH_VEXPRESS,上述.dtb都会由对应的.dts编译出来。因为arch/arm/Makefile中含有一个dtbs编译target项目。

    Device Tree Blob (.dtb)

    .dtb是.dts被DTC编译后的二进制格式的Device Tree描述,可由Linux内核解析。通常在我们为电路板制作NAND、SD启动image时,会为.dtb文件单独留下一个很小的区域以存放之,之后bootloader在引导kernel的过程中,会先读取该.dtb到内存。

    Binding

    对于Device Tree中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt。这些文档位于内核的Documentation/devicetree/bindings目录,其下又分为很多子目录。

    Bootloader

    Uboot mainline 从 v1.1.3开始支持Device Tree,其对ARM的支持则是和ARM内核支持Device Tree同期完成。
    为了使能Device Tree,需要编译Uboot的时候在config文件中加入
    #define CONFIG_OF_LIBFDT
    在Uboot中,可以从NAND、SD或者TFTP等任意介质将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot运行命令fdt addr命令设置.dtb的地址,如:
    U-Boot> fdt addr 0x71000000
    fdt的其他命令就变地可以使用,如fdt resize、fdt print等。
    对于ARM来讲,可以透过bootz kernel_addr initrd_address dtb_address的命令来启动内核,即dtb_address作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参数为initrd的地址,若不存在initrd,可以用 -代替。

    3.    Device Tree引发的BSP和驱动变更

    有了Device Tree后,大量的板级信息都不再需要,譬如过去经常在arch/arm/plat-xxx和arch/arm/mach-xxx实施的如下事情:
    1.    注册platform_device,绑定resource,即内存、IRQ等板级信息。

    透过Device Tree后,形如

     

    90 static struct resource xxx_resources[] = {
    91         [0] = {
    92                 .start  = …,
    93                 .end    = …,
    94                 .flags  = IORESOURCE_MEM,
    95         },
    96         [1] = {
    97                 .start  = …,
    98                 .end    = …,
    99                 .flags  = IORESOURCE_IRQ,
    100         },
    101 };
    102
    103 static struct platform_device xxx_device = {
    104         .name           = "xxx",
    105         .id             = -1,
    106         .dev            = {
    107                                 .platform_data          = &xxx_data,
    108         },
    109         .resource       = xxx_resources,
    110         .num_resources  = ARRAY_SIZE(xxx_resources),
    111 };

    之类的platform_device代码都不再需要,其中platform_device会由kernel自动展开。而这些resource实际来源于.dts中设备结点的reg、interrupts属性。典型地,大多数总线都与“simple_bus”兼容,而在SoC对应的machine的.init_machine成员函数中,调用of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);即可自动展开所有的platform_device。譬如,假设我们有个XXX SoC,则可在arch/arm/mach-xxx/的板文件中透过如下方式展开.dts中的设备结点对应的platform_device:

     

     

    18 static struct of_device_id xxx_of_bus_ids[] __initdata = {
    19         { .compatible = "simple-bus", },
    20         {},
    21 };
    22
    23 void __init xxx_mach_init(void)
    24 {
    25         of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);
    26 }
    32
    33 #ifdef CONFIG_ARCH_XXX
    38
    39 DT_MACHINE_START(XXX_DT, "Generic XXX (Flattened Device Tree)")
    41         …
    45         .init_machine   = xxx_mach_init,
    46         …
    49 MACHINE_END
    50 #endif

     

     

     

    2.    注册i2c_board_info,指定IRQ等板级信息。

    形如

     

    145 static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
    146         {
    147                 I2C_BOARD_INFO("tlv320aic23", 0x1a),
    148         }, {
    149                 I2C_BOARD_INFO("fm3130", 0x68),
    150         }, {
    151                 I2C_BOARD_INFO("24c64", 0x50),
    152         },
    153 };

    之类的i2c_board_info代码,目前不再需要出现,现在只需要把tlv320aic23、fm3130、24c64这些设备结点填充作为相应的I2C controller结点的子结点即可,类似于前面的

     

     

          i2c@1,0 {
                compatible = "acme,a1234-i2c-bus";
                …
                rtc@58 {
                    compatible = "maxim,ds1338";
                    reg = <58>;
                    interrupts = < 7 3 >;
                };
            };

    Device Tree中的I2C client会透过I2C host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter);被自动展开。

     

    3.    注册spi_board_info,指定IRQ等板级信息。

    形如

     

    79 static struct spi_board_info afeb9260_spi_devices[] = {
    80         {       /* DataFlash chip */
    81                 .modalias       = "mtd_dataflash",
    82                 .chip_select    = 1,
    83                 .max_speed_hz   = 15 * 1000 * 1000,
    84                 .bus_num        = 0,
    85         },
    86 };

    之类的spi_board_info代码,目前不再需要出现,与I2C类似,现在只需要把mtd_dataflash之类的结点,作为SPI控制器的子结点即可,SPI host驱动的probe函数透过spi_register_master()注册master的时候,会自动展开依附于它的slave。

     

    4.    多个针对不同电路板的machine,以及相关的callback。

    过去,ARM Linux针对不同的电路板会建立由MACHINE_START和MACHINE_END包围起来的针对这个machine的一系列callback,譬如:

     

    373 MACHINE_START(VEXPRESS, "ARM-Versatile Express")
    374         .atag_offset    = 0x100,
    375         .smp            = smp_ops(vexpress_smp_ops),
    376         .map_io         = v2m_map_io,
    377         .init_early     = v2m_init_early,
    378         .init_irq       = v2m_init_irq,
    379         .timer          = &v2m_timer,
    380         .handle_irq     = gic_handle_irq,
    381         .init_machine   = v2m_init,
    382         .restart        = vexpress_restart,
    383 MACHINE_END

    这些不同的machine会有不同的MACHINE ID,Uboot在启动Linux内核时会将MACHINE ID存放在r1寄存器,Linux启动时会匹配Bootloader传递的MACHINE ID和MACHINE_START声明的MACHINE ID,然后执行相应machine的一系列初始化函数。

     

    引入Device Tree之后,MACHINE_START变更为DT_MACHINE_START,其中含有一个.dt_compat成员,用于表明相关的machine与.dts中root结点的compatible属性兼容关系。如果Bootloader传递给内核的Device Tree中root结点的compatible属性出现在某machine的.dt_compat表中,相关的machine就与对应的Device Tree匹配,从而引发这一machine的一系列初始化函数被执行。

     

    489 static const char * const v2m_dt_match[] __initconst = {
    490         "arm,vexpress",
    491         "xen,xenvm",
    492         NULL,
    493 };
    495 DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
    496         .dt_compat      = v2m_dt_match,
    497         .smp            = smp_ops(vexpress_smp_ops),
    498         .map_io         = v2m_dt_map_io,
    499         .init_early     = v2m_dt_init_early,
    500         .init_irq       = v2m_dt_init_irq,
    501         .timer          = &v2m_dt_timer,
    502         .init_machine   = v2m_dt_init,
    503         .handle_irq     = gic_handle_irq,
    504         .restart        = vexpress_restart,
    505 MACHINE_END

    Linux倡导针对多个SoC、多个电路板的通用DT machine,即一个DT machine的.dt_compat表含多个电路板.dts文件的root结点compatible属性字符串。之后,如果的电路板的初始化序列不一样,可以透过int of_machine_is_compatible(const char *compat) API判断具体的电路板是什么。

     

        譬如arch/arm/mach-exynos/mach-exynos5-dt.c的EXYNOS5_DT machine同时兼容"samsung,exynos5250"和"samsung,exynos5440":

     

    158 static char const *exynos5_dt_compat[] __initdata = {
    159         "samsung,exynos5250",
    160         "samsung,exynos5440",
    161         NULL
    162 };
    163
    177 DT_MACHINE_START(EXYNOS5_DT, "SAMSUNG EXYNOS5 (Flattened Device Tree)")
    178         /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
    179         .init_irq       = exynos5_init_irq,
    180         .smp            = smp_ops(exynos_smp_ops),
    181         .map_io         = exynos5_dt_map_io,
    182         .handle_irq     = gic_handle_irq,
    183         .init_machine   = exynos5_dt_machine_init,
    184         .init_late      = exynos_init_late,
    185         .timer          = &exynos4_timer,
    186         .dt_compat      = exynos5_dt_compat,
    187         .restart        = exynos5_restart,
    188         .reserve        = exynos5_reserve,
    189 MACHINE_END

         它的.init_machine成员函数就针对不同的machine进行了不同的分支处理:

     

    126 static void __init exynos5_dt_machine_init(void)
    127 {
    128         …
    149
    150         if (of_machine_is_compatible("samsung,exynos5250"))
    151                 of_platform_populate(NULL, of_default_bus_match_table,
    152                                      exynos5250_auxdata_lookup, NULL);
    153         else if (of_machine_is_compatible("samsung,exynos5440"))
    154                 of_platform_populate(NULL, of_default_bus_match_table,
    155                                      exynos5440_auxdata_lookup, NULL);
    156 }

     

     

     

    使用Device Tree后,驱动需要与.dts中描述的设备结点进行匹配,从而引发驱动的probe()函数执行。对于platform_driver而言,需要添加一个OF匹配表,如前文的.dts文件的"acme,a1234-i2c-bus"兼容I2C控制器结点的OF匹配表可以是:

     

    436 static const struct of_device_id a1234_i2c_of_match[] = {
    437         { .compatible = "acme,a1234-i2c-bus ", },
    438         {},
    439 };
    440 MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);
    441
    442 static struct platform_driver i2c_a1234_driver = {
    443         .driver = {
    444                 .name = "a1234-i2c-bus ",
    445                 .owner = THIS_MODULE,
    449                 .of_match_table = a1234_i2c_of_match,
    450         },
    451         .probe = i2c_a1234_probe,
    452         .remove = i2c_a1234_remove,
    453 };
    454 module_platform_driver(i2c_a1234_driver);

     

    对于I2C和SPI从设备而言,同样也可以透过of_match_table添加匹配的.dts中的相关结点的compatible属性,如sound/soc/codecs/wm8753.c中的:

     

    1533 static const struct of_device_id wm8753_of_match[] = {
    1534         { .compatible = "wlf,wm8753", },
    1535         { }
    1536 };
    1537 MODULE_DEVICE_TABLE(of, wm8753_of_match);
    1587 static struct spi_driver wm8753_spi_driver = {
    1588         .driver = {
    1589                 .name   = "wm8753",
    1590                 .owner  = THIS_MODULE,
    1591                 .of_match_table = wm8753_of_match,
    1592         },
    1593         .probe          = wm8753_spi_probe,
    1594         .remove         = wm8753_spi_remove,
    1595 };
    1640 static struct i2c_driver wm8753_i2c_driver = {
    1641         .driver = {
    1642                 .name = "wm8753",
    1643                 .owner = THIS_MODULE,
    1644                 .of_match_table = wm8753_of_match,
    1645         },
    1646         .probe =    wm8753_i2c_probe,
    1647         .remove =   wm8753_i2c_remove,
    1648         .id_table = wm8753_i2c_id,
    1649 };

    不过这边有一点需要提醒的是,I2C和SPI外设驱动和Device Tree中设备结点的compatible 属性还有一种弱式匹配方法,就是别名匹配。compatible 属性的组织形式为<manufacturer>,<model>,别名其实就是去掉compatible 属性中逗号前的manufacturer前缀。关于这一点,可查看drivers/spi/spi.c的源代码,函数spi_match_device()暴露了更多的细节,如果别名出现在设备spi_driver的id_table里面,或者别名与spi_driver的name字段相同,SPI设备和驱动都可以匹配上:

     

     

     

    90 static int spi_match_device(struct device *dev, struct device_driver *drv)
    91 {
    92         const struct spi_device *spi = to_spi_device(dev);
    93         const struct spi_driver *sdrv = to_spi_driver(drv);
    94
    95         /* Attempt an OF style match */
    96         if (of_driver_match_device(dev, drv))
    97                 return 1;
    98
    99         /* Then try ACPI */
    100         if (acpi_driver_match_device(dev, drv))
    101                 return 1;
    102
    103         if (sdrv->id_table)
    104                 return !!spi_match_id(sdrv->id_table, spi);
    105
    106         return strcmp(spi->modalias, drv->name) == 0;
    107 }
    71 static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
    72                                                 const struct spi_device *sdev)
    73 {
    74         while (id->name[0]) {
    75                 if (!strcmp(sdev->modalias, id->name))
    76                         return id;
    77                 id++;
    78         }
    79         return NULL;
    80 }

     

    4.    常用OF API

    在Linux的BSP和驱动代码中,还经常会使用到Linux中一组Device Tree的API,这些API通常被冠以of_前缀,它们的实现代码位于内核的drivers/of目录。这些常用的API包括:

    int of_device_is_compatible(const struct device_node *device,const char *compat);

    判断设备结点的compatible 属性是否包含compat指定的字符串。当一个驱动支持2个或多个设备的时候,这些不同.dts文件中设备的compatible 属性都会进入驱动 OF匹配表。因此驱动可以透过Bootloader传递给内核的Device Tree中的真正结点的compatible 属性以确定究竟是哪一种设备,从而根据不同的设备类型进行不同的处理。如drivers/pinctrl/pinctrl-sirf.c即兼容于"sirf,prima2-pinctrl",又兼容于"sirf,prima2-pinctrl",在驱动中就有相应分支处理:

     

    1682 if (of_device_is_compatible(np, "sirf,marco-pinctrl"))
    1683      is_marco = 1;

    struct device_node *of_find_compatible_node(struct device_node *from,

     

             const char *type, const char *compatible);

     

    根据compatible属性,获得设备结点。遍历Device Tree中所有的设备结点,看看哪个结点的类型、compatible属性与本函数的输入参数匹配,大多数情况下,from、type为NULL。

    int of_property_read_u8_array(const struct device_node *np,

                         const char *propname, u8 *out_values, size_t sz);

    int of_property_read_u16_array(const struct device_node *np,

                          const char *propname, u16 *out_values, size_t sz);

    int of_property_read_u32_array(const struct device_node *np,

                          const char *propname, u32 *out_values, size_t sz);

    int of_property_read_u64(const struct device_node *np, const char

    *propname, u64 *out_value);

    读取设备结点np的属性名为propname,类型为8、16、32、64位整型数组的属性。对于32位处理器来讲,最常用的是of_property_read_u32_array()。如在arch/arm/mm/cache-l2x0.c中,透过如下语句读取L2 cache的"arm,data-latency"属性:

     

    534         of_property_read_u32_array(np, "arm,data-latency",
    535                                    data, ARRAY_SIZE(data));

    在arch/arm/boot/dts/vexpress-v2p-ca9.dts中,含有"arm,data-latency"属性的L2 cache结点如下:

     

    137         L2: cache-controller@1e00a000 {
    138                 compatible = "arm,pl310-cache";
    139                 reg = <0x1e00a000 0x1000>;
    140                 interrupts = <0 43 4>;
    141                 cache-level = <2>;
    142                 arm,data-latency = <1 1 1>;
    143                 arm,tag-latency = <1 1 1>;
    144         }


    有些情况下,整形属性的长度可能为1,于是内核为了方便调用者,又在上述API的基础上封装出了更加简单的读单一整形属性的API,它们为int of_property_read_u8()、of_property_read_u16()等,实现于include/linux/of.h:

     

     

    513 static inline int of_property_read_u8(const struct device_node *np,
    514                                        const char *propname,
    515                                        u8 *out_value)
    516 {
    517         return of_property_read_u8_array(np, propname, out_value, 1);
    518 }
    519
    520 static inline int of_property_read_u16(const struct device_node *np,
    521                                        const char *propname,
    522                                        u16 *out_value)
    523 {
    524         return of_property_read_u16_array(np, propname, out_value, 1);
    525 }
    526
    527 static inline int of_property_read_u32(const struct device_node *np,
    528                                        const char *propname,
    529                                        u32 *out_value)
    530 {
    531         return of_property_read_u32_array(np, propname, out_value, 1);
    532 }


    int of_property_read_string(struct device_node *np, const char

     

    *propname, const char **out_string);

    int of_property_read_string_index(struct device_node *np, const char

        *propname, int index, const char **output);

    前者读取字符串属性,后者读取字符串数组属性中的第index个字符串。如drivers/clk/clk.c中的of_clk_get_parent_name()透过of_property_read_string_index()遍历clkspec结点的所有"clock-output-names"字符串数组属性。

     

    1759 const char *of_clk_get_parent_name(struct device_node *np, int index)
    1760 {
    1761         struct of_phandle_args clkspec;
    1762         const char *clk_name;
    1763         int rc;
    1764
    1765         if (index < 0)
    1766                 return NULL;
    1767
    1768         rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
    1769                                         &clkspec);
    1770         if (rc)
    1771                 return NULL;
    1772
    1773         if (of_property_read_string_index(clkspec.np, "clock-output-names",
    1774                                   clkspec.args_count ? clkspec.args[0] : 0,
    1775                                           &clk_name) < 0)
    1776                 clk_name = clkspec.np->name;
    1777
    1778         of_node_put(clkspec.np);
    1779         return clk_name;
    1780 }
    1781 EXPORT_SYMBOL_GPL(of_clk_get_parent_name);


    static inline bool of_property_read_bool(const struct device_node *np,

     

                                             const char *propname);

     

    如果设备结点np含有propname属性,则返回true,否则返回false。一般用于检查空属性是否存在。

     

    void __iomem *of_iomap(struct device_node *node, int index);

     

    通过设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的reg属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况,index为0。采用Device Tree后,大量的设备驱动通过of_iomap()进行映射,而不再通过传统的ioremap。

     

    unsigned int irq_of_parse_and_map(struct device_node *dev, int index);

    透过Device Tree或者设备的中断号,实际上是从.dts中的interrupts属性解析出中断号。若设备使用了多个中断,index指定中断的索引号。

     

    还有一些OF API,这里不一一列举,具体可参考include/linux/of.h头文件。

    5.    总结

    ARM社区一贯充斥的大量垃圾代码导致Linus盛怒,因此社区在2011年到2012年进行了大量的工作。ARM Linux开始围绕Device Tree展开,Device Tree有自己的独立的语法,它的源文件为.dts,编译后得到.dtb,Bootloader在引导Linux内核的时候会将.dtb地址告知内核。之后内核会展开Device Tree并创建和注册相关的设备,因此arch/arm/mach-xxx和arch/arm/plat-xxx中大量的用于注册platform、I2C、SPI板级信息的代码被删除,而驱动也以新的方式和.dts中定义的设备结点进行匹配。

    更多精华文章请扫描下方二维码关注Linux阅码场

    展开全文
  • LayUI—tree树形结构的使用

    万次阅读 多人点赞 2019-09-01 21:46:46
    树形结构在实际开发中很长用到,...官网地址:https://www.layui.com/v1/doc/modules/tree.html 先看一下显示的效果图。 点击节点右面会显示对应部门的详情信息,可以修改。可以自定义按钮添加部门,也可以直接...

    树形结构在实际开发中很长用到,比如部门管理,权限菜单等。因为用树形结构来展示会显的很清晰明了。最近写了一个个人博客小项目中用到了LayUI的树形结构,之后写了一个小案例整理一下。

    官网地址:https://www.layui.com/v1/doc/modules/tree.html

    先看一下显示的效果图。

    点击节点右面会显示对应部门的详情信息,可以修改。可以自定义按钮添加部门,也可以直接用自带的方法对部门进行新增,修改和删除。可以获取选中的节点,根据项目需求(有的需要选中保存)。

    先需要引入LayUI的样式文件JS和CSS。

    案例对应的实体类Dept。

    @Entity
    public class Dept {
        private Integer id;
        private String name;    //部门名称
        private String deptName;    //部门负责人
        private String phone;   //电话号
        private String number;  //编号
        private double idx;     //排序
        @JsonIgnore
        private Dept parent;
        @JsonIgnore
        private List<Dept> children = new ArrayList<>();
    
        @Id
        @GeneratedValue
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDeptName() {
            return deptName;
        }
    
        public void setDeptName(String deptName) {
            this.deptName = deptName;
        }
    
        public String getPhone() {
            return phone;
        }
    
        public void setPhone(String phone) {
            this.phone = phone;
        }
    
        public String getNumber() {
            return number;
        }
    
        public void setNumber(String number) {
            this.number = number;
        }
    
        public double getIdx() {
            return idx;
        }
    
        public void setIdx(double idx) {
            this.idx = idx;
        }
    
        @ManyToOne
        @CreatedBy
        public Dept getParent() {
            return parent;
        }
    
        public void setParent(Dept parent) {
            this.parent = parent;
        }
    
        @OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
        @OrderBy(value="idx")
        public List<Dept> getChildren() {
            return children;
        }
    
        public void setChildren(List<Dept> children) {
            this.children = children;
        }
    
        public Dept(Integer id, String name, String deptName, String phone, String number, double idx, Dept parent, List<Dept> children) {
            this.id = id;
            this.name = name;
            this.deptName = deptName;
            this.phone = phone;
            this.number = number;
            this.idx = idx;
            this.parent = parent;
            this.children = children;
        }
    
        public Dept(Integer id) {
            this.id = id;
        }
    
        public Dept() {
        }
    }

    显示LayUI树形菜单,只需要一个标签容器即可。

    <div id="dept_tree">
    </div>

    在案例中还有一些其他样式,比如右边的详情信息,新增按钮等。完整代码如下。

    <style type="text/css">
        #dept_main, #dept_particulars{
            width: 48.5%;
            display: inline-block;
            vertical-align: top;
            padding: 20px;
            background: white;
            box-sizing: border-box;
        }
        #dept_tree{
            margin-top: 20px;
        }
    </style>
    
    <div id="dept_main" style="margin-right: 2%;">
        <fieldset class="layui-elem-field layui-field-title">
            <legend>所有部门</legend>
        </fieldset>
        <button class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal" lay-demo="addDept"><i class="layui-icon">&#xe654;</i>添加部门</button>
        <button class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal" lay-demo="gain">获取选中节点</button>
        <div id="dept_tree">
    
        </div>
    </div>
    <div id="dept_particulars">
        <fieldset class="layui-elem-field layui-field-title">
            <legend>部门详情</legend>
        </fieldset>
        <div id="dept_home">
            <div class="layui-tree-emptyText">无数据</div>
        </div>
    </div>

    JS请求数据渲染页面代码,data为请求数据源,当时直接放入的请求链接,好像不行,所以之后才写了一个方法去请求数据源。

    layui.use(['tree', 'util'], function() {
        var tree = layui.tree;
        var util = layui.util;
        tree.render({
            elem: '#dept_tree',
            data: getData(),
            id: 'treeId',
            showCheckbox: true,     //是否显示复选框
            onlyIconControl: true
        });
    });
    
    function getData(){
        var data = [];
        $.ajax({
            url: "dept/treeload",    //后台数据请求地址
            type: "post",
            async:false,
            success: function(resut){
                data = resut;
            }
        });
        return data;
    }

     tree 组件提供的有以下基础参数,可根据需要进行相应的设置。

    参数选项 说明 类型 示例值
    elem 指向容器选择器 String/Object -
    data 数据源 Array -
    id 设定实例唯一索引,用于基础方法传参使用。 String -
    showCheckbox 是否显示复选框 Boolean false
    edit 是否开启节点的操作图标。默认 false。
    • 若为 true,则默认显示“改删”图标
    • 若为 数组,则可自由配置操作图标的显示状态和顺序,目前支持的操作图标有:addupdatedel,如: 
      edit: ['add', 'update', 'del']
    Boolean/Array ['update', 'del']
    accordion 是否开启手风琴模式,默认 false Boolean false
    onlyIconControl 是否仅允许节点左侧图标控制展开收缩。默认 false(即点击节点本身也可控制)。若为 true,则只能通过节点左侧图标来展开收缩 Boolean false
    isJump 是否允许点击节点时弹出新窗口跳转。默认 false,若开启,需在节点数据中设定 link 参数(值为 url 格式) Boolean false
    showLine 是否开启连接线。默认 true,若设为 false,则节点左侧出现三角图标。 Boolean true
    text 自定义各类默认文本,目前支持以下设定:

    text: {

         defaultNodeName: '未命名' //节点默认名称

         ,none: '无数据' //数据为空时的提示文本

    }

    Object -

    因为tree指定了json数据的键名称,所以后台传递过来的数据对应的键名不一样时需要做一下处理,或者实体类中的属性名就和tree的JSON数据的键名称一样。

    键名:

    属性选项 说明 类型 示例值
    title 节点标题 String 未命名
    id 节点唯一索引,用于对指定节点进行各类操作 String/Number 任意唯一的字符或数字
    children 子节点。支持设定选项同父节点 Array [{title: '子节点1', id: '111'}]
    href 点击节点弹出新窗口对应的 url。需开启 isJump 参数 String 任意 URL
    spread 节点是否初始展开,默认 false Boolean true
    checked 节点是否初始为选中状态(如果开启复选框的话),默认 false Boolean true
    disabled 节点是否为禁用状态。默认 false Boolean false

    后台请求数据的方法。

    @RequestMapping(value = "/treeload")
    @ResponseBody
    public Object treeload(){
        Sort sort = Sort.by("idx");
        List<Dept> dpet = deptService.findByParentIsNull(sort);    //查找所有菜单
        List<HashMap<String, Object>> result = new ArrayList<>();    //定义一个map处理json键名问题
        return fun(dpet, result);
    }
    
    private Object fun(List<Dept> dpet, List<HashMap<String, Object>> result) {
        for(Dept d : dpet){
            HashMap<String, Object> map = new HashMap<>();
            map.put("id", d.getId());
            map.put("title", d.getName());
            map.put("spread", true);      //设置是否展开
            List<HashMap<String, Object>> result1 = new ArrayList<>();
            List<Dept> children = d.getChildren();    //下级菜单
            //这里可以根据自己需求判断节点默认选中
            /*if(m.getParent() != null || m.getChildren().size() == 0){
                map.put("checked", true);    //设置为选中状态
            }*/
            map.put("children", fun(children, result1));
            result.add(map);
        }
        return result;
    }

    因为这里新建的实体类字段名和tree指定了json数据的键名称不一样,所以这里用了一个fun递归方法处理的。中间可以根据项目需求,根据条件判断是否需要选中该节点。

    返回的JSON数据格式

    [
    	{
    		"children": [		//子节点
    			{
    				"children": [
    					{
    						"children": [],
    						"id": 30,
    						"title": "测试",
    						"spread": true
    					}, {
    						"children": [],
    						"id": 31,
    						"title": "开发",
    						"spread": true
    					}, {
    						"children": [
    							{
    								"children": [],
    								"id": 36,
    								"title": "测试节点",
    								"spread": true
    							}
    						],
    						"id": 32,
    						"title": "测试",
    						"spread": true
    					}
    				],
    				"id": 2,
    				"title": "技术部",
    				"spread": true
    			}, {
    				"children": [],
    				"id": 19,
    				"title": "财务部",
    				"spread": true
    			}
    		],
    		"id": 1,	//节点id
    		"title": "某某公司",	//节点名称
    		"spread": true
    	}, {
    		"children": [],
    		"id": 33,
    		"title": "测试",
    		"spread": true
    	}
    ]

    设置节点点击回调方法(在加载数据方法tree.render中添加以下代码)。

    click: function (obj) {
        var id = obj.data.id;
        $("#dept_home").load("dept/show?id="+id);
    }

    把请求过来的详情页面load到右边的div中显示。后台请求方法

    @RequestMapping(value = "/show")
    public void show(DeptForm form, ModelMap map) throws InstantiationException, IllegalAccessException {
        Dept model = new Dept();
        Integer id = form.getId();
        Integer parentId = 0;
        if(id!=null) {
            model = deptService.findById(id);
            parentId = model.getParent()==null?0:model.getParent().getId();
        }
        map.put("parentId", parentId);
        map.put("model", model);
    }

    DeptForm类为一个接收类,其中字段和实体类中一样。根据请求传递过来的id,查询这条数据的详细信息,之后把查询的当前部门详情数据及父级节点id(用于下拉树TreeSelect)传递给详情页面。

    show.html详情页面代码。

    <meta charset="UTF-8" />
    <style type="text/css">
        .myData .layui-form-item{
            margin: 20px 100px 10px 45px;
        }
        .myData .layui-form-label{
            width: 85px;
        }
        .layui-input-block {
            margin-left: 120px;
        }
    </style>
    <form class="layui-form myData" action="save" method="post" lay-filter="stuform">
        <input type="hidden" name="id" data-th-value="${model.id}" />
    
        <div class="layui-form-item">
            <label class="layui-form-label">上级部门:</label>
            <div class="layui-input-block">
                <input type="text" name="parentId" id="tree" lay-filter="tree" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">部门名称:</label>
            <div class="layui-input-block">
                <input type="text" name="name" lay-verify="required" th:value="${model.name}" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">部门负责人:</label>
            <div class="layui-input-block">
                <input type="text" name="deptName" th:value="${model.deptName}" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">电话:</label>
            <div class="layui-input-block">
                <input type="text" name="phone" th:value="${model.phone}" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">编号:</label>
            <div class="layui-input-block">
                <input type="text" name="number" th:value="${model.number}" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">排序:</label>
            <div class="layui-input-block">
                <input type="text" name="idx" value="0" th:value="${model.idx}" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label"></label>
            <div class="layui-input-block">
                <button lay-submit class="layui-btn layui-btn-radius layui-btn-normal" lay-filter="btnSub">
                    <i class="layui-icon">&#xe642;</i>修改并保存
                </button>
            </div>
        </div>
    </form>
    
    <script th:inline="javascript">
        layui.use(["treeSelect", "form", "tree"], function () {
            var form = layui.form;
            var tree = layui.tree;
            form.render('select');
            var treeSelect = layui.treeSelect;
            treeSelect.render({
                // 选择器
                elem: '#tree',
                // 数据
                data: 'dept/treeSelect?id='+[[${model.id==null ? 0 : model.id}]],
                // 异步加载方式:get/post,默认get
                type: 'post',
                // 占位符
                placeholder: '上级菜单',
                // 是否开启搜索功能:true/false,默认false
                search: true,
                // 一些可定制的样式
                style: {
                    folder: {
                        enable: true
                    },
                    line: {
                        enable: true
                    }
                },
                // 加载完成后的回调函数
                success: function (d) {
                    // 选中节点,根据id筛选
                    treeSelect.checkNode('tree', [[${model.parent == null? parentId: model.parent.id}]]);
                    treeSelect.refresh('tree');
                }
            });
    
            form.on('submit(btnSub)', function (data) {
                $.post('dept/save', data.field, function (result) {
                    if (result.success) {
                        tree.reload('treeId', {data: getData()});
                    }
                    layer.msg(result.msg, {offset: 'rb'});
                });
                return false;
            });
        });
    </script>
    

    上级部门使用的是LayUI下拉树显示的,下拉树数据请求方法。关于下拉树的使用,可以访问LayUI下拉树TreeSelect的使用

    @RequestMapping(value="/treeSelect")
    @ResponseBody
    public Object treeSelect(Integer id) {
        Sort sort = Sort.by("idx");
        Specification<Dept> spec = buildSpec1();
        List<Dept> list = deptService.findAll(spec,sort);
        return buildTree(list, id);
    }
    
    private Object buildTree(List<Dept> list, Integer id) {
        List<HashMap<String, Object>> result=new ArrayList<>();
        for (Dept dept : list) {
            if(dept.getId() != id) {
                HashMap<String, Object> node=new HashMap<>();
                node.put("id", dept.getId());
                node.put("name",dept.getName());
                node.put("open", false);
                node.put("checked", false);
                if(dept.getChildren().size() != 0) {
                    node.put("children",buildTree(dept.getChildren(), id));
                }
                result.add(node);
            }
        }
    
        return result;
    }
    
    public Specification<Dept> buildSpec1() {
        Specification<Dept> specification = new Specification<Dept>() {
    
            private static final long serialVersionUID = 1L;
    
            @Override
            public Predicate toPredicate(Root<Dept> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                HashSet<Predicate> rules=new HashSet<>();
                Predicate parent = cb.isNull(root.get("parent"));
                rules.add(parent);
                return cb.and(rules.toArray(new Predicate[rules.size()]));
            }
    
        };
        return specification;
    }

    显示的效果。

    上面修改并保存后台方法(因为修改和新增共用的一个方法,用id区分的)。

    @Override
    public Object save(DeptForm form) {
        try {
            Dept model = new Dept();
            Integer id = form.getId();
            if(id != null) {
                model = deptService.findById(id);
            }
            //父级菜单id
            Integer parentId = form.getParentId();
            if(parentId == null) {
                model.setParent(null);
            }else {
                model.setParent(new Dept(parentId));
            }
            BeanUtils.copyProperties(form, model,"id", "parent");
            deptService.save(model);
            return new AjaxResult("数据保存成功!");
        } catch (Exception e) {
            return new AjaxResult(false,"数据保存失败");
        }
    }

    设置节点操作(在加载数据方法tree.render中添加以下代码)。

    edit: ['add', 'update', 'del'], //操作节点的图标
    operate: function(obj){
        var type = obj.type; //得到操作类型:add、edit、del
        var data = obj.data; //得到当前节点的数据
        var elem = obj.elem; //得到当前节点元素
    
        var id = data.id;
        var name = data.title;
        if(type === 'add'){ //增加节点
            $.post("dept/save", {parentId: id, name: "未命名"}, function (result) {
                tree.reload('treeId', {data: getData()});
            })
            //返回 key 值
            return ;
        } else if(type === 'update'){ //修改节点
            $.post("dept/update", {id: id, name: name}, function () {
                tree.reload('treeId', {data: getData()});
            })
        } else if(type === 'del'){ //删除节点
            $.post("dept/delete", {id: id}, function () {
                tree.reload('treeId', {data: getData()});
            });
        };
    }

    其中operate为操作节点回调方法。

    obj.type为操作类型,add为新增,update为修改,edl为删除。obj.data为操作节点后的数据。

    新增节点后,向后台发送请求添加节点,save方法和上面修改方法一样,id为新建节点的父级节点id。

    修改节点,同样,向后台发送修改请求,并传递对象的id,和修改后的数据作为参数。后台响应方法。

    @RequestMapping(value = "/update")
    @ResponseBody
    public Object update(DeptForm form) {
        try {
            Dept model = deptService.findById(form.getId());
            model.setName(form.getName());
            deptService.save(model);
            return new AjaxResult("数据保存成功!");
        } catch (Exception e) {
            return new AjaxResult(false,"数据保存失败");
        }
    }

    删除节点同理,传递删除节点的id。删除请求方法。

    @RequestMapping(value="/delete")
    @ResponseBody
    public Object delete(Integer id) {
    	try {
    		deptService.deleteById(id);
    		return new AjaxResult("数据删除成功");
    	} catch (Exception e) {
    		return new AjaxResult(false,"数据删除失败");
    	}
    }

    使用按钮操作树形菜单。

    现在页面中定义两个按钮,给按钮添加lay-demo=""属性,并设置属性值,JS通过这个属性值,绑定点击事件。

    <button class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal" lay-demo="addDept"><i class="layui-icon">&#xe654;</i>添加部门</button>
    <button class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal" lay-demo="gain">获取选中节点</button>

    绑定添加部门和获取选中节点按钮的点击事件的JS代码。

    util.event('lay-demo', {
        addDept: function(othis){
            $.get('dept/edit', function(data) {
                layer.open({
                    type: 1,
                    title: '新增',
                    area: ['530px'],
                    content: data,
                    btn: ['提交', '退出'],
                    yes: function () {
                    },
                    success: function (layero, index) {
                        layui.use('form', function () {
                            var form = layui.form;
                            layero.addClass('layui-form');
                            var submitBtn = layero.find('.layui-layer-btn0');
                            submitBtn.attr('lay-filter', 'formVerify').attr('lay-submit', '');
                            layero.keydown(function (e) {
                                if (e.keyCode == 13) {
                                    submitBtn.click();
                                }
                            });
    
                            form.on('submit(formVerify)', function (data) {
                                $.post('dept/save', data.field, function (result) {
                                    if (result.success) {
                                        layer.close(index);
                                        tree.reload('treeId', {data: getData()});
                                    }
                                    layer.msg(result.msg, {offset: 'rb'});
                                });
                                return false;
                            });
                        });
                    }
                })
            })
        },
        gain: function () {
            var checkData = tree.getChecked('treeId');
            var str = JSON.stringify(checkData);
            $.post('dept/checkedGain', {data: str}, function () {
            });
            layer.alert(JSON.stringify(checkData), {shade:0});
        }
    });

    添加部门按钮点击事件,先发送请求到后台,跳转到eidt新增页面,edit.html新增页面代码,和上面的show.html显示部门详情页面差不多。上级部门同样使用的LayUI下拉树显示的,下拉树数据请求方法,和上面的详情页面下拉树请求方法一致。LayUI下拉树TreeSelect的使用。新增后的保存方法也和上面的保存方法一致。

    后台请求方法代码,跳转到edit页面。

    @RequestMapping(value = "/edit")
    public void edit(){
        
    }

    edit.html页面完整代码如下。

    <meta charset="UTF-8" />
    <style type="text/css">
        .myData .layui-form-item{
            margin: 20px 100px 10px 45px;
        }
        .myData .layui-form-label{
            width: 85px;
        }
        .layui-input-block {
            margin-left: 120px;
        }
    </style>
    <form class="layui-form myData" action="save" method="post" lay-filter="stuform">
    
        <div class="layui-form-item">
            <label class="layui-form-label">上级部门:</label>
            <div class="layui-input-block">
                <input type="text" name="parentId" id="tree2" lay-filter="tree2" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">部门名称:</label>
            <div class="layui-input-block">
                <input type="text" name="name" lay-verify="required" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">部门负责人:</label>
            <div class="layui-input-block">
                <input type="text" name="deptName" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">电话:</label>
            <div class="layui-input-block">
                <input type="text" name="phone" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">编号:</label>
            <div class="layui-input-block">
                <input type="text" name="number" class="layui-input" />
            </div>
        </div>
        <div class="layui-form-item" >
            <label class="layui-form-label">排序:</label>
            <div class="layui-input-block">
                <input type="text" name="idx" value="0" class="layui-input" />
            </div>
        </div>
    </form>
    
    <script th:inline="javascript">
        layui.use(["treeSelect", "form"], function () {
            var form = layui.form;
            form.render('select');
            var treeSelect = layui.treeSelect;
            treeSelect.render({
                // 选择器
                elem: '#tree2',
                // 数据
                data: 'dept/treeSelect',
                // 异步加载方式:get/post,默认get
                type: 'post',
                // 占位符
                placeholder: '上级菜单',
                // 是否开启搜索功能:true/false,默认false
                search: true,
                // 一些可定制的样式
                style: {
                    folder: {
                        enable: true
                    },
                    line: {
                        enable: true
                    }
                },
                // 加载完成后的回调函数
                success: function (d) {
                }
            });
        });
    </script>
    

    页面效果。

    获取选中节点按钮点击事件。如果项目需要保存数据时,就需要获取到选中节点的数据了。这里可以获取到选中节点的数据,之后当参数传递到后台。传递到后台是一个JSON数据的字符串,需要转换一下,这里给推荐大家两个很好用的JSON转换工具net.sf.json.JSONObjectAlibaba Fastjson。这里用的是Alibaba Fastjson,需要引入以下依赖。

    <dependency>
    	<groupId>com.alibaba</groupId>
    	<artifactId>fastjson</artifactId>
    	<version>1.2.47</version>
    </dependency>

    这里用于输出,新建了一个和tree的json数据的键名称一样的工具类DeptTree,代码如下。

    import java.util.ArrayList;
    import java.util.List;
    
    public class DeptTree {
        private Integer id;
        private String title;
        private boolean checked;
        private boolean spread;
        private List<DeptTree> children = new ArrayList<>();
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public List<DeptTree> getChildren() {
            return children;
        }
    
        public void setChildren(List<DeptTree> children) {
            this.children = children;
        }
    
        public boolean isChecked() {
            return checked;
        }
    
        public void setChecked(boolean checked) {
            this.checked = checked;
        }
    
        public boolean isSpread() {
            return spread;
        }
    
        public void setSpread(boolean spread) {
            this.spread = spread;
        }
    }

    后台接收到传递过来的JSON数据字符串,转换后输出方法。

    @RequestMapping(value = "/checkedGain")
    @ResponseBody
    public void checkedGain(String data){
        List<DeptTree> array2 = JSONArray.parseArray(data, DeptTree.class);
        treeData(array2);
    }
    
    //递归输出选中数据
    private void treeData(List<DeptTree> array2) {
        for (DeptTree tree: array2){
            System.out.println(tree.getTitle()+"==="+tree.getId());
            if(tree.getChildren() != null){
                treeData(tree.getChildren());
            }
        }
    }

    选中节点,点击获取选中节点数据。

    后台对应方法接收到数据,转换后输出结果。

    数据拿到了,之后保存方法就简单了。

    后台方法代码基本都在上面了,页面全部代码。

    <style type="text/css">
        #dept_main, #dept_particulars{
            width: 48.5%;
            display: inline-block;
            vertical-align: top;
            padding: 20px;
            background: white;
            box-sizing: border-box;
        }
        #dept_tree{
            margin-top: 20px;
        }
    </style>
    
    <div id="dept_main" style="margin-right: 2%;">
        <fieldset class="layui-elem-field layui-field-title">
            <legend>所有部门</legend>
        </fieldset>
        <button class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal" lay-demo="addDept"><i class="layui-icon">&#xe654;</i>添加部门</button>
        <button class="layui-btn layui-btn-sm layui-btn-radius layui-btn-normal" lay-demo="gain">获取选中节点</button>
        <div id="dept_tree">
    
        </div>
    </div>
    <div id="dept_particulars">
        <fieldset class="layui-elem-field layui-field-title">
            <legend>部门详情</legend>
        </fieldset>
        <div id="dept_home">
            <div class="layui-tree-emptyText">无数据</div>
        </div>
    </div>
    
    <script type="text/javascript">
        layui.use(['tree', 'util', 'layer'], function() {
            var tree = layui.tree;
            var util = layui.util;
            var layer = layui.layer;
            tree.render({
                elem: '#dept_tree',
                data: getData(),
                id: 'treeId',
                showCheckbox: true,     //时候显示复选框
                onlyIconControl: true,
                edit: ['add', 'update', 'del'], //操作节点的图标
                click: function (obj) {
                    var id = obj.data.id;
                    $("#dept_home").load("dept/show?id="+id);
                },
                operate: function(obj){
                    var type = obj.type; //得到操作类型:add、edit、del
                    var data = obj.data; //得到当前节点的数据
                    var elem = obj.elem; //得到当前节点元素
    
                    var id = data.id;
                    var name = data.title;
                    if(type === 'add'){ //增加节点
                        $.post("dept/save", {parentId: id, name: "未命名"}, function (result) {
                            tree.reload('treeId', {data: getData()});
                        })
                        //返回 key 值
                        return ;
                    } else if(type === 'update'){ //修改节点
                        $.post("dept/update", {id: id, name: name}, function () {
                            tree.reload('treeId', {data: getData()});
                        })
                    } else if(type === 'del'){ //删除节点
                        $.post("dept/delete", {id: id}, function () {
                            tree.reload('treeId', {data: getData()});
                        });
                    };
                }
            });
    
            util.event('lay-demo', {
                addDept: function(othis){
                    $.get('dept/edit', function(data) {
                        layer.open({
                            type: 1,
                            title: '新增',
                            area: ['530px'],
                            content: data,
                            btn: ['提交', '退出'],
                            yes: function () {
                            },
                            success: function (layero, index) {
                                layui.use('form', function () {
                                    var form = layui.form;
                                    layero.addClass('layui-form');
                                    var submitBtn = layero.find('.layui-layer-btn0');
                                    submitBtn.attr('lay-filter', 'formVerify').attr('lay-submit', '');
                                    layero.keydown(function (e) {
                                        if (e.keyCode == 13) {
                                            submitBtn.click();
                                        }
                                    });
    
                                    form.on('submit(formVerify)', function (data) {
                                        $.post('dept/save', data.field, function (result) {
                                            if (result.success) {
                                                layer.close(index);
                                                tree.reload('treeId', {data: getData()});
                                            }
                                            layer.msg(result.msg, {offset: 'rb'});
                                        });
                                        return false;
                                    });
                                });
                            }
                        })
                    })
                },
                gain: function () {
                    var checkData = tree.getChecked('treeId');
                    var str = JSON.stringify(checkData);
                    $.post('dept/checkedGain', {data: str}, function () {
                    });
                    layer.alert(JSON.stringify(checkData), {shade:0});
                }
            });
        });
    
        function getData(){
            var data = [];
            $.ajax({
                url: "dept/treeload",    //后台数据请求地址
                type: "post",
                async:false,
                success: function(resut){
                    data = resut;
                }
            });
            return data;
        }
        
    </script>

    如果对你有帮助,点赞关注一下呗^_^,留下你的足迹。

    展开全文
  • Trie Tree和Radix Tree

    千次阅读 2019-06-13 22:38:28
    前言 在实际应用场景中,很多时候我们会用到字典集的查找。通过一个键值key,去拿到它对应的值...本节笔者将要谈论的是对于基数统计来说,使用上更为适用的1种数据结构Trie Tree以及它的衍生优化版Radix Tree。 ...

    前言


    在实际应用场景中,很多时候我们会用到字典集的查找。通过一个键值key,去拿到它对应的值对象。这种方法确实很高效,很快,但是这里有个问题,当字典集储存的键值很多的情况时,毫无疑问,这里会消耗掉大量的内存空间。这个在我们做基数计数的统计应用时,这个空间会膨胀地特别厉害。本节笔者将要谈论的是对于基数统计来说,使用上更为适用的1种数据结构Trie Tree以及它的衍生优化版Radix Tree。

    Trie Tree


    我们先来看第一种树结构Trie Tree,名叫字典树。Trie Tree的原理是将每个key拆分成每个单位长度字符,然后对应到每个分支上,分支所在的节点对应为从根节点到当前节点的拼接出的key的值。它的结构图如下所示。

    在这里插入图片描述

    上图中每个节点存储的值为一个数值,当然这不是绝对的,我们可以假想这可以是key值所对应的任何值对象。只是计数统计在平时使用中更为常见一些。那么问题来了,相比较于字典集的存储方式,Trie Tree将key以树型结构构造,有什么用意呢?一个最大的帮助是公共的前缀被共享了,这样可以避免被重复地存储了。而在普通的字典集结构中,这种重复key前缀都是独立被包含在每个key内的。假设当字典集中所存储的key都带有大量重复的前缀时,Trie Tree将会消耗比普通字典结构更少的空间。

    如上述所描述的,通过共享公共前缀的方式空间省了不少,但是它的查询效率如何呢?按照树的查询方式,一个查询key长度m,首先我们会将key拆分成m个字符,然后对应每个长度,从根节点依次向下,总共会进行到m次查找。因此我们可以得出,它的查询时间复杂度为O(m),随着查询key长度的增加,它所消耗的时间将会线性增长。

    往深层面来看查询时间的问题,其实本质上这是树的深度引起的问题,对于长key而言,它的key对应的节点构造的深度过深。那么如果说我们将这“棵”树构造得更紧凑一些,会如何呢?于是我们有了另外一个衍生版本树:Radix Tree(基数树)。

    Radix Tree


    Radix Tree名为基数树,它的计数统计原理和Trie Tree极为相似,一个最大的区别点在于它不是按照每个字符长度做节点拆分,而是可以以1个或多个字符叠加作为一个分支。这就避免了长字符key会分出深度很深的节点。Radix Tree的结构构造如下图所示:

    在这里插入图片描述
    从上图我们可以看到,上面每个分支可以是局部部分字符串。以简单的字符查找为例,Radix Tree的搜索查找过程如下:

    在这里插入图片描述

    针对Radix Tree的构造规则,它的节点插入和删除行为相比较于Trie Tree来说,略有不同。

    • 对于节点插入而言,当有新的key进来,需要拆分原有公共前缀分支。
    • 对于节点删除而言,当删除一个现有key后,发现其父节点只有另外一个子节点key,则此子节点可以和父节点合并为一个新的节点,以此减少树的比较深度。

    Radix Tree的insert过程如下图所示:
    在这里插入图片描述

    Trie Tree、Radix Tree的局限性


    的确Trie Tree、Radix Tree在某些应用场景可以帮助我们节省内存使用空间,但是它们也有其使用的局限性。比如这类树结构无法适用于所有的数据类型,目前来看主要适用于能够用string字符等可作为表达式查询key的场景。

    引用


    [1].https://en.wikipedia.org/wiki/Radix_tree
    [2].https://en.wikipedia.org/wiki/Trie

    展开全文
  • 快速理解平衡二叉树、B-tree、B+tree、B*tree

    万次阅读 多人点赞 2017-10-18 00:34:32
    快速理解平衡二叉树、B-tree、B+tree、B*tree

    原文:https://my.oschina.net/u/3370829/blog/1301732

    觉得这篇文章比较好,特此分享

     1、平衡二叉树

    (1)由来:平衡二叉树是基于二分法的策略提高数据的查找速度的二叉树的数据结构;

    (2)特点:

    平衡二叉树是采用二分法思维把数据按规则组装成一个树形结构的数据,用这个树形结构的数据减少无关数据的检索,大大的提升了数据检索的速度;平衡二叉树的数据结构组装过程有以下规则:

    非叶子节点只能允许最多两个子节点存在,每一个非叶子节点数据分布规则为左边的子节点小当前节点的值,右边的子节点大于当前节点的值(这里值是基于自己的算法规则而定的,比如hash值);

     

     

    平衡树的层级结构:因为平衡二叉树查询性能和树的层级(h高度)成正比、为了保证树的结构左右两端数据大致平衡降低二叉树的查询难度一般会采用一种算法机制实现节点数据结构的平衡,实现了这种算法的有比如AVL、Treap、红黑树,使用平衡二叉树能保证数据的左右两边的节点层级相差不会大于1.,通过这样避免树形结构由于删除增加变成线性链表影响查询效率,保证数据平衡的情况下查找数据的速度近于二分法查找;

     

     

     

     

    总结平衡二叉树特点:

    (1)非叶子节点最多拥有两个子节点;

    (2)非叶子节值大于左边子节点、小于右边子节点;

    (3)树的左右两边的层级数相差不会大于1;

    (4)没有值相等重复的节点;

      二叉树的优点:

      二叉排序树是一种比较有用的折衷方案。  
      数组的搜索比较方便,可以直接用下标,但删除或者插入某些元素就比较麻烦。  
      链表与之相反,删除和插入元素很快,但查找很慢。  
      二叉排序树就既有链表的好处,也有数组的好处。  
      在处理大批量的动态的数据是比较有用。

     

    文件系统和数据库系统一般都采用树(特别是B树)的数据结构数据,主要为排序和检索的效率。二叉树是一种最基本最典型的排序树,用于教学和研究树的特性,本身很少在实际中进行应用,因为缺点太明显了(看看教科书怎么说的)。就像冒泡排序一样,虽然因为效率问题并不实用,单不失一种教学例子的好手段。

     

    平衡二叉树都有哪些应用场景

    二叉树支持动态的插入和查找,保证操作在O(height)时间,这就是完成了哈希表不便完成的工作,动态性。但是二叉树有可能出现worst-case,如果输入序列已经排序,则时间复杂度为O(N)

    平衡二叉树/红黑树就是为了将查找的时间复杂度保证在O(logN)范围内。
    所以如果输入结合确定,所需要的就是查询,则可以考虑使用哈希表,如果输入集合不确定,则考虑使用平衡二叉树/红黑树,保证达到最大效率

    平衡二叉树主要优点集中在快速查找
    如果你知道SGI/STL的set/map底层都是用红黑树(平衡二叉树的一种)实现的,相信你会对这些树大有兴趣。

     

     

    2、B树(B-tree)

    注意:之前有看到有很多文章把B树和B-tree理解成了两种不同类别的树,其实这两个是同一种树;

     

    1、概念:B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径不只两个),数据库索引技术里大量使用者B树和B+树的数据结构,让我们来看看他有什么特点;

     

     

    2、规则:

    (1)树种的每个节点最多拥有m个子节点且m>=2,空树除外(注:m阶代表一个树节点最多有多少个查找路径,m阶=m路,当m=2则是2叉树,m=3则是3叉);

    (2)除根节点外每个节点的关键字数量大于等于ceil(m/2)-1个小于等于m-1个;(注:ceil()是个朝正无穷方向取整的函数 如ceil(1.1)结果为2)

    (3)所有叶子节点均在同一层、叶子节点除了包含了关键字和关键字记录的指针外也有指向其子节点的指针只不过其指针地址都为null对应下图最后一层节点的空格子

    (4)如果一个非叶节点有N个子节点,则该节点的关键字数等于N-1;

    (5)所有节点关键字是按递增次序排列,并遵循左小右大原则;

     

    最后我们用一个图和一个实际的例子来理解B树(这里为了理解方便我就直接用实际字母的大小来排列C>B>A)

     

     

    3、B树的查询流程: 如上图我要从上图中找到E字母,查找流程如下

    (1)获取根节点的关键字进行比较,当前根节点关键字为M,E要小于M(26个字母顺序),所以往找到指向左边的子节点(二分法规则,左小右大,左边放小于当前节点值的子节点、右边放大于当前节点值的子节点);

    (2)拿到关键字D和G,D<E<G 所以直接找到D和G中间的节点;

    (3)拿到E和F,因为E=E 所以直接返回关键字和指针信息(如果树结构里面没有包含所要查找的节点则返回null);

     

    4、B树的插入节点流程

    定义一个5阶树(平衡5路查找树;),现在我们要把3、8、31、11、23、29、50、28 这些数字构建出一个5阶树出来;

    遵循规则:

    (1)当前是要组成一个5路查找树,那么此时m=5,关键字数必须大于等于cei(5/2)-1小于等于5-1(关键字数小于cei(5/2)-1 就要进行节点合并,大于5-1就要进行节点拆分);

    (2)满足左大右小的排序规则;

     

     

     

     

     

     

    5、B树节点的删除

    规则:

    (1)当前是要组成一个5路查找树,那么此时m=5,关键字数必须大于等于cei(5/2)-1小于等于5-1;

    (2)满足左大右小的排序规则;

    (3)关键字数小于二时先从子节点取,子节点没有符合条件时就向向父节点取,取中间值往父节点放;

     

     

     

    3、特点:

    B树相对于平衡二叉树的不同是,每个节点包含的关键字增多了,特别是在B树应用到数据库中的时候,数据库充分利用了磁盘块的原理(磁盘数据存储是采用块的形式存储的,每个块的大小一般为4K,每次IO进行数据读取时,同一个磁盘块的数据可以一次性读取出来)把节点大小限制和充分使用在磁盘快大小范围;把树的节点关键字增多后树的层级比原来的二叉树少了,减少数据查找的次数和复杂度;

     

    3、B+树

    B+树是B树的一个升级版,相对于B树来说B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度完全接近于二分法查找。为什么说B+树查找的效率要比B树更高、更稳定;我们先看看两者的区别

    (1)B+跟B树不同B+树的非叶子节点不保存关键字记录的指针,这样使得B+树每个节点所能保存的关键字大大增加;

    (2)B+树叶子节点保存了父节点的所有关键字和关键字记录的指针,每个叶子节点的关键字从小到大链接;

    (3)B+树的根节点关键字数量和其子节点个数相等;

    (4)B+的非叶子节点只进行数据索引,不会存实际的关键字记录的指针,所有数据地址必须要到叶子节点才能获取到,所以每次数据查询的次数都一样;

     

     

    特点:

    在B树的基础上每个节点存储的关键字数更多,树的层级更少所以查询数据更快,所有指关键字指针都存在叶子节点,所以每次查找的次数都相同所以查询速度更稳定;

     

     

    4、B*树

    B*树是B+树的变种,相对于B+树他们的不同之处如下:

    (1)首先是关键字个数限制问题,B+树初始化的关键字初始化个数是cei(m/2),b*树的初始化个数为(cei(2/3*m))

    (2)B+树节点满时就会分裂,而B*树节点满时会检查兄弟节点是否满(因为每个节点都有指向兄弟的指针),如果兄弟节点未满则向兄弟节点转移关键字,如果兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据创建一个新的节点出来;

    特点:

    在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而又存有兄弟节点的指针,可以向兄弟节点转移关键字的特性使得B*树额分解次数变得更少;

     

     

    总结:从平衡二叉树、B树、B+树、B*树总体来看它们的贯彻的思想是相同的,都是采用二分法和数据平衡策略来提升查找数据的速度;

    不同点是他们一个一个在演变的过程中通过IO从磁盘读取数据的原理进行一步步的演变,每一次演变都是为了让节点的空间更合理的运用起来,从而使树的层级减少达到快速查找数据的目的;

     

    如果还没理解的话推荐以下资料描叙的很详细:

    推荐资料:

    附一(二分法查找):https://zhuanlan.zhihu.com/p/27597160

    附二(B、B+、B*树):http://blog.csdn.net/v_JULY_v/article/details/6530142/

    附三(B、B+、B*树):http://blog.csdn.net/endlu/article/details/51720299


    展开全文
  • B-Tree和B+Tree

    千次阅读 2019-08-12 10:49:48
    目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree作为索引结构,在本文的下一节会结合存储器原理及计算机存取原理讨论为什么B-Tree和B+Tree在被如此广泛用于索引,这一节先单纯从数据结构角度描述它们。...
  • win tree命令 tree导出目录 tree显示树形结构 windows下,如何使用tree命令导出目录,如何使用tree命令显示文件目录的树形结构?tree命令如何使用呢?下面就为大家分享一下,如何使用tree命令显示一级文件...
  • Merkle Tree学习

    万次阅读 2019-05-17 21:07:20
    Merkle Tree概念    Merkle Tree,通常也被称作Hash Tree,顾名思义,就是存储hash值的一棵树。Merkle树的叶子是数据块(例如,文件或者文件的集合)的hash值。非叶节点是其对应子节点串联字符串的hash。[1]  1....
  • MySQL索引原理及B-Tree / B+Tree结构详解

    万次阅读 2019-08-09 12:30:22
    MySQL索引原理及B-Tree / B+Tree结构详解 目录 摘要 数据结构及算法基础 索引的本质 B-Tree和B+Tree B-Tree B+Tree 带有顺序访问指针的B+Tree 为什么使用B-Tree(B+Tree) 主存存取原理 磁盘存取原理 ...
  • JSTree

    万次阅读 2016-05-09 17:45:12
    JSTree是一个JQuery插件,当前最新版本v3.3.1(2016-05-05MIT),使用JSTree可以很方便的通过简单的配置快速实现复杂的树形结构,支持模糊查询、动态加载。
  • Symmetric Tree

    千次阅读 2018-06-05 23:33:33
    Symmetric Tree Description Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). Example 1: For example, this binary tree [1,2,2,3,4,4,3] is symmetric: ...
  • B-tree, AVL tree, RB tree

    千次阅读 2014-07-04 13:04:01
    http://stackoverflow.com/questions/1589556/when-to-choose-rb-tree-b-tree-or-avl-tree/1589587#1589587 http://stackoverflow.com/questions/647537/b-tree-faster-than-avl-or-redblack-tree/1195288#1
  • radix tree

    千次阅读 2018-05-12 21:15:51
      最近看了看代码,研究了一下Linux内核中诸多数据结构中的radix tree。radix tree数据结构在Linux内核中实现的很精致,没怎么看的明白!今天先来简单记录一下这段时间的一些测试和想法。 获取实例代码 ...
  • B-tree/B+tree/B*tree

    千次阅读 2016-08-25 18:27:01
    B~树  1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(Balanced Binary Search Tree),红黑树 (Red-Black Tree ),B-tree/B+-tree/ B*-t
  • Antd Tree组件Tree props#属性 checkStrictly

    千次阅读 2019-08-02 18:47:46
    Antd 3.20.7 版本 Tree组件Tree props#属性 checkStrictly值为true,则点击选中树
  • linux命令tree

    千次阅读 2020-10-14 22:06:31
    linux命令tree 显示文件和目录由根目录开始的树形结构 当你需要观察整个目录的时候,这个命令很管用,不需要你每次都进出每个目录去看 工作中也经常遇到
  • B-tree与二叉树的关系 B-tree与二叉树都是树形算法,他们之间的区别在于二叉树的每个节点只能存储一个值,二B-tree可以存储M个值(其中M为用户设定的节点所包含键值对的数量)  二叉树(每个节点只有一个键值对)...
  • Segment Tree and Binary index tree

    千次阅读 2016-06-23 10:46:14
    Segment Tree: http://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/ http://www.geeksforgeeks.org/segment-tree-set-1-range-minimum-query/ Binary Indexed Tree: http://www.geeks
  • bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library? 几经周折才知道是bs4调用了python自带的html解析器,我用的mac,默认安装的...
  • B tree、B-tree和B+tree

    千次阅读 2017-08-29 16:07:24
    (1)B tree 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right); 2.所有结点存储一个关键字; 3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树; B树的搜索,从根...
  • easyUI 附加tree节点到tree

    千次阅读 2012-08-13 10:50:59
    这个教程向你展示如何附加节点到tree,我们将创建一个食品tree包含水果和蔬菜节点,然后添加一些其他水果到已存在的水果节点. 创建 foods(食品) tree 首先,我们创建食品树,代码就像这样. 注意:tree...
  • 详解KDTree

    万次阅读 多人点赞 2014-11-26 16:51:42
    一个KDTree的例子上图的树就是一棵KDTree,形似二叉搜索树,其实KDTree就是二叉搜索树的变种。这里的K = 3.首先来看下树的组织原则。将每一个元组按0排序(第一项序号为0,第二项序号为1,第三项序号为2),在树的第...
  • Java中的B-Tree,B+Tree,B*Tree

    千次阅读 2018-03-24 21:45:43
    (一)什么是B-Tree、B+Tree、B*Tree?what?(1)B-Tree1.0 是一种多路搜索树(并不是二叉的) ①任意非叶子节点最多有M个儿子;且M&gt;2; ②根结点的儿子数为[2,M]; ③除根结点以外的非叶子结点的儿子数...
  • <el-tree :data="roledata" show-checkbox default-expand-all node-key="id" ref="tree" highlight-current :props="defaultProps" ...
  • Angular Tree Control

    千次阅读 2018-04-17 15:54:19
    github网址:https://github.com/wix/angular-tree-controldemo网址:http://wix.github.io/angular-tree-control/安装:&lt;script type="text/javascript" src="/angular-tree-control.js"...
  • Tree Size Problem

    2017-10-17 02:28:31
    For a tree metric, it has been shown that the tree size is unique, i.e., it is impossible to find two trees of different sizes realizing the same tree metric. Your task is to design a program to ...
  • easyUI Tree

    千次阅读 2012-08-03 17:23:10
    覆盖默认值 $.fn.tree.defaults. tree显示一个树结构分层数据在web页面中,它提供用户, expand(展开), collapse(折叠), drag(拖动) ,editing (编辑)和异步加载功能. 相关依赖 draggabledroppable ...
  • Merkle Tree(HashTree)

    千次阅读 2015-06-26 17:12:24
    Merkle Tree是基于数据HASH构建的一个树,Merkle Tree的叶子节点的value是数据集合的单元数据或者单元数据hash,Merke Tree非叶子节点value是其所有子节点value的HASH值。 Merkle tree可以用来进行大数据的比对,...
  • B-Tree、B+Tree

    千次阅读 2018-01-03 21:31:35
    B-Tree、B+Tree B-Tree B 树又叫平衡多路查找树。一棵m阶的B树的特性如下: 树中每个结点最多含有m个孩子(m>=2); 除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子(其中ceil(x)是一个...
  • KD-Tree算法

    万次阅读 多人点赞 2018-05-06 21:49:08
    kd树(k-dimensional树的简称),是一种分割k维数据空间的数据结构,主要... 一、Kd-tree其实KDTree就是二叉查找树(Binary Search Tree,BST)的变种。二叉查找树的性质如下:1)若它的左子树不为空,则左子树上所...

空空如也

1 2 3 4 5 ... 20
收藏数 328,292
精华内容 131,316
关键字:

tree