EventBus 开源库学习(二)

整体流程阅读

EventBus在使用的时候基本分为以下几步:
1、注册订阅者

EventBus.getDefault().register(this);

2、订阅者解注册,否者会导致内存泄漏

EventBus.getDefault().unregister(this);

3、在订阅者中编写注解为Subscribe的事件处理函数

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true, priority = 1)
public void onMsgEventReceived(MsgEvent event) {Toast.makeText(this, event.getMsg(), Toast.LENGTH_LONG).show();
}

4、事件发送

EventBus.getDefault().post("msg1 - coming!!!");

我们先按使用的流程大体看下源码逻辑,源码版本3.3.1:

注册源码逻辑

    public static EventBus getDefault() {EventBus instance = defaultInstance;if (instance == null) {synchronized (EventBus.class) {instance = EventBus.defaultInstance;if (instance == null) {instance = EventBus.defaultInstance = new EventBus();}}}return instance;}

EventBus 使用了双重校验锁的单例设计模式,保证用到的对象是唯一的,首次使用对象为空的时候通过下面构造创建一个。

    public EventBus() {this(DEFAULT_BUILDER);}

DEFAULT_BUILDER是一个final常量,在加载的时候就进行初始化,赋一个EventBusBuilder对象如下面代码所示。

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

EventBusBuilderEventBus的建造类,里面参数在加载的时候进行了初始化。

public class EventBusBuilder {private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();boolean logSubscriberExceptions = true;boolean logNoSubscriberMessages = true;boolean sendSubscriberExceptionEvent = true;boolean sendNoSubscriberEvent = true;boolean throwSubscriberException;boolean eventInheritance = true;boolean ignoreGeneratedIndex;boolean strictMethodVerification;ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;List<Class<?>> skipMethodVerificationForClasses;List<SubscriberInfoIndex> subscriberInfoIndexes;Logger logger;MainThreadSupport mainThreadSupport;...
}

如果有需要的话,我们也可以通过配置EventBusBuilder来更改EventBus的属性,在EventBus中有一个静态方法直接返回一直新的EventBusBuilder对象,设置完参数后调用build()来以新的配置来新建一个EventBus对象。

#EventBuspublic static EventBusBuilder builder() {return new EventBusBuilder();}#EventBusBuilder/** Builds an EventBus based on the current configuration. */public EventBus build() {return new EventBus(this);}

然后通过下面的调用来设置:

EventBus.builder().eventInheritance(false).logSubscriberExceptions(false).build().register(this);

拿到EventBus对象以后,我们可以调用其register方法进行订阅者注册了。

    public void register(Object subscriber) {Class<?> subscriberClass = subscriber.getClass();List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);}}}

首先获取订阅者类subscriber,然后通过findSubscriberMethods方法获取该类中以@Subscribe注解的函数,由于一个类中可能监听多个事件,因此获取的方法可能是多个,所有的方法赋值到一个List列表中,然后遍历这个列表进行注册。

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {Class<?> eventType = subscriberMethod.eventType;Subscription newSubscription = new Subscription(subscriber, subscriberMethod);CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions == null) {subscriptions = new CopyOnWriteArrayList<>();subscriptionsByEventType.put(eventType, subscriptions);} else {if (subscriptions.contains(newSubscription)) {throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType);}}int size = subscriptions.size();for (int i = 0; i <= size; i++) {if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {subscriptions.add(i, newSubscription);break;}}List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {subscribedEvents = new ArrayList<>();typesBySubscriber.put(subscriber, subscribedEvents);}subscribedEvents.add(eventType);....}

上面就是注册最主要的代码,步骤解析如下:

  • 根据注解方法获取监听事件的类型eventType,并对订阅者subscriber和订阅函数subscriberMethod建立一个订阅关系对象newSubscription

  • 根据eventTypesubscriptionsByEventType(HashMap)中获取所有该事件类型的订阅关系列表subscriptions

  • 如果订阅关系列表subscriptions为空就新建一个,然后以keyeventTypevaluenewSubscription添加进去;

  • 如果订阅关系列表subscriptions不为空,判断是否存在newSubscription,如果存在,说明之前已经注册过,抛出异常;

  • 如果订阅关系列表subscriptions不为空,列表页没有订阅关系newSubscription,我们遍历添加进去,这里通过订阅函数的priority 来决定存放在列表中的位置,从这里也能看出priority 越大,存放位置越靠前,和上一篇中分析的:值越大,优先级越高,越优先接收到事件。我们可以猜出是通过遍历这个表来进行事件发送的,在表里的位置越靠前,越先收到事件。

  • 然后通过订阅者subscriber在另一个HashMap - typesBySubscriber中获取该订阅者订阅的所有事件,因为一个订阅者可以订阅多个不同的事件,因此获取的是个List列表subscribedEvents

  • 首先判断subscribedEvents列表是不是空的,如果是空说明以前没有订阅过任何事件,新建一个List,然后以keysubscribervaluesubscribedEvents添加到typesBySubscriber

  • 然后在新建的subscribedEvents中添加我们订阅的事件eventType

这里出现了两个HashMap:subscriptionsByEventTypetypesBySubscriber,通过上面的解析可以知道:
subscriptionsByEventType:一个事件可能有多个订阅者,key是事件,value是所有订阅该事件的所有的订阅者;
typesBySubscriber:一个订阅者可能订阅多个事件,key是订阅者,value是订阅者订阅的所有事件;
看命名就知道By后面的是key值,前面的是value值,一个好的命名就是这样吧。

解注册源码逻辑

在使用EventBus时,注册完后我习惯接着去写解注册的代码,怕后面会忘,因此按照写代码习惯在讲下解注册的源码实现。

    /** Unregisters the given subscriber from all event classes. */public synchronized void unregister(Object subscriber) {List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);if (subscribedTypes != null) {for (Class<?> eventType : subscribedTypes) {unsubscribeByEventType(subscriber, eventType);}typesBySubscriber.remove(subscriber);} else {logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());}}

如果是我们自己写这部分代码,根据前面注册的过程,我们肯定是要把添加到两个HashMap中的值移除掉。好了,看上面源码,首先在typesBySubscriber中获取该订阅者订阅的事件列表subscribedTypes

如果为空说明该订阅者没有订阅任何事件,无任何操作;如果不为空,遍历所有事件调用unsubscribeByEventType方法进行解注册,然后在typesBySubscriber中移除这个订阅者subscriber

    /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);if (subscriptions != null) {int size = subscriptions.size();for (int i = 0; i < size; i++) {Subscription subscription = subscriptions.get(i);if (subscription.subscriber == subscriber) {subscription.active = false;subscriptions.remove(i);i--;size--;}}}}

在上一个方法中,已经把typesBySubscriber这个订阅者移除了,那么unsubscribeByEventType函数就是遍历事件所有的订阅者,然后把解注册的订阅者在subscriptionsByEventType中给移除掉。上面的逻辑就是干这个事情。不过一边遍历一边移除是有风险的,这个大家要注意,index需要也跟着进行减少。

总结下:注册和解注册就是往两个HashMap添加和移除数据的过程。

事件发送post源码逻辑

    /** Posts the given event to the event bus. */public void post(Object event) {PostingThreadState postingState = currentPostingThreadState.get();List<Object> eventQueue = postingState.eventQueue;eventQueue.add(event);····}

上面是部分post代码,第一行先解释这个变量currentPostingThreadState及内部类PostingThreadState

    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {@Overrideprotected PostingThreadState initialValue() {return new PostingThreadState();}};/** For ThreadLocal, much faster to set (and get multiple values). */final static class PostingThreadState {final List<Object> eventQueue = new ArrayList<>();boolean isPosting;boolean isMainThread;Subscription subscription;Object event;boolean canceled;}

currentPostingThreadState是一个ThreadLocal,相当于是线程的私有财产,里面维护的变量只属于当前线程,线程间不会共享。维护的变量是一个自定义类PostingThreadState,用来保存发送线程的发送状态信息:当前线程是否为主线程,是否在发送事件,发送的事件列表、接收事件的订阅者等。

    /** Posts the given event to the event bus. */public void post(Object event) {PostingThreadState postingState = currentPostingThreadState.get();List<Object> eventQueue = postingState.eventQueue;eventQueue.add(event);if (!postingState.isPosting) {postingState.isMainThread = isMainThread();postingState.isPosting = true;if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");}try {while (!eventQueue.isEmpty()) {postSingleEvent(eventQueue.remove(0), postingState);}} finally {postingState.isPosting = false;postingState.isMainThread = false;}}}

现在继续看post逻辑。首先,获取到当前线程的发送状态postingState,然后拿到事件列表,并把需要post的事件加入到列表中。判断是否启动了事件发送流程,如果已经启动了,不在做处理,加入列表中的事件会轮到处理。如果没有启动就启动处理流程,并将isPosting赋值为true。循环从事件列表中获取事件,通过postSingleEvent进行处理。

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {Class<?> eventClass = event.getClass();boolean subscriptionFound = false;if (eventInheritance) {List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();for (int h = 0; h < countTypes; h++) {Class<?> clazz = eventTypes.get(h);subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);}} else {subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);}if (!subscriptionFound) {if (logNoSubscriberMessages) {logger.log(Level.FINE, "No subscribers registered for event " + eventClass);}if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&eventClass != SubscriberExceptionEvent.class) {post(new NoSubscriberEvent(this, event));}}}

postSingleEvent中,有一个变量的判断eventInheritance,这个变量表示是否查找发送事件的父类或接口的订阅者,默认是true。例如,发送事件MsgEvent这个对象,MsgEvent继承至Event这个抽象类,发送后,不光所有订阅MsgEvent的订阅者可以收到事件,所有订阅Event的这个事件的订阅者也会收到事件。前面有分析这个字段的值是可以重新配置的,代码如下。

EventBus.builder().eventInheritance(false)   //发送的时候不考虑事件父类.logSubscriberExceptions(false).build().register(this);

eventInheritancetrue,通过lookupAllEventTypes,向上找到所有父类事件类,然后遍历找到所有事件的订阅者,并发送事件,如果为false,直接将当前事件发送给订阅者。

postSingleEvent函数主要是找到需要post的所有相关事件,然后进一步调用postSingleEventForEventType发送给订阅者,因此函数后面的逻辑是判断postSingleEventForEventType的返回值subscriptionFound,即判断当前事件有没有订阅者进行处理,如果没有处理的,会发送一个NoSubscriberEvent。例如:如果我在上一节的例子中post一个新的对象student,但是没有订阅者和接收函数,如果监听了NoSubscriberEvent,会收到一个NoSubscriberEvent的事件,告知调用者你post的对象没有订阅者。

@Subscribe()
public void onMsgEventReceived(NoSubscriberEvent event) {Log.i(TAG, "NoSubscriberEvent : " + event);
}Student student = new Student(1,"jane");
EventBus.getDefault().post(student);
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {subscriptions = subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {for (Subscription subscription : subscriptions) {postingState.event = event;postingState.subscription = subscription;boolean aborted;try {postToSubscription(subscription, event, postingState.isMainThread);aborted = postingState.canceled;} finally {postingState.event = null;postingState.subscription = null;postingState.canceled = false;}if (aborted) {break;}}return true;}return false;}

从前面分析可知,最后都走到了postSingleEventForEventType这个函数里面,首先在subscriptionsByEventType中找到所有订阅该事件的订阅者subscriptionssubscriptions为空或者个数是0则返回false。有订阅者的话,遍历订阅者,然后通过postToSubscription进行发送,并返回true

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case POSTING:invokeSubscriber(subscription, event);break;case MAIN:if (isMainThread) {invokeSubscriber(subscription, event);} else {mainThreadPoster.enqueue(subscription, event);}break;case MAIN_ORDERED:if (mainThreadPoster != null) {mainThreadPoster.enqueue(subscription, event);} else {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(subscription, event);}break;case BACKGROUND:if (isMainThread) {backgroundPoster.enqueue(subscription, event);} else {invokeSubscriber(subscription, event);}break;case ASYNC:asyncPoster.enqueue(subscription, event);break;default:throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);}}

这段代码比较简单就是根据订阅者中注解@SubscribethreadMode值来分别进行处理,上一节也讲过,POSTING表示在哪个线程发送就在哪个线程接收处理,因此直接调用invokeSubscriber通过反射来调用订阅者中的接收事件的方法。

    void invokeSubscriber(Subscription subscription, Object event) {try {subscription.subscriberMethod.method.invoke(subscription.subscriber, event);} catch (InvocationTargetException e) {handleSubscriberException(subscription, event, e.getCause());} catch (IllegalAccessException e) {throw new IllegalStateException("Unexpected exception", e);}}

如果threadModeMAIN,而当前线程是子线程,通过 mainThreadPoster.enqueue(subscription, event)将事件加入主线程队列。

mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;public interface MainThreadSupport {boolean isMainThread();Poster createPoster(EventBus eventBus);
}public class DefaultAndroidMainThreadSupport implements MainThreadSupport {public boolean isMainThread() {return Looper.getMainLooper() == Looper.myLooper();}@Overridepublic Poster createPoster(EventBus eventBus) {return new HandlerPoster(eventBus, Looper.getMainLooper(), 10);}
}

mainThreadPoster是由mainThreadSupport 创建的,mainThreadSupportMainThreadSupport实例,而MainThreadSupport是一个接口,实现类为DefaultAndroidMainThreadSupport,因此,最终是调用到的DefaultAndroidMainThreadSupport中的createPoster,新建了一个HandlerPoster

public class HandlerPoster extends Handler implements Poster {private final PendingPostQueue queue;private final int maxMillisInsideHandleMessage;private final EventBus eventBus;private boolean handlerActive;public HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {super(looper);this.eventBus = eventBus;this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;queue = new PendingPostQueue();}public void enqueue(Subscription subscription, Object event) {PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);synchronized (this) {queue.enqueue(pendingPost);if (!handlerActive) {handlerActive = true;if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}}}}@Overridepublic void handleMessage(Message msg) {...}
}

从上面的源码可以看到,HandlerPoster就是一个Handler,当执行mainThreadPoster.enqueue(subscription, event)时,会将订阅者和事件封装成一个PendingPost,然后加入到PendingPostQueue这个队列中,如果handlerActivetrue表示当前Handler正常处理事件,将入队列的事件等着被处理即可。如果为false则启动处理,调用sendMessage发送消息。

public class HandlerPoster extends Handler implements Poster {private final PendingPostQueue queue;private final int maxMillisInsideHandleMessage;private final EventBus eventBus;private boolean handlerActive;public HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {super(looper);...}public void enqueue(Subscription subscription, Object event) {...}@Overridepublic void handleMessage(Message msg) {boolean rescheduled = false;try {long started = SystemClock.uptimeMillis();while (true) {PendingPost pendingPost = queue.poll();if (pendingPost == null) {synchronized (this) {// Check again, this time in synchronizedpendingPost = queue.poll();if (pendingPost == null) {handlerActive = false;return;}}}eventBus.invokeSubscriber(pendingPost);long timeInMethod = SystemClock.uptimeMillis() - started;if (timeInMethod >= maxMillisInsideHandleMessage) {if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}rescheduled = true;return;}}} finally {handlerActive = rescheduled;}}
}

handleMessage()方法将中将PendingPost对象循环出队列,交给invokeSubscriber()方法进一步处理。这样就把线程通过Handler切回了主线程。

backgroundPoster.enqueue()asyncPoster.enqueue也类似,内部都是先将事件入队列,然后再出队列,但是会通过线程池去进一步处理事件。

粘性事件发送postSticky源码逻辑

    public void postSticky(Object event) {synchronized (stickyEvents) {stickyEvents.put(event.getClass(), event);}// Should be posted after it is putted, in case the subscriber wants to remove immediatelypost(event);}

从上面代码可以看到,先把事件放在了stickyEvents列中中,然后调用了post,也就是上面我们解析过的流程。让我们再来回顾下注册的代码:

// Must be called in synchronized blockprivate void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {Class<?> eventType = subscriberMethod.eventType;Subscription newSubscription = new Subscription(subscriber, subscriberMethod);CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);...subscribedEvents.add(eventType);if (subscriberMethod.sticky) {if (eventInheritance) {// Existing sticky events of all subclasses of eventType have to be considered.// Note: Iterating over all events may be inefficient with lots of sticky events,// thus data structure should be changed to allow a more efficient lookup// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();for (Map.Entry<Class<?>, Object> entry : entries) {Class<?> candidateEventType = entry.getKey();if (eventType.isAssignableFrom(candidateEventType)) {Object stickyEvent = entry.getValue();checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}} else {Object stickyEvent = stickyEvents.get(eventType);checkPostStickyEventToSubscription(newSubscription, stickyEvent);}}}

注册源码分析的时候,就分析到subscriberMethod.sticky这个句上面,现在让我们看看下面的逻辑,同样先判断eventInheritance的值,然后将之前放在stickyEvents中的事件拿出来,执行checkPostStickyEventToSubscription

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {if (stickyEvent != null) {// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)// --> Strange corner case, which we don't take care of here.postToSubscription(newSubscription, stickyEvent, isMainThread());}}

checkPostStickyEventToSubscription中的代码比较水,又调用了postToSubscription,这个方法上面贴出来了,这里方面看再贴一遍,熟悉的味道:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case POSTING:invokeSubscriber(subscription, event);break;case MAIN:if (isMainThread) {invokeSubscriber(subscription, event);} else {mainThreadPoster.enqueue(subscription, event);}break;case MAIN_ORDERED:if (mainThreadPoster != null) {mainThreadPoster.enqueue(subscription, event);} else {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(subscription, event);}break;case BACKGROUND:if (isMainThread) {backgroundPoster.enqueue(subscription, event);} else {invokeSubscriber(subscription, event);}break;case ASYNC:asyncPoster.enqueue(subscription, event);break;default:throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);}}

所以,在注册的时候,如果你订阅者的接收方法加了sticky注解,那么在注册的时候就会看下订阅的事件之前有没有通过postSticky发送过,如果有就会立马收到这个事件。

以上是我们平时使用过程的源码解析,码字不易,喜欢就点赞收藏啊。

参考文章:
EventBus 原理解析

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

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

相关文章

ceil(),floor(),round()函数C++详解

ceil&#xff08;&#xff09; ceil()函数是这样的&#xff1a; double ceil(double x) ceil函数可以把x上取整。 例子&#xff1a; #include <bits/stdc.h> using namespace std; int main() {double a, b;cin >> a >> b;printf("ceil(%.2f) %.2…

TensorFlow搭建神经网络

TensorFlow版本&#xff1a;2.13.0TensorFlow官方文档TensorFlow官方文档中文版 TensorFlow中搭建并训练一个神经网络分为以下几步&#xff1a; 定义神经网络配置损失函数以及优化器训练&#xff1a;反向传播、梯度下降 下面以LeNet-5为例&#xff0c;搭建一个卷积神经网络用…

消息队列项目(2)

我们使用 SQLite 来进行对 Exchange, Queue, Binding 的硬盘保存 对 Message 就保存在硬盘的文本中 SQLite 封装 这里是在 application.yaml 中来引进对 SQLite 的封装 spring:datasource:url: jdbc:sqlite:./data/meta.dbusername:password:driver-class-name: org.sqlite.…

自定义elementui的主题

通常情况下&#xff0c;我们使用elementui框架的时候默认组件的主题都是白色的&#xff0c;比如&#xff1a; 但是如果想自定义主题&#xff0c;改变主题颜色&#xff0c;以及各种默认颜色&#xff0c;其实也不难&#xff1a; 配置默认主题&#xff0c;选好后点击下载 在vu…

【云计算 | Docker】Docker容器后台运行不了?entrypoint在作妖?

1. 问题 使用镜像alpine起个容器&#xff0c;使其保持后台运行&#xff0c;正常情况有如下的效果&#xff0c;可以发现容器保持运行状态。 [rootk8s-master helloWorld]# docker run -dit docker.io/alpine /bin/sh 8d39d7579d5e4f1a560aef16ba57ab5cae2506ea9105e21cbc0634…

python + seaborn绘制条形图

文章目录 条形图countplotpointplot条形图 条形图是我们熟悉的一种绘图方式,它可以实现这一目标。在 seaborn 中,barplot() 函数对一个完整的数据集进行操作,并应用一个函数来获取估计值(默认情况下取平均值)。当每个类别中有多个观测值时,它还会使用引导法计算估计值的…

墨觉Run Plus耳机真的很好嘛?南卡、韶音、墨觉哪个好

随着近年来蓝牙耳机市场的快速变化&#xff0c;耳机的样式也在不断演变&#xff0c;从入耳式发展到无需入耳的骨传导耳机。骨传导耳机因其可以通过骨传导技术进行声音传输&#xff0c;无需贴合耳道即可实现耳机功能&#xff0c;因此备受市场欢迎。尤其是近两年&#xff0c;骨传…

关于简单C++函数在内存中的存储问题

举例 int main(){... }int data(){... }int add(){... } 在分段式存储管理时&#xff0c;根据人们的需求&#xff0c;人为分段存储 如图&#xff1a;

ios_base::out和ios::out、ios_base::in和ios::in、ios_base::app和ios::app等之间有什么区别吗?

2023年8月2日&#xff0c;周三晚上 今天我看到了这样的两行代码&#xff1a; std::ofstream file("example.txt", std::ios_base::out);std::ofstream file("example.txt", std::ios::out);这让我产生了几个疑问&#xff1a; 为什么有时候用ios_base::o…

Matlab实现支持向量机算法(附上多个完整仿真源码)

支持向量机是一种常见的机器学习算法&#xff0c;它可以用于分类和回归问题。在Matlab中使用支持向量机&#xff0c;可以方便地构建和训练模型&#xff0c;并进行预测和评估。本文将介绍Matlab支持向量机的基本原理以及一个简单的分类案例。 文章目录 1. 支持向量机的基本原理2…

JAVA实现动态代理的两种方式及主要的区别

两种动态代理 JAVA中实现动态代理主要目的是为了实现AOP&#xff0c;即面向切面编程。 而动态代理主要是在程序运行期间&#xff0c;基于原类生成代理类&#xff0c;并且将需要织入的代码加入到代理类的方法中&#xff0c;可以实现动态的代码链接。 JAVA实现动态代理的两种方式…

如何通过 WordPress 数据库启用插件?【进不去后台可用】

如果您无法访问 WordPress 后台并需要激活插件以恢复访问权限&#xff0c;则可以通过 WordPress 数据库来实现。本文将向您展示如何使用数据库轻松激活 WordPress 插件。 何时使用数据库激活 WordPress 插件&#xff1f; 许多常见的 WordPress 错误会阻止网站所有者访问 WordP…

k8s目录

k8s笔记目录&#xff0c;更新中... 一 概念篇 1.1概念介绍 1.2 pod 1.3 controller 1.3.1 deployment 1.3.2 statefulset 1.3.3 daemonset 1.3.4 job和cronJob1 1.4 serivce和ingress 1.5 配置与存储 1.5.1 configMap 1.5.2 secret 1.5.3 持久化存储 1.5.4 pv、…

selenium 和 chromedriver 使用的一些总结

1 selenium 下载地址 selenium PyPIhttps://pypi.org/project/selenium/ 2 chromedriver 下载地址 &#xff0c;可以下载最新版的 chromedriver ChromeDriver - WebDriver for Chrome - Downloadshttps://chromedriver.chromium.org/downloadsChrome for Testing availabi…

管理类联考——写作——论说文——实战篇——行文篇——通用性强,解释多种现象的经典理论——谈必要

前言 本节内容涉及“社会分工理论”“资源稀缺性”“瓶颈理论”等理论。这些理论一般用在“利大于弊式结构”中“整体有必要”的部分&#xff0c;也可用于“AB二元类”题目“谈好处”的部分。 需要注意的是&#xff0c;“有好处”一般指有它更好&#xff1b;“有必要”一般指没…

Kafka介绍

目录 1&#xff0c;kafka简单介绍 2&#xff0c;kafka使用场景 3&#xff0c;kafka基本概念 kafka集群 数据冗余 分区的写入 读取分区数据 顺序消费 顺序消费典型的应用场景&#xff1a; 批量消费 提交策略 kafka如何保证高并发 零拷贝技术&#xff08;netty&#…

MySQL迁移到PostgreSQL操作指南

将MySQL数据库迁移到PostgreSQL数据库需要一些步骤和注意事项。以下是一个简单的指南&#xff0c;帮助您进行这样的迁移&#xff1a; 数据迁移&#xff1a; 首先&#xff0c;您需要将MySQL数据库中的数据迁移到PostgreSQL。有几种方法可以实现这一点&#xff1a; 使用工具&…

MIAOYUN获评“2023年度一云多芯稳定安全运行优秀案例”

2023年7月25日至26日&#xff0c;由中国信息通信研究院&#xff08;简称“中国信通院”&#xff09;、中国通信标准化协会主办的以“云领创新&#xff0c;算启新篇”为主题的“2023可信云大会”在北京成功举办。会上公布了多项前瞻领域的评估结果和2023年度最佳实践案例&#x…

IOS看书最终选择|源阅读转换|开源阅读|IOS自签

环境&#xff1a;IOS想使用 换源阅读 问题&#xff1a;换新手机&#xff0c;源阅读下架后&#xff0c;没有好的APP阅读小说 解决办法&#xff1a;自签APP 转换源仓库书源 最终预览 &#xff1a;https://rc.real9.cn/ 背景&#xff1a;自从我换了新iPhone手机&#xff0c;就无法…

zjzcyList.stream().map(Pb_zjzcy::getZjid).collect(Collectors.toList()); 解释一下

zjzcyList.stream().map(Pb_zjzcy::getZjid).collect(Collectors.toList()); 解释一下 这段代码是使用Java 8的流式处理&#xff08;Stream&#xff09;对一个存储了对象的列表&#xff08;zjzcyList&#xff09;进行操作&#xff0c;并最终返回一个包含了列表中每个对象的Zji…