Jetpack系列 -- LiveData源码原理解析(解决黏性问题)

一、LiveData是什么?

注意:一般情况下,LiveData要配合ViewModel一起使用的,但是今天是单独使用LiveData,作为学习的话,我们可以只关注LiveData了。

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

二、各种LiveData各种系列使用

1.使用方式一:

MyLiveData.kt 

object MyLiveData {// 这里为info1的MutableLiveData 懒加载初始化(懒加载:用到时才加载)val info1 : MutableLiveData<String> by lazy { MutableLiveData() }init {info1.value = "default"}
}

MainActivity.kt

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val textView: TextView = findViewById(R.id.tv_textview)activity_main.xml// 1.观察者 眼睛 环节MyLiveData.info1.observe(this, {textView.text = it})// 2.触发数据 环节MyLiveData.info1.value = "default"thread {Thread.sleep(3000)MyLiveData.info1.postValue("三秒钟后,修改了哦")}thread {Thread.sleep(6000)MyLiveData.info1.postValue("六秒钟后,修改了哦")}// -------------- 下面是 触发修改数据 的写法// lambda 写法如下 observe:MyLiveData.info1.observe(this, {})// lambda 写法如下 observeForever:MyLiveData.info1.observeForever({})// 详细写法如下 observe:MyLiveData.info1.observe(this, object : Observer<String> {override fun onChanged(t: String?) {}})// 详细写法如下 observeForever:MyLiveData.info1.observeForever(object : Observer<String> {override fun onChanged(t: String?) {}})}
}

2.使用方式二:

MainActivity2.kt

class MainActivity2 : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main2)val button = findViewById<Button>(R.id.button)button.setOnClickListener {startService(Intent(this, MyService::class.java))Toast.makeText(MainActivity2@this, "推送服务器启动成功",Toast.LENGTH_SHORT).show()}MyLiveData.data1.observe(this, {Log.d("server", "界面可见,说明用户在查看微信列表界面啦,更新消息列表UI界面:${it}")Toast.makeText(this, "更新消息列表UI界面成功:${it}",Toast.LENGTH_SHORT).show()})}
}

MyLiveData.kt

object MyLiveData {// 这里为info1的MutableLiveData 懒加载初始化(懒加载:用到时才加载)val data1: MutableLiveData<String> by lazy { MutableLiveData() }// 注意:这里会奔溃 因为是在 thread { 首次实例化MyLiveData对象的,而下面确实setValue就会奔溃/*init {data1.value = "default"}*/
}

MyService.kt

class MyService : Service() {override fun onBind(intent: Intent): IBinder? = nulloverride fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int{thread {for (x in 1..100000) {Log.d("server", "服务器给推你推送消息啦(叮咚声响),消息内容是:${x}")MyLiveData.data1.postValue("服务器给推你推送消息啦,消息内容是:${x}")Thread.sleep(5000) // 2秒钟推一次}}return super.onStartCommand(intent, flags, startId)}
}

3.使用方式三(黏性):

MainActivity3.kt

class MainActivity3 : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main3)val button = findViewById<Button>(R.id.button)button.setOnClickListener {MyLiveData.value1.value = "我就是我,不一样的烟火"startActivity(Intent(this, MainActivity4::class.java))}}
}

MainActivity4.kt

class MainActivity4 : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main4)MyLiveData.value1.observe(this, {Toast.makeText(this, "观察者数据变化:$it", Toast.LENGTH_SHORT).show()})}
}

MyLiveData.kt

object MyLiveData {// 这里为info1的MutableLiveData 懒加载初始化(懒加载:用到时才加载)val value1 : MutableLiveData<String> by lazy { MutableLiveData() }
}

三、LiveData的源码原理解析

1. MutableLiveData 

继承了LiveData是一个可变的LiveData
是一个被观察者,是一个数据持有者
提供了 setValue 和 postValue方法,其中postValue可以在子线程调用
postValue方法,我们下面会具体分析 

public class MutableLiveData<T> extends LiveData<T> {/*** Creates a MutableLiveData initialized with the given {@code value}.** @param value initial value*/public MutableLiveData(T value) {super(value);}/*** Creates a MutableLiveData with no value assigned to it.*/public MutableLiveData() {super();}@Overridepublic void postValue(T value) {super.postValue(value);}@Overridepublic void setValue(T value) {super.setValue(value);}
}

2.MutableLiveData的observe方法参数的this

此接口是宿主生命周期的代表

public interface LifecycleOwner{@NonNull Lifecycle getLifecycle();
}

3.MutableLiveData的observe方法参数的Observer

Observer是一个观察者
Observer中有一个回调方法,在 LiveData 数据改变时会回调此方法

public interface Observer<T> {/*** 当数据改变时调用。* @param t 新数据*/void onChanged(T t);
}

4.源码分析

首先我们上面示例中的 LiveData.observe()方法开始。

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val textView: TextView = findViewById(R.id.tv_textview)MyLiveData.info1.observe(this, {textView.text = it})thread {Thread.sleep(3000)MyLiveData.info1.postValue("三秒钟后,修改了哦")}thread {Thread.sleep(6000)MyLiveData.info1.postValue("六秒钟后,修改了哦")}}
}

我们点进observe方法中去它的源码

5.LiveData源码

1)在LiveData的observe方法中

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T>
observer) {assertMainThread("observe");
// 一:首先会通过LifecycleOwner获取Lifecycle对象然后获取Lifecycle 的State,如果是DESTROYED直接 return 了。忽略这次订阅if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignorereturn;}
// 二:把LifecycleOwner和Observer包装成LifecycleBoundObserver对象,至于为什么包装成这个对象,我们下面具体讲,而且这个是重点。LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner,observer);
// 三:把观察者存到 Map 中ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
// 四:之前添加过LifecycleBoundObserver,并且LifecycleOwner不是同一个,就抛异常if (existing != null && !existing.isAttachedTo(owner)) {throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}
// 五:通过Lifecycle和添加 LifecycleBoundObserver观察者,形成订阅关系owner.getLifecycle().addObserver(wrapper);
}

到现在,我们知道了LiveData的observe方法中会判断 Lifecycle 的生命周期,
会把LifecycleOwner和Observer包装成LifecycleBoundObserver对象,
然后 Lifecycle().addObserver(wrapper) 
Lifecycle 这个被观察者会在合适的时机 通知 观察者的回调方法。

2)什么时候通知,怎么通知的呢?这个具体流程是什么,继续看下面代码

thread {Thread.sleep(3000)MyLiveData.info1.postValue("三秒钟后,修改了哦")
}
thread {Thread.sleep(6000)MyLiveData.info1.postValue("六秒钟后,修改了哦")
}

在点击按钮的时候 LiveData会调用postValue ----> setValue方法,来更新最新的值,这时候我们
的观察者Observer就会收到回调,来更新 TextView。
所以接下来我们先看下 LiveData的setValue方法做了什么,LiveData还有一个postValue方法,我
们也一并分析一下。

6.LiveData的setValue与postValue

1)setValue

// LiveData.java
@MainThread
protected void setValue(T value){assertMainThread("setValue");mVersion++;mData=value;dispatchingValue(null); // 注意:这里的分发value值
}

调用了dispatchingValue方法,继续跟代码

void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;if (initiator != null) { // 如果传递进来的 非null,就进此ifconsiderNotify(initiator);不管如何判断,都是调用了considerNotify() 方法initiator = null;} else { // 如果传递进来的 null,就进此iffor (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>>iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());不管如何判断,都是调用了considerNotify() 方法if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;}

不管如何判断,都是调用了considerNotify()方法

private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;//noinspection unchecked// 最终调用了observer.mObserver.onChanged((T) mData)方法,// 这个observer.mObserver就是我们的 Observer接口,然后调用它的onChanged方法。observer.mObserver.onChanged((T) mData);// 恭喜恭喜 :到现在整个被观察者数据更新通知观察者这个流程就通了。}

2)postValue

子线程发送消息通知更新 UI,嗯?Handler 的味道

// LiveData.javaprotected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}// 利用Handler切换到主线程去执行ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);}

可以看到一行关键代码
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
点 postToMainThread 方法进去看下

// ArchTaskExecutor.javaprivateTaskExecutor mDelegate;@Overridepublic void postToMainThread(Runnable runnable) {mDelegate.postToMainThread(runnable);}

看到 mDelegate 是 TaskExecutor对象,现在目标是看下 mDelegate 的具体实例对象是谁

// ArchTaskExecutor.java
private ArchTaskExecutor(){mDefaultTaskExecutor=new DefaultTaskExecutor();mDelegate=mDefaultTaskExecutor; // 目前的重点是看下DefaultTaskExecutor是个啥
}

然后看它的postToMainThread方法

// DefaultTaskExecutor.javaprivatevolatileHandler mMainHandler;@Overridepublic void postToMainThread(Runnable runnable) {if (mMainHandler == null) {synchronized (mLock) {if (mMainHandler == null) {// 实例了一个 Handler 对象,注意构造参数 Looper.getMainLooper() 是主线的 Looper。// 那么就可做到线程切换了。mMainHandler = new Handler(Looper.getMainLooper()); // 注意:这里主线程已经切换过来了}}}//noinspection ConstantConditions// 调用post 方法 注意:重点看runnable,这里面就是真正主线程的执行功能了mMainHandler.post(runnable); }

下面看下这个 Runnable
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
这里面的方法参数是mPostValueRunnable是个 Runnable,我们看下代码

// LiveData.javaprivate final Runnable mPostValueRunnable = new Runnable() {@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}//noinspection unchecked// 注意:postValue方法其实最终调用也是setValue方法,然后和setValue方法走的流程就是一样的了,// 这个上面已经分析过了setValue((T) newValue);}};

7.LifecycleBoundObserver

我们要详细看一下LifecycleBoundObserver类了,它包装了LifecycleOwner和Observer,这就是
接下来的重点内容了。

1)observe

    @MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T>observer) {assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {// ignorereturn;}// 用LifecycleBoundObserver对LifecycleOwner 和 Observer进行了包装LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner,observer);ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);if (existing != null && !existing.isAttachedTo(owner)) {throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}owner.getLifecycle().addObserver(wrapper);}

来看下LifecycleBoundObserver类,它是LiveData的内部类

class LifecycleBoundObserver extends ObserverWrapper implementsGenericLifecycleObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T>observer) {super(observer);mOwner = owner;}
}

两个参数,一个 owner被成员变量mOwner存储,observer参数被ObserverWrapper的 mObserver存储。
LifecycleEventObserver是LifecycleObserver的子接口里面有一个onStateChanged方法,这个方法会在 Activity、Fragment 生命周期回调时调用(这个和Lifecycle有关)
ObserverWrapper 是Observer包装类

2)ObserverWrapper

活跃状态指的是 Activity、Fragment 等生命周期处于活跃状态

private abstract class ObserverWrapper {final Observer<? super T> mObserver;boolean mActive;int mLastVersion = START_VERSION;// 获取了我们的 Observer 对象,存储在 成员变量mObserver身上ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}// 抽象方法,当前是否是活跃的状态abstract boolean shouldBeActive();boolean isAttachedTo(LifecycleOwner owner) {return false;}void detachObserver() {}void activeStateChanged(boolean newActive) {if (newActive == mActive) {return;}// immediately set active state, so we'd never dispatch anything to inactive// ownermActive = newActive;boolean wasInactive = LiveData.this.mActiveCount == 0;LiveData.this.mActiveCount += mActive ? 1 : -1;if (wasInactive && mActive) {// 可以继承 LiveData 来达到扩展 LiveData 的目标,并且是在活跃的状态调用onActive();}if (LiveData.this.mActiveCount == 0 && !mActive) {// 可以继承 LiveData 来达到扩展 LiveData 的目标,并且是在非活跃的状态调用onInactive();}if (mActive) {// 活跃状态,发送最新的值,来达到通知的作用,dispatchingValue(this) 方法咋这么眼熟,// 对之前在 LiveData 调用 setValue 方法时,最终也会调用到此方法。// 那ObserverWrapper类中的dispatchingValue这个方法是在activeStateChanged方法中调用,// 那activeStateChanged啥时候调用呢?// 我来看下ObserverWrapper的子类也就是最重要的那个类LifecycleBoundObserver,// 现在看它的完整代码(看下面代码 LifecycleBoundObserver完整代码)dispatchingValue(this);}}
}

LifecycleBoundObserver完整代码

class LifecycleBoundObserver extends ObserverWrapper implementsGenericLifecycleObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T>observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {// 判断当前的 Lifecycle 的生命周期是否是活跃状态,会在回调观察则 Observer 的时候进行判断,// 只有在活跃状态,才会回调观察者Observer的onChanged方法。return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {// onStateChanged每次 Activity、Fragment的生命周期回调的时候,都会走这个方法。// 获取Lifecycle对象然后获取Lifecycle 的State如果为DESTROYED则移除观察者,// 在 Activity、Fragment的生命周期走到 onDestroy 的时候,就会取消订阅,避免内存泄漏。if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}// 调用父类ObserverWrapper 的activeStateChanged方法,// 层层调用到观察者Observer的onChanged方法。(自己看下源码一目了然)activeStateChanged(shouldBeActive());}@Overrideboolean isAttachedTo(LifecycleOwner owner) {return mOwner == owner;}@Overridevoid detachObserver() {// 移除观察者Observer,解除订阅关系。mOwner.getLifecycle().removeObserver(this);}
}

四、自定义JLiveDataBus解决黏性问题

JLiveDataBus.kt

object JLiveDataBus {private const val TAG = "JLiveDataBus"private val busMap: MutableMap<String, BusMutableLiveData<Any>> by lazy { HashMap<String, BusMutableLiveData<Any>>() }@Synchronizedfun <T> with(key: String, type: Class<T>, isStick: Boolean = true): BusMutableLiveData<T> {Log.d(TAG, "with isStick $isStick")if (!busMap.containsKey(key)) {busMap[key] = BusMutableLiveData(isStick)} else {(busMap[key] as BusMutableLiveData<T>).isStick = isStick}return busMap[key] as BusMutableLiveData<T>}class BusMutableLiveData<T> private constructor() : MutableLiveData<T>() {// 启用粘性事件var isStick: Boolean = false// 次构造函数,必须调用主构造函数constructor(isStick: Boolean) : this() {Log.d(TAG, "constructor isStick $isStick")this.isStick = isStick}// 重写 增加hookoverride fun observe(owner: LifecycleOwner, observer: Observer<in T>) {super.observe(owner, observer)if (!isStick) {// 不启用粘性事件hook(observer = observer)Log.d(TAG, "不启动粘性事件")} else {Log.d(TAG, "启动粘性事件")}}private fun hook(observer: Observer<in T>) {// TODO 1.利用反射得到mLastVersion// 获取到LiveData类中的mObservers对象val liveDataClass = LiveData::class.javaval mObserversField: Field = liveDataClass.getDeclaredField("mObservers")// 设置权限,私有修饰也可以访问mObserversField.isAccessible = true// 获取到mObservers这个成员变量的对象val mObservers: Any = mObserversField.get(this)// 获取到mObservers的class对象val mObserversClass: Class<*> = mObservers.javaClass// 获取到mObservers对象的get方法val get: Method = mObserversClass.getDeclaredMethod("get", Any::class.java)get.isAccessible = true// 执行get方法val invokeEntry: Any = get.invoke(mObservers, observer)// 获取到entry中的valuevar observerWraper: Any? = nullif (invokeEntry != null && invokeEntry is Map.Entry<*, *>) {observerWraper = invokeEntry.value}if (observerWraper == null) {throw NullPointerException("observerWraper is null.")}// 获取到observerWraper的类对象val supperClass: Class<*> = observerWraper.javaClass.superclass!!val mLastVersion: Field = supperClass.getDeclaredField("mLastVersion")mLastVersion.isAccessible = true// TODO 2.得到mVersionval mVersion: Field = liveDataClass.getDeclaredField("mVersion")mVersion.isAccessible = true// TODO 3.mLastVersion = mVersionval mVersionValue: Any = mVersion.get(this)mLastVersion.set(observerWraper, mVersionValue)}}}

BusActivity.kt

class BusActivity : AppCompatActivity() {private lateinit var binding: ActivityBusBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityBusBinding.inflate(layoutInflater)setContentView(binding.root)JLiveDataBus.with("test1", String::class.java, false).observe(this) {binding.busTv.text = it}thread {Thread.sleep(2000)JLiveDataBus.with("test1", String::class.java).postValue("新数据")}}
}

startActivity

JLiveDataBus.with("test1", String::class.java).value = "老数据"textView.setOnClickListener {startActivity(Intent(activity, BusActivity::class.java))}

由于我们在BusActivity使用的是不启用粘性,JLiveDataBus.with("test1", String::class.java, false).observe(this),所以当BusActivity启动时不会调用binding.busTv.text = "老数据",延迟2S后,正常接收到启动后的 新数据。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vvic API接口接入说明:解锁新一代数据可视化的无限可能

随着大数据时代的来临&#xff0c;数据可视化已成为我们理解、分析和呈现复杂数据的重要手段。在这个领域中&#xff0c;vvic以其独特的优势&#xff0c;引领着数据可视化的发展潮流。其强大的API接口&#xff0c;更是为开发者提供了无限可能&#xff0c;让数据可视化变得更为简…

数据库_之常用API的使用

数据库_之电商API MySQL C API 使用&#xff08;基本函数&#xff09; Mysql C API函数详解 MySQL的常用API 一个常用的程序调用MySQL数据库的时候通常都会调用以下API,下面来逐个分析. mysql_init() //函数原型 MYSQL *STDCALL mysql_init(MYSQL *mysql);这个API主要是用来分…

嵌入式C 语言中的三块技术难点

​ C 语言在嵌入式学习中是必备的知识&#xff0c;甚至大部分操作系统都要围绕 C 语言进行&#xff0c;而其中有三块技术难点&#xff0c;几乎是公认级别的“难啃的硬骨头”。 今天就来带你将这三块硬骨头细细拆解开来&#xff0c;一定让你看明白了。 0x01 指针 指针是公认…

旋转角度对迭代次数的影响

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由5张二值化的图片组成&#xff0c;让A中有3个1&#xff0c;B中全是0&#xff0c;统计迭代次数并排序。 在3*5的空间内分布3个点有19种可能&#xff0c;但不同的分布只有6种 差值就诶够 …

直方图均衡化,画出均衡化后的直方图(数字图像处理大题复习 P2)

文章目录 1. 频率2. 累计直方图3. 取整4. 得到对应关系5. 累加对应关系&#xff0c;得出结果6. 画出均衡化后的直方图 1. 频率 一般题目会给出各个灰度级的概率分布&#xff0c;如果没有给概率&#xff0c;而是给了频率&#xff0c;比如&#xff1a; 在 8x8 的图像中&#xf…

IDEA(2023)解决运行乱码问题

&#x1f607;作者介绍&#xff1a;一个有梦想、有理想、有目标的&#xff0c;且渴望能够学有所成的追梦人。 &#x1f386;学习格言&#xff1a;不读书的人,思想就会停止。——狄德罗 ⛪️个人主页&#xff1a;进入博主主页 &#x1f5fc;专栏系列&#xff1a;无 &#x1f33c…

【Linux网络编程】Socket-UDP实例

这份代码利用下面所有知识编写了一个简易聊天室&#xff08;基于Linux操作系统&#xff09;。虽然字数挺多其实并不复杂&#xff0c;这里如果能够看完或许会对你的知识进行一下串联&#xff0c;这篇文章比较杂并且网络编程这块知识需要用到系统编程的知识&#xff0c;希望能帮助…

Java入坑之语法糖

一、for和for-each 1.1for和for-each概念 for 循环是一种常用的循环结构&#xff0c;它可以通过一个变量&#xff08;通常是 i&#xff09;来控制循环的次数和范围。for 循环的语法格式如下&#xff1a; for (初始化; 布尔表达式; 更新) {//代码语句 }for-each 循环是 Java …

C语言指针详解(4)———找工作必看指针笔试题汇总

指针对于编程工作的重要性 C语言指针在找工作中具有重要性。以下是几个原因&#xff1a; 1.高效的内存管理&#xff1a;C语言指针可以帮助程序员高效地管理内存&#xff0c;包括动态内存分配和释放&#xff0c;以及数据的访问和操作。这对于开发性能优化的应用程序非常重要&am…

操作系统学习笔记---计算机系统概述

目录 概念 功能和目标 特征 并发 共享&#xff08;资源共享&#xff09; 虚拟 异步 发展与分类 手工操作阶段&#xff08;无OS&#xff09; 批处理阶段 单道批处理系统 多道批处理系统 分时操作系统 实时操作系统 网络操作系统 分布式计算机系统 个人计算机操…

Ubuntu安装Android Studio

一、Android Studio安装 官方教程&#xff1a;安装 Android Studio | Android Developers 1、下载&#xff1a;Download Android Studio & App Tools - Android Developers&#xff0c;选择linux版本 2、 提取/解压 将下载的安装包提取出来 3、 64位ubuntu系统&#…

Bash脚本学习:AWK, SED

1. AWK AWK 是一种编程语言&#xff0c;设计用于处理文件或数据流中基于文本的数据&#xff0c;或者使用 shell 管道。 可以将 awk 与 shell 脚本结合使用或直接在 shell 提示符下使用。 以上展示使用AWK分别打印第一个位置变量和第二个位置变量。 建立一个文档 csvtest.cs…

无涯教程-JavaScript - MATCH函数

描述 MATCH函数在单元格范围内搜索指定的项目,然后返回该项目在该范围内的相对位置。 当您需要某个项目在范围中的位置而不是项目本身时,请使用MATCH而不是LOOKUP函数之一。如。您可以使用MATCH函数为INDEX函数的row_num参数提供一个值。 语法 MATCH (lookup_value, lookup…

【数据结构】红黑树的删除(抽丝剥茧,带你理清每一种情况)

文章目录 前言正文1.所删除的结点为红色1.1delnode的左右都为空1.2delnode的左为空&#xff0c;且右不为空1.3delnode的左不为空&#xff0c;右为空1.4delnode的左不为空&#xff0c;且右不为空 2.所删除的结点为黑色2.1 调整后所在树每条路径黑色结点的个数不发生变化2.1 左结…

【问题处理】GIT合并解决冲突后,导致其他人代码遗失的排查

GIT合并解决冲突后&#xff0c;导致其他人代码遗失的排查 项目场景问题描述分析与处理&#xff1a;1. 警告分析2. 文件分析3. 问题关键4. 验证 解决策略总结 &#x1f4d5;作者简介&#xff1a;战斧&#xff0c;从事金融IT行业&#xff0c;有着多年一线开发、架构经验&#xff…

ChatGPT追祖寻宗:GPT-2论文要点解读

论文地址&#xff1a;Language Models are Unsupervised Multitask Learners 上篇&#xff1a;GPT-1论文要点解读 在上篇&#xff1a;GPT-1论文要点解读中我们介绍了GPT1论文中的相关要点内容&#xff0c;其实自GPT模型诞生以来&#xff0c;其核心模型架构基本没有太大的改变&a…

华为云云服务器云耀L实例评测 | 在华为云耀L实例上搭建电商店铺管理系统:一次场景体验

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

java写一个用于生成雪花id的工具类

我们创建一个类 叫 SnowflakeIdGenerator 作为生成雪花id的工具类 然后 编写代码如下 public class SnowflakeIdGenerator {private static final long START_TIMESTAMP 1609459200000L; // 设置起始时间戳&#xff0c;可以根据需要进行调整private static final long WORKER…

HBase 记录

HBase 管理命令 hbase hbck -details TABLE_NAME hbase hbck -repair TABLE_NAMEHBase概览 Master、RegionServer作用 RegionServer与Region关系 数据定位原理 https://blogs.apache.org/hbase/entry/hbase_who_needs_a_master RegionServer HBase Essentials.pdf (P25)…

四种常用的自动化测试框架

一直想仔细研究框架&#xff0c;写个流水账似的测试程序不难&#xff0c;写个低维护成本的测试框架就很难了&#xff0c;所以研究多种测试框架还是很有必要的&#xff0c;知道孰优孰劣&#xff0c;才能在开始编写框架的时候打好基础&#xff0c;今天读到了KiKi Zhao的翻译文章&…