RecyclerView 调用 notifyItemInserted 自动滚动到底部的问题

项目中发现一个奇怪的现象

RecyclerView 加载完数据以后,调用 notifyItemInserted 方法,RecyclerView 会滑动到底部。

简化后的效果图:

在这里插入图片描述

因为这个 RecyclerView 的适配器有一个 FootViewHolder,所以怀疑是 FootViewHolder 的问题。通过源码分析,果然是 FootViewHolder 的问题。接下来就一步一步分析一下原因。

适配器代码

class TestAnimatorAdapter(
    private val context: Context
) : RecyclerView.Adapter<TestAnimatorAdapter.ViewHolder>() {

    companion object {
        val TYPE_FOOT = 1
        val FOOT_COUNT = 1
    }

    val dataList = mutableListOf<CheckBoxModel>()

    fun onDataSourceChanged(dataList: MutableList<CheckBoxModel>) {
        this.dataList.clear()
        this.dataList.addAll(dataList)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        if (viewType == TYPE_FOOT) {
            val view =
                LayoutInflater.from(context).inflate(R.layout.foot_view_load_more, parent, false)
            return FootViewHolder(view)
        }
        val view =
            LayoutInflater.from(context).inflate(R.layout.item_test_animation, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        if (position == itemCount - 1) {
            return
        }
        val model = dataList[position]
        holder.checkBox?.isSelected = model.isChecked
        holder.textDescription?.text = model.description
    }

    override fun getItemCount(): Int {
        return dataList.size + FOOT_COUNT
    }

    override fun getItemViewType(position: Int): Int {
        if (position == itemCount - 1) {
            return TYPE_FOOT
        }
        return super.getItemViewType(position)
    }

    open class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var checkBox: CheckBox? = null
        var textDescription: TextView? = null

        init {
            checkBox = itemView.findViewById(R.id.check_box)
            textDescription = itemView.findViewById(R.id.text_description)
        }
    }

    class FootViewHolder(itemView: View) : ViewHolder(itemView)

}

适配器有一个 FooterViewHolder。

测试代码:添加4个数据,然后调用 notifyItemInserted 方法。

binding.btnNotifyItemChanged.setOnClickListener {

    val newArrayList = arrayListOf<CheckBoxModel>()
    for (i in 0 until 4) {
        newArrayList.add(CheckBoxModel("hi Hello$i", false))
    }
    testAnimatorAdapterAdapter.onDataSourceChanged(newArrayList)
    for (index in 0 until 4) {
        //总共添加了4条数据,调用4次 notifyItemInserted 
        testAnimatorAdapterAdapter.notifyItemInserted(index)
    }
}

调用 Adapter#notifyItemInserted 方法以后,会调用 RecyclerView 的 dispatchLayout 方法。

void dispatchLayout() {
    //...
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
        //注释1处,调用dispatchLayoutStep1方法。
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        //注释2处,调用dispatchLayoutStep2方法。
        dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
            || mLayout.getHeight() != getHeight()) {
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        mLayout.setExactMeasureSpecsFrom(this);
    }
    //注释3处,调用dispatchLayoutStep3方法。
    dispatchLayoutStep3();
}

dispatchLayoutStep1 预布局阶段

在预布局阶段,首先会调用 RecyclerView 的 offsetPositionRecordsForInsert 方法,将已有的 FootViewHolder 向后移动,为插入的ViewHolder 留出位置。在我们的例子中,添加了4条数据,调用4次 notifyItemInserted 。最后 FootViewHolder 的 position 从 0 变化到 4 。

void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for(int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if(holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
            if(sVerboseLoggingEnabled) {
                Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " + holder + " now at position " + (holder.mPosition + itemCount));
            }
            holder.offsetPosition(itemCount, false);
            mState.mStructureChanged = true;
        }
    }
    mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
    requestLayout();
}

在debug的时候,评估一下FootViewHolder 。Evaluate FootViewHolder

 FootViewHolder{bd564b0 position=4 id=-1, oldPos=0, pLpos:0}

然后就没什么特殊的,dispatchLayoutStep1方法内部会调用一次 mLayout.onLayoutChildren(mRecycler, mState);,进行预布局。预布局结束的时候,只有一个FootViewHolderFootViewHolder还是被布局在了 position = 0 的位置。 预布局的时候,使用的是 pLpos = 0

这里要注意一下:预布局结束的时候,FootViewHolder 的 position=4 。在 dispatchLayoutStep2 阶段布局的时候,使用的是 position。也就是说会把 FootViewHolder 布局在 position=4 的位置。

dispatchLayoutStep2

内部会调用一次 mLayout.onLayoutChildren(mRecycler, mState);,进行布局。

这个时候先评估一下 LinearLayoutManager.mAnchorInfo 的值

AnchorInfo{mPosition=4, mCoordinate=0, mLayoutFromEnd=false, mValid=true}

注意:此时锚点位置 mAnchorInfo.mPosition = 4

onLayoutChildren 方法内部,

  1. detachAndScrapAttachedViews 回收 FootViewHolder。没啥可说的。

  2. 然后调用 updateLayoutStateToFillEnd(AnchorInfo anchorInfo) 方法。将 mLayoutState.mCurrentPosition 设置为 4。

  3. 然后调用 fill 方法进行填充。这时候,锚点位置是 4,对应的 ViewHolder是 FootViewHolder ,所以会先布局 FootViewHolder。FootViewHolder ,布局位置(layoutDecoratedWithMargins)是 top = 0,bottom = 144。(FootView 的高度就是144)

  4. 然后 FootViewHolder 后面没有数据了。此时 mLayoutState.mCurrentPosition = 5。(Adapter 只有 4条数据加一个Foot,position最大是4)。从锚点开始向下填充结束。

接下来要从锚点开始向上填充

LinearLayoutManager 的 onLayoutChildren 方法中部分代码

 // fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
//注释1处,这里会将 mLayoutState.mCurrentPosition 改为3
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);

先调用 updateLayoutStateToFillStart(AnchorInfo anchorInfo) 方法。更新一些信息。将mLayoutState.mLayoutDirection 赋值为 LayoutState.LAYOUT_START(值是-1);

紧接着调用了一行代码 mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;。向上填充的时候,mLayoutState.mItemDirection = -1。计算出来,mLayoutState.mCurrentPosition = 4 - 1 = 3

然后调用 fill 方法向上填充:

layoutChunk 方法中 ViewHolder3 的 布局 layoutDecoratedWithMargins(view, left, top, right, bottom); 位置是在 FootViewHolder 上面 top = -900,bottom = 0。

ViewHolder2 的 布局位置是 top = -1800,bottom = -900。

ViewHolder1 的 布局位置是 top = -2700,bottom = -1800。

布局完 ViewHolder1,以后,remainingSpace < 0 ,结束向上填充。

为什么会结束呢,在我们的例子中,remainingSpace = 2255 ,布局完 144 + Math.abs(-2700),已经大于 2255 了。

这个时候,FootViewHolder 的位置是 top = 0,bottom = 144。距离 RecyclerView 的底部还有很大的一段距离(在我们的例子中是 2111像素)。然后会走到 fixLayoutEndGap 方法。

private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,
    RecyclerView.State state, boolean canOffsetChildren) {
    //注释1处,这里大于0,2111px,表示end方向有空隙
    int gap = mOrientationHelper.getEndAfterPadding() - endOffset;
    int fixOffset = 0;
    if(gap > 0) {
        //注释2处,向下滚动
        fixOffset = -scrollBy(-gap, recycler, state);
    } else {
        return 0; // nothing to fix
    }
    // move offset according to scroll amount
    endOffset += fixOffset;
    if(canOffsetChildren) {
        // re-calculate gap, see if we could fix it
        gap = mOrientationHelper.getEndAfterPadding() - endOffset;
        if(gap > 0) {
            mOrientationHelper.offsetChildren(gap);
            return gap + fixOffset;
        }
    }
    return fixOffset;
}

注释1处,这里大于0,表示end方向有空隙。

注释2处,向下滚动。这个时候,最大滚动距离是 2111 像素。

int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if(getChildCount() == 0 || delta == 0) {
        return 0;
    }
    ensureLayoutState();
    mLayoutState.mRecycle = true;
    final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
    final int absDelta = Math.abs(delta);
    updateLayoutState(layoutDirection, absDelta, true, state);
    final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);
    if(consumed < 0) {
        if(DEBUG) {
            Log.d(TAG, "Don't have any more elements to scroll");
        }
        return 0;
    }
    final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
    //注释1处,偏移子View
    mOrientationHelper.offsetChildren(-scrolled);
    if(DEBUG) {
        Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
    }
    mLayoutState.mLastScrollDelta = scrolled;
    return scrolled;
}

注释1处,偏移所有的子View。也就是说所有的子View向下滚动了2111像素。

FootViewHolder会偏移到 RecyclerView 的底部。 FootViewHolder 的 top = 2111,bottom = 2255。

滚动了这么多的距离,需要填充新的ViewHolder吗?不需要,我们在上面分析中,ViewHolder1 的 top 是 -2700足够滚动到屏幕中,还有剩余589px。

ViewHolder3 的 top 是 1211 ,bottom 是 2111。

ViewHolder2 的 top 是 311,bottom 是 1211。

ViewHolder1 的 top 是 -589,bottom 是 311。

dispatchLayoutStep2 结束

dispatchLayoutStep3 阶段,执行动画

记录当前阶段的动画信息,对比 dispatchLayoutStep1 阶段记录的动画信息,执行合适的动画。

FootViewHolder 会执行 move 动画。此时 FootViewHolder 的 top 是 2111。

动画开始前,把 FootViewHolder 的 translationY 设置为 -2111。在动画过程中,变化到 translationY = 0 。 实现了从上滑动到底部的效果。

新增的 ViewHolder 会执行 alpha 透明度动画。动画开始前 alpha = 0,动画结束后 alpha = 1。

dispatchLayoutStep3 结束

先说下结论

  • 调用 notifyItemInserted 方法的时候,会把 FootViewHolder 的 position 向下偏移。在预布局 dispatchLayoutStep1 结束的时候, FootViewHolder 的 position = 4。
  • 在 dispatchLayoutStep2 阶段,会以 FootViewHolder 为锚点 position = 4 进行填充。先填充 FootViewHolder。此时FootViewHolder 布局在屏幕中的坐标是 top = 0,bottom = 144。(FootView 的高度就是144)
  • 从 position =5 向锚点下方填充,此时没有更多的数据。
  • position = 3 向锚点上方填充,直到没有更多空间。
  • 此时 FootViewHolder 距离 RecyclerView底部还有很大一段距离。RecyclerView 会向下偏移所有的子View,结束后,FootViewHolder的bottom 就是 RecyclerView的 最底部的坐标。
  • dispatchLayoutStep3 阶段,FootViewHolder 执行一个 move 动画,从上向下移动一段距离。
  • 新创建的 ViewHolder 执行 alpha 动画,从透明到不透明。

在搞明白了这个问题以后,又想到另一个问题。

如果给适配器加一个HeadViewHolder,那么 notifyItemInserted 以后,RecyclerView 就会以会以 HeadViewHolder 为锚点,从上到下进行布局,是不是就可以解决 因为 有FootViewHolder 而导致 RecyclerView自动滚动到底部的问题呢?,我们来验证一下。

改造过后的适配器代码 有一个 HeadViewHolder,并且新增了一个 myNotifyItemInserted 方法。。

class TestAnimatorAdapter(
    private val context: Context
) : RecyclerView.Adapter<TestAnimatorAdapter.ViewHolder>() {

    companion object {
        val TYPE_HEADER = -1
        val TYPE_FOOTER = 1

        val HEAD_COUNT = 1
        val FOOT_COUNT = 1

        private const val TAG = "TestAnimatorAdapterAdap"
    }

    val dataList = mutableListOf<CheckBoxModel>()

    fun onDataSourceChanged(dataList: MutableList<CheckBoxModel>) {
        this.dataList.clear()
        this.dataList.addAll(dataList)
    }


    /**
     * 这里一定要注意了,因为有head,所以要加上head的数量
     */
    fun myNotifyItemInserted(position: Int) {
        notifyItemInserted(position + HEAD_COUNT)
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ViewHolder {
        if (viewType == TYPE_HEADER) {
            val view = LayoutInflater.from(context).inflate(R.layout.head_view, parent, false)
            return HeadViewHolder(view)
        }
        if (viewType == TYPE_FOOTER) {
            val view =
                LayoutInflater.from(context).inflate(R.layout.foot_view_load_more, parent, false)
            return FootViewHolder(view)
        }
        val view =
            LayoutInflater.from(context).inflate(R.layout.item_test_animation, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        if (position == 0) {
            return
        }
        if (position == itemCount - 1) {
            return
        }
        val dataPosition = position - 1
        val model = dataList[dataPosition]
        holder.checkBox?.isSelected = model.isChecked
        holder.textDescription?.text = model.description
        Log.i(
            TAG,
            "onBindViewHolder: dataPosition = $dataPosition  holder = $holder model = $model"
        )
    }

    override fun getItemCount(): Int {
        return dataList.size + HEAD_COUNT + FOOT_COUNT
    }


    override fun getItemViewType(position: Int): Int {
        if (position == 0) {
            return TYPE_HEADER
        }
        if (position == itemCount - 1) {
            return TYPE_FOOTER
        }
        return super.getItemViewType(position)
    }

    open class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var checkBox: CheckBox? = null
        var textDescription: TextView? = null

        init {
            checkBox = itemView.findViewById(R.id.check_box)
            textDescription = itemView.findViewById(R.id.text_description)
        }
    }

    class HeadViewHolder(itemView: View) : ViewHolder(itemView) {

    }

    class FootViewHolder(itemView: View) : ViewHolder(itemView) {

    }

这里一定要注意了:


/**
* 这里一定要注意了,因为有head,所以要加上head的数量
 */
fun myNotifyItemInserted(position: Int) {
    notifyItemInserted(position + HEAD_COUNT)
}

因为有 head,在 notifyItemInserted 的时候,position 要要加上 head 的数量。

测试代码

binding.btnNotifyItemChanged.setOnClickListener {

    val newArrayList = arrayListOf<CheckBoxModel>()
    for (i in 0 until 4) {
        newArrayList.add(CheckBoxModel("hi Hello$i", false))
    }
    testAnimatorAdapterAdapter.onDataSourceChanged(newArrayList)
    for (index in 0 until 4) {
        //总共添加了4条数据,调用4次 notifyItemInserted 
        testAnimatorAdapterAdapter.myNotifyItemInserted(index)
    }
}

效果图:

在这里插入图片描述

可以看到,RecyclerView 不会自动滚动到底部。

如果这里不加上 Head 的数量,RecyclerView 会以 HeadViewHolder 为锚点,向下布局,然后再以 HeadViewHolder 为锚点向上布局。在我们的例子中,导致最后的结果是,RecyclerView 还是会自动滚动到底部。

测试代码 调用 testAnimatorAdapterAdapter.notifyItemInserted(index)

binding.btnNotifyItemChanged.setOnClickListener {

    val newArrayList = arrayListOf<CheckBoxModel>()
    for (i in 0 until 4) {
        newArrayList.add(CheckBoxModel("hi Hello$i", false))
    }
    testAnimatorAdapterAdapter.onDataSourceChanged(newArrayList)
    for (index in 0 until 4) {
        //总共添加了4条数据,调用4次 notifyItemInserted 
        testAnimatorAdapterAdapter.notifyItemInserted(index)
    }
}

效果图:

在这里插入图片描述

为什么呢?因为 notifyItemInserted 从 0 开始布局,会将 HeadViewHolder 向下偏移。最后 HeadViewHolder 的 position = 4 。关键的方法是 RecyclerView 的 offsetPositionRecordsForInsert 方法:

void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for(int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        //注释1处,偏移 position >= positionStart 的 ViewHolder
        if(holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
            holder.offsetPosition(itemCount, false);
            mState.mStructureChanged = true;
        }
    }
    mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
    requestLayout();
}

注释1处,偏移 position >= positionStart 的 ViewHolder。在我们的例子中,HeadViewHolder 的 position = 0,从 0 开始 notifyItemInserted,会将 HeadViewHolder 向下偏移。最后 HeadViewHolder 的 position = 4 。然后开始布局的时候,position = 4 的位置 itemType 是正常的ViewHolder,所以 position = 4 的位置布局的是正常的ViewHolder。 position = 5 是 FootViewHolder。

还想到一个问题,只有 FootView 的时候,为什么调用 notifyDataChanged 以后,RecyclerView 不会自动滚动到底部呢?

在这里插入图片描述

原因是:

  1. 调用notifyDataSetChanged 不会偏移 FootViewHolder。FootViewHolder 的 position = 0。
  2. dispatchLayoutStep2 阶段,会以 FootViewHolder 为锚点,从 position = 0 开始布局。
  3. position = 0 的位置 ItemType 是正常的 ViewHolder。
  4. 然后一直向下布局,直到没有更多的空间 remainingSpace ,结束布局。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

金属氧化物压敏电阻的冲击破坏机理高能压敏电阻分析

以氧化锌为主的金属氧化物阀片在一定的电压和电流作用下的破坏可分为热破坏和冲击破坏两类。 热破坏是指氧化锌电阻在交流电压持续作用时发生的破坏,即由于阀片在交流作用下的发热超过了其散热能力而导致的热平衡失控的现象。交流引起的热破坏可以分为几种不同情况:一种是由于…

unity学习(76)--窗口化和后台运行

1.通过如下方式将编译的游戏设置为窗口模式。 成功&#xff1a; 2.现在只有鼠标点击的窗体游戏运动&#xff0c;其他窗体游戏都会卡住。 2.1build setting中 2.2unity内部Project Settings 也被同步修改了

生成式 AI 学习资源大汇总

这里汇聚了该领域的海量学习资源&#xff0c;从研究更新到面试技巧&#xff0c;从课程材料到免费课程&#xff0c;还有实用代码&#xff0c;一应俱全&#xff0c;是你工作流程中的得力助手&#xff01; 前沿研究&#xff1a;每月精心筛选的最佳生成式 AI 论文列表&#xff0c;让…

SpringMVC学习记录

SpringMVC简介与配置 SpringMVC是一种基于Java实现MCV模型的轻量级Web框架&#xff0c;我们该如何使用呢&#xff1f; 首先在Maven中添加坐标 <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><ver…

iOS UIFont-实现三方字体的下载和使用

UIFont 系列传送门 第一弹加载本地字体:iOS UIFont-新增第三方字体 第二弹加载线上字体:iOS UIFont-实现三方字体的下载和使用 前言 在上一章我们完成啦如何加载使用本地的字体。如果我们有很多的字体可供用户选择,我们当然可以全部使用本地字体加载方式,可是这样就增加了…

算法---动态规划练习-7(按摩师)【类似打家劫舍】

按摩师 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 首先&#xff0c;给定一个整数数组 nums&#xff0c;其中 nums[i] 表示第 i 天的预约时间长度。 定义两个辅助数组 f 和 g&#xff0c;长度都为 n&#xff08;n 是数组…

STM32时钟简介

1、复位&#xff1a;使时钟恢复原始状态 就是将寄存器状态恢复到复位值 STM32E10xxx支持三种复位形式,分别为系统复位、上电复位和备份区域复位。 复位分类&#xff1a; 1.1系统复位 除了时钟控制器的RCC_CSR寄存器中的复位标志位和备份区域中的寄存器以外,系统 复位将复位…

SQL-CRUD-2数据库实验

目录 第一关任务描述 相关知识 插入完整内容的行 插入选定内容的行 编程要求 测试说明 第一关代码 第二关任务描述 相关知识 删除表中的指定行 删除表中的所有行 编程要求 测试说明 第二关代码 第三关任务描述 相关知识 更新表中的指定行 编程要求 测试说明…

【学习】信创产品软件测试企业建设参考清单

“信创&#xff0c;即信息技术应用创新产业&#xff0c;涉及IT基础设施、基础软件、应用软件、信息安全等方面&#xff0c;产品覆盖面广、专业性强。作为目前的一项国家战略&#xff0c;也是当今形势下国家经济发展的新动能&#xff0c;信创产业发展已经成为促进经济数字化转型…

SlerfTools:简化操作,激发Solana生态创新潜能

在区块链世界的快速演变中,Solana生态系统以其独特的高性能吸引了全球的目光。然而,随着生态系统的蓬勃发展,用户和开发者面临的挑战也日渐增多。正是在这样的背景下,一个名为SlerfTools的新星项目应运而生,它承诺将为Solana带来一场革命性的变革。 项目的诞生 SlerfTools并非…

【QT+QGIS跨平台编译】043:【libprotobuf-lite+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、libprotobuf-lite介绍二、文件下载三、文件分析四、pro文件五、编译实践一、libprotobuf-lite介绍 libprotobuf-lite 是 Protocol Buffers 的 C++ 轻量级运行时库,专门设计用于在资源受限的环境下使用。与标准的 libprotobuf(Protocol Buffers…

深入浅出的揭秘游标尺模式与迭代器模式的神秘面纱 ✨

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自&#xff1a;设计模式深度解析&#xff1a;深入浅出的揭秘游标尺模式与迭代…

力扣 718. 最长重复子数组

题目来源&#xff1a;https://leetcode.cn/problems/maximum-length-of-repeated-subarray/description/ C题解&#xff08;思路来源代码随想录&#xff09;&#xff1a;动态规划 确定dp数组&#xff08;dp table&#xff09;以及下标的含义。dp[i][j] &#xff1a;以下标i - …

速通数据结构第三站 单链表

系列文章目录 速通数据结构与算法系列 1 速通数据结构与算法第一站 复杂度 http://t.csdnimg.cn/sxEGF 2 速通数据结构与算法第二站 顺序表 http://t.csdnimg.cn/WVyDb 感谢佬们支持&#xff01; 目录 系列文章目录 前言一、单链表 1 结构体 …

踏上机器学习之路:探索数据科学的奥秘与魅力

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

hxp CTF 2021 - A New Novel LFI(新颖的解法)

一、环境 unbentu&#xff0c;docker https://2021.ctf.link/assets/files/includers%20revenge-25377e1ebb23d014.tar.xz 二、解析 PHP Filter 当中有一种 convert.iconv 的 Filter &#xff0c;可以用来将数据从字符集 A 转换为字符集 B &#xff0c;其中这两个字符集可以…

记录pycharm配置Anaconda环境时没有反应的问题

记录pycharm配置Anaconda环境时没有反应的问题 背景 下载最新pycharm后在设置中配置add interpreter Anaconda环境时&#xff0c;x选中conda.ba文件点击Load Enviroments后&#xff0c;没有反应&#xff0c;就闪了一下&#xff0c;也有添加成功 探索路程 试过了重启&#x…

NineData与StarRocks商业化运营公司镜舟科技完成产品兼容认证

近日&#xff0c;镜舟科技与NineData完成产品兼容测试。在经过联合测试后&#xff0c;镜舟科技旗下产品与NineData云原生智能数据管理平台完全兼容&#xff0c;整体运行高效稳定。 镜舟科技致力于帮助中国企业构建卓越的数据分析系统&#xff0c;打造独具竞争力的“数据护城河”…

量化交易入门(二十五)什么是RSI,原理和炒股实操

前面我们了解了KDJ&#xff0c;MACD&#xff0c;MTM三个技术指标&#xff0c;也进行了回测&#xff0c;结果有好有坏&#xff0c;今天我们来学习第四个指标RSI。RSI指标全称是相对强弱指标(Relative Strength Index),是通过比较一段时期内的平均收盘涨数和平均收盘跌数来分析市…

leetcode热题100.柱状图中最大的矩形

Problem: 84. 柱状图中最大的矩形 文章目录 题目思路复杂度Code 题目 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;hei…