精华内容
下载资源
问答
  • 2020-08-09 16:02:10

    介绍

    该笔记是在学习拉勾教育 Java 高薪训练营后,结合课程和老师的视频,自己跟踪源码后做的笔记。

    动态 SQL 概念

    顾名思义,SQL 是动态拼接成的,根据传入的变量值进行逻辑操作,并动态拼接,方便实现多条件下的数据库操作。 在业务逻辑复杂,即简单 SQL 无法完成时,需要拼接时就要使用动态 SQL。

      以查询为例,会先判断 id 是否不为 0,如果不为 0,才会传入该占位符对应的 id 值。假设 id = 2333,即 “select * from User id = 2333”。如果为 0,则为 “select * from User”。

    <select id = "findByCondition" parameterType = "user" resultType = "user">
        SELECT * FROM User
        <where>
            <if test = "id != 0">
                AND id = #{id}
            </if>
        </where>
    </select>
    

    动态 SQL 的种类

    MyBatis 3 进行精简,有以下几类。

    • if,根据 if 条件来查询。 例子在前面已经给出,是最常用的一种;
    • choose、when、otherwise,从多个条件中选择一个使用。 类似 Java 中的 switch 语句;
    • trim、where、set,trim 标签是用户自定义标签, 可用 trim 标签替换 where 标签和 set 标签;
    • foreach,对集合进行遍历, 会循环执行 sql 的拼接操作,特别是在构建 IN 条件语句时;
    • script,能在带注解的映射器接口类中使用动态 SQL;
    • bind,可以使用 OGNL 表达式创建一个变量并将其绑定到上下文中。

    choose、when、otherwise

    多个条件选择一个使用,传入 “title” 就按 “title” 查找,传入 “author” 就按 “author” 查找,两个都没有,则默认调用 <otherwise> 标签中的值。

    <select id = "findActiveBlogLike" resultType = "Blog">
        SELECT * FROM BLOG WHERE state = "ACTIVE"
        <choose>
            <when test = "title != null">
                AND title LIKE #{title}
            </when>
            <when test = "author != null and author.name != null">
                AND author_name LIKE #{author.name}
            </when>
            <otherwise>
                AND featured = 1
            </otherwise>
        </choose>
    </select>
    

    trim、where、set

    trim 标签是用户自定义标签,可用 trim 标签替换 where 标签和 set 标签。
      下面例子使用 if 标签,当第一个条件不满足时,SQL 语句为 “SELECT * FROM User WHERE AND user_name = #{userName}”,就会出错。

    <select id = "findByCondition" parameterType = "user" resultType = "user">
        SELECT * FROM User WHERE
        <if test = "id != 0">
            id = #{id}
        </if>
        <if test = "userName != null">
            AND user_name = #{userName}
        </if>
    </select>
    

    一个解决方法是用 where 标签,会自动覆盖掉第一个 AND 或 OR。另一种等价写法,是使用 trim。

    <select id = "findByCondition" parameterType = "user" resultType = "user">
        SELECT * FROM User
        <where>
            <if test = "id != 0">
                AND id = #{id}
            </if>
            <if test = "userName != null">
                AND user_name = #{userName}
            </if>
        </where>
    </select>
    
    <!--使用 trim 实现 where 标签功能-->
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        ...
    </trim>
    

    set 标签是用于动态更新语句,会动态地在行首插入 SET 关键字,并删掉额外的逗号。

    <update id = "updateAuthorIfNecessary">
        UPDATE Author
        <set>
            <if test = "username !=  null">username = #{username},</if>
            <if test = "password !=  null">password = #{password},</if>
            <if test = "bio !=  null">bio = #{bio}</if>
        </set>
        WHERE id = #{id}
    </update>
    
    <!--trim 标签等价实现 set 标签-->
    <trim prefix="SET" suffixOverrides=",">
      ...
    </trim>
    

    foreach

    拼接后的 SQL 语句为 “SELECT * FROM USER WHERE id IN (1, 2, 5)”,占位符 id 中传入值为 1, 2, 5。

    <select id = "findByIds" parameterType = "list" resultType = "user">
        SELECT * FROM User
        <where>
            <foreach collection = "array" open = "id in(" close = ")" item = "id" separator = ",">
                #{id}
            </foreach>
        </where>
    </select>
    

    script

    能在带注解的映射器接口类中使用动态 SQL。如下,在代码中通过 script 标签实现动态 SQL。

        @Update({"<script>",
          "update Author",
          "  <set>",
          "    <if test='username != null'>username=#{username},</if>",
          "    <if test='password != null'>password=#{password},</if>",
          "    <if test='email != null'>email=#{email},</if>",
          "    <if test='bio != null'>bio=#{bio}</if>",
          "  </set>",
          "where id=#{id}",
          "</script>"})
        void updateAuthorValues(Author author);
    

    bind

    可以使用 OGNL 表达式创建一个变量并将其绑定到上下文中。如下例子,该 SQL 语句在不同的环境中运行可能会出错。
      因为 CONCAT 函数在 MYSQL 中有多个参数,可正常拼接,但在 Oracle 中只有两个参数,会出错。

    <select id = "findByCondition" parameterType = "user" resultType = "user">
        SELECT * FROM User
        <where>
            <if test = "id != 0">
                AND id = #{id}
            </if>
            <if test = "userName != null">
                AND user_name LIKE CONCAT ('%', #{userName}, '%')
            </if>
        </where>
    </select>
    

    使用 bind 来拼接,可避免因更换数据库而修改 SQL,也能预防 SQL 注入。 如下传入 user_name 的值,比如 “martin”,经过 bind 标签后,为 “%martin%”。

    <select id = "findByCondition" parameterType = "user" resultType = "user">
        SELECT * FROM User
        <where>
            <if test = "id != 0">
                AND id = #{id}
            </if>
            <if test = "userName != null">
                <bind name="user_name" value="'%' + userName + '%'"/>
                AND user_name LIKE #{userName}
            </if>
        </where>
    </select>
    

    动态 SQL 的执行原理

    在 XMLScriptBuilder#parseScriptNode 的 parseDynamicTags 方法中,会解析 SQL,判断是否为动态 SQL,是则会调用 DynamicSqlSource#getBoundSql 方法,将 MixedSqlNode 传入 DynamicSqlSource 的构造函数中。
      调用不同标签对应的处理器,比如 choose 标签调用 ChooseSqlNode,if 标签调用 IfSqlNode,然后根据标签功能和传入的值来完成逻辑操作,并动态拼接成 SQL。

        public SqlSource parseScriptNode() {
            // 解析 SQL
            MixedSqlNode rootSqlNode = parseDynamicTags(context);
            // 创建 SqlSource 对象
            SqlSource sqlSource;
            if (isDynamic) {
                sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
            } else {
                sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
            }
            return sqlSource;
        }
    

    DynamicSqlSource#getBoundSql

    动态的 SqlSource 实现类。在 BaseExecutor#query() 中会调用 MappedStatement#getBoundSql() 获取 SQL 语句,MappedStatement#getBoundSql() 方法中调用 sqlSource.getBoundSql(),这里 sqlSource 为 DynamicSqlSource。
      解析动态 sql 的方法为 rootSqlNode.apply(), 前面在 parseScriptNode() 方法中会创建 MixedSqlNode 传入,所以这里调用都是 MixedSqlNode.apply()。

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            // 应用 rootSqlNode
            DynamicContext context = new DynamicContext(configuration, parameterObject);
            rootSqlNode.apply(context);
            // ...
            return boundSql;
        }
    

    MixedSqlNode#apply

    为混合的 SqlNode 实现类,遍历 SqlNode 数组,有如下这些,用于处理对应的标签。

        @Override
        public boolean apply(DynamicContext context) {
            // 遍历 SqlNode 数组,逐个应用
            for (SqlNode sqlNode : contents) {
                sqlNode.apply(context);
            }
            return true;
        }
    
    • ChooseSqlNode#apply,<choose /> 标签的 SqlNode 实现类;
    • ForEachSqlNode#apply,<foreach /> 标签的 SqlNode 实现类。
    • IfSqlNode#apply,<if /> 标签的 SqlNode 实现类。
    • StaticTextSqlNode#apply,静态文本的 SqlNode 实现类;
    • TextSqlNode#apply,文本的 SqlNode 实现类。
    • TrimSqlNode#apply,<trim />标签的 SqlNode 实现类;
    • VarDeclSqlNode#apply,<bind /> 标签的 SqlNode 实现类。

    StaticTextSqlNode

    以静态文本的 SqlNode 为例,它是直接进行拼接的,没有逻辑判断。

        @Override
        public boolean apply(DynamicContext context) {
            // 直接拼接到 context 中
            context.appendSql(text);
            return true;
        }
    

    IfSqlNode

    if 条件先判断是否成立,是则会继续往下执行,如果 if 条件内嵌了其他动态标签,则会继续调用其他动态标签的 apply 方法。
      假设这里没有内嵌其他标签的话,则会调用 StaticTextSqlNode,直接进行拼接,即 if 条件成立,会动态拼接 SQL。

        @Override
        public boolean apply(DynamicContext context) {
            // 判断是否符合条件
            if (evaluator.evaluateBoolean(test, context.getBindings())) {
                // 符合,执行 contents 的应用
                contents.apply(context);
                // 返回成功
                return true;
            }
            // 不符合,返回失败
            return false;
        }
    

    总结

    BaseExecutor#query -> MappedStatement#getBoundSql -> sqlSource.getBoundSql -> rootSqlNode.apply。

    • BaseExecutor#query,查询语句;
    • MappedStatement#getBoundSql,获取 SQL 语句;
    • sqlSource.getBoundSql,调用动态 SQL;
    • rootSqlNode.apply,遍历 SQL 标签对应的处理器;
    • 处理器会根据标签功能、和传入的值来完成逻辑操作,并动态拼接成 SQL。

    reference

    更多相关内容
  • 为准确高效的完成数据的准备工作,提出在属性集不同、对象集相同形式背景下多区间概念格的动态横向合并算法.首先,为保证格结构的完整性,对区间概念格的渐进式生成算法进行改进,将区间概念分为存在概念、冗余概念和空...
  • 深入分析区间概念格的由上下界外延和内涵构成的区间概念特征和层次结构特性,融合基于决策粗糙集的三支决策理论,提出区间三支决策空间概念,通过降低决策损失实现决策的动态调控从而达到决策方案最优。将区间概念...
  • 动态编程 动态编程的不同概念和实现 即将提出不同的概念和示例...
  • 动态存储分配及链表的概念 主讲人张静 1动态存储分配 2链表 头指针存放开始结点的地址 开始结点头结点 尾结点指针域为空 结点 图5-16 一个简单的单向链表 2链表 谢谢观看再见
  • UML基本概念——动态视图

    千次阅读 2020-08-23 23:50:20
    需要注意的是,动态视图不能够独立存在,它必须特指一个静态视图或UML元素,说明在静态视图规定的事物结构下它们的动态行为。本节讲述的动态视图包括活动图、状态图、时序图和协作图。 4.2.1 活动图 活动图描述了...

    内容来源:《Thinking in UML》第二版。仅供交流学习,若涉及版权,会立即删除。

    4.2 动态视图

    故名思义,动态视图是描述事物动态行为的。需要注意的是,动态视图不能够独立存在,它必须特指一个静态视图或UML元素,说明在静态视图规定的事物结构下它们的动态行为。本节讲述的动态视图包括活动图、状态图、时序图和协作图。

    4.2.1 活动图

    活动图描述了为了完成某一个目标需要做的活动以及这些活动的执行顺序。UML中有两个层面的活动图,一种用于描述用例场景,另一种用于描述对象交互。在正式介绍活动图之前,让我们先来讨论一下有关活动图的一些争议。活动图被引入UML中是有争议的,因为活动图实际上描述的是业务流程,是一种过程化的分析方法,很多人担心面向过程的活动图引入会导致面向对象的类职责的混乱,这种担心是有道理的。在1.1.3面向对象方法一节中我们讲过,在面向对象的眼中是没有业务流程这种东西的,所谓流程只不过是在某个外部力量推动下对象之间相互交流的一个过程,它只是“瞬时”的。如果从活动图的观点来描述业务,实际上是不能直接看到对象是如何发挥作用的。这样在观念上很容易导致对象独立性被破坏,例如有的设计者可能会试图得到一个从头到尾参与了整个业务流程的“对象”。

    但是,在UML中引入活动图可能也是无奈之举,因为在1.1.4面向对象的困难一节中我们提到过从现实世界映射到对象世界有着诸多困难。面向对象要求对象越独立、封装度越高越好,可是面向对象越纯粹,我们越难以理解这些对象将会干什么。正所谓上帝什么都能做,但其实他什么也没有做;纯粹的对象结构也许能做无数的事情,但实际上我们只需要明确其中的一件。我们面临着这样一个矛盾,既要保持面向对象观点中对象的独立性,又要保持现实世界中业务目标的过程化描述。活动图的引入解决了业务目标过程化描述,但也给对象分析造成了混乱。虽然如此,活动图在描述用例场景时仍然是十分有效的工具,关键还是建模者自己要避免被过程化的观点所困扰,而不必忌讳使用活动图。

    笔者要提示读者,在使用活动图时,要随时保持清醒的头脑,活动图只是我们用来描述业务目标的达成过程并借此来发现对象的工具,它不是我们的分析目标,也不是编程的依据,它只是对象的应用场景之一。我们使用活动图来描述用例场景,帮助我们认识问题领域,从问题领域中发现关键的对象,然后就应该把活动图中的流程忘掉,而专心研究关键对象的特性。最后,再来验证一下这些关键对象的某个交互结果是否的确能够达到用例场景所描述的业务目标。

    4.2.1.1 用例活动图

    4.2.1.2 对象活动图

    4.2.1.3 泳道

    4.2.1.4 业务场景建模

    4.2.1.5 用例场景建模

    4.2.2 状态图

    4.2.3 时序图

    时序图用于描述按时间顺序排列的对象之间的交互模式;它按照参与交互的对象所具有的“生命线”和它们相互发送的消息来显示这些对象。在时序图中包含对象和主角实例,以及说明它们如何交互的消息。时序图描述了在参与交互的对象中所发生的事件(从激活的角度来说明),以及这些对象如何通过相互发送消息进行通信。可以为用例事件流的各种不同形式制作时序图。以上是官方文档对时序图的定义。通常我们使用时序图来描述用例实现,通过贡献于该用例实现的对象之间的交互来说明用例是如何被对象实现的。使用时序图来描述用例实现是一种从现实世界到对象世界的映射方法,它对我们确定对象职责和接口有着显著的作用。而对象的核心就是职责和接口。时序图与协作图是可以互相转换的,与协作图不同的是,时序图强调消息事件的发生顺序,更方便于阐述事件流的过程;但是时序图却难以表达对象之间的关系。在4.1.2类图一节中我们提到过类有三个层次的观点:概念层、说明层和实现层,它们分别对应于业务建模阶段、概念建模阶段和设计建模阶段。相应的,也可以在这三个层次上分别对业务实体对象、分析类对象和设计类对象绘制时序图。

    下面就结合4.1.2类图一节中三个层次的静态类图来看看时序图如何应用于这三个层次,并且实现网上购物这一业务目标。相应的,三个层次的时序图是业务模型时序图、概念模型时序图和设计模型时序图。

    4.2.3.1 业务模型时序图

    业务模型时序图用于为领域模型中的业务实体交互建模,其目标是实现业务用例。在绘制业务实体时序图之前,你应当已经绘制了业务用例实现过程的活动图。在4.2.1.5用例场景建模一节中提到,活动图可以帮助我们发现业务实体,实际上,如果之前已经有了活动图再来绘制业务实体时序图时,你会发现有迹可循,非常容易。图4.18展示了图4.7所示业务实体如何实现网上购物过程的时序图。这个时序图对这些业务实体对象如何参与业务提供了非常直观的描述,从图中我们可以非常容易地分辨出对象的职责、生命周期和会话过程。对业务模型时序图的理解将有助于我们了解业务架构。

     ■ 对象表示参与交互的对象。

    每个对象都带有一条生命周期线,对象被激活(创建或者被引用)时,生命周期线上会出现一个长条(会话),表示对象的存在。

    ■ 生命周期线

    生命周期线表示对象的存在,当对象被激活(创建或者被引用)时,生命周期线上出现会话,表示对象参与了这个会话。

    ■ 消息

    消息由一个对象的生命周期线指向另一个对象的生命周期线。如果消息指到空白的生命周期线,将创建一个新的会话;如果消息指到已有的会话,表示该对象延续已有会话。与实际的编程环境相似,消息有许多不同的类型。[插图]为简单消息。简单消息适用于大多数情况。它不强调消息的类型,仅表示一个交互。一般情况下使用简单消息就足够了,除非在设计模型的类交互时需要强调消息类型时才使用其他消息类型。

    为返回消息。返回消息为源消息的返回体,而非新的消息。一般来说不需要为每个源消息都绘制返回消息,一方面因为默认情况下源消息都有返回,另一方面太多的返回消息会使图变得更复杂。

    为同步消息。同步消息表示发出消息的对象将停止所有后续动作一直等到接收消息方响应。同步消息将阻塞源消息对象的所有行为。同步消息最为常用,通常程序之间的方法调用都是同步消息。

    为限时消息。限时消息是同步消息的一种特殊情况。源消息对象发出消息后将等待响应一段时间,在限定时间内还没有响应时,源消息对象将取消阻塞状态而执行后续操作。限时消息也很常用,例如访问一个网站,在限定时间内没有响应时浏览器会显示“找不到指定网址”的信息。

    为异步消息。异步消息表示源消息对象发出消息后不等待响应,而可以继续执行其他操作。异步消息一般需要消息中间件的支持,如JMS、MQ等。Rose里还定义了其他一些类型的消息,但这里介绍的这些消息类型已经足够了。笔者建议,在建模过程尤其是业务和概念模型中没有必要花费时间在研究消息类型上。图形简洁才能更有表达力,太多的细节只会复杂化,相反不利于理解。

    ■ 会话

    会话表示一次交互,在会话过程中所有对象共享一个上下文环境。例如事务上下文、安全上下文等。

    ■ 销毁

    销毁绘制在生命周期线上,表示对象生命周期的终止。虽然示例图中绘制了,但销毁也没有必要强调。最后,绘制业务模型时序图时要注意:第一,时序图以达成业务目标为准则;第二,这个阶段处于业务阶段,使用的描述语言应当采用业务术语;第三,时序图表达的内容会对将来的分析设计带来帮助,但是相对于编码实现来讲由于太粗略而不能够作为依据。

    4.2.3.2 概念模型时序图

    概念阶段的时序图采用分析类来绘制,目标同样是实现业务用例。但是,由于分析类本身代表了系统原型,所以这个阶段的时序图已经带有计算机理解。概念用例时序图通常是依据业务模型场景图来绘制的,它将业务模型场景用分析类重新绘制一遍,这样,既保留了实际业务需求,又得到了计算机实现的基本理念。图4.19展示了图4.8所示的说明层类图的一个时序图片段,描述了分析类实现查询商品的过程。

            请注意对比图4.8,我们看到其中的计算机实现理念的引入。这时的时序图依稀已经有了实现的影子。实际上,分析类所展示出来的已经是系统实现的原型,在设计模型阶段要做的工作就是选择适合的实现方式来实现这个蓝图。

    4.2.3.3 设计模型时序图

    设计模型时序图使用设计类作为对象绘制。目标是实现概念模型中的某个事件流,一般以一个完整交互为单位,消息细致到方法级别。显然,在实际工作中我们很难为所有的交互都绘制时序图,那将是一个巨大的工作量。好在统一方法是讲究架构驱动的,并且近几年来不使用现成软件框架的软件项目已经很少了,所以笔者建议在设计模型阶段,只需要用框架中的关键类描述典型的交互场景即可,不需要为每一个交互都绘制时序图。例如我们只需要绘制通过框架来进行增删改查的事件流,具体的查询都遵循同样的编程模型,因此参考框架事件流即可,不需要一一绘制。

    为了保证软件实现满足需求,省略了大量设计模型时序图的同时,要求有更多的概念模型时序图,这样才能保留足够的信息来说明需求与实现之间的过渡。当然,与设计模型时序图相比,概念模型时序图需要处理的信息量要少得多,工作量自然也就少得多。图4.20展示了图4.9所示的实现层类图在J2EE架构下实现查询商品过程的片断。在这个例子中,所有的类和方法都与实际编程无异,已经可以看作是伪代码了。

    小结:时序图的三种应用场合是在建模过程中经常使用的动态视图。除了这些场合,在任何时候需要表达对象间的交互时,或者想分析对象的职责和接口时都可以使用时序图。特别的,在建立软件架构时,为了说明架构中的关键对象交互场景,或者为了说明应用程序如何使用架构的编程模型,也可以使用时序图来说明。这些时序图可以作为架构文档的一部分,也可用作编程规范和指南使用。甚至,在非正式建模工作中,例如一时不能确定如何设计接口,或者不能确定设计是否合理时都可以用时序图帮助分析。时序图是十分有用的工具,掌握并随时使用它是很好的分析设计习惯。接下来,我们将学习最后一种动态视图:协作图。

    4.2.4 协作图

    协作图描述了对象间交互的一种模式;它通过对象之间的连接和它们相互发送的消息来显示参与交互的对象。协作图中可以有对象和主角实例,以及描述它们之间关系和交互的连接和消息。通过说明对象间如何通过互相发送消息来实现通信,协作图描述了参与对象中发生的情况。可以为用例事件流的每一个变化形式制作一个协作图。与时序图的作用相似,协作图用于显示对象之间如何进行交互以执行特定用例或用例中特定部分的行为,协作图的建模结果用于获取对象的职责和接口。与时序图不同的是,协作图因为展示了对象间的关系,使得它更适用于获得对对象结构的理解,而时序图则更适于获得对于调用过程的理解。不过在本质上,它们是可以互换的。

    同样的,通常我们也使用协作图来描述用例实现,通过贡献于该用例实现的对象之间的交互来说明用例是如何被对象实现的。也同样可以针对概念层、说明层和实现层分别对业务实体对象、分析类对象和设计类对象绘制协作图。如果你更在意对象间的结构关系,请选择使用协作图;如果你更在意对象交互的执行顺序,则请选择使用时序图。下面将使用与时序图相同的例子来描述协作图如何绘制。

    4.2.4.1 业务模型协作图

    业务模型协作图同样采用业务实体来绘制,目标也是实现用例场景。不过有时候协作图并不要求实现完整的场景,只需要将影响对象的关键消息绘制出来即可。因为协作图更在意的是对象的结构及其相互的影响。协作图(图4.21)展示了与时序图(图4.18)同样的信息,请读者体会它们之间在表达上不同的视觉感受和蕴含的侧面意义。

    协作图与时序图相比,对象间的结构一目了然,并且很容易就能知道哪些消息影响了对象(或者说对象需要提供哪些接口)。不过虽然用数字标明了消息的顺序,从图中我们还是很难看出执行的顺序,更无法了解一次完整的会话过程。协作图和时序图展示着对象不同的方面。 

    ■ 对象表示参与协作的对象。对象可以指定它的类,也可以直接用空对象表示,在将来再指定它的类。

    ■ 对象关联连接两个对象,表示两者的关联。与类关系不同,协作图中的对象关联是临时关联,即只在本次交互中存在;而类关系是永久关联,例如继承关系不论在什么情况下都是存在的。Rose中还定义了对象关联的可见属性,它们是:[插图]:域(Field)可见。表示关联的对象在交互域内是一直可见的。这有些类似于Java中的包内可见的性质。[插图]:参数(Parameters)可见。表示关联的对象仅在交互过程中可见,它们是通过参数传递产生关联的。[插图]:本地(Local)可见。表示关联的对象在本地可见。本地的概念类似于指对象在同一个JVM(Java虚拟机)或者同一个Server中,或者同一个进程中是可见的。[插图]:全局(Global)可见。表示关联的对象是全局可见的。全局的概念类似于指对象在整个分布式应用程序中,或者一个服务器群集中,或者整个万维网中是可见的。

    ■ 消息协作图中的消息与时序图中的消息定义完全一样。请参看4.2.3.1业务模型时序图一节中关于消息的解释部分。不过在Rose中并不能展示不同消息类型的不同符号,消息类型在打开消息属性对话框时才能看到。

    ■ 消息序号其实消息序号也是消息的一部分,这里分开讲只是为了强调。序号表明消息传递的先后顺序。在Rose中这个序号是由Rose自动维护的,并且不能够手工调整。正因为如此,如果要在已经完成的图中插入一条消息,基本上需要把整个图重画一遍来重新调整顺序。遇到这种情况时,“必杀技”就派上用场了,我们可以将协作图转化成时序图,在时序图中插入消息,再转换回协作图。

    4.2.4.2 概念模型协作图与时序图相同,

    概念阶段的协作图采用分析类来绘制,目标是实现业务用例。同样这个阶段的协作图已经带有计算机理解。图4.22展示了与时序图(图4.19)表达内容完全相同的协作图。读者可参照理解,这里就不再赘述了。

     4.2.4.3 设计模型协作图与时序图相同,

    设计模型协作图使用设计类为对象来绘制。目标是实现概念模型中的某个事件流,一般以一个完整交互为单位,消息细致到方法级别。协作图(图4.23)展示了与时序图(图4.20)完全相同的信息,读者可参照理解,不再赘述。

     小结:到此为止UML核心视图中的动态视图就学习完了。在动态视图中,我们学习了活动图、状态图、时序图和协作图,这些视图各有其适用的场合。笔者在本章的学习过程中按适用场合进行了一些讲解,不过肯定不能覆盖所有可能的情况。我们知道静态视图表达事物的结构性观点,而动态视图则表达事物的行为性观点。一个好的建模,结构性和行为性缺一不可,而且要相得益彰。既要说明该事物长得像什么样子,还要说明该事物应该怎么用。不论是静态视图还是动态视图都是建模的重要工具,熟练掌握它们除了学习基本概念之外,诀窍就是多用。这些视图不但可以用在软件建模过程中,也可以用在分析现实生活中的一些事例。只要愿意,总可以从生活中找到非常多的例子来练习。相对于掌握工具,理解其背后的本质才是更重要的。而这些理解是只可意会不能言传的,尽管作者作了以上的努力,仍然不能保证读者能深刻理解。希望大家多学多用,达到手中无剑心中有剑的层次。下一章,我们将开始学习UML的核心模型。预习:简单的理解,其实一个模型就是一堆有意义的静态图和动态图组合在一起,表达了一个有意义的中心思想。因此,学习模型最重要的不是死记硬背一个模型需要做什么,而是需要理解模型的中心思想。

     

     

     

    展开全文
  • 进程与线程概念

    万次阅读 多人点赞 2018-09-18 15:34:48
    进程是指一个具有一定独立功能的程序,在一个数据集合上的一次动态执行过程。 如下图所示,源代码文件经过编译链接形成可执行文件,将可执行文件加载进内存进行执行。为什么要加载进内存呢?这是因为负责解析和...

    进程的定义

    进程是指一个具有一定独立功能的程序,在一个数据集合上的一次动态执行过程。

    如下图所示,源代码文件经过编译链接形成可执行文件,将可执行文件加载进内存进行执行。为什么要加载进内存呢?这是因为负责解析和运行程序的CPU需要通过内部的程序计数器来指定内存地址,然后才能读出程序;并且,加载进内存,能对数据进行处理,让程序能够运行。那CPU不能直接读取外部磁盘的数据和程序吗?可以。但是访问磁盘的速度远远低于访问内存,速度很慢。
    这里写图片描述
    进程就体现了,计算机在执行程序,处理数据时,操作系统是如何对这个过程进行维护的。

    进程包含了正在运行的一个程序的所有状态信息:
    代码;数据;通用寄存器;进程占用系统资源(打开文件,已分配内存等), 组成进程控制块。

    进程的特点
    动态性:可动态的创建,结束进程
    并发性:进程可以被独立调度并占用CPU
    独立性:不同进程的工作互不影响
    制约性:因访问共享数据/资源或进程间同步而产生制约

    进程是操作系统中处于执行状态程序的抽象。
    程序 = 文件(静态的可执行文件)
    进程 = 执行中的程序 = 程序 + 执行状态
    同一个程序的多次执行过程对应为不同的进程。

    进程执行需要的资源:
    内存,CPU

    进程与程序的区别:
    进程的动态的,程序是静态的
    进程是暂时的,程序是永久的
    进程与程序的组成不同,进程包括程序,数据和进程控制块

    进程控制块:(PCB,process control block)?
    操作系统管理控制进程运行所用的信息集合。
    操作系统用PCB来描述进程的基本情况以及运行变化的过程
    PCB是进程存在的唯一标识(每个进程在操作系统中都有一个对应的PCB)

    进程创建时生成PCB,进程中止时回收PCB,进程的组织管理通过对PCB的组织管理来实现。

    PCB具体包含了哪些信息呢?
    进程标识信息,处理机现场保护,进程控制信息
    (PC,SP,寄存器,PID,UID,调度优先级,打开文件列表)
    控制信息:
    调度和状态信息(调度进程和处理机使用情况)
    进程间通信信息(进程间通信相关的标识)
    存储管理信息(指向进程映像存储空间数据结构)
    进程所用资源(进程使用的系统资源,如打开的文件等)
    有关数据结构连接信息(与PCB相关的进程队列)

    怎么组织进程控制块?
    链表;索引表。。
    链表:同一状态的进程PCB组成一个链表,多个状态对应不同的链表(就绪链表,阻塞链表。。。)
    索引表:同一状态的进程归入一个索引表(由索引指向PCB)多个状态对应多个不同的索引表(指向PCB的指针放入表中)

    进程的生命周期:
    进程创建,进程执行,进程等待,进程抢占,进程唤醒,进程结束

    进程状态转换:
    (三状态进程模型)
    这里写图片描述
    (挂起进程模型)

    处于挂起状态的进程映像在磁盘上,目的是减少进程占用内存。
    这里写图片描述

    线程

    为啥会有线程这种东西?
    因为有并发进行并且共享数据的这种需求;进程不能满足这种需求吗?进程可以!但是进程来做这样的事情的话,它的开销很大(创建进程,进程结束,进程切换… …)。
    多线程的解决思路:
    在进程内部增加一类实体,满足以下特性:(1)实体之间可以并发执行 (2)实体之间共享相同的地址空间

    线程的概念:

    线程是进程的一部分,描述指令流执行状态。它是进程中的指令执行流的最小单位,是CPU调度的基本单位。
    这个时候,进程就是资源分配的角色:进程由一组相关资源构成,包括地址空间(代码段,数据段),打开的文件等资源
    这里写图片描述
    线程 = 进程 - 共享资源
    线程优点:1.一个进程中可以同时存在多个线程。2.各个线程之间可以并发的执行。3.各个线程之间可以共享地址空间和文件等资源
    线程缺点:一个线程崩溃,会导致其所属进程的所有线程崩溃

    线程与进程比较:
    进程是资源分配单位,线程是CPU调度单位
    进程拥有一个完整的资源平台,而线程只独享指令流执行的必要资源,如寄存器和栈
    线程具有就绪,等待和运行三种基本状态和状态间转换关系
    线程能减少并发执行的时间和空间开销:线程的创建时间比进程短;线程的终止时间比进程短;同一进程内的线程切换时间比进程短;由于同一进程的各线程间共享内存各文件资源,可以不通过内核进行直接通信

    线程的实现方式:
    1.用户线程:在用户空间实现
    POSIX Pthreads,Mach C-threads,Solaris threads
    由一组用户级的线程库函数来完成线程的管理,包括线程的创建,终止,同步和调度等
    特征:不依赖于操作系统内核(内核不了解用户线程的存在;可用于不支持线程的多进程操作系统)。在用户空间实现的线程机制(每个进程有私有的线程控制块列表;TCB由线程库函数维护)。同一进程内的用户线程切换速度快(无需用户态/内核态的切换)。允许每个进程拥有自己的线程调度算法
    缺点:线程发起系统调用而阻塞时,整个进程进入等待;不支持基于线程的处理机抢占;只能按进程分配CPU时间
    2.内核线程:在内核中实现
    Windows,Solaris,Linux
    进程由内核通过系统调用实现的线程机制,由内核完成线程的创建,终止和管理(内核中PCB中有指针指向线程控制块TCB)
    特征:由内核维护PCB和TCB。线程执行系统调用而被阻塞不影响其他线程。线程的创建,终止和切换开销相对较大(通过系统调用/内核函数,在内核实现)。以线程为单位进行CPU时间分配(多线程的进程可获得更多的CPU时间)
    3.轻量级进程:在内核中实现,支持用户线程
    Solaris (LightWeight Process)
    内核支持的用户线程。一个进程可有一个或多个轻量级进程,每个轻权进程由一个单独的内核线程来支持(Solaris/Linux)

    用户线程与内核线程的对应关系:
    1.N个用户线程对应N个内核线程;2.N个用户线程对应一个内核线程;3.N个用户线程对应M个内核线程
    三种当中,现在的操作系统以第一种为主。

    展开全文
  • 动态环境中基于模糊概念的机器人路径搜索方法pdf,动态环境中基于模糊概念的机器人路径搜索方法
  • 动态规划(一):动态规划的基本概念和基本方程

    万次阅读 多人点赞 2016-11-12 19:33:37
    参考资料:《运筹学》(第三版) 清华大学出版社 ISBN:978-7-302-10214-4 戳我下载动态规划的应用场景等等就不介绍了,后面会写一些能用动态规划方法解决的常见问题。这一部分基本抄书上到1.1 多阶段决策问题及实例...

    写作目的:边看书边记录,主要供自己学习,所以写的有问题的地方,请多指教
    参考资料:《运筹学》(第三版) 清华大学出版社 ISBN:978-7-302-10214-4 戳我下载

    动态规划的应用场景等等就不介绍了,后面会写一些能用动态规划方法解决的常见问题。这一部分基本抄书上到

    1.1 多阶段决策问题及实例

    1. 多阶段决策问题

      多阶段决策问题状态-决策示意图

      多阶段决策问题很多,比如最短路线问题,机器负荷问题等等,把解决这一类问题的的方法称为动态规划方法。

    1.2 动态规划的基本概念

    1. 阶段
      将所给问题的过程,恰当的分为若干相互联系的阶段,以便能按一定的次序求解问题。阶段的划分一般是根据时间和空间的特征进行的,但是要能够把问题的过程转化为多阶段决策问题。

    2. 状态
      状态表示每个阶段开始所处的自然状况或者客观条件。比如在最短线路问题中,线路网络图如下:

    最短线路问题

    途中连线的数字表示花费,问题是求得一条从A到G的最小花费路线。这个问题里,状态就是某阶段的出发位置(比如我们现在在C,要确定到达D的线路)。通常一个阶段有多个状态,第一阶段的状态就是A,第二阶段的状态就是{B1, B2},即第k阶段所有出发点的集合。
    描述过程状态的变量称为状态变量,可用一个数,一组数或一个向量来描述,常用 Sk 表示第 k 阶段的状态变量。如 S3 = {C2, C2, C3, C4} 就表示上例中第三阶段的可达状态集合,用 sk 表示第 k 个阶段实际取得的状态。这里所说的状态有个重要的性质:无后效性(即马尔科夫性,戳我了解更多知识知识知识,通俗的讲就是过去的历史只能通过当前的状态去影响它未来的发展,当前状态是以往历史的一个总结(以前我以为的无后效性是未来的发展只与当前有关,而与过去无关,现在看来之前的理解有偏颇啊,概率论里面有一个指数分布有无记忆性,好像和这个一样?),在建模的时候,务必注意无后效性这一点,不能仅由描述过程的具体特征这一点规定状态变量,而要充分注意是否满足无后效性的要求。咦,好像很抽象的样子,书上正好有一个例子:已知物体任意时刻所受的外力(方向大小均已知),研究物理运动的轨迹问题。这个时候我们可以把状态定为物体的坐标(xk, yk, zk),但是没有办法确定之后的运动方向和轨迹。即不满足无后效性(这里可以加一点我对此处不满足无后效性的理解,一方面是知道此刻的状态仍然无法求出下一个时刻的状态,另一方面如果要求出下一时刻的状态仅仅知道当前时刻的状态还不够,还需要知道以前一段时间的状态,从而计算出当前时刻速度的大小和方向,然后就能计算出下一时刻的状态了),但是如果我们把位置(xk, yk, zk)和速度(vxk, vyk, vzk)一起作为状态变量后,下一步物体的运动方向和轨迹就完全确定啦。
    3. 决策
    决策表示当过程处于某一阶段某一状态时,可以做出的决定,从而确定下一阶段的状态,这个决定就叫做决策。描述决策的变量,称为决策变量。可以是一个数一组数,也可以是一个向量。常用 uk(sk) 表示第 k 个阶段处于sk时的决策变量,可见决策变量是状态的函数,也就是处于不同的状态时所能的决策与当前状态有关。实际问题中,决策变量的取值常常限制在某一范围内,此范围称为允许决策集合,常用 Dk(sk) 表示,显然有 uk(sk) 属于Dk(sk) ,如上面例子中,若从B1出发,允许决策集合D2(B1) = {C1, C2, C3},接下来如果选择C2,则u2(B1) = C2

    1. 策略
      策略是按顺序排列的策略组成的集合。由过程的第k阶段开始到终止状态为止的过程,称为问题的后部子过程(或称为k子过程)。由每段的决策按照顺序排列组成的决策函数序列 {uk(sk), uk+1(sk+1), … , un(kn)} 称为k子过程策略,简称为子策略,记为 pk,n(sk) 。当 k =1时,该决策序列称为全过程的一个策略,简称策略,即 p1,n(s1) 。同样的可以定义允许策略集合,其中达到最优效果的称为最优策略

    2. 状态转移方程
      状态转移方程是确定过程由一个状态到另一个状态的演变过程。由前面的讨论我们知道,如果给定第 k 个阶段的状态变量 sk 的取值,那么该阶段的决策变量 uk(sk) 一经确定,第 k+1 阶段的状态变量 uk+1(sk+1) 的取值也就决定了。即 sk+1 的值随 skuk 的值变化而变化,这种对应关系,记为:sk+1 = Tk(sk, uk),称之为状态转移方程,Tk为状态转移函数,在上例中,状态转移方程就是:sk+1 = uk(sk)

    3. 指标函数和最优值函数
      指标函数即用来衡量所实现过程优劣的数量指标,它定义在全过程和所有后部子过程上确定的数量函数,用 Vk,n 表示,即 Vk,n = Vk,n(sk, uk, sk+1, uk+1, … , sn+1) , k = 1, 2, …, n。该指标函数应该具有可分离性,并满足递推关系,即Vk,n可以表示为sk,uk,Vk+1,n的函数。常见的指标函数有(1)过程和它的任一子过程的指标是它所包含的各个阶段的指标的和;(2)过程和它的任一子过程的指标是它所包含的各个阶段的指标的乘积。
      最优值函数是指标函数的最优值,记为fk(sk),表示从第k阶段的状态开始到第n阶段的终止状态的过程,采取最优策略所得到的指标函数值(一般是最大/最小值),即:

    最优函数值

    不同的问题中指标函数的含义不同,可能是距离、利润、成本、产量或资源消耗等等。

    1.3 动态规划的基本思想和基本方程

    书中以解决最短路线问题介绍了动态规划的基本思想。再次贴出最短路问题中的网络图:

    最短路线问题

    容易看出,在最短路线问题中,如果A->B1->C2->D1->E2->F2->G是从A到G的最短路线,那么D1->E2->F2->G也是从D1到G的最短路线,可以用反证法证明:如果D1到G中存在一条比D1->E2->F2->G更短的路线,那么A->B1->C2->D1->E2->F2->G肯定就不是从A到G的最短路线。即如果由起点A经过P点和H点而到达终点G是一条最短路线,则由P出发经过H而到达G点的这条子路线,也必定是P点到达G点的最短路线。根据最短路线问题的这一特性,寻找最短路线问题的方法就是:从最后一段开始,用由后向前逐步递推的方法,求出各点到G点的最短路线,再求出A点到G点的最短路线。所以,动态规划的方法就是从终点逐段向起点方向寻找最短路线的一种方法,如下图所示:

    动态规划求解最短路线问题的基本思想

    下面按照动态规划的方法,从最后一段开始计算,由后向前逐步推移至A点。(直接附书上方法了,了解思想后这个比较容易)

    最短路线问题-动态规划-1
    最短路线问题-动态规划-2

    而最短路线再按照计算顺序的反方向(按阶段顺序)反推就可了(如A到G的最短路线是通过B1得到的,等等)可以很容易得到最短路线为:A->B1->C2->D1->E2->F2->G。
    从上面的计算过程中可以看出,在求解的各个阶段,我们利用了k阶段与k+1阶段之间的递推关系:

    最短路线问题-动态规划-递推关系

    一般情况下,k阶段与k+1阶段的递推关系式可写为(注意其中的边界条件):

    递推关系式

    其中vk(sk, uk(sk))表示第k阶段作出决策uk(sk) 时的阶段指标(对比上面的最短路线问题的递推式看就一目了然啦)
    上面的这种递推关系式就是动态规划问题的基本方程

    现在归纳一下动态规划问题的基本思想
    (1)动态规划问题的关键在于正确地写出基本的递推关系式和恰当的边界条件(也就是基本方程)。要做到这一点,必须将问题的过程划分成几个相互联系的阶段,选取恰当的状态变量,决策变量以及定义最优值函数,从而把一个大问题化成一族同类型的子问题 , 然后逐个求解。即从边界条件开始 , 逐段递推寻优 , 在每一个子问题的求解中 , 均利用了它前面的子问题的最优化结果 , 依次进行 , 最后一个子问题所得的最优解 , 就是整个问题的最优解。
    (2)在多阶段决策的过程中,动态规划方法是既把当前一段和未来各段分开,又把当前效益和未来效益结合起来考虑的一种最优化方法。因此,每段决策的选取是从全局来考虑的,与该段的最优选择答案一般不同。
    (3)在求整个问题的最优策略时,由于初始状态时已知的,而每段的策略都是该段状态的函数,故最优策略所经过的各段状态便可以逐次变换得到,从而确定了最优路线。
    上面的解法称为逆序解法,如果我们把G点作为起点,A点作为终点,则得到了顺序解法(思想一样,不做赘述)。但是动态规划的求解方向都与行进方向是相反的。

    从上面的例子的计算中,我们明显看到了动态规划问题相比穷举法(暴力法)有如下有点:
    (1)减小了计算量; (2)丰富了计算结果,即我们不仅得到了A点到G点的最短路线,也得到了中间各点到G点的最短路线,在很多实际问题中是有用的,有利于帮助分析所得结果,如下图:

    逆序解法

    在明确了动态规划的基本概念和基本思想之后 , 我们看到 , 给一个实际问题建立动态规划模型时 , 必须做到下面五点 :
    - (1) 将问题的过程划分成恰当的阶段 ;
    - (2) 正确选择状态变量 sk , 使它既能描述过程的演变 , 又要满足无后效性 ;
    - (3) 确定决策变量 uk 及每阶段的允许决策集合 D k ( sk ) ;
    - (4) 正确写出状态转移方程 ;
    - (5) 正确写出指标函数 V k,n , 它应满足下面三个性质 : ① 是定义在全过程和所有后部子过程上的数量函数 ;② 要具有可分离性 , 并满足递推关系。 即Vk,n( sk , uk , … , sn+1 ) = ψ k [ sk , uk , Vk+1,n(sk+1,uk+1 , … , sn+1 ) ];③ 函数 ψ k ( sk , uk , Vk+1 , n ) 对于变量 Vk+1, n 要严格单调。

    以上五点是构造动态规划模型的基础 , 是正确写出动态规划基本方程的基本要素。

    关于动态规划的基本概念就写这么多,接下来会写一些动态规划应用举例的文章


    更多详细内容可以参考文章开头提到的《运筹学》一书,戳我下载

    展开全文
  • Nacos - nacos基础概念

    千次阅读 2019-12-03 18:31:25
    文章目录Nacos - nacos基础概念1、什么是nacos2、nacos 的主要功能3、nacos 引入的一些基础概念4、nacos 架构设计 Nacos - nacos基础概念 1、什么是nacos 一个易于使用的动态服务发现,配置和服务管理平台,用于...
  • 1.静态资源和动态资源的概念 简单来说: 静态资源:一般客户端发送请求到web服务器,web服务器从内存在取到相应的文件,返回给客户端,客户端解析并渲染显示出来。 动态资源:一般客户端请求的动态资源,先将请求...
  • 教你彻底学会动态规划——入门篇

    万次阅读 多人点赞 2015-08-11 13:26:41
    网上也有很多关于讲解动态规划的文章,大多都是叙述概念,讲解原理,让人觉得晦涩难懂,即使一时间看懂了,发现当自己做题的时候又会觉得无所适从。我觉得,理解算法最重要的还是在于练习,只有通过自己练习,才可以...
  • 动态路由概述

    千次阅读 2021-12-12 14:00:20
    一:动态路由概念 二:RIP是距离矢量路由选择协议 三:动态路由和静态路由的区别 四:度量值 五:RIP的度量值与更新时间 六:RIP的验证与配置 七:动态路由协议 1、动态路由 基于某种路由协议实现 ...
  • 图像的灰度级和动态范围的概念

    千次阅读 2018-09-07 20:54:00
    1、图像的灰度级:一幅灰度图像它的像素的强度值的取值范围表示为[0, k-1],其中。一般情况下k = 8。...2、图像的动态范围:统计一下每一点的像素灰度值,([0,255]这256个候选人,对他们投票。)看看这幅图...
  • 一文搞懂常用的网络概念:域名、动态IP、DNS、DDNS

    千次阅读 多人点赞 2020-03-31 11:32:43
    网络通讯概念扫盲
  • UML--状态图的基本概念和作用

    万次阅读 多人点赞 2019-10-27 19:36:49
    状态图描述了一个实体基于事件反映的动态行为,显示了该实体是如何根据当前所处的状态对不同的事件作出反应的。 在UML中,状态图由表示状态的节点和表示状态之间转换的带箭头的直线组成。状态的转换由事件触发,...
  • 关于VB中的数组教程,数组的概念,动态数组的建立与声明
  • 概念漂移简介

    千次阅读 2020-08-10 15:03:44
    本文在总结文献的基础上进行一些个人的看法和分析。主要分为概念漂移的产生背景,概念漂移分类,概念漂移相关技术以及概念漂移的应用领域。
  • Java多线程——基本概念

    万次阅读 多人点赞 2019-10-23 10:36:25
    线程和多线程 ...进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态概念 主线程:(每个Java程序都有一个...
  • Java基本概念:多态

    千次阅读 2021-03-01 06:16:25
    一、简介描述:多态性是面向对象编程中的一个重要特性,主要是用来实现动态联编的。换句话说,就是程序的最终状态只有在执行过程中才被决定,而非在编译期间就决定了。这对于大型系统来说能提高系统的灵活性和扩展性...
  • 动态URL、静态URl、伪静态URL概念 及区别

    千次阅读 多人点赞 2019-04-16 13:29:22
    我们说url的动态、静态、伪静态三种形式,其实从严格分类上来说,伪静态也是动态的一种,只是表现形式为静态。 参考:https://bk.likinming.com/post-2674.html 动态URl 动态页面的特征 1、以ASP、PHP、JSP、ASP...
  • Web的基本概念总结

    千次阅读 2022-03-27 19:47:30
    Javaweb基础概念 基本概念 web:网页 静态web html,css 提供给其他人数据不会发生改变 动态web 几乎所有网站 提供给其他人数据会发生改变,每个人看到的不同 技术栈:servlet、ASP、PHP 在java中,动态web...
  • 运筹学——动态规划

    千次阅读 2021-02-19 10:33:56
    动态规划的基本概念与建模2. 动态规划问题求解![在这里插入图片描述](https://img-blog.csdnimg.cn/20210219103544190.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG...
  • 网站基本概念

    千次阅读 2020-09-11 22:18:06
    文章目录 网站基本概念 网站 服务器 IP 域名 DNS 端口 静态网站 动态网站
  • 知识图谱第6享:动态本体

    千次阅读 2019-11-24 21:13:19
    动态本体旨在通过互表性能让计算机理解资源的语义,互表性是指事物是相互表示的,即一个概念离开与之有关的概念是没有意义的,如“教师”这个概念离开“学生”这个概念就失去了意义,反之亦然。 这次分享主要从定义...
  • AOP概念及作用详细解释

    千次阅读 多人点赞 2022-03-20 15:59:51
    今天学习spring的时候接触到一个新的知识以及概念AOP,老师当时讲这个知识点的时候,并没有讲太多关于这方面的知识,我们所学习到的知识侧重于应用,所以对这一块的理论知识是有一点缺少,以至于小编在学习完AOP之后...
  • VLAN的概念和作用

    千次阅读 2021-12-03 15:58:35
    目录 一、VLAN的概念与优势 1.VLAN的概述: 2.WLAN的优势 3.VLAN的种类 二、vlan的范围 三、vlan的接口类型 四、配置vlan的命令 五、trunk的作用 ...一、VLAN的概念与优势 ...动态VLAN 基于MAC 地址划分动...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 776,567
精华内容 310,626
关键字:

动态概念