GaiaX开源解读 | 给Stretch(Rust编写的Flexbox布局引擎)新增特性,我掉了好多头发

news2024/11/27 2:26:55

GaiaX(盖亚),是在阿里文娱内广泛使用的Native动态化方案,其核心优势是性能、稳定和易用。本系列文章《GaiaX开源解读》,带大家看看过去三年GaiaX的发展过程。

GaiaX的布局方案 - Flexbox

阿里文娱业务作为一个内容分发、消费综合体,不仅覆盖Phone端,在Pad、OTT、Mac、车机、IOT带屏设备上都为广大用户提供在线视频媒体服务。
GaiaX作为文娱分发场景的一个重要端渲染解决方案,在进行布局技术方案设计时,必须充分考虑多端、多屏的响应式布局诉求。众所周知,浏览器场景很好的解决了屏幕窗口多尺寸的动态布局问题,其采用的布局方案即为Flexbox。
在这里插入图片描述

移动端原生Frame布局是Native方案的最优解,因为双端可最大限度的发挥OS的渲染特性来保证渲染性能。为了验证Flexbox的实测性能,我们针对单层及多层嵌套设计了测试用例,通过数据表现(耗时单位为ms)来进一步证明Flexbox方案是否可以作为GaiaX的布局方案。
在这里插入图片描述

实验结果的产出,完全打消了我们对Flexbox在性能上的顾虑,再加之其符合W3C规范、社区丰富、学习复杂度低等优势,最终团队选择Flexbox作为GaiaX的布局技术方案。

Flexbox高性能解析方案 - Stretch

为什么选择Stretch作为布局基础

在确定了布局方案后,接下来要做的事就是确定解析的技术选型。 社区支持Flexbox布局解析的技术方案,最为大家耳熟能详的是facebook推出的yoga,这也是业内的主流技术选型。
相对于yoga来说,Strectch具有以下的优势:

● 包大小体积
● 支持多线程的局部计算
● 支持跨平台的能力(iOS、Andriod、JS等)
● 基于rust语言实现,具备高性能,低内存占用等特性

Stretch的这些的优势是基于Rust语言特性所带来的:
更安全的内存管理:
与 C/C++ 语言相比,使用 Rust 语言来进行程序设计可以有助于从源头预防出现诸如空指针,缓存溢出和内存泄漏的内存问题。
更好的运行性能:
与 Java/C# 等语言相比,Rust 语言的内存管理不是依靠垃圾回收器机制( GC )来实现的。这个设计提高了程序运行的性能。
原生支持多线程开发:
Rust 语言的所有权机制和内存安全的特性为没有数据竞争的并发提供了语言层面上的原生支持。

网上已经有了很多关于Rust性能分析对比的文章,以下就是性能对比结果,性能上可以跟C++性能不相上下,某些场景下效率甚至优于C++。

Rust vs C++:
在这里插入图片描述

Stretch入门

由于Stretch是采用Flexbox布局的方式,在开始介绍Stretch核心布局计算逻辑之前,还需要简单了解一下Flexbox的基础概念
● 采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item)。
● 容器默认存在两根轴:
○ 水平的主轴(main axis):主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;
○ 垂直的交叉轴(cross axis):交叉轴的开始位置叫做cross start,结束位置叫做cross end。
● 项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。
Flexbox解析布局逻辑流程,实际上就是处理Flex容器和Flex项目的尺寸、排列方向、对齐方式、比例关系、绝对布局项目的代码逻辑。

Stretch实现原理

Flexbox布局解析主链路

在这里插入图片描述

核心算法

在这里插入图片描述

Stretch重点剖析

在Stretch整个布局算法中,有9个主要的环节,接下来我们重点介绍三个重要的环节,
● 确定Flex项目的基准值(flex_basis)
● Flex项目的主轴尺寸
● Flex项目的交叉轴尺寸

确定Flex项目的基准值(flex_basis)

在该阶段,会生成Flex项目集合用作逻辑处理,随后会遍历该集合,给每个Flex项目的flex_basis赋值,Flex项目有了初始值之后会便于后续主轴和交叉轴的处理。
每个Flex项目的flex_basis的值,主要受到以下几个方面影响:
● 如果flex_basis已经有值,则直接使用。
● 如果align-items被设置stretch,那么flex项目的高度会默认被拉伸到最大元素的高度。
● 如果有设置aspect-ratio值,那么需要根据宽度计算出高度赋给flex_basis,或者根据高度计算出宽度赋给flex_basis。

确定Flex项目的主轴尺寸

当有了flex_basis之后,需要进一步确定flex项目主轴尺寸 - target_main_size。
首先要确认当前flex容器中已经使用的空间,以及当前Flex容器中剩余的空间,然后根据Flex项目的Flex因子来收缩和扩展Flex项目的主轴尺寸。
所谓Flex因子就是flex_grow和flex_shrink。
如果使用flex_grow,需要计算Flex项目的增长比例系数,并结合剩余空间计算出Flex项目的增长值,加上Flex项目的基准值,作为Flex项目的主轴尺寸。
Flex项目的增长长度与flex_grow数值成正比关系。

if growing {
    for target in &mut unfrozen {
        let child: &mut FlexItem = target;
        if free_space.is_normal() && sum_flex_grow > 0.0 {
            let grow_after = child.flex_basis + free_space * (self.nodes[child.node].style.flex_grow / sum_flex_grow);
            child.target_size.set_main(dir, grow_after);
        }
    }
}

如果使用flex_shrink,需要计算Flex项目的收缩比例系数,并结合剩余空间计算出Flex项目的收缩值,加上Flex项目的基准值,作为Flex项目的主轴尺寸。
Flex项目收缩的长度与flex_shrink成正比关系。

if shrinking && sum_flex_shrink > 0.0 {
    let sum_scaled_shrink_factor: f32 = unfrozen
        .iter()
        .map(|child: &&mut FlexItem| {
            let child_style: Style = self.nodes[child.node].style;
            child.inner_flex_basis * child_style.flex_shrink
        })
        .sum();
    for target in &mut unfrozen {
        let child: &mut FlexItem = target;
        let scaled_shrink_factor = child.inner_flex_basis * self.nodes[child.node].style.flex_shrink;
        if free_space.is_normal() && sum_scaled_shrink_factor > 0.0 {
            let shrink_after = child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor);
            child.target_size.set_main(dir, shrink_after)
        }
    }
}

确定Flex项目的交叉轴尺寸

交叉轴的尺寸比较复杂一些,其中涉及到三个子环节:
● 确定每个Flex项目的猜测的交叉轴尺寸。
● 确定每行下的Flex项目交叉轴尺寸最大的高度。
● 根据行的交叉轴高度以及aspect-ratio、align-self: stretch等参数,计算出每个Flex项目交叉轴的实际宽度。

出现哪些问题,增加哪些新特性

当前Stretch也并非完美的,我们在使用的过程中遇到不少问题
● iOS的symbol覆盖问题导致crash
● Andriod端GC回收crash
● 多线程布局计算crash问题
● apsect-ratio属性重定义
● Flex多层嵌套规则下存在隐藏的问题 (flex-shrink/flex-grow)
接下来就针对其中几个问题进行剖析,并给出我们对Stretch的改动方案。

iOS在32位机器上Crash问题

原因分析

  1. Stretch库是通过Rust实现,并通过cargo将Rust的运行时和stretch跨平台编译iOS armv7,arm64等平台的静态库集成到GaiaX的项目中,但是我们在测试的过程中发现在 armv7以及armv7s(32位机器)上出现了crash,crash在 _modsi3 macros.rs:255 ,经过分析Rust运行时代码,这个宏是模运算的功能,在编译stretch静态库的时候,会将Rust运行时一起打包编译,并将需要的宏展开,覆盖iOS动态链接 _modesi3运算导致异常。
  2. 我们查看rust的底层库a%b_modesi3实现,发现在a%b,在b为空的情况下是种未定义的行为,会导致program panics行为,在iOS系统表现为crash行为。
#[maybe_use_optimized_c_shim]
pub extern "C" fn __modsi3(a: i32, b: i32) -> i32 {
   a.mod_(b)
}

trait Mod: Int {
   /// Returns `a % b`
   fn mod_(self, other: Self) -> Self {
       let s = other >> (Self::BITS - 1);
       // NOTE(wrapping_sub) see comment in the `div`
       let b = (other ^ s).wrapping_sub(s);
       let s = self >> (Self::BITS - 1);
       let a = (self ^ s).wrapping_sub(s);
       let r = a.unsigned().aborting_rem(b.unsigned());
       (Self::from_unsigned(r) ^ s) - s
   }
}

fn aborting_rem(self, other: Self) -> Self {
   unwrap(<Self>::checked_rem(self, other))
}

// 在a或b为none时,unwrap的值为none时会导致program panics,在iOS系统出现crash问题。

解决方案

  1. 我们将Stretch打包生成的静态.a库,并通过工具将modis3的symbol进行重命名,然后集成到GaiaX库中,在生成的link map文件中可以发现重命名的几个symbol和iOS动态库中的modis3符号都已经能够正常link,。
  2. 同时我们也进行大量的机型测试,以及针对性case的单元测试来保证其稳定性。

aspect-ratio问题的修复

aspect-ratio属性是在CSS中用来保持纵横比(width/height的比率)。

原因分析

Stretch库的设计是以交叉轴为基准进行处理该属性的,并将其作为flex_basis使用,这就导致了如果想要正确的使用这个属性必须做到一下两点:
● flex item必须嵌套在flex container中来进行使用。
● aspect-ratio的数值,在container的排列方向是竖向时,须写成高宽比;在container的排列方向是横向时,须写成宽高比。

随着使用aspect-ratio的地方越来越多,我们发现这种需要依赖于container的排列方向的计算方式给模板搭建的使用者带来了困惑,同时也降低开发的效率。

解决方案

首先我们对aspect-ratio重新的定义,让其变的更加通俗易懂,根据双端讨论约定的如下规则:
● aspect-ratio代表宽高比(width / height)。
● 当flex-item有确定的宽度时, aspect-ratio需要以宽度计算出高度。
● 当flex-height有确定的高度时, aspect-ratio需要以高度计算出宽度。
● aspect-ratio不再受flex container排列方向的影响。
由于前期对Rust语法不够熟悉,再加上Flexbox布局解析逻辑非常复杂,整个工作的难度还是非常高的。
采取的策略就是每修改一处逻辑必须通过单元测试保证不会影响到现有逻辑,每增加一处代码都增加相应的测试用例来保证可靠性。

主要针对Flex项目初始宽度赋值、主轴计算、交叉轴计算时进行拆解处理,适配aspec-ratio的逻辑。
例如,在初始宽度赋值时,根据Flex项目宽度、高度以及宽高比进行计算赋值:

// fix: aspect_ratio_both_dimensions_defined_column
fn get_aspect_ratio_size(child_style: &Style, target_size: Size<Number>) -> Size<Number> {
    return Size {
        width: Forest::get_aspect_ratio_width(child_style, target_size),
        height: Forest::get_aspect_ratio_height(child_style, target_size),
    };
}

// fix: aspect_ratio_both_dimensions_defined_column
fn get_aspect_ratio_height(child_style: &Style, target_size: Size<Number>) -> Number {
    // 若有定义宽度,且存在比例关系,那么使用高度计算宽度
    if target_size.width.is_defined() && child_style.aspect_ratio.is_defined() {
        let width = target_size.width.or_else(0.0);
        let aspect_ratio = child_style.aspect_ratio.or_else(0.0);
        return Number::Defined(width / aspect_ratio);
    }
    return target_size.height;
}

fn get_aspect_ratio_width(child_style: &Style, target_size: Size<Number>) -> Number {
    // 若没有定义宽度,并且有定义高度,且存在比例关系,那么使用高度计算宽度
    if !target_size.width.is_defined() && target_size.height.is_defined() && child_style.aspect_ratio.is_defined() {
        let height = target_size.height.or_else(0.0);
        let aspect_ratio = child_style.aspect_ratio.or_else(0.0);
        return Number::Defined(height * aspect_ratio);
    }
    return target_size.width;
}

另外在计算交叉轴尺寸时,同时也需要考虑到很多属性之间(stretch、aspect-ratio、flex-basis、flex-grow、flex-shrink)的关联关系等,争取将影响降到最低。

if is_row && child_style.aspect_ratio.is_defined() && node_size.height.is_defined() && child_style.flex_shrink > 0.0 {
    let final_cross = child.hypothetical_inner_size.cross(dir).maybe_min(node_size.height);
    if !child_style.size.width.is_defined() && !child_style.min_size.width.is_defined() && !child_style.max_size.width.is_defined() {
         // fix: aspect_ratio_height_as_flex_basis
         // fix: aspect_ratio_flex_shrink
        let desire_height = child.target_size.width / child_style.aspect_ratio.or_else(0.0);
        desire_height
    } else if !child_style.size.width.is_defined() && child_style.min_size.width.is_defined() && !child_style.max_size.width.is_defined() {
         // fix: aspect_ratio_flex_shrink_2
        let desire_height = child.target_size.width / child_style.aspect_ratio.or_else(0.0);
        final_cross.maybe_min(Number::Defined(desire_height))
    } else if child_style.size.width.is_defined() {
        // fix: aspect_ratio_width_height_flex_grow_row
        let desire_height = child.target_size.width / child_style.aspect_ratio.or_else(0.0);
        desire_height
    } else {
        final_cross
    }
} else {
    let final_cross = child.hypothetical_inner_size.cross(dir);
    final_cross
}

线程问题

原因分析

由于Stretch库的实现是非线程安全的。Nodes和Forest都是通过Map以及Array进行存储,在多线程情况下动态构建Forest,以及创建和移除子Node节点时候极易出现数据访问安全的问题导致crash。


//stretch初始化
pub fn with_capacity(capacity: usize) -> Self {
    Self {
        id: INSTANCE_ALLOCATOR.allocate(),
        nodes: id::Allocator::new(),
        nodes_to_ids: crate::sys::new_map_with_capacity(capacity),
        ids_to_nodes: crate::sys::new_map_with_capacity(capacity),
        forest: Forest::with_capacity(capacity),
    }
}

//添加子节点
pub fn add_child(&mut self, node: Node, child: Node) -> Result<(), Error> {
    let node_id = self.find_node(node)?;
    let child_id = self.find_node(child)?;

    self.forest.add_child(node_id, child_id);
    Ok(())
}

//移除子节点
pub fn remove_child(&mut self, node: Node, child: Node) -> Result<Node, Error> {
    let node_id = self.find_node(node)?;
    let child_id = self.find_node(child)?;

    let prev_id = unsafe { self.forest.remove_child(node_id, child_id) };
    Ok(self.ids_to_nodes[&prev_id])
}

解决方案

为了避免修改Stretch带来其他未知的影响,双端在Stretch上层进行单例化,并对所有的节点操作进行加锁访问,以及动态更新Style指针的安全访问。

// GXStretch
+ (instancetype)stretch{
    static GXStretch *stretch = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (nil == stretch) {
            stretch = [[GXStretch alloc] init];
        }
    });
    return stretch;
}

//添加child
- (void)addChild:(void *)child forNode:(void *)node{
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    stretch_node_add_child(_stretchptr, node, child);
    dispatch_semaphore_signal(_semaphore);
}
    
//更新rust指针
- (void)updateRustPtr{
    //先释放原有指针
    if ([self isValidPtr:_rustptr]) {
        _prevRustptr = _rustptr;
    }
    //再生成新的指针
    _rustptr = stretch_style_create(...);
}

//释放上次的rust指针
- (void)freePrevRustptr{
    if ([self isValidPtr:_prevRustptr]) {
        stretch_style_free(_prevRustptr);
        _prevRustptr = NULL;
    }
}

问题验证

在对于Stretch增加新特性的同时,为了保证代码安全性和可靠性,我们增加了大量的测试用例,覆盖绝大多数的场景对修改点的验证。
例如,在交叉轴确定的情况下,来验证通过apsect-ratio来计算布局:

#[test]
fn aspect_ratio_cross_defined() {
    let mut stretch = Stretch::new();

    let root = stretch.new_node(Style {
        size: Size { width: Dimension::Points(100.0), height: Dimension::Points(100.0) },
        ..Default::default()
    }, &[]).unwrap();

    let root_child0 = stretch.new_node(Style {
        size: Size { width: Dimension::Points(50.0), ..Default::default() },
        aspect_ratio: Number::Defined(1.0),
        ..Default::default()
    }, &[]).unwrap();

    stretch.add_child(root, root_child0).unwrap();

    stretch.compute_layout(root, Size { width: Number::Defined(375.0), height: Number::Defined(1000.0) }).unwrap();

    assert_eq!(stretch.layout(root_child0).unwrap().size.width, 50.0);
    assert_eq!(stretch.layout(root_child0).unwrap().size.height, 50.0);
}

得益于Rust语言的安全性,以及敏捷单元测试和丰富的类型系统和所有权模型,在编译期就能够消除各种各样的错误,也避免了因为不熟悉语法而导致编写错误,在此基础上,能够顺利跑通所有测试用例的代码就能保证在使用期间的可靠性。

总结与展望

Stretch开源项目原开发团队已不在维护,为了保证GaiaX项目的迭代诉求,团队不但fork了源码,并自研新增了大量新特性。 在这个过程中,我们不仅对Stretch库有了全面的了解,也同时深入到RUST语言中,这个过程既痛苦又享受,相信广大开发同学在编程职业生涯中,也有过类似的经历。
随着GaiaX在阿里文娱业务及开源社区越来越多的应用,双端需要支持的容器及技术能力与诉求也越来越多,仅凭借GaiaX团队一己之力,很难快速响应社区开发者及文娱业务本身的双线需求。因此,团队希望通过开源的方式,让更多的开发者参与进来,借助社区力量,让更多有跨端动态化技术诉求的个人和团体在技术上受益。

团队介绍

我们来自 优酷产品技术与创新中心应用技术部 ,从日常工作中真实存在的研发效能及痛点问题出发,我们自研推出了GaiaX动态模板技术体系,目标是解决多端卡片化UI组件的研发效能问题,帮助提升开发者体验及效率。目前GaiaX项目已经在GitHub开源 【https://github.com/alibaba/GaiaX】 ,也殷切的希望广大移动端及前端技术爱好者与我们进行技术交流与共建。

系列文章

《GaiaX开源解读》系列文章预告如下,欢迎持续关注:
第一篇:《GaiaX开源解读 | 基于优酷业务特色的跨平台技术》
第二篇:《GaiaX开源解读 | 跨端动态化模板引擎详解,看完你也能写一个》
第三篇:《GaiaX开源解读 | 给Stretch(Rust编写的Flexbox布局引擎)新增特性,我掉了好多头发》
第四篇:《GaiaX开源解读 | 表达式作为逻辑动态化的基础,我们是如何设计的》
第五篇:《GaiaX开源解读 | 向经典致敬 ReactNaitve与GaiaX渲染核心技术分析》
第六篇:《GaiaX开源解读 | 为了保障双端一致性,我们做了哪些努力》
第七篇:《GaiaX开源解读 | 一条龙的模板研发体系,你不来看看么》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux调试器——gdb

gdb什么是gdbdebug与releasegdb的基本操作查看代码与断点执行与调试监视变量什么是gdb 之前用的一直都是VS编译器进行调试&#xff0c;调试是一个非常重要的过程&#xff0c;在Linux中调试需要用到一个工具就是gdb。 在调试思路上VS编译器和gdb是一样的&#xff0c;但是调试过…

【云原生进阶之容器】第一章Docker核心技术1.5.1节——cgroup综述

1. cgroups概述 1.1 为什么需要cgroup 在Linux里,一直以来就有对进程进行分组的概念和需求,比如session group, progress group等,后来随着人们对这方面的需求越来越多,比如需要追踪一组进程的内存和IO使用情况等,于是出现了cgroup,用来统一将进程进行分组,并在分组的…

51单片机——静态数码管实验,小白讲解

数码管介绍&#xff1a; 数码管是一种半导体发光器件&#xff0c;其基本单元是发光二极管。数码管也称LED数码管&#xff0c;不同行业人士对数码管的称呼不一样&#xff0c;其实都是同样的产品。数码管按段数可分为七段数码管和八段数码管&#xff0c;八段数码管比七段数码管多…

中国计算机大会CNCC【笔记】

中国计算机大会CNCC【笔记】前言推荐中国计算机大会CNCCCNCC 青年精英思想秀主题&#xff1a;当呼吸化为空气——物联网安全云原生一站式数据管理与服务 : 构建云计算数据平台生态计算产业未来应用场景与创新方向展望用开源打造云原生数据库的生态系统CCF 优博的培养与成长最后…

华为HI第二款车,阿维塔11的智能化有什么特点?

作者 | 德新 编辑 | 于婷阿维塔11&#xff0c;这款车不用过多介绍&#xff0c;长安 x 宁德 x 华为 3家联合打造。外观独特&#xff0c;在宝马17年的顶尖设计师Nader Faghihzadeh主导的设计&#xff1b;用料也很足&#xff0c;90度 - 116度的电池&#xff0c;34.99万的起售价&am…

利用 ALV 实现增删改查系列之一:让 ALV 报表进入可编辑状态试读版

在 CSDN 和我的知识星球里有朋友向我提出同样的问题&#xff0c;询问如何在 ALV 里实现增删改查操作。 虽然需求只有一句话&#xff0c;但是这个需求背后涉及到的知识点不少&#xff0c;因此笔者会通过几篇文章的篇幅&#xff0c;来介绍这个需求的详细实现步骤。 本文先解决第…

【Linux学习】之访问命令行

【Linux学习】之访问命令行 文章目录【Linux学习】之访问命令行一、基础知识二、练习1.使用 date 命令来显示当前的日期和时间。2.以12小时制显示当前时间(例如&#xff0c;11:42:11AM)。3. 查看/home/student/zcat 的文件类型&#xff0c;是否被人读取?4.使用wc命令和 Bash 快…

看过来,2022最后一期大咖说-大厂可观测来啦~

可观测性的应用创新与落地研讨会 「UGeek大咖说-大厂可观测」 最后一期 活动时间&#xff1a;2022/12/21 15:00 — 17:30 活动平台&#xff1a;线上网络直播间 主办单位&#xff1a;优维科技 内容介绍 白驹过隙&#xff0c;转眼就到了2022年末啦~ 「UGeek大咖说-大厂可观…

A. Tower(暴力 + 看数据范围)

Problem - A - Codeforces 彭教授建造了n个不同高度的积木塔。第i座塔的高度为ai。 寿教授不喜欢这些塔&#xff0c;因为它们的高度是任意的。他决定首先精确地移除其中的m个&#xff0c;然后执行以下一些&#xff08;或不执行&#xff09;操作。 选择一座塔&#xff0c;将其…

MongoDB——Java Client API(Spring Data MongoDB)

[TOC](MongoDB——Java Client API(Spring Data MongoDB)) MongoDB——Java Client API(Spring Data MongoDB) 关于文档注解 由于mongoDB使用是BSON进行存储&#xff0c;Java则是类与对象的概念&#xff0c;所以设计了一套注解用于标注 Document 范围&#xff1a;类 作用&…

表的增删查改基本查询(where-group by-having)

文章目录表的操作表的创建修改表属性&#xff08;轻易不要改&#xff09;数据类型分类类型测试表的增删查改增加插入insert插入否则更新替换Retrieve&#xff08;检索&#xff09;查找selectwhere条件语句的添加姓孙的和孙某where语句无法使用别名的问题&#xff1f;语文成绩&g…

OAK相机depthai最全上手教程

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…

一种数据驱动的自动驾驶汽车前馈补偿器优化方法(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 一个可靠的控制器对于自动驾驶汽车的安全和平稳操纵的执行至关重要。控制器必须对外部干扰&#xff08;如路面、天气、风况等&…

javaweb文件下载案例

html代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <a href"./img/1.jpg">图片</a> <!--这个下载效果是download实…

pikachu靶场越权漏洞实战

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是pikachu靶场越权漏洞实战。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1a;严禁对未…

01背包问题以及有关题目

一、01背包问题详解 确定dp数组以及下标的含义 使用二维数组 dp[i] [j] 表示从下标为[0-i]的物品里任意取&#xff0c;放进容量为j的背包&#xff0c;价值总和最大是多少。 确定递推公式 dp数组的初始化 首先从dp[i][j] 的定义出发&#xff0c;如果背包容量j为0的话&#…

kobject应用实例--在/sys下创建设备的属性节点

本文讲解如何利用内核提供的接口&#xff0c;在/sys下创建设备的属性节点&#xff0c;实现属性的读写接口。 1、主要数据结构 一、kobject --> 目录&#xff1b;kobj_type --> 属性文件 使用到的内核数据结构如下&#xff1a; struct kobject {const char *name;…

1996-2020年31省主要农业机械年末拥有量相关数据

1996-2020年31省主要农业机械年末拥有量 1、时间&#xff1a;1996-2020年 2、范围&#xff1a;包括全国31省 3、指标包括&#xff1a; 农用机械总动力&#xff08;万千瓦&#xff09;、大中型拖拉机&#xff08;台&#xff09;、小型拖拉机&#xff08;台&#xff09;、大中…

jsp+ssm计算机毕业设计本科毕业设计过程管理系统【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

如何识别网络应用层协议?

能够标识出 Internet上每个流所使用的应用层协议是一系列网络应用的前提和基础。然而随着网络的高速化和协议的复杂化&#xff0c;传统的基于端 口识别应用层协议的算法已经不够准确&#xff0c;因此各种新的协议识别算法成为研究热点 。 本篇文章将重点介绍协议识别问题的几个…