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

news2024/11/26 0:52:15

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

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 ,结束布局。

参考链接:

  • RecyclerView第一次设置LayoutManager和Adapter之后的源码分析
  • RecyclerView源码分析之二 滚动时候的ViewHolder的回收和复用
  • RecyclerView notifyDataSetChanged 之后的源码分析
  • RecyclerView notifyItemInserted 之后的源码分析
  • RecyclerView notifyItemRemoved 之后的源码分析

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

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

相关文章

选择排序---算法

1、算法概念 首先在未排序列中找到最小(大)元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后&#xff0c;再从剩余未排序元素中继续寻找最小(大)元素&#xff0c;然后放到已排序序列的末尾。以此类推&#xff0c;直到所有元素均排序完毕。 选择排序的思想其实和冒泡排…

LLaMA-Factory参数的解答

打开LLaMA-Factory的web页面会有一堆参数 &#xff0c;但不知道怎么选&#xff0c;选哪个&#xff0c;这个文章详细解读一下&#xff0c;每个参数到底是什么含义这是个人写的参数解读&#xff0c;我并非该领域的人如果那个大佬看到有参数不对请反馈一下&#xff0c;或者有补充的…

【网络安全】常见的网站攻击方式及危害

常见的网站攻击方式多种多样&#xff0c;每一种都有其独特的特点和危害。以下是一些常见的网站攻击方式&#xff1a; 跨站脚本攻击&#xff08;XSS&#xff09;&#xff1a;攻击者通过在目标网站上注入恶意脚本&#xff0c;当用户浏览该网站时&#xff0c;恶意脚本会在用户的浏…

类的函数成员(二):析构函数

一.定义 析构函数(destructor) 与构造函数相反&#xff0c;当对象结束其生命周期&#xff0c;如对象所在的函数已调用完毕时&#xff0c;系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作。 例如&#xff0c;在建立对象时用new开辟了一片内存空间&#xff0c;dele…

单链表就地逆置

算法思想&#xff1a;构建一个带头结点的单链表L&#xff0c;然后访问链表中的每一个数据结点&#xff0c;将访问到的数据结点依此插入到L的头节点之后。 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef s…

Mysql重点思考(上)--mysql的索引优化

mysql的索引优化 expalin关键字的用法explain索引优化示例 type列用法执行查询的顺序类型概述 索引概念索引的定义索引的分类主键&唯一区别 唯一索引的创建和查询创建一个唯一索引查询一个唯一索引 场景题合集唯一索引的场景题主键索引的场景题&#xff08;B树&#xff09;…

蓝桥备赛——矩阵读入

题目描述 如上图所示&#xff0c;是一道有关二维前缀和的问题&#xff0c;因为涉及到二维&#xff0c;肯定就是以矩阵的形式进行读入的。 为此&#xff0c;针对矩阵的读入形式进行总结&#xff0c;可以大致总结出两种类型如下&#xff1a; 二维列表推导式 n, m, k map(int…

HTML教程(详细汇总)

目录 前言&#xff1a; Sublime Text(收费): VS Code(免费): HBuilderX(免费): Dreamweaver(免费): Webstorm(收费): 一.网站的概念&#xff1a; 1.什么是网页&#xff1a; 2.什么是网站&#xff1a; 3.服务器&#xff1a; 总结&#xff1a; 二.HTML简介&#xff…

【JavaEE初阶系列】——CAS

目录 &#x1f388;什么是 CAS &#x1f4dd;CAS 伪代码 &#x1f388;CAS 是怎么实现的 &#x1f388;CAS 有哪些应用 &#x1f6a9;实现原子类 &#x1f308;伪代码实现: &#x1f6a9;实现自旋锁 &#x1f308;自旋锁伪代码 &#x1f388;CAS 的 ABA 问题 &#…

黄金票据制作-新手向

黄金票据制作 文章目录 黄金票据制作0x01 前言0x02 黄金票据的制作一、靶场搭建二、收集制作信息获取域名称获取域SID值获取域用户krbtgt密码hash值 二、制作票据 0x03 验证票据有效性 0x01 前言 最近&#xff0c;我学习了内网渗透的相关知识&#xff0c;其中包括了黄金票据的…

浏览器页面缓存机制

HTTP缓存机制的核心思想是&#xff0c;对于已经请求过的资源&#xff0c;如果其在服务器上没有发生变化&#xff0c;那么浏览器就可以直接从本地缓存中获取这些资源&#xff0c;而无需再次向服务器发送请求。 强缓存 就是确定可用的缓存 浏览器和和服务器对每个缓存资源先商量一…

fread和fwirte函数

✨✨ 欢迎大家来到莉莉的博文✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 一、fread函数 ——>从文件流中读取二进制数据到ptr指向的数组 从流&#xff08;二进制文件&#xff09;中读取数据块 ptr&#xff1a;指向大小至…

基于JSP在线订花系统

基于JSP在线订花系统的设计与实现 摘要 近年来&#xff0c;随着人们对于生活品质的重视度日益提升&#xff0c;鲜花的需求量也在不断增加同时带动了鲜花电商的飞速发展和应用&#xff0c;平台化的鲜花交易模式也逐渐从传统的鲜花销售转型为个性化鲜花定制。同时随着鲜花速递行…

基于 RisingWave 和 ScyllaDB 构建事件驱动应用

概览 在构建事件驱动应用时&#xff0c;人们面临着两大挑战&#xff1a;1&#xff09;低延迟处理大量数据&#xff1b;2&#xff09;实现流数据的实时摄取和转换。 结合 RisingWave 的流处理功能和 ScyllaDB 的高性能 NoSQL 数据库&#xff0c;可为构建事件驱动应用和数据管道…

MTMT:构建比特币生态平行世界 打造铭文生态繁荣

近年来&#xff0c;随着铭文市场的火爆以及比特币ETF成功通过&#xff0c;比特币生态正经历着一场复兴&#xff0c;尤其是铭文市场作为新一代Web3的叙事&#xff0c;带来了全新的生产方式&#xff0c;可以预见&#xff0c;铭文就像流动性挖矿对于上一轮DeFi Summer的推动一样会…

KNN算法 | K近邻:KD Tree、球树、KNN数据下采样策略

目录 一. KNN算法实现方式1. 蛮力实现(brute)2. KD树(kd_tree)3. 球树(ball_tree) 二. KD Tree算法1. 构建方式2. KD Tree 查找最近邻 三. 球树(Ball Tree)1. 构建方式 四. KNN评价1. 优点2. 缺点 五. 延申1. KNN数据下采样策略策略1策略2策略3策略4 Condensed Nearest Neighbo…

Macs Fan Control Pro--精准掌控Mac风扇,优化散热新选择

Macs Fan Control Pro是一款专为Mac电脑设计的高级风扇控制工具。它具备强大的温度监测能力&#xff0c;可以实时监测Mac电脑各个核心组件的温度&#xff0c;并通过直观的界面展示给用户。同时&#xff0c;用户可以根据个人需求自定义风扇速度&#xff0c;或者选择预设的自动风…

蓝桥杯算法题-图形排版

题目描述 小明需要在一篇文档中加入 N 张图片&#xff0c;其中第 i 张图片的宽度是 Wi&#xff0c;高度是 Hi。   假设纸张的宽度是 M&#xff0c;小明使用的文档编辑工具会用以下方式对图片进行自动排版&#xff1a; 1. 该工具会按照图片顺序&#xff0c;在宽度 M 以内&…

LabVIEW转动设备故障诊断系统

LabVIEW转动设备故障诊断系统 随着工业自动化技术的不断进步&#xff0c;转动设备在电力、化工、船舶等多个行业中扮演着越来越重要的角色。然而&#xff0c;这些设备在长期运行过程中难免会出现故障&#xff0c;如果不能及时诊断和处理&#xff0c;将会导致生产效率下降&…

【JavaSE】一维数组和二维数组详解

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 一维数组 基本语法 初始化 遍历和打印 数组是引用型变量 基本类型变量与引用类型变量的区别 null 数组传参和返回 总结 二维数组 基本语法 初始化 遍历和打印 一维数组…