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

Android-RecyclerView学习总结

​​面试官​:

“你在项目中有遇到过 RecyclerView 滑动卡顿的情况吗?当时是怎么解决的?”

​(回忆项目场景,自然带入):
“有的!之前我们团队做了一款新闻阅读 App,首页的资讯列表用 RecyclerView 展示图文内容。上线后发现快速滑动时会出现掉帧,尤其在低端手机上,用户体验挺差的。

我们先用 Android Studio 的 Profiler 工具抓了一下性能数据,发现 onBindViewHolder 方法耗时特别长。仔细一看,原来每个 Item 里都直接加载高清大图,而且图片还是从网络请求来的,没做任何压缩。

后来我们做了几个优化:

  1. 图片压缩​:在后台线程把图片缩放到 Item 的显示尺寸(比如 300x300),再加载到 ImageView,主线程压力立马小了很多。
  2. 内存缓存​:用了 Glide 的 LruCache,避免重复解码同一张图。
  3. 布局扁平化​:把原来嵌套三层的 LinearLayout 换成了 ConstraintLayout,测量时间减少了 40%。

改完之后再测,帧率从原来的 30 帧提到了 60 帧,用户反馈也好了很多。不过中间有个坑,Glide 的缓存策略一开始没配置好,导致频繁 GC,后来调整了缓存大小才稳定下来。”


面试官追问​:

“如果列表里有多种类型的 Item(比如文字、图片、视频),怎么保证 RecyclerView 的流畅度?”

​(结合技术细节,口语化解释):
“这个问题我们还真踩过坑!之前做社交 App 的时候,动态列表有十几种样式——文字、九宫格图片、视频、分享链接等等。一开始滑动起来特别卡,尤其快速翻页的时候。

后来发现是因为每种类型的 ViewHolder 都独立缓存,但 RecyclerView 的默认缓存池太小,导致频繁创建新 ViewHolder。我们的解决办法挺直接的:

  • 合并相似类型​:比如把纯文字和带表情的文字合并成一种 ViewHolder,通过数据字段区分样式。
  • 动态计算 ViewType​:比如视频封面加载中和已加载的用同一个 ViewType,但根据数据状态显示不同布局。
  • 扩大缓存池​:recyclerView.recycledViewPool.setMaxRecycledViews(viewType, 10),这样即使突然滑动到历史消息,也能快速复用已有的 ViewHolder。

不过最关键的还是 ​避免在 onBindViewHolder 里做复杂计算。比如视频封面需要根据分辨率计算缩略图尺寸,我们改到后台线程预处理,主线程只负责显示。”


面试官挑战​:

“假设现在有个需求:一个页面里外层是 ScrollView,内嵌一个 RecyclerView(比如商品详情页的‘猜你喜欢’模块),怎么解决滑动冲突?”

​(用故事化解技术难点):
“这个需求我们做过!刚开始开发的时候,用户反馈说‘猜你喜欢’的区域根本滑不动,手指一划就触发外层 ScrollView 滚动。

我们试了几种方案:

  1. 粗暴解法​:把 RecyclerView 的固定高度设为全部内容高度,这样它自己就不滚动了,完全依赖外层 ScrollView 滚动。但这样如果‘猜你喜欢’有 100 个商品,页面会变得巨长,直接 OOM。
  2. 改用 NestedScrollView​:这是系统提供的支持嵌套滚动的容器,设置 android:fillViewport="true" 后,内层 RecyclerView 可以正常滑动,外层也能联动。但实测在旧机型上还是有卡顿。
  3. 自定义滚动逻辑​:通过 NestedScrollingParentNestedScrollingChild 接口协调滚动优先级。比如当用户手指在 RecyclerView 区域垂直滑动时,优先让 RecyclerView 滚动;滚动到底部后,再触发外层滚动。

最后选了第二种方案,因为开发成本低。不过上线前用 ​云真机测试​ 跑了一遍主流机型,发现华为部分机型有兼容性问题,加了版本判断代码才解决。”


面试官陷阱题​:

“DiffUtil 用起来会不会有性能问题?比如数据量特别大的时候。”

​(暴露思考过程,展示深度):
“这要看怎么用了!我们之前有个日程管理 App,每次同步数据时要用 DiffUtil 对比新旧 5000 条日程。一开始在主线程跑 DiffUtil.calculateDiff(),直接 ANR 了。

后来改到后台线程计算差异,再切回主线程 dispatchUpdatesTo,问题就解决了。不过这里有个细节:​DiffUtil 的时间复杂度是 O(N)​,如果数据量真的超大(比如 10 万条),对比耗时可能超过 100ms,连后台线程都会卡。

我们的优化方案是:

  • 分片对比​:比如每次只对比当前屏幕可见的 20 条数据。
  • 增量更新​:后端返回数据时带上版本号,只拉取增量的数据,减少对比量。
  • 替代方案​:对于实时性要求不高的列表,直接用 notifyItemRangeChanged 手动控制刷新范围。

不过现在 Jetpack 的 ​Paging 3 库​ 已经内置了分页和差异对比,能自动处理这些优化,我们现在新项目都用这个方案了。”


面试官终极问题​:

“如果让你设计一个像抖音那样的全屏视频滑动列表,你会怎么保证流畅度?”

​(展现架构思维):
“抖音的流畅体验背后有很多细节!我们之前做过类似的短视频模块,核心优化点有三个:

  1. 预加载机制​:

    • 当前播放第 N 个视频时,预加载 N+1 和 N-1 的视频资源。
    • RecyclerView.addOnScrollListener 监听滑动方向,提前 500ms 加载下一批数据。
  2. 视图复用​:

    • 每个全屏 Item 的 ViewHolder 都包含视频播放器组件。
    • 滑动时,复用的 ViewHolder 不需要重新初始化播放器,只需替换数据源(比如 ExoPlayerprepare 新 URL)。
  3. 内存控制​:

    • 限制同时缓存的视频数(比如最多缓存 3 个),其他 ViewHolder 的播放器释放资源。
    • WeakReference 缓存解码后的第一帧图片,避免 OOM。

不过最难的还是 ​手势冲突处理。比如用户上下滑动切换视频时,如果横向滑动触发点赞控件,体验会很割裂。我们最后通过自定义 GestureDetector 判断滑动方向,水平滑动超过 45 度才触发点赞,否则执行翻页。”


面试官​:

“RecyclerView 的缓存机制你了解吗?能简单说说它的工作原理吗?”

​(自然带入项目经验):
“RecyclerView 的缓存机制我们项目里优化过好几次,确实是个挺关键的点。比如之前做一款社交 App 的聊天页面,消息列表特别长,快速滑动的时候总感觉有点卡。后来我们仔细研究了一下缓存机制,发现它其实分了几个‘暂存区’,用来回收和复用 ViewHolder。”


面试官追问​:

“哦?具体有哪些‘暂存区’?能举个例子吗?”

​(用生活场景比喻):
“可以想象成快递站的包裹柜——

  1. 临时货架(mAttachedScrap)​​:比如你正在取快递,手头拿着的几个包裹暂时放在身边,等会儿可能还要用。RecyclerView 在布局的时候,会把当前屏幕上的 ViewHolder 先放在这里,方便快速调整位置。
  2. 最近包裹区(mCachedViews)​​:快递员会把最近送到但暂时没人取的快递放在这里,比如你刚扫了一眼的某个消息项滑出屏幕,但可能马上又会滑回来,这时候直接从这拿,不用重新绑数据。
  3. 大仓库(RecycledViewPool)​​:如果快递太多放不下,就会按类型分类存到仓库里。比如所有图片消息的 ViewHolder 放一个区,文本消息放另一个区,下次需要的时候虽然要重新绑数据,但至少不用重新造个新柜子。”

面试官深入​:

“那你们项目里是怎么利用这些机制优化的?”

​(结合实战案例):
“之前聊天页面的图片消息特别多,用户快速滑动时经常出现白屏。我们用 Android Studio 的 Profiler 一查,发现 onCreateViewHolder 耗时特别高,说明 ViewHolder 创建太频繁。
后来我们做了两件事:

  • ​扩大‘最近包裹区’:recyclerView.setItemViewCacheSize(10),让更多滑出屏幕的 ViewHolder 留在 mCachedViews 里,反向滑动时直接复用,省去了重新绑定图片的时间。
  • 共享仓库​:因为 App 里还有个‘动态’页面也用图片消息,我们让两个页面的 RecyclerView 共用同一个 RecycledViewPool,这样滑到‘动态’页时,可以直接复用聊天页缓存过的图片 ViewHolder。”

面试官挑战​:

“如果遇到特别复杂的 Item 布局(比如直播间的弹幕),缓存机制还能有效吗?”

​(暴露问题并给出方案):
“确实会遇到挑战!我们做直播功能的时候,弹幕 Item 包含头像、昵称、消息内容,还有各种动画。一开始快速滚动时,FPS 直接掉到 40 以下。
后来分析发现,问题出在 ​缓存命中率低——因为弹幕类型多(普通弹幕、打赏消息、系统通知),每种类型的 ViewHolder 都被单独缓存,但缓存池默认每个类型只存 5 个。
我们的解决方案:

  1. 合并相似类型​:把打赏消息和系统通知都合并成‘特殊消息’类型,通过数据字段区分样式。
  2. 预加载关键 ViewHolder​:在进入直播间时,提前创建 10 个弹幕 ViewHolder 并缓存,避免高峰时段密集创建。
  3. ​优化 onBindViewHolder:把头像加载改成 Glide 的预加载机制,避免在滚动时主线程解码图片。”

面试官追问​:

“听起来你们对缓存机制理解很深,那如果让你设计一个新的列表控件,会参考 RecyclerView 的缓存设计吗?”

​(展示设计思维):
“肯定会参考它的分层思想!比如最近我们在做一个相机滤镜列表,需要横向滚动展示大量滤镜预览图。
借鉴 RecyclerView 的经验,我们设计了:

  • 预览图缓存池​:保留最近使用过的 5 个滤镜预览 Renderer,避免每次滑动都重新初始化 OpenGL 资源。
  • 动态回收策略​:如果用户 30 秒没滑动,自动释放一半缓存,平衡内存和流畅度。
    不过我们也改了一点——因为滤镜列表是横向的,所以 mCachedViews 改成了优先缓存左右两侧的 ViewHolder,这样快速来回滑动更顺滑。”

面试官​:

“你在项目里用过 RecyclerView 的 DiffUtil 吗?能说说它的作用和你们是怎么用的吗?”

​(自然带入场景):
“当然用过!我们团队做新闻 App 的时候,首页的资讯列表经常需要更新,比如用户下拉刷新或者加载更多。一开始用 notifyDataSetChanged(),结果每次刷新整个列表都会闪一下,体验特别差。后来引入了 DiffUtil,只更新有变化的 Item,流畅多了。

比如有一次,用户点了一篇新闻的‘点赞’按钮,点赞数要从 100 变成 101。用 DiffUtil 的话,它只会刷新这一行,其他没变的新闻标题、图片都不用动,看起来就像瞬间更新了一样,完全没有闪烁。”


面试官追问​:

“听起来不错,那 DiffUtil 具体是怎么判断哪些数据变化的?”

​(比喻化解释):
“可以把它想象成一个‘数据侦探’!它会拿着新旧两份数据清单,挨个对比:

  1. 第一步:找熟人​(areItemsTheSame):比如通过新闻的 ID 判断是不是同一条数据。
  2. 第二步:查细节​(areContentsTheSame):如果 ID 对上了,再检查标题、图片这些内容有没有变化。
  3. 第三步:记小本本​(getChangePayload):如果只是某个小地方变了(比如点赞数),就记下来,告诉 Adapter 只更新这个部分,不用整个重画。”

面试官挑战​:

“那你们在实现的时候有没有踩过什么坑?比如数据量很大的时候会不会卡?”

​(暴露问题并给出方案):
“还真踩过!有一次测试同学扔了个 5000 条数据的列表过来,结果一刷新就 ANR 了。后来发现是因为在主线程跑 DiffUtil.calculateDiff(),计算量太大直接卡死主线程。

我们当时的解决方案:

  1. 扔到后台线程​:用 Kotlin 协程或者 RxJava 在后台计算差异,算完了再切回主线程更新 UI。
  2. 数据分片​:比如每次只对比当前屏幕能看到的 20 条数据,而不是全量 5000 条。
  3. 增量更新​:让后端同学改接口,只返回变化的数据,比如‘新增了 10 条,删了 2 条’,这样 DiffUtil 只要处理 12 条,速度飞快。”

面试官深入​:

“如果遇到数据顺序变化,比如用户拖拽排序,DiffUtil 能自动处理吗?”

​(结合动画效果):
“可以的!比如我们做过一个任务管理 App,用户长按拖拽调整任务顺序。DiffUtil 会识别到位置变化,自动触发 notifyItemMoved,配合 RecyclerView 的默认动画,任务项会‘滑’到新位置,特别丝滑。

不过有个细节:如果数据类的 equals 方法没重写,可能会导致 DiffUtil 误判内容变化,触发不必要的刷新。所以我们强制所有数据类必须实现 equalshashCode,只用 ID 和关键字段做对比。”


面试官陷阱题​:

“有人说用了 DiffUtil 就不需要 notifyItemChanged(position) 了,对吗?”

​(指出误区):
“不完全对!比如有个特殊场景:用户修改了某条数据的某个字段,但这个字段不在 areContentsTheSame 的对比范围内。这时候 DiffUtil 会认为内容没变,跳过刷新。

我们的解决方案:

  • 方案一​:在 areContentsTheSame 里加入这个字段的对比。
  • 方案二​(更灵活):手动调用 notifyItemChanged,但用 Payload 告诉 Adapter 只更新特定控件。比如点赞数变化时,只改数字,不碰标题和图片。”

基础知识扩展: 


RecyclerView 缓存机制

一、缓存层级与核心设计思想

RecyclerView 的缓存机制通过 ​多级缓存池​ 实现高效复用,核心目标是 ​减少 ViewHolder 的重复创建和布局测量,从而提升滚动性能。其缓存层级可分为四个部分:

缓存层级存储内容复用条件生命周期
mAttachedScrap当前屏幕可见的 ViewHolder同位置同类型短暂(仅在布局阶段有效)
mCachedViews近期滑出屏幕的 ViewHolder同位置同类型长期(容量满时淘汰到下一级)
RecycledViewPool按类型分类的 ViewHolder同类型即可复用长期(应用生命周期内有效)
ViewCacheExtension开发者自定义缓存(极少使用)开发者控制自定义

二、各级缓存详解与实战场景
1. mAttachedScrap:临时缓存,用于布局优化
  • 工作原理​:在 onLayoutChildren() 过程中,屏幕可见的 ViewHolder 会被临时存入 mAttachedScrap。当布局完成后,未被复用的 ViewHolder 会回到 mCachedViews 或 RecycledViewPool。
  • 场景案例​:快速来回滑动时,刚滑出的 ViewHolder 可能还在 mAttachedScrap 中,直接复用无需重新绑定数据。
  • 关键代码​:
    // RecyclerView 源码中的处理逻辑
    void layoutChildren() {// 将当前可见的 ViewHolder 存入 mAttachedScrapscrapOrRecycleView(recycler, i, view);// 重新布局时优先从 mAttachedScrap 获取ViewHolder holder = getScrapOrCachedViewForPosition(position);
    }
2. mCachedViews:高频复用缓存(默认容量 2)​
  • 工作原理​:ViewHolder 滑出屏幕后,优先存入 mCachedViews。当用户反向滑动时,直接从 mCachedViews 取出复用(无需 onBindViewHolder)。
  • 优化技巧​:若列表项固定(如消息列表),增大 mCachedViews 容量可提升反向滑动性能。
    recyclerView.setItemViewCacheSize(10); // 增大缓存容量
  • 淘汰策略​:当 mCachedViews 容量满时,最旧的 ViewHolder 会被转移到 RecycledViewPool。
3. RecycledViewPool:跨列表共享的全局缓存
  • 存储结构​:按 viewType 分类,每个类型默认缓存 5 个 ViewHolder。
  • 复用规则​:不同位置、不同 RecyclerView 的同类型 ViewHolder 可复用(需重新绑定数据)。
  • 共享场景​:ViewPager 中多个 RecyclerView 共享同一个 Pool,避免重复创建。
    RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
    recyclerView1.setRecycledViewPool(pool);
    recyclerView2.setRecycledViewPool(pool);
  • 容量调整​:
    pool.setMaxRecycledViews(TYPE_IMAGE, 10); // 增大图片类型缓存
4. ViewCacheExtension:自定义缓存(高级用法)​
  • 使用场景​:需要特殊复用逻辑时(如根据业务状态缓存),但 99% 的项目无需使用。
  • 示例代码​:
    public class CustomCacheExtension extends RecyclerView.ViewCacheExtension {private SparseArray<ViewHolder> mCache = new SparseArray<>();@Overridepublic View getViewForPositionAndType(int position, int type) {return mCache.get(position); // 根据位置返回缓存视图}public void addToCache(int position, ViewHolder holder) {mCache.put(position, holder);}
    }

三、缓存工作流程(以向下滑动为例)​
  1. ViewHolder 滑出屏幕

    • 存入 mCachedViews(若未满) → 复用时不触发 onBindViewHolder
    • 若 mCachedViews 已满,转移到 RecycledViewPool。
  2. 新 ViewHolder 需要显示

    • 优先从 mAttachedScrap 查找(布局阶段)。
    • 若未找到,从 mCachedViews 查找(同位置)。
    • 若未找到,从 RecycledViewPool 获取(同类型)。
    • 若未找到,调用 onCreateViewHolder 创建新实例。
  3. ViewHolder 回收到池中

    • 从 RecycledViewPool 获取的 ViewHolder 必须重新绑定数据(onBindViewHolder)。

四、性能优化实战技巧
1. 提升缓存命中率
  • 预加载布局​:在空闲期预创建 ViewHolder。
    recyclerView.post(() -> {RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();layoutManager.scrollToPosition(preloadPosition); // 触发预加载
    });
  • 避免频繁变更 ViewType​:相同数据尽量使用相同 ViewType。
2. 监控缓存状态
  • 通过 RecyclerView.Recycler 调试​:
    RecyclerView.Recycler recycler = recyclerView.getRecycler();
    int cachedCount = recycler.getCachedViews().size(); // mCachedViews 当前数量
    int poolSize = recycler.getRecycledViewPool().getRecycledViewCount(TYPE_TEXT); // 某类型缓存数
3. 解决常见问题
  • 卡顿问题​:检查 onBindViewHolder 是否耗时,避免主线程操作。
  • 内存泄漏​:在 onViewRecycled() 中释放资源。
    @Override
    public void onViewRecycled(@NonNull ViewHolder holder) {Glide.with(holder.imageView).clear(holder.imageView); // 释放图片资源
    }

五、大厂面试高频问题
问题 1:mCachedViews 和 RecycledViewPool 的区别?​
  • mCachedViews​:按位置缓存,复用无需重新绑定数据,容量小(默认 2)。
  • RecycledViewPool​:按类型缓存,复用需重新绑定数据,容量可跨列表共享。
问题 2:如何实现类似微信聊天列表的流畅滑动?​
  • 优化点​:
    1. 使用 DiffUtil 局部更新,减少 onBindViewHolder 触发次数。
    2. 增大 mCachedViews 容量(setItemViewCacheSize(20))。
    3. 避免在 onBindViewHolder 中加载图片,用 Glidepreload() 预加载。
问题 3:为什么 RecyclerView 比 ListView 更高效?​
  • 缓存机制​:RecyclerView 通过多级缓存和 ViewHolder 模式,减少布局测量和视图创建开销。
  • 布局解耦​:支持横向、网格、瀑布流等布局,避免 ListView 的全局重绘。

DiffUtil

1. DiffUtil 是什么?​

DiffUtil 是 Android 中用于优化 RecyclerView 数据更新的工具。它通过智能对比新旧数据集,精确计算出哪些数据项发生了改变(增、删、改、移动),从而触发局部刷新,避免无脑调用 notifyDataSetChanged() 导致整个列表重绘。


2. 为什么需要 DiffUtil?​
  • 传统方法的弊端​:
    使用 notifyDataSetChanged() 会强制刷新整个列表,即使只有一项数据变化,所有 Item 都会重新执行 onBindViewHolder,导致性能浪费(如卡顿、闪烁)。

  • DiffUtil 的优势​:

    • 仅更新变化的 Item,减少 UI 操作次数。
    • 自动处理移动动画(如数据项位置交换时的平滑过渡)。
    • 支持局部更新(仅刷新变化的控件,如点赞数)。

3. 核心原理:DiffUtil.Callback

要使用 DiffUtil,需实现 DiffUtil.Callback 抽象类,定义四个关键方法:

方法作用
getOldListSize()返回旧数据集的长度。
getNewListSize()返回新数据集的长度。
areItemsTheSame(oldPos, newPos)判断新旧位置的数据项是否代表同一对象(通常通过唯一 ID 比较)。
areContentsTheSame(oldPos, newPos)判断同一对象的数据内容是否变化(如标题、图片是否修改)。
getChangePayload()(可选)返回变化的“载荷”(如仅标题变化),用于更细粒度的局部更新。

4. 使用步骤
步骤 1:实现 DiffUtil.Callback
class MyDiffCallback(private val oldList: List<Item>,private val newList: List<Item>
) : DiffUtil.Callback() {override fun getOldListSize() = oldList.sizeoverride fun getNewListSize() = newList.sizeoverride fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {// 通过唯一 ID 判断是否是同一项return oldList[oldPos].id == newList[newPos].id}override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {// 判断内容是否一致(需重写数据类的 equals())return oldList[oldPos] == newList[newPos]}// 可选:返回变化的部分数据override fun getChangePayload(oldPos: Int, newPos: Int): Any? {val oldItem = oldList[oldPos]val newItem = newList[newPos]return if (oldItem.title != newItem.title) "UPDATE_TITLE" else null}
}
步骤 2:在后台线程计算差异
// 在协程或异步任务中执行
GlobalScope.launch(Dispatchers.Default) {val diffResult = DiffUtil.calculateDiff(MyDiffCallback(oldList, newList))withContext(Dispatchers.Main) {// 先更新数据源,再应用变更adapter.updateData(newList)diffResult.dispatchUpdatesTo(adapter)}
}
步骤 3:Adapter 处理局部更新
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List<Any>) {if (payloads.isEmpty()) {// 全量更新holder.bind(dataList[position])} else {// 局部更新(如仅更新标题)payloads.forEach { payload ->if (payload == "UPDATE_TITLE") {holder.titleView.text = dataList[position].title}}}
}
步骤 4:启用稳定 ID(关键!)​
class MyAdapter : RecyclerView.Adapter<ViewHolder>() {init {setHasStableIds(true) // 必须启用}override fun getItemId(position: Int): Long {return dataList[position].id // 返回唯一 ID}
}

5. 常见错误与避坑指南
  1. 未在后台线程计算差异​:

    • 问题​:大数据集下 DiffUtil.calculateDiff() 会阻塞主线程,导致卡顿。
    • 解决​:始终在后台线程执行计算,通过协程或 AsyncTask 切回主线程更新。
  2. areItemsTheSame 实现错误​:

    • 错误示例​:直接比较对象引用(oldItem == newItem),而非唯一 ID。
    • 解决​:确保比较的是业务唯一标识(如数据库主键)。
  3. 未设置 setHasStableIds(true)​​:

    • 问题​:RecyclerView 无法正确匹配新旧项,导致动画异常或数据错乱。
    • 解决​:在 Adapter 初始化时调用 setHasStableIds(true),并正确实现 getItemId()
  4. 数据更新顺序错误​:

    • 错误流程​:先调用 diffResult.dispatchUpdatesTo(adapter),再更新数据源。
    • 正确顺序​:先更新 Adapter 的数据源,再应用差异。

6. 性能优化技巧
  • 合理设计数据类​:
    重写 equals()hashCode(),确保 areContentsTheSame 能正确判断内容变化。

  • 使用 Payload 局部更新​:
    对于部分变化的项(如点赞数),通过 getChangePayload 返回变化字段,减少 onBindViewHolder 的计算量。

  • 分页加载大数据集​:
    避免一次性对比数万条数据,采用分页加载减少单次计算量。

相关文章:

  • 新疆工程系列建筑专业职称评审条件
  • 流程引擎选型指南
  • zabbix 常见问题
  • 繁体字与简体中文转换
  • 基于springboot+vue的人口老龄化社区服务与管理平台(源码+数据库+文档)
  • 火语言UI组件--控件事件触发
  • 测试文章1
  • Keil5 MDK LPC1768 RT-Thread KSZ8041NL uIP1.3.1实现UDP网络通讯(服务端接收并发数据)
  • Unity基础学习(六)Mono中的重要内容(2)协同程序
  • XXE(外部实体注入)
  • 我店模式系统开发打造本地生活生态商圈
  • 【深度学习-Day 15】告别“盲猜”:一文读懂深度学习损失函数
  • 2. Java 基础语法通关:变量、数据类型与运算符详解​
  • CST求解器
  • HarmonyOS 鸿蒙应用开发基础:父组件调用子组件方法的几种实现方案对比
  • Linux Docker下安装tomcat
  • 首次使用倍福工控机修改IP
  • Redis--SpringDataRedis详解
  • 基于 Free2AI 的企业知识库搭建全流程实战:从数据采集到智能问答
  • 笔记:将一个文件服务器上的文件(一个返回文件数据的url)作为另一个http接口的请求参数
  • 常州网站建设机构/最新实时大数据
  • 网站建设仟首先金手指13/网站定制
  • 响应式网站开发图标/企业软文范例
  • 西安市建设银行网站/百中搜优化
  • 企业做网站的/企业文化的重要性和意义
  • 找别人做网站的注意事项/企业品牌推广