当前位置: 首页 > news >正文

【Android】RecyclerView回收复用机制

概述

RecyclerView 是 Android 中用于高效显示大量数据的视图组件,它是 ListView 的升级版本,支持更灵活的布局和功能。

我们创建一个RecyclerView的Adapter:

public class MyRecyclerView extends RecyclerView.Adapter<MyRecyclerView.MyHolder> {private List<String> strings;private Context context;public MyRecyclerView(List<String> strings, Context context) {this.strings = strings;this.context = context;}@NonNull@Overridepublic MyRecyclerView.MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);MyRecyclerView.MyHolder viewHolder = new MyHolder(view);Log.d("MyRecyclerView", "onCreateViewHolder: ");return viewHolder;}@Overridepublic void onBindViewHolder(@NonNull MyRecyclerView.MyHolder holder, int position) {holder.textView.setText("第" + position + "项");Log.d("MyRecyclerView", "onBindViewHolder: " + position);}@Overridepublic int getItemCount() {return strings == null ? 0 : strings.size();}public class MyHolder extends RecyclerView.ViewHolder {private TextView textView;public MyHolder(@NonNull View itemView) {super(itemView);textView = itemView.findViewById(android.R.id.text1);}}
}

我们在onCreateViewHolder和onBindViewHolder都打印log。

onCreateViewHolder()会在创建一个新view的时候调用,onBindViewHolder()会在已存在view,绑定数据的时候调用。

我们来看一下运行时打印的log:

image-20241119212232289

在最开始加载view的时候,两个方法onCreateViewHolder()onBindViewHolder()都执行了,但是当我们上下滑动RecyclerView的时候,我们会发现只执行了onBindViewHolder()方法。所以说,RecyclerView并不是会一直重新创建View,而是会对view进行复用。

复用机制

当我们想去通过看源码去了解缓存复用机制的时候,我们要去想看源码的入口在哪里。上文我们提到是在滑动RecyclerView的时候进行了缓存复用,所以我们会想到去看 onTouchEvent 这个方法:

@Override
public boolean onTouchEvent(MotionEvent e) {...case MotionEvent.ACTION_MOVE: {final int index = e.findPointerIndex(mScrollPointerId);if (index < 0) {Log.e(TAG, "Error processing scroll; pointer index for id "+ mScrollPointerId + " not found. Did any MotionEvents get skipped?");return false;}final int x = (int) (e.getX(index) + 0.5f);final int y = (int) (e.getY(index) + 0.5f);int dx = mLastTouchX - x;int dy = mLastTouchY - y;if (mScrollState != SCROLL_STATE_DRAGGING) {boolean startScroll = false;if (canScrollHorizontally) {if (dx > 0) {dx = Math.max(0, dx - mTouchSlop);} else {dx = Math.min(0, dx + mTouchSlop);}if (dx != 0) {startScroll = true;}}if (canScrollVertically) {if (dy > 0) {dy = Math.max(0, dy - mTouchSlop);} else {dy = Math.min(0, dy + mTouchSlop);}if (dy != 0) {startScroll = true;}}if (startScroll) {setScrollState(SCROLL_STATE_DRAGGING);}}if (mScrollState == SCROLL_STATE_DRAGGING) {mReusableIntPair[0] = 0;mReusableIntPair[1] = 0;if (dispatchNestedPreScroll(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,mReusableIntPair, mScrollOffset, TYPE_TOUCH)) {dx -= mReusableIntPair[0];dy -= mReusableIntPair[1];// Updated the nested offsetsmNestedOffsets[0] += mScrollOffset[0];mNestedOffsets[1] += mScrollOffset[1];// Scroll has initiated, prevent parents from interceptinggetParent().requestDisallowInterceptTouchEvent(true);}mLastTouchX = x - mScrollOffset[0];mLastTouchY = y - mScrollOffset[1];if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,e)) {getParent().requestDisallowInterceptTouchEvent(true);}if (mGapWorker != null && (dx != 0 || dy != 0)) {mGapWorker.postFromTraversal(this, dx, dy);}}} break;...return true;
}

case:MotionEvent.ACTION_MOVE里有 scrollByInternal() 这个方法:

boolean scrollByInternal(int x, int y, MotionEvent ev) {int unconsumedX = 0;int unconsumedY = 0;int consumedX = 0;int consumedY = 0;consumePendingUpdateOperations();if (mAdapter != null) {mReusableIntPair[0] = 0;mReusableIntPair[1] = 0;scrollStep(x, y, mReusableIntPair);consumedX = mReusableIntPair[0];consumedY = mReusableIntPair[1];unconsumedX = x - consumedX;unconsumedY = y - consumedY;}if (!mItemDecorations.isEmpty()) {invalidate();}mReusableIntPair[0] = 0;mReusableIntPair[1] = 0;dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,TYPE_TOUCH, mReusableIntPair);unconsumedX -= mReusableIntPair[0];unconsumedY -= mReusableIntPair[1];boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;// Update the last touch co-ords, taking any scroll offset into accountmLastTouchX -= mScrollOffset[0];mLastTouchY -= mScrollOffset[1];mNestedOffsets[0] += mScrollOffset[0];mNestedOffsets[1] += mScrollOffset[1];if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);}considerReleasingGlowsOnScroll(x, y);}if (consumedX != 0 || consumedY != 0) {dispatchOnScrolled(consumedX, consumedY);}if (!awakenScrollBars()) {invalidate();}return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}

里面的 scrollStep() 方法:

void scrollStep(int dx, int dy, @Nullable int[] consumed) {startInterceptRequestLayout();onEnterLayoutOrScroll();TraceCompat.beginSection(TRACE_SCROLL_TAG);fillRemainingScrollValues(mState);int consumedX = 0;int consumedY = 0;if (dx != 0) {consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);}if (dy != 0) {consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);}TraceCompat.endSection();repositionShadowingViews();onExitLayoutOrScroll();stopInterceptRequestLayout(false);if (consumed != null) {consumed[0] = consumedX;consumed[1] = consumedY;}
}

scrollHorizontallyByscrollVerticallyBy

@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,RecyclerView.State state) {if (mOrientation == VERTICAL) {return 0;}return scrollBy(dx, recycler, state);
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state) {if (mOrientation == HORIZONTAL) {return 0;}return scrollBy(dy, recycler, state);
}

两个都执行的 scrollBy

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 ? LinearLayoutManager.LayoutState.LAYOUT_END : LinearLayoutManager.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;mOrientationHelper.offsetChildren(-scrolled);if (DEBUG) {Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);}mLayoutState.mLastScrollDelta = scrolled;return scrolled;
}

里面的 fill 方法最为关键:

int fill(RecyclerView.Recycler recycler, LinearLayoutManager.LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {...while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();if (RecyclerView.VERBOSE_TRACING) {TraceCompat.beginSection("LLM LayoutChunk");}layoutChunk(recycler, state, layoutState, layoutChunkResult);if (RecyclerView.VERBOSE_TRACING) {TraceCompat.endSection();}if (layoutChunkResult.mFinished) {break;}layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;/*** Consume the available space if:* * layoutChunk did not request to be ignored* * OR we are laying out scrap children* * OR we are not doing pre-layout*/if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null|| !state.isPreLayout()) {layoutState.mAvailable -= layoutChunkResult.mConsumed;// we keep a separate remaining space because mAvailable is important for recyclingremainingSpace -= layoutChunkResult.mConsumed;}if (layoutState.mScrollingOffset != LinearLayoutManager.LayoutState.SCROLLING_OFFSET_NaN) {layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if (layoutState.mAvailable < 0) {layoutState.mScrollingOffset += layoutState.mAvailable;}recycleByLayoutState(recycler, layoutState);}if (stopOnFocusable && layoutChunkResult.mFocusable) {break;}}if (DEBUG) {validateChildOrder();}return start - layoutState.mAvailable;
}

这个方法功能是填充给定的布局,通过while循环不断进行填充,其中的 layoutChunk() 方法:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {View view = layoutState.next(recycler);//获取下一项需要布局的视图if (view == null) {if (DEBUG && layoutState.mScrapList == null) {throw new RuntimeException("received null view when unexpected");}// if we are laying out views in scrap, this may return null which means there is// no more items to layout.result.mFinished = true;return;}RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();if (layoutState.mScrapList == null) {if (mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START)) {addView(view);//将视图添加到布局的末尾} else {addView(view, 0);//将视图添加到布局的开头}} else {if (mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START)) {addDisappearingView(view);} else {addDisappearingView(view, 0);}}...
}

依次点击:

image-20241120203800209

image-20241120204024167

image-20241120204140384

最后我们就找到了回收复用的最关键的代码。

ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {if (position < 0 || position >= mState.getItemCount()) {throw new IndexOutOfBoundsException("Invalid item position " + position+ "(" + position + "). Item count:" + mState.getItemCount()+ exceptionLabel());}boolean fromScrapOrHiddenOrCache = false;ViewHolder holder = null;// 0) If there is a changed scrap, try to find from thereif (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position);fromScrapOrHiddenOrCache = holder != null;}// 1) Find by position from scrap/hidden list/cacheif (holder == null) {holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);if (holder != null) {if (!validateViewHolderForOffsetPosition(holder)) {// recycle holder (and unscrap if relevant) since it can't be usedif (!dryRun) {// we would like to recycle this but need to make sure it is not used by// animation logic etc.holder.addFlags(ViewHolder.FLAG_INVALID);if (holder.isScrap()) {removeDetachedView(holder.itemView, false);holder.unScrap();} else if (holder.wasReturnedFromScrap()) {holder.clearReturnedFromScrapFlag();}recycleViewHolderInternal(holder);}holder = null;} else {fromScrapOrHiddenOrCache = true;}}}if (holder == null) {final int offsetPosition = mAdapterHelper.findPositionOffset(position);if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "+ "position " + position + "(offset:" + offsetPosition + ")."+ "state:" + mState.getItemCount() + exceptionLabel());}final int type = mAdapter.getItemViewType(offsetPosition);// 2) Find from scrap/cache via stable ids, if existsif (mAdapter.hasStableIds()) {holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);if (holder != null) {// update positionholder.mPosition = offsetPosition;fromScrapOrHiddenOrCache = true;}}if (holder == null && mViewCacheExtension != null) {// We are NOT sending the offsetPosition because LayoutManager does not// know it.final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);if (view != null) {holder = getChildViewHolder(view);if (holder == null) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view which does not have a ViewHolder"+ exceptionLabel());} else if (holder.shouldIgnore()) {throw new IllegalArgumentException("getViewForPositionAndType returned"+ " a view that is ignored. You must call stopIgnoring before"+ " returning this view." + exceptionLabel());}}}if (holder == null) { // fallback to poolif (DEBUG) {Log.d(TAG, "tryGetViewHolderForPositionByDeadline("+ position + ") fetching from shared pool");}holder = getRecycledViewPool().getRecycledView(type);if (holder != null) {holder.resetInternal();if (FORCE_INVALIDATE_DISPLAY_LIST) {invalidateDisplayListInt(holder);}}}if (holder == null) {long start = getNanoTime();if (deadlineNs != FOREVER_NS&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {// abort - we have a deadline we can't meetreturn null;}holder = mAdapter.createViewHolder(RecyclerView.this, type);if (ALLOW_THREAD_GAP_WORK) {// only bother finding nested RV if prefetchingRecyclerView innerView = findNestedRecyclerView(holder.itemView);if (innerView != null) {holder.mNestedRecyclerView = new WeakReference<>(innerView);}}long end = getNanoTime();mRecyclerPool.factorInCreateTime(type, end - start);if (DEBUG) {Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");}}}// This is very ugly but the only place we can grab this information// before the View is rebound and returned to the LayoutManager for post layout ops.// We don't need this in pre-layout since the VH is not updated by the LM.if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);if (mState.mRunSimpleAnimations) {int changeFlags = ItemAnimator.buildAdapterChangeFlagsForAnimations(holder);changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,holder, changeFlags, holder.getUnmodifiedPayloads());recordAnimationInfoIfBouncedHiddenView(holder, info);}}boolean bound = false;if (mState.isPreLayout() && holder.isBound()) {// do not update unless we absolutely have to.holder.mPreLayoutPosition = position;} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {if (DEBUG && holder.isRemoved()) {throw new IllegalStateException("Removed holder should be bound and it should"+ " come here only in pre-layout. Holder: " + holder+ exceptionLabel());}final int offsetPosition = mAdapterHelper.findPositionOffset(position);bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);}final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();final LayoutParams rvLayoutParams;if (lp == null) {rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();holder.itemView.setLayoutParams(rvLayoutParams);} else if (!checkLayoutParams(lp)) {rvLayoutParams = (LayoutParams) generateLayoutParams(lp);holder.itemView.setLayoutParams(rvLayoutParams);} else {rvLayoutParams = (LayoutParams) lp;}rvLayoutParams.mViewHolder = holder;rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;return holder;
}

从代码中我们可以看出,复用的并不是一个个控件,而是 ViewHolder(ItemView)

我们可以通过上面代码看出来RecyclerView的复用机制

第一层:Changed Scrap

代码:

if (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position);fromScrapOrHiddenOrCache = holder != null;
}

解释

  • 如果当前处于预布局(isPreLayout == true),会优先从变更缓存中获取。
  • getChangedScrapViewForPosition(position)查找变更缓存(mChangedScrap),这里保存的是那些被标记为需要更新的ViewHolder
  • 如果找到,直接返回,不需要从其他层中查找。

RecyclerView 的布局过程中,预布局(Pre-Layout)RecyclerView 为支持动画效果(如插入、删除、移动等操作)而执行的一个特殊布局阶段。

以下操作都会触发 预布局阶段,并可能从变更缓存中获取视图来进行后续处理:

  • 插入、移除、范围更新notifyItemInserted, notifyItemRemoved, notifyItemRangeChanged 等)
  • 视图的布局和数据绑定(例如,setAdapter, setHasStableIds
  • 布局管理器或动画的变化setLayoutManager, setItemAnimator
  • 视图的移动notifyItemMoved
  • 所有与动画相关的操作(包括添加、删除、移动动画)

第二层:Scrap/Hidden/Cache

代码:

if (holder == null) {holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);if (holder != null) {if (!validateViewHolderForOffsetPosition(holder)) {if (!dryRun) {holder.addFlags(ViewHolder.FLAG_INVALID);if (holder.isScrap()) {removeDetachedView(holder.itemView, false);holder.unScrap();} else if (holder.wasReturnedFromScrap()) {holder.clearReturnedFromScrapFlag();}recycleViewHolderInternal(holder);}holder = null;} else {fromScrapOrHiddenOrCache = true;}}
}

解释

  • 如果第一层缓存未命中,尝试从

    普通缓存层中获取,包括:

    1. Scrap:表示那些视图项暂时不可见但还可以复用的视图,它们是最常见的一种缓存方式。被标记为废弃的视图,在没有被回收之前可以复用。
    2. Hidden:隐藏视图与 Scrap 类似,但它们的存在通常是为了支持更复杂的布局切换、动画等。它们在显示区域外,但仍然保留在缓存中,直到需要重新显示。
    3. CacheRecyclerView 使用缓存来存储那些根据视图 ID 或类型等条件频繁访问的视图。它们通常在视图池中存储较长时间,直到达到缓存容量限制。
  • 调用

    validateViewHolderForOffsetPosition(holder)
    

    检查缓存的有效性:

    • 如果无效(比如位置错位),将其回收。
    • 如果有效,标记fromScrapOrHiddenOrCache = true

第三层:Stable ID Cache

代码:

if (holder == null && mAdapter.hasStableIds()) {holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);if (holder != null) {holder.mPosition = offsetPosition;fromScrapOrHiddenOrCache = true;}
}

解释

  • 如果Adapter支持稳定ID(hasStableIds == true),尝试通过稳定ID查找缓存中的ViewHolder
  • 调用getScrapOrCachedViewForId,通过ID获取匹配的ViewHolder
  • 如果找到,将其位置更新为offsetPosition,并标记为来自缓存。

如何启用 Stable ID?

为了使 RecyclerView 使用 Stable ID Cache,必须确保以下两点:

  1. 实现 hasStableIds() 方法: 你需要在你的 RecyclerView.Adapter 中重写 hasStableIds() 方法并返回 true。这是启用 Stable ID Cache 的前提。
  2. 返回稳定的 ID: 在适配器的 getItemId() 方法中为每个项返回唯一的 ID。这个 ID 通常是数据中的唯一标识符(比如数据库中的主键)。

示例代码:

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {@Overridepublic boolean hasStableIds() {return true; // 启用 Stable ID}@Overridepublic long getItemId(int position) {// 假设你的数据项有一个唯一的 id 字段return myDataList.get(position).getId();}// 其他适配器方法...
}

或者在构造方法中进行设置:

public MyRecyclerViewAdapter(List<String> strings, Context context) {this.strings = strings;this.context = context;setHasStableIds(true); // 启用稳定 ID
}

第四层:Recycled Pool

代码:

if (holder == null) {holder = getRecycledViewPool().getRecycledView(type);if (holder != null) {holder.resetInternal();}
}

解释

  • Recycled PoolRecyclerView 内部维护的一个缓存池,用于存储已经被回收并不再使用的视图(View)。这些视图通常是已经滑出屏幕或者暂时不可见的视图。通过回收池,RecyclerView 可以避免每次滚动时都重新创建视图,而是将已回收的视图重新利用,从而提升滚动性能。

    回收池的工作机制是,RecyclerView 会在视图不再需要时将它们放入回收池(即已回收的视图池)。当需要新的视图时,RecyclerView 会从回收池中获取一个合适的视图进行重用。

  • 回收池存储的是所有超出缓存数量限制的ViewHolder,按type分类。

  • 如果找到,调用resetInternal()重置其状态。

最后一层:创建新 ViewHolder

代码中涉及的部分:

if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type);
}

说明

  • 如果所有缓存机制都未找到匹配的 ViewHolder,最终会调用 Adapter.createViewHolder 来创建新的实例。
  • 这是性能代价最高的一步。

我们通过点击

image-20241120215023059

image-20241120215058358

image-20241120215142001

就可以找到我们每次写Adapter都用重写的 onCreateViewHolder方法了。

在后面的代码中,我们依次点击:

image-20241120215850814

image-20241120215912483

image-20241120215934540

image-20241120215952052

image-20241120220013888

就可以找到我们每次写Adapter都用重写的 onBindViewHolder方法了。


已经到底啦!!

相关文章:

【Android】RecyclerView回收复用机制

概述 RecyclerView 是 Android 中用于高效显示大量数据的视图组件&#xff0c;它是 ListView 的升级版本&#xff0c;支持更灵活的布局和功能。 我们创建一个RecyclerView的Adapter&#xff1a; public class MyRecyclerView extends RecyclerView.Adapter<MyRecyclerVie…...

麒麟系统性能瓶颈分析

1.使用率&#xff0c;表示资源用于服务的时间或容量百分比。100% 的使用率&#xff0c;表示容量已经用尽或者全部时 间都用于服务。 2. 饱和度&#xff0c;表示资源的繁忙程度&#xff0c;通常与等待队列的长度相关。100% 的饱和度&#xff0c;表示资源无法接受 更多的请求。 3…...

Java二分查找+冒泡排序

二分查找在编程中是用来查找目标元素在有序数组中的位置,并返回目标元素的索引 先给定一个有序数组,在创建一个方法来进行二分 主要思想是:根据数组具有下标的特点来分别计算,最左边的索引,以及最右边的索引,在判断目标元素与中间元素的大小,如果目标元素小于中间元素,我们可…...

(三)手势识别——动作识别应用【代码+数据集+python环境(免安装)+GUI系统】

&#xff08;三&#xff09;手势识别——动作识别应用【代码数据集python环境&#xff08;免安装&#xff09;GUI系统】 &#xff08;三&#xff09;手势识别——动作识别【代码数据集python环境GUI系统】 背景意义 随着互联网的普及和机器学习技术的进一步发展&#xff0c;手…...

大数据实战——MapReduce案例实践

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 大数据实战——MapReduce案例实践 一&#xff0e;过程分析&#xff08;截图&#xff09;1. 确定Hadoop处于启动状态2. 在/usr/local/filecotent…...

OpenCV基础(3)

1.图像直方图 1.1.像素统计 计算图像均值&#xff1a; Scalar cv::mean(InputArray src,InputArray masknoArray()); src&#xff1a;输入图像mask&#xff1a;掩膜层过滤 返回值是对输入图像通道数计算均值后的Scalar对象 计算图像均值与方差&#xff1a; void cv::meanSt…...

大语言模型---RewardBench 介绍;RewardBench 的主要功能;适用场景

文章目录 1. RewardBench 介绍2. RewardBench 的主要功能3. 适用场景 1. RewardBench 介绍 RewardBench: Evaluating Reward Models是一个专门用于评估 Reward Models&#xff08;奖励模型&#xff09; 的公开平台&#xff0c;旨在衡量模型在多种任务上的性能&#xff0c;包括…...

泷羽sec-linux

基础之linux 声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…...

栈、队列、链表

一、栈 1. 定义 栈是一种线性数据结构&#xff0c;遵循后进先出&#xff08;LIFO, Last In First Out&#xff09;的原则。这意味着最后被添加到栈中的元素将会是最先被移除的元素。 2. 基本操作 Push&#xff1a;将一个元素添加到栈顶。Pop&#xff1a;移除并返回栈顶的元…...

【maven】配置下载私有仓库的快照版本

1、setting.xml配置 <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/SETTINGS/1.0.0https://maven.apache.org/xsd/settings-1.0.0.…...

LabVIEW引用类型转换问题

一、问题描述 在LabVIEW中&#xff0c;refnum&#xff08;引用编号&#xff09;用于引用各种资源&#xff0c;如文件、队列、控件等。这些引用是与具体类型相关的&#xff0c;通常情况下&#xff0c;LabVIEW会根据引用的类型自动进行处理。然而&#xff0c;当不同类型的引用需…...

GUI智能代理:用AI代理玩米哈游游戏《崩坏》

项目名称:The Dawn of GUI Agent研究对象:Claude 3.5 Computer Use特点:首个公测版GUI智能代理系统 技术创新 首创性:这是首个提供公测版图形界面控制功能的前沿AI模型。交互方式:实现了从自然语言到桌面操作的端到端控制,用户可以通过简单的自然语言指令完成复杂的桌面…...

系统思考—环路图的好处

每次内部学习&#xff0c;我们都会用系统环路图拆解那些动态性复杂的议题。这不仅仅是我们教学的工具&#xff0c;更是我们在实践中不断应用和打磨的利器。 我常在课程中和大家分享&#xff0c;什么原因要持续使用系统环路图&#xff1f; &#x1f3af; 1. 落地全局思维 环路图…...

torch.set_printoptions

torch.set_printoptions 设置pytorch打印张量时的选项&#xff0c;比如限制打印的元素数量、设置精度等。在打印大张量或者需要更精确控制输出格式时非常有用。 torch.set_printoptions(precisionNone, thresholdNone, edgeitemsNone, linewidthNone, profileNone, sci_modeN…...

Nexus搭建go私有仓库,加速下载go依赖包

一、搭建go私库 本文我们梳理一下go依赖包的私库搭建以及使用。 它只分为proxy和group两种仓库&#xff0c;这一点和maven仓库有所不同。 1、创建Blob Stores 为了区分不同的私库依赖包&#xff0c;存储的位置分隔开。 2、新建go proxy官网 Remote storage&#xff1a;htt…...

Qt6 Android设置文件读写权限设置

一.概述 1.在Qt中设置Android应用程序的文件读写权限,你需要在Android的Manifest文件中声明所需的权限。对于文件读写,通常需要声明以下权限: android.permission.READ_EXTERNAL_STORAGE:允许应用程序从外部存储读取数据。 android.permission.WRITE_EXTERNAL_STORAGE:允…...

TCP快速重传机制为啥出现重复ACK?

TCP快速重传机制为啥出现重复ACK 简单来说&#xff0c;丢失数据包后发送方至少发了三个请求&#xff0c;每个请求返回接收方下一次期待的序列号ACK&#xff0c;也就是丢失数据包之前的一个正常请求的确认ACK值 在 TCP&#xff08;Transmission Control Protocol&#xff0c;传…...

SSM--SpringMVC复习(二)

请求 URL匹配&#xff1a; RequestMapping RequestMapping 负责将请求映射到对应的控制器方法上。 RequestMapping 注解可用于类或方法上。用于类上&#xff0c;表示类中的所有响应请求的方法都以该地址作为父路径。 在整个 Web 项目中&#xff0c;RequestMapping 映射的请求…...

C语言蓝桥杯组题目

系列文章目录 文章目录 系列文章目录前言题目第一题.1, 2, 3, 4 能组成多少个互不相同且无重复数字的三位数&#xff1f;都是多少&#xff1f;思路 第二题: 一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问该数是多少…...

【解决】Unity TMPro字体中文显示错误/不全问题

问题描述&#xff1a;字体变成方块 原因&#xff1a;字体资源所承载的长度有限 1.找一个中文字体放入Assets中 2.选中字体创建为TMPro 字体资源 3.选中创建好的字体资源&#xff08;蓝色的大F&#xff09; 在右边的属性中找到Atlas Width h和 Atlas Heigth,修改的大一点&…...

【Threejs进阶教程-着色器篇】9.顶点着色器入门

【Threejs进阶教程-着色器篇】9.顶点着色器入门 本系列教程第一篇地址&#xff0c;建议按顺序学习认识顶点着色器varying介绍顶点着色器与片元着色器分别的作用Threejs在Shader中的内置变量各种矩阵gl_Position 尝试使用顶点着色器增加分段数增强效果 制作平面鼓包效果鼓包效果…...

质量留住用户:如何通过测试自动化提供更高质量的用户体验

在当今竞争异常激烈的市场中&#xff0c;用户手头有无数种选择&#xff0c;但有一条真理至关重要&#xff1a; 质量留住用户。 产品的质量&#xff0c;尤其是用户体验 (UX)&#xff0c;直接决定了客户是留在您的品牌还是转而选择竞争对手。随着业务的发展&#xff0c;出色的用户…...

【CSP CCF记录】201803-1第13次认证 跳一跳

题目 样例输入 1 1 2 2 2 1 1 2 2 0 样例输出 22 思路 没有技术含量的一道题&#xff0c;解题的关键是理解游戏规则。用state标记跳跃状态&#xff0c;以下是对游戏规则的分析&#xff1a; 1. state1&#xff0c;跳到方块上但没跳到中心&#xff0c;得1分 2. state2&#xf…...

详解Qt 中使用虚拟键盘(软键盘qtvirtualkeyboard)

文章目录 详解 Qt 中使用虚拟键盘&#xff08;软键盘&#xff1a;QtVirtualKeyboard&#xff09;1. 虚拟键盘简介1.1 虚拟键盘的应用场景 2. 安装和配置2.1 安装 QtVirtualKeyboard2.2 配置环境变量 3. 使用虚拟键盘3.1 示例代码main.cppwidget.hwidget.cpp 4. 总结 详解 Qt 中…...

cocoscreater3.8.4生成图集并使用

1.安装texturepacker&#xff0c;去官网下载https://www.codeandweb.com/texturepacker 2.将图片拖动进来&#xff0c;即可自动生成精灵表&#xff0c;这里输出选用cocos2d-x&#xff0c;打包用免费版的“基本”就行&#xff0c;高级模式是收费的&#xff0c;然后点击“发布精…...

IDEA如何快速地重写方法,如equals、toString等

前言 大家好&#xff0c;我是小徐啊。我们在使用IDEA的时候&#xff0c;有时候是需要重写equals和toString等方法的。这在IDEA中已经很方便的给我们准备好了快速的操作了。今天就来讲解一下。 如何重写 首先&#xff0c;打开要重写方法的文件&#xff0c;让鼠标定位到这个文…...

网络安全——SpringBoot配置文件明文加密

一、前言 在日常开发中&#xff0c;项目中会有很多配置文件。比如SpringBoot项目核心的数据库配置、Redis账号密码配置都在properties、yml配置文件 中。 如果这些信息以明文的方式存储&#xff0c;你的电脑被拿去修理&#xff0c;就会容易泄露&#xff0c;一旦被其他人获取到…...

LightRAG开源了…结合本地ollama实现股票数据接口Akshare智能问答

LightRAG是由香港大学研究团队推出的一种检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;系统。该系统通过整合图结构索引和双层检索机制&#xff0c;显著提升了大型语言模型在信息检索中的准确性和效率。LightRAG 不仅能够捕捉实体间的复杂依赖关系…...

【PCB设计】AD16教程:分配位号

1、前提条件 确保已经基本画完原理图 2、点击【Tools-Annotate Schematics】 3、依次点击【Reset All】、【Update Changes Lise】、【Close】 最后位号就被自动分配好了...

ElasticSearch7.x入门教程之索引概念和基础操作(三)

文章目录 前言一、索引基本概念二、索引基本使用elasticsearch-head插件Kibana使用 总结 前言 要想熟悉使用ES的索引&#xff0c;则必须理解索引相关的概念&#xff0c;尤其是在工作当中。 在此记录&#xff0c;方便开展工作。 一、索引基本概念 尽量以通俗的话语。 1、集群…...