由浅入深,详解 LiveData 的那些事

article/2025/11/11 5:58:46

引言

关于LiveData,在2022尾声的今天,从事 Android 开发的小伙伴一定不会陌生。相应的,关于 LiveData 解析与使用的文章更是数不胜数,其中不乏优秀的创作者,在众多的文章以及前辈面前,本篇也不敢妄谈能写的多么深入,易懂。

本篇主要想着重聊聊 LiveData 的实现思想,以及与之相关联的一些问题,试着从另一角度告诉你这些答案,或者说是个人的浅薄理解。

在我的认知里,如果你了解完这些,那么对于 LiveData ,我想就也就不会再有疑问:)

在阅读本文前,建议读者有以下前置知识储备:

  • 熟悉并会使用 LiveData
  • 理解 Lifecycle 的设计;

导航

本篇将从以下几个方面解析 LiveData:

  • LiveData 简要快析;
  • LiveData 源码简析;
  • LiveData 设计思想;
  • LiveDataLifecycle 的关联;
  • LiveData 用作事件通知时的隐患;
  • LiveDataEventBus 的区别是什么;
  • LiveDataFlow 我该怎么选;

好了,让我们开始吧! 🐊

LiveData简要快析

在官方的描述中,LiveData 如下所示:

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 activityfragmentservice)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

说简单就是 LiveData 是一个可观察的数据存储类,内部借助了 Lifecycle,从而实现了生命周期感知,同时,这个可观察指的是,在其存储的数据更新时,它会去通知观察者。又因为生命周期感知的存在,所以可以做到 何时通知、何时解绑,从而做到安全无泄漏,就是如此:)

LiveData与Lifecycle的关联

说一句比较夸大的话,没有 Lifecycle,自然也不会存在 LiveData,或者说应该改名为 ObserveData 🤪。LiveData 作为作为生命感知型组件一部分,自诞生之初其,就离不开 Lifecycle 这个基石。

LiveData 规定了,当我们开发者订阅数据通知(调用observe())时,必须传递相应的 lifecycle 对象,其内部自然就是为了注册相应的观察者,从而做到生命周期感知,不然它怎么能自己解绑呢?

当我们的观察者生命周期处于 STARTD 或者 RESUMED 状态,LiveData 就会认为当前观察者处于活跃状态,此时就会触发相应的更新通知,而非活跃的观察者自然不会收到通知。也正是因为 Lifecycle 的原因,所以 LiveData 做到了自动解绑,从而避免内存泄漏。

关于 Lifecycle ,这里也顺便再提一下:

说到Lifecycle,在sdk26以后,Lifecycle已经被写入了我们 Androidx 基础组件,默认会在 ComponentActivityFragmeent 中初始化,并且支持开发者自行调用 lifecycle 对象,从而添加相应的生命周期观察者,从而免除模版代码。相应的,Lifecycle 将生命周期划分为了如下几个阶段:

  • DESTROYED
  • INITIALIZED
  • CREATED
  • STARTED
  • RESUMED

这几个阶段与我们开发者其实并不相关,开发者往往关注的是其对应的Event。即 Lifecycle 将生命周期划分为多个状态,当生命周期改变时,就会触发生命周期事件通知(比如 onResume() 等),从而同步当前的状态,而状态相当于一个事件集,其代表了当前 lifecycle 的状态,从而不拘泥于现在 Event 到底处于什么。

LiveData设计思想

其实,要理解 LiveData 的设计思想,最简单的方式就是手动实现一遍,所以本小节将完整叙述一遍 LiveData 的整体设计流程。👨🏻‍💻

在开始之前,我们先看一段普通的示例代码,如下所示:

private val _livedata: MutableLiveData<String> = MutableLiveData()
val liveData: LiveData<String> = _livedatafun manager(){_livedata.postValue(x)_livedata.setValue(x)
}fun observeX(){liveData.observe(lifecycle,Observer)
}

LiveData 的使用一般如上所示,我们一般会先初始化一个 MutableLiveData 对象,然后对外暴漏 LiveData 对象,从而遵循开闭原则,外部调用者只允许订阅观察者,观察数据更新,而不允许主动通知数据更新,当然这也是 LiveData 的标准推荐用法。


如果我们自己要实现一个 LiveData ,其内部维护着一个数据,并且要保证这个数据在更新时,观察者可以收到通知,并且要在页面活跃状态才行。此时,就有如下几个问题🧐:

  • 数据怎么维护?
  • 数据什么时候通知? 通知时机呢?

而要说清上述问题,即正是对LiveData的设计思想做一个阐述。

  1. 要满足上述条件,我们需要设计一个类,假设名字叫做 ObserveData,并且内部持有一个数据T,因为要支持多种数据类型,所以泛型也必不可少;
  2. 为了支持数据监听,我们需要新增一个具体的监听数据更新方法,假设名字叫做 observe() ,当然也需要传入具体的观察者 IObserve 接口对象;
  3. 为了支持数据更改,我们需要新增一个具体的设置数据的方法,假设名字叫做 setValue();
  4. 为了在用户调用 setValue() 更新数据时,通知用户变更,我们需要新增一个观察者列表map,从而将用户 observe() 传递进来的观察者保存起来;
  5. 为了符合Android的生命周期,保证页面活跃状态才能收到通知,从而避免非活跃观察者被通知到,节省性能;以及能不能将解绑逻辑让框架自行执行,从而免除调用者手动调用模版代码;自然而然,我们就会想到 Lifecycle ,所以我们可以在 observe() 这里做改动:
    • 我们更改了 observe() 方法,调用者必须传递 lifecycle 对象进来;
    • 我们新增了一个新的包装类假设名字叫做 ObserverLifecycleWrapper ,其需要实现 LifecycleEvent 接口,以及内部保存着我们的观察者;
    • 最后,当用户在调用 observe() 订阅数据更新时,我们就将用户传递的观察者使用包装类包装起来,并缓存到我们的观察者map中,接着再将其 add()lifecycle 的生命周期观察数组里,从而便于收到生命周期更新通知;
  6. 上述的实现看似简单,但仔细思考就有个问题,如果观察者此时处于不活跃状态呢?此时用户更改了数据,那这个数据更改就没法通知给用户;那如果观察者又转为活跃状态了,本次更改岂不是跳过了?相应的,我们又怎么确保同一个数据更新不会触达用户两次呢?
    • 为了解决上述问题,我们增加了 [版本号] 的概念,我们的 ObserveData 中持有一个最新版本号,每一个观察者包装类 ObserverLifecycleWrapper 也维护着这个的版本号。即当用户每次手动更新数据时,我们对 版本号进行++ ,然后再去通知相应的观察者,如果这个 观察者的版本号<小于当前ObserveData最新的版本号,我们就认为这个观察者依然持有着旧的数据,就对其进行更新,并将新的版本号赋值给这个观察者;
    • 相应的,因为我们的观察者订阅了 lifecycle 生命周期更新,所以当生命周期由非活跃转为活跃状态时,我们就再去对比一下当前观察者的最新数据版本号与我们当前最新的版本号是否一致,如果不一致,则主动更新;否则跳过。

上述思路看着很繁琐,但其实比较简单,也即是 LiveData 的整个设计思路,但如果你理解 Lifecycle ,上述的理解我想对你来说,就是 so easy🤌。

但仔细观察,不难发现上述的思路中,似乎隐藏着一些问题,而这些问题,似乎也是充满一些争议,比如每次 observe() 时,因为lifecycle + version 的问题,会导致新的观察者重新订阅后数据被回推,而关于这个问题我们也会在后面进行补充。

LiveData源码简析

在上面我们阐述了 LiveData 的设计思想,有了上面的基础,那么再看源码就非常简单了🏃🏻。

而要探究 LiveData 的源码,我们只需要去看看相应的 observe()postValue() 即可,为什么这么说呢?

原因很简单,一个好的框架库,会遵循 开闭与最少原则,即暴漏给开发者往往只有几个主要方法。而在 LiveData 的设计中,observe()postValue() 两个方法是离我们开发者最接近的,而了解完这两个方法,也就不难理解LiveData的底层实现,以及为其他问题解析做出铺垫。

虽然也有 observeForever()removeObserve(),但这些都比较简单,不影响我们阅读主流程。

observe()

用于订阅LiveData的数据更新,源码如下:

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {// 创建生命周期绑定观察者,这里相当于是对我们观察者的一个包装LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer)// 将观察者添加到缓存中,如果存在,则跳过ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper)...// 将观察者添加到Lifecycle订阅列表中,即赋予生命周期订阅owner.getLifecycle().addObserver(wrapper)
}

在调用 observe() 订阅 Livedata 数据更新时,这里相当于添加了一个观察者,方法内部会将我们传递的 LifecycleOwner 与 观察者 包装为一个具体的生命周期观察者 wrapper(LifecycleEventObserver),接着将这个 wrapper 添加到当前的观察者列表中,如果存在则停止本次订阅操作,否则将这个观察者添加到 lifecycle 生命周期订阅列表。

因为 LifecycleEventObserver 实现了 LifecycleEventObserver 接口,故这个 wrapper 实则具备了生命感知,所以不难猜测,LiveData 为什么能做到自动解绑,页面活跃时接收消息,也是因为 lifecycle 的原因。


postValue()

用于在非主线程更新 LiveData 中持有的数据,内部最终会调用 setValue() ,具体如下:

protected void postValue(T value) {boolean postTask;// 进入对象锁synchronized (mDataLock) {// 数据是否set过postTask = mPendingData == NOT_SET;// 待同步的数据mPendingData = value;}// 当前正在postValue,忽略本次操作if (!postTask) {return;}// 将任务发送到主线程执行ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}private final Runnable mPostValueRunnable = new Runnable() {public void run() {Object newValue;synchronized (mDataLock) {// 获取最新待设置的数据newValue = mPendingData;// 重置待同步的数据为默认mPendingData = NOT_SET;}// 设置数据setValue((T) newValue);}};

上述方法的实现很巧妙,内部会先判断当前是否正在更新数据(即数据是否为默认),然后将我们要设置的数据保存起来,如果正在更新,则跳过本次任务发送,否则将本次更新任务发送到主线程去执行(不难猜测内部也是handler执行),在具体的 runable 中,会直接去取最新待同步的值,然后将其置为默认值,最后执行真正的数据更新,即 setValue();

不过需要注意的,多线程下调用,可能会丢失某次的通知。


setValue()

用于在主线程更新 LiveData 持有的数据,其内部实则分为了三个步骤,如下所示:

-1. 设置数据:

protected void setValue(T value) {// 版本号++mVersion++;// 同步数据mData = value;// 分发数据dispatchingValue(null);
}

-2. 分发数据:

void dispatchingValue(@Nullable ObserverWrapper initiator) {...// 不为null时证明是lifecycle状态变为活跃if (initiator != null) {considerNotify(initiator);initiator = null;} else {//setValue时触发, 轮训观察者列表去更新for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());// 如果分发失效,直接跳出(非关键点)if (mDispatchInvalidated) {break;}}}...
}

-3. 通知观察者:

private void considerNotify(ObserverWrapper observer) {// check当前观察者持有的生命周期状态,即非onsStart-onPause时直接returnif (!observer.mActive) return;// 再次check观察者最新状态,即检查lifecycle对应的状态if (!observer.shouldBeActive()) {// 如果非活跃状态,通知观察者当前非活跃状态observer.activeStateChanged(false);return;}// 版本检测,如果当前观察者持有的版本>=当前的版本,即证明已经更新过了if (observer.mLastVersion >= mVersion) {return;}// 更新观察者当前的版本observer.mLastVersion = mVersion;// 执行数据通知observer.mObserver.onChanged((T) mData);
}

让我们总结一下上述的整体思路,当我们调用 setValue() 时,内部会对当前 LiveData 持有的版本号 version 进行自增,然后调用dispatchingValue() 去分发本次数据,然后会去轮训当前的观察者列表,然后判断观察者是否是活跃状态,即是否是 onStrat() - onPause() 之间,如果是并且当前观察者的版本号小于 LiveData 维护的版本号,由此证明当前观察者尚未通知过,从而触发通知。

LiveData用作事件通知时的隐患

关于这个问题,经常会被开发者提起,或者叫做数据倒灌,数据回推更为合适,但这些问题其实都是在 [不正确] 的背景下使用LiveData导致。

比如常见于共享的 LiveData ,使用 LiveData 作为事件通知,大家会发现为什么刚刚 observe() 的观察者,马上就响应了数据更新,并且还是旧数据,那这是为什么呢?

问题很简单,在上面我们已经说过了,当我们调用 observe() 添加数据观察者时,内部实际会被包装为 LifecycleBoundObserver,从而添加到 lifecycle 的生命周期观察者列表。而熟悉 lifecycle 的小伙伴,肯定明白了,当我们添加 lifecycle 生命周期观察者时,其观察者的生命周期状态会被相应的执行到当前的 lifecycle 状态,所以自然会调用 LifecycleBoundObserver 中的状态更新方法,从而触发了数据分发。而又因为这个观察者是新添加进去的,观察者持有的数据版本号是默认的,即-1,但是 LiveData 内部的数据版本号可不是啊!,所以自然触发了数据更新通知。


那这个问题属于LiveData的设计问题吗?

并不属于,相反这个设计,是非常符合生命周期组件的定义。

LiveData 往往是为了界面数据的状态同步而作准备,所以当添加观察者后,被再次通知,也不难理解。因为对于页面而言,这个观察者的确是新添加的,如果 LiveData 中存在数据,肯定需要第一时间同步到页面更新。

具体我们看一眼官方对其的描述:

image-20221204112710436


但既然 LiveData 这么安全好用,所以就会有开发者想着使用 LiveData 用于事件通知,此时它的设计在某种程度上就成了问题,虽然在官方的建议里,非常不建议直接这么用。

常见有如下几个解决思路:

  • 反射解决version

    在调用 observe() 方法里,反射相应的包装类 ObserverWrapper ,把其的版本号更改为 LiveData 现有的版本号;

  • SingleLiveEvent

    计算机科学领域一直流传着一句格言:任何问题都可以通过增加一个间接的中间层来解决。

    我们手动维护一个标记,并在 observe() 方法里,并再次包装观察者 Observer,这样当数据每次通知时,我们就可以拦截,从而用这个标记做判断,如果符合要求,则调用真实观察者的通知方法,并更新标记值。在我们每次 setValue() 时,再重置这个标记即可。

    具体可参见 Android-architecture-simple-SingleLiveEvent

  • 手动维护version

    这个方式可以说是对 SingleLiveEvent 的一个完善与补充。

    既然 version 我们无法避免,那么不如我们自己维护一个 version ,即继承 LiveData ,自己维护 version ,同时添加一个新的观察者包装类,内部持有一个版本号,对传递进来的观察者进行包装,并重写相应的 onChanged() 方法,内部会去判断观察者当前版本号,如果当前版持有的版本号<我们自己维护,则触发更新,并且更新观察者版本号;当我们每次 setValue() 时,并对 version 进行自增;在 observe() 时,再将当前持有的 version 赋值给我们的包装类,从而完成了整个套娃流程。

    具体可参见 KunMinx的UnPeek-LiveData-ProtectedUnPeekLiveData

  • 改用其他方式

    解决不了问题,就把提出问题的解决了:)

    人生苦短,我选 Flow(SharedFlow)。

LiveData与EventBus怎么选

先说结论,这两者并不冲突,主要因为其各自负责的事情不一样。

  • LiveData 用于处理[界面]的数据的状态,即常用于界面的数据状态同步;
  • EventBus 是用于事件总线,即是分发App中所有事件的一个中转站;

前者常用于于处理界面数据状态,并且遵循 Android 生命周期模式。而后者是作用于事件通知,即可以确保本次发出的事件一定会被可观察的接收者收到,虽然后者也支持 Sticky ,这点似乎和LiveData相似,但这两者在思想上本来就是大不相同。

对于开发者而言,因为两者使用起来的共性何其的相似,特别是作用于共享的页面时,开发者很容易会想到二选一问题,但事实上,仔细分析的话,就会发现:

  • 对于LiveData,这属于共享页面的数据状态同步;
  • 对于EventBus而言,这属于共享页面[事件]的通知;

两者完全不在一个领域,即EvenBus不会关心你的数据后续,它只关心事件通知了吗? 我要不要在你订阅时再告诉你这个事件?而LiveData会帮你持有这个数据状态,同时需要关心我必须在合适的生命周期内再告诉你,以及在你重新订阅时再次告诉你(如果存在数据)。

因为LiveData其本身的设计驱使,由此也很容易诞生LiveDataBus,在具体的功能上,其做的事情和 EventBus 相似,在某些特性上,甚至优于后者。具体可以参见美团的 LiveEventBus

LiveData和Flow怎么选

这里的 Flow 通常其实指 StateFlowSharedFlow

这个问题,也常被开发者提起。诸如,官方推荐在 MVVMMVI 中使用 Flow ,就是要革了 LiveData 的命?但其实,这两者也没什么直接冲突。

搞点小彩头,对于 非Kotlin 项目,你怎么用 Flow ? 😅

Flow: 那我走?

Rx: 我来我来。

先说说 Flow ,其指的是 Kotlin 中的数据流,虽然功能上不如Rx强大,但在 Kotlin 的背景下,其无疑是最佳搭档,毕竟有协程这个好兄弟在,因此,Android团队建议使用 Flow 替换 LiveData

再说说 LiveData ,其本设计简单轻巧,但功能不强,仅仅只能用于数据状态的同步。在多线程下 postValue ,甚至会丢掉某次的数据更改(其本身也不推荐用于通知事件的作用),不过也没什么问题,因为其本身就不是用来帮你做频繁数据处理的。

说的更详细点:

在2017年,Kotlin 的占有率可没那么高,所以 LiveData 作为 AAC 的重要组件自然承担了大部分责任。而在2022的今天,Kotlin 在Android开发中的占有率早已经超过63%(这只是2021年统计),随着日益增加的业务与架构挑战,LiveData 显然不能满足更多需求,架构也需要更先进的组件支持。

相比 LiveData ,Flow 就显得更加强大,不仅独立于具体的视图层,而且其可以单独的集成到业务模块。在功能上,支持数据的各种处理,搭配协程,是 Kotlin 背景下不可获取的利刃。相应的,在 Android上 面,Flow 也可以通过 asLiveData() 从而转为LiveData,由此兼容使用。

如果你的项目是 Java 编写,那 LiveData 仍然是你维护页面数据状态的最好搭档。

如果你的项目是 Kotlin 编写,那么 LiveData 依然可以满足你的需求。但如果你想做更多事,比如想在发送数据时顺便处理一下,从而更自动的完成数据状态的处理,Flow 也许更加符合你的要求,当然你也可以随时将 Flow 转为传统的 LiveData 使用(对外部调用者而言)。当然需要注意的是,Flow 并不能感知 Android 的生命周期,你可能需要再增加一些模版代码,但好在Android团队做了各种扩展方法,这个成本在今天也是非常小。

总结

本篇,我们通过问题解答的方式,由浅入深,回顾了 LiveData 的设计思想,以及其相关的一些疑问,从而从根源上解释了这些问题。

到了这里,我们也无需再拘泥于人云亦云的数据是否倒灌、设计是否合理、到底和其他组件该怎么选、项目中到底该用什么,这些问题我相信都不是问题。因为在不谈的背景的情况下,没有绝对的标准与统一的准则,那就更别提对与错。但至少对于 LiveData 而言,了解完本篇的你,我相信再也不会再有相关疑问。

参阅

  • 官方文档-LiveData概述
  • 如何优雅的使用LiveData实现一套EventBus(事件总线)
  • [Android]/architecture-samples/SingleLiveEvent
  • [KunMinx]/UnPeek-LiveData/ProtectedUnPeekLiveData

关于我

我是 Petterp ,一个 Android工程师 ,如果本文对你有所帮助,欢迎点赞支持,你的支持是我持续创作的最大鼓励!


http://chatgpt.dhexx.cn/article/mSZsxDF2.shtml

相关文章

LiveData的原理和使用

LiveData Livedata是什么&#xff1f;它的作用是什么&#xff1f;我们能用它来干什么&#xff1f; 首先&#xff0c;LiveData是一种可观察的数据存储类。这句话可以看成两个部分&#xff0c;一个是可观察的类&#xff0c;另一个是数据存储的类。 LiveData 是可以被观察的&am…

python电子书合集

Python电子书合集 小编最早接触的编程语言是C&#xff0c;决心走上编程之路的是JavaScript&#xff0c;后来入职靠的Java&#xff0c;后来发现还有一门短小精悍的语言叫——Python。 本来以为是一门新语言&#xff0c;结果才发现是自己out了&#xff0c;没想到这门语言比小编还…

《Python Cookbook》(中文第三版):电子书

《Python Cookbook&#xff08;第3版&#xff09;中文版》介绍了Python应用在各个领域中的一些使用技巧和方法&#xff0c;其主题涵盖了数据结构和算法&#xff0c;字符串和文本&#xff0c;数字、日期和时间&#xff0c;迭代器和生成器&#xff0c;文件和I/O&#xff0c;数据编…

python编程入门电子书-《Python编程 从入门到实践》高清电子书免费下载

今天给大家分享一本书 获取方式 公众号后台回复 Python基础 获取百度网盘下载链接 书籍简介 本书旨在让你成为优秀的程序员&#xff0c;具体地说&#xff0c;是优秀的Python程序员。通过阅读本书&#xff0c;你将迅速掌握编程概念&#xff0c;打下坚实的基础&#xff0c;并…

【小沐学Python】Python实现在线电子书制作(MkDocs + readthedocs + github + Markdown)

文章目录 1、简介2、安装3、创建新项目4、添加页面5、编辑导航页6、设置主题7、更改图标图标8、构建网站9、部署9.1 准备github项目9.2 注册登录Read the Docs9.3 导入github项目到 Read the Docs 10、Markdown语法10.1 横线10.2 标题10.3 段落10.4 文字高亮10.5 换行10.6 斜体…

超全的Python完全版电子书——从基础到爬虫、分析等高级应用,限时下载

python3.11即将于下半年发布&#xff0c;新的版本速度提升2倍&#xff0c;以弥补与其他编程语言在速度上的缺陷。可以预见Python语言在未来的应用范围会越来越广。 python学习方向建议&#xff1a; 如果你是本科及以下学历&#xff0c;建议你学习以下两个方向 1、爬虫。简单的…

【小沐学Python】Python实现在线电子书制作(Sphinx + readthedocs + github + Markdown)

文章目录 1、简介2、安装3、创建测试工程4、项目文件结构5、编译为本地文件6、编译为http服务7、更改样式主题8、支持markdown9、修改文档显示结构10、项目托管到github11、部署到ReadtheDocs结语 1、简介 Sphinx 是一个 文档生成器 &#xff0c;您也可以把它看成一种工具&…

python编程入门电子书-Python3零基础教材电子书合集

Python3零基础教材电子书合集&#xff0c;传送门&#xff1a;https://www.52pojie.cn/thread-676318-1-1.html 一、《Python编程从入门到实践》 链接&#xff1a;https://pan.baidu.com/s/1o9wJq0y 密码&#xff1a;12od 这书楼主现在也在看&#xff0c;讲的很细&#xff0c;…

学习Python必看的经典书籍(附电子书)

哈喽&#xff0c;我是牙儿 今天给大家推荐几本经典的Python书籍 一起来看看都有哪些吧~ 1 《Python学习手册&#xff08;第4版&#xff09;》 这本书全面、深入地介绍了 Python 语言&#xff0c;不管你是编程新手还是 Python 初学者&#xff0c;它将帮助你快速实现使用 Pyt…

超全的Python完全版电子书.pdf !从基础到爬虫、分析等高级应用,限时下载

python3.11即将于下半年发布&#xff0c;新的版本速度提升2倍&#xff0c;以弥补与其他编程语言在速度上的缺陷。可以预见Python语言在未来的应用范围会越来越广。 python学习方向建议&#xff1a; 如果你是本科及以下学历&#xff0c;建议你学习以下两个方向 1、爬虫。简单…

python入门经典电子书-推荐6本学习Python的免费电子书

便宜并不是没好货&#xff0c;这里的一些书籍已经被很多大学作为课本来使用&#xff0c;比如麻省理工的计算机科学与编程入门课程&#xff0c;加利福尼亚大学的编程思想课程都用到了下面的某(几)本书籍。 简明 简明 Python 教程是Swaroop C.H. 教授为Python初学者写的一本书。…

Python 开源电子书资源

转载自公众号&#xff1a;Mocun6 昨天给大伙儿送了书&#xff0c;留言区的篇幅占了整篇文章的一半&#xff0c;看来大家都想好&#xff08;把&#xff09;好&#xff08;我&#xff09;学&#xff08;掏&#xff09;习&#xff08;空&#xff09;。今天不送纸质书了&#xff0c…

100多本python书,免费电子版下载

推荐&#xff1a; 1、Coffee Break Python Slicing: 24 Workouts to Master Slicing in Python, Once and for All 切片&#xff08;Slicing&#xff09;是 Python 里非常有用的一个功能&#xff0c;属于 Python 开发人员最基本的技能之一。 如果你是初学者而且想了解 Slicin…

学习 Python 必看的书单(附电子书链接)

本文为你分享入门Python的必读书单。 学 Python 看什么书&#xff1f; 这是刚接触 Python 的朋友最疑惑的问题。 今天就结合自己入门时的学习历程和大家来聊一聊如何入门 Python&#xff0c;为了更有说服性一些&#xff0c;这里我把入门时看过的一些大佬推荐的书单进行了汇总…

python入门电子版-Python3零基础教材电子书合集

Python3零基础教材电子书合集,传送门:https://www.52pojie.cn/thread-676318-1-1.html 一、《Python编程从入门到实践》 链接:https://pan.baidu.com/s/1o9wJq0y 密码:12od 这书楼主现在也在看,讲的很细,建议大家零基础的从这书开始最好。个人觉得比《简明python教程》…

Python爬虫获取电子书资源实战

最近在学习Python&#xff0c;相对java来说python简单易学、语法简单&#xff0c;工具丰富&#xff0c;开箱即用&#xff0c;适用面广做全栈开发那是极好的&#xff0c;对于小型应用的开发&#xff0c;虽然运行效率慢点&#xff0c;但开发效率极高。大大提高了咱们的生产力。为…

全套Python零基础学习资料,电子书整理好了,想要进行技术提升,转行的自取!

今天分享Python入门级宝典 所有资料都是专业大佬总结整理出来的 Python的知识体系&#xff0c;从0开始学习Python看这一篇就够了! 《Python入门思维导图》 《看漫画学Python电子版》 《Python学习路线图》 《100道Python练习题》 《70个Python项目》 今天把这些分享给真…

【MATLAB统计分析与应用100例】案例015:matlab读取Excel数据,进行值聚类分析

1. 聚类分析轮廓图 2. matlab完整代码 %*****计算例9.1的距离矩阵 x = [1, 2, 6, 8, 11];

Python使用K-means聚类分析

Python使用K-means聚类分析 文章目录 Python使用K-means聚类分析介绍1.集群标签作为特征 一、k-均值聚类二、示例 - 加州住房2.KMeans 总结 介绍 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 本文将使用所谓的无监督学习算法。 无监督算法不使用目标&…

机器学习算法精讲20篇(一)-k-means聚类算法应用案例(附示例代码)

前言 k-means算法是非监督聚类最常用的一种方法,因其算法简单和很好的适用于大样本数据,广泛应用于不同领域,本文详细总结了k-means聚类算法原理 。 以下是我为大家准备的几个精品专栏,喜欢的小伙伴可自行订阅,你的支持就是我不断更新的动力哟! MATLAB-30天带你从入门…