观察者设计模式
行为型设计模式
行为型模式(Behavioral Patterns):这类模式主要关注对象之间的通信。它们 分别是:
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
1. 观察者设计模式
1.1 概述
观察者模式是一种行为设计模式,允许对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。在这种模式中,发生状态改变的对象被称为“主题”(Subject),依赖它的对象被称为“观察者”(Observer)。所以观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。
被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer等等。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作是观察者模式。
我们通过一个例子来实现观察者模式。假设我们有一个气象站(WeatherStation),需要向许多不同的显示设备(如手机App、网站、电子屏幕等)提供实时天气数据。

首先,我们需要创建一个Subject接口,表示被观察的主题:
/*** 接口描述:被观察者应该提供注册、删除、通知观察者的能力** @Author crysw* @Version 1.0* @Date 2024/1/1 21:43*/
public interface Subject {/*** 注册观察者** @param observer*/void registerObserver(Observer observer);/*** 删除观察者** @param observer*/void removeObserver(Observer observer);/*** 通知观察者*/void notifyObservers();
}
接下来,我们创建一个Observer接口,表示依赖主题的观察者接口:
/*** 接口描述:观察者接口** @Author crysw* @Version 1.0* @Date 2024/1/1 21:50*/
public interface Observer {/*** 更新新的天气气温** @param temperature*/void update(float temperature);
}
创建一个具体的主题,如WeatherStation,实现Subject接口:
/*** 类描述:气象站(被观察者)** @Author crysw* @Version 1.0* @Date 2024/1/1 21:49*/
public class WeatherStation implements Subject {// 温度private float temperature;// 所有依赖的观察者private List<Observer> observers = new ArrayList<>();// 修改温度public void changeTemperature(float temperature) {this.temperature = temperature;// 通知所有观察者更新温度notifyObservers();}@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {// 通知所有观察者更新温度for (Observer observer : observers) {observer.update(temperature);}}
}
最后,我们创建具体的观察者并实现Observer接口,如AppClient、WebClient:
/*** 类描述:手机客户端** @Author crysw* @Version 1.0* @Date 2024/1/1 21:55*/
@Slf4j
public class AppClient implements Observer {@Overridepublic void update(float temperature) {log.info("app更新了温度,现在的温度是: {}", temperature);}
}/*** 类描述:网页客户端** @Author crysw* @Version 1.0* @Date 2024/1/1 21:57*/
@Slf4j
public class WebClient implements Observer {@Overridepublic void update(float temperature) {log.info("网页客户端更新了气温,温度是:{}", temperature);}
}
测试用例:
/*** 类描述:观察者设计模式测试案例** @Author crysw* @Version 1.0* @Date 2024/1/1 21:57*/
public class ObserverTest {@Testpublic void test() {// 定义气象站(主题-被观察者)WeatherStation weatherStation = new WeatherStation();// 定义观察者客户端(观察者)Observer appClient = new AppClient();Observer webClient = new WebClient();// 注册观察者weatherStation.registerObserver(appClient);weatherStation.registerObserver(webClient);// 更新温度weatherStation.changeTemperature(20.05f);}
}
测试结果:
[main] INFO cn.itcast.designPatterns.observer.weather.AppClient - app更新了温度,现在的温度是: 20.05
[main] INFO cn.itcast.designPatterns.observer.weather.WebClient - 网页客户端更新了气温,温度是:20.05
使用观察者模式的优点:
- 观察者和主题之间的解耦:主题只需要知道观察者实现了Observer接口,而无需了解具体的实现细节。提高代码的可维护性和可扩展性。
- 可以动态添加和删除观察者:通过调用registerObserver和removeObserver方法,可以在运行时添加和删除观察者。
- 主题和观察者之间的通信是自动的:当主题的状态发生变化时,观察者会自动得到通知并更新自己的状态。
上面的小例子算是观察者模式的“模板代码”,可以反映该模式大体的设计思路。在真实的软件开发中,并不需要照搬上面的模板代码。观察者模式的实现方法各式各样,函数、类的命名等会根据业务场景的不同有很大的差别,比如 register 函数还可以叫作 attach,remove 函数还可以叫作 detach 等等。不过,万变不离其宗,设计思路都是差不多的。
了解了观察者设计模式的基本使用方式,我们接下来看看他的具体使用场景。
1.2 使用场景
以下是一些使用观察者设计模式的例子:
-
股票行情应用:股票价格更新可以作为被观察者,投资者可以作为观察者。当股票价格发生变化时,所有订阅了该股票的投资者都会收到通知并更新自己的投资策略。
-
网络聊天室:聊天室服务器可以作为被观察者,用户可以作为察者。当有新消息时,聊天室服务器会通知所有在线用户更新聊天记录。
-
拍卖系统:拍卖系统可以作为被观察者,用户可以作为观察者。当出价发生变化时,所有关注该拍品的用户都会收到通知并更新自己的出价策略。
-
订阅系统:内容发布可以作为被观察者,用户可以作为观察者。当有新内容发布时,所有订阅了该内容的用户都会收到通知并获取最新内容。
-
电子邮件通知系统:任务状态更新可以作为被观察者,相关人员可以作为观察者。当任务状态发生变化时,所有关注该任务的人员都会收到通知并查看任务详情。
-
社交网络:被关注的用户可以作为被观察者,关注者可以作为观察者。当被关注的用户发布新动态时,所有关注者都会收到通知并查看动态。
1.3 发布订阅
发布-订阅模式和观察者模式都是用于实现对象间的松耦合通信的设计模式。尽管它们具有相似之处,但它们在实现方式和使用场景上存在一些关键区别。他们在概念上有一定的相似性,都是用于实现对象间的松耦合通信。可以将发布-订阅模式看作是观察者模式的一种变体或扩展。
观察者模式定义了一种一对多的依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。在这个模式中,被观察者和观察者之间存在直接的关联关系。观察者模式主要包括两类对象:被观察者(Subject)和观察者(Observer)。
发布-订阅模式(生产者和消费者)引入了第三方组件(通常称为消息代理或事件总线),该组件负责维护发布者和订阅者之间的关系。这意味着发布者和订阅者彼此不直接通信,而是通过消息代理进行通信。这种间接通信允许发布者和订阅者在运行时动态地添加或删除,从而提高了系统的灵活性和可扩展性。
Java中的发布-订阅模式示例:
// 订阅者接口
public interface Subscriber {/*** 接收事件通知** @param event*/void onEvent(String event);
}// 具体的订阅者
@Slf4j
public class ConcreteSubscriber implements Subscriber {@Overridepublic void onEvent(String event) {log.info("ConcreteSubscriber收到事件: {}", event);}
}// 具体的订阅者2
@Slf4j
public class ConcreteSubscriber2 implements Subscriber {@Overridepublic void onEvent(String event) {log.info("ConcreteSubscriber2收到事件: {}", event);}
}
订阅者与事件发布之间通过消息总线(代理)来联系.
/*** 类描述:消息总线(代理),实现主题(事件)发布,注册订阅者,删除订阅者等** @Author crysw* @Version 1.0* @Date 2024/1/3 22:17*/
public class EventBus {// 使用一个map维护,消息类型和该消息的订阅者private Map<String, List<Subscriber>> subscribers = new HashMap<>();/*** 订阅消息** @param eventType 事件类型* @param subscriber 订阅者*/public void subscribe(String eventType, Subscriber subscriber) {List<Subscriber> subs = subscribers.get(eventType);if (subs == null) {subs = new ArrayList<>();}subs.add(subscriber);subscribers.put(eventType, subs);}/*** 删除订阅** @param eventType* @param subscriber*/public void unSubscribe(String eventType, Subscriber subscriber) {List<Subscriber> subs = subscribers.get(eventType);if (subs != null) {subs.remove(subscriber);}}/*** 发布事件消息** @param eventType* @param event*/public void publish(String eventType, String event) {List<Subscriber> subs = subscribers.get(eventType);if (subs != null) {for (Subscriber sub : subs) {sub.onEvent(event);}}}
}
发布订阅的测试案例
@Test
public void test2() {// 创建消息代理EventBus eventBus = new EventBus();// 创建订阅者Subscriber subscriber = new ConcreteSubscriber();Subscriber subscriber2 = new ConcreteSubscriber2();// 订阅事件String eventType = "eventA";eventBus.subscribe(eventType, subscriber);eventBus.subscribe(eventType, subscriber2);// 发布事件eventBus.publish(eventType, "这是事件A发布的消息");log.info("===============================================");// 取消订阅eventBus.unSubscribe(eventType, subscriber2);// 再次发布事件eventBus.publish(eventType, "事件A又来发布的消息again");}
测试结果:
[main] INFO cn.itcast.designPatterns.observer.subscriber.ConcreteSubscriber - ConcreteSubscriber收到事件: 这是事件A发布的消息
[main] INFO cn.itcast.designPatterns.observer.subscriber.ConcreteSubscriber2 - ConcreteSubscriber2收到事件: 这是事件A发布的消息
[main] INFO cn.itcast.designPattern.ObserverTest - ===============================================
[main] INFO cn.itcast.designPatterns.observer.subscriber.ConcreteSubscriber - ConcreteSubscriber收到事件: 事件A又来发布的消息again
总结一下两者的区别:
- 通信方式:观察者模式中,观察者与被观察者之间存在直接的关联关系,而发布-订阅模式中,发布者和订阅者通过一个第三方组件(消息代理或事件总线)进行通信,彼此之间不存在直接关联关系。
- 系统复杂性:发布-订阅模式引入了一个额外的组件(消息代理或事件总线),增加了系统的复杂性,但同时也提高了系统的灵活性和可扩展性。
- 使用场景:观察者模式适用于需要将状态变化通知给其他对象的情况,而发布-订阅模式适用于事件驱动的系统,尤其是那些需要跨越多个模块或组件进行通信的场景。
发布-订阅模式的优点:
-
解耦:在发布-订阅模式中,发布者和订阅者之间没有直接关联,它们通过一个中间组件(消息代理或事件总线)进行通信。这种间接通信可以使发布者和订阅者在运行时动态地添加或删除,从而进一步降低了它们之间的耦合度。
-
可扩展性:发布-订阅模式更容易向系统中添加新的发布者和订阅者,而无需修改现有的代码。这使得系统在不同组件之间通信时具有更好的可扩展性。
-
模块化:由于发布者和订阅者之间的通信通过中间组件进行,可以将系统划分为更小、更独立的模块。这有助于提高代码的可维护性和可读性。
-
异步通信:发布-订阅模式通常支持异步消息传递,这意味着发布者和订阅者可以在不同的线程或进程中运行。这有助于提高系统的并发性能和响应能力。
-
消息过滤:在发布-订阅模式中,可以利用中间组件对消息进行过滤,使得订阅者只接收到感兴趣的消息。这可以提高系统的性能,减少不必要的通信开销。
发布-订阅模式也有一些缺点,例如增加了系统的复杂性,因为引入了额外的中间组件。根据具体的应用场景和需求来选择合适的设计模式。
1.4 源码使用
1.4.1 jdk中的观察者
java.util.Observable类实现了主题(Subject)的功能,而java.util.Observer接口则定义了观察者(Observer)的方法。
通过调用Observable对象的notifyObservers()方法,可以通知所有注册的Observer对象,让它们更新自己的状态。
案例:假设有一个银行账户类,它的余额是可变的。当余额发生变化时,需要通知所有的观察者(比如说银行客户),以便它们更新自己的显示信息。
使用观察者模式来实现银行客户对自己账户余额的实时监控。
首先创建主题类,实现存款,取款,获取余额,注册订阅者,删除订阅者等
package cn.itcast.designPatterns.observer.jdkimpl;import java.util.Observable;/*** 类描述:银行账户类,实现Observable类** @Author crysw* @Version 1.0* @Date 2024/1/9 21:37*/
public class BankAccount extends Observable {/*** 余额*/private double balance;public BankAccount(double balance) {this.balance = balance;}/*** 获取当前余额** @return*/public double getBalance() {return balance;}/*** 存款操作** @param amount*/public void deposit(double amount) {balance += amount;// 继承下来的方法,表示状态发生改变setChanged();// 继承下来的方法,通知所有观察者notifyObservers();}/*** 取款操作** @param amount*/public void withdraw(double amount) {balance -= amount;setChanged();notifyObservers();}
}
再创建观察者类, 订阅者
/*** 类描述:银行客户1-观察者** @Author crysw* @Version 1.0* @Date 2024/1/9 21:45*/
@Slf4j
public class ClientOberver implements Observer {@Overridepublic void update(Observable observable, Object arg) {log.info("客户1查看余额已更新为: {}", ((BankAccount) observable).getBalance());}
}/*** 类描述:银行客户2-观察者** @Author crysw* @Version 1.0* @Date 2024/1/9 21:45*/
@Slf4j
public class ClientOberver2 implements Observer {@Overridepublic void update(Observable observable, Object arg) {log.info("客户2查看余额已更新为: {}", ((BankAccount) observable).getBalance());}
}
测试用例:
/*** 测试jdk中观察者模式的实现*/
@Test
public void test3() {// 创建发布事件(主题)对象BankAccount bankAccount = new BankAccount(100.00);log.info("银行账户余额是:{}", bankAccount.getBalance());// 创建订阅者ClientOberver clientOberver = new ClientOberver();ClientOberver2 clientOberver2 = new ClientOberver2();// 注册订阅者bankAccount.addObserver(clientOberver);bankAccount.addObserver(clientOberver2);// 存款50bankAccount.deposit(50.00);log.info("================================");// 取款25bankAccount.withdraw(25.00);log.info("================================");// 删除订阅者bankAccount.deleteObserver(clientOberver2);log.info("================================");// 取款20bankAccount.withdraw(20.00);
}
测试结果:
[main] INFO cn.itcast.designPattern.ObserverTest - 银行账户余额是:100.0
[main] INFO cn.itcast.designPatterns.observer.jdkimpl.ClientOberver2 - 客户2查看余额已更新为: 150.0
[main] INFO cn.itcast.designPatterns.observer.jdkimpl.ClientOberver - 客户1查看余额已更新为: 150.0
[main] INFO cn.itcast.designPattern.ObserverTest - ================================
[main] INFO cn.itcast.designPatterns.observer.jdkimpl.ClientOberver2 - 客户2查看余额已更新为: 125.0
[main] INFO cn.itcast.designPatterns.observer.jdkimpl.ClientOberver - 客户1查看余额已更新为: 125.0
[main] INFO cn.itcast.designPattern.ObserverTest - ================================
[main] INFO cn.itcast.designPattern.ObserverTest - ================================
[main] INFO cn.itcast.designPatterns.observer.jdkimpl.ClientOberver - 客户1查看余额已更新为: 105.0
这个案例中,BankAccount类继承了java.util.Observable类,表示它是一个主题(Subject)。在存款或取款操作时,它会调用setChanged()方法表示状态已经改变,并调用notifyObservers()方法通知所有观察者(Observer)。
两个观察者(clientOberver和clientOberver2),它们分别实现了Observer接口的update()方法。当观察者收到更新通知时,它们会执行自己的业务逻辑,比如更新显示信息。
1.4.2 Guava中的消息总线
Guava 库中的 EventBus 类提供了一个简单的消息总线实现,可以帮助您在 Java应用程序中实现发布-订阅模式。以下是一个简单的示例,演示了如何使用 Guava 的EventBus 来实现一个简单的消息发布和订阅功能。
添加依赖项:
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version>
</dependency>
定义一个事件消息类
/*** 类描述:事件消息类** @Author crysw* @Version 1.0* @Date 2024/1/9 22:02*/
public class MessageEvent {private String message;public MessageEvent(String message) {this.message = message;}public String getMessage() {return message;}
}
创建一个订阅者类。在订阅者类中,定义一个方法并使用 @Subscribe 注解标记该方法,以便 EventBus 能够识别该方法作为事件处理器:
/*** 类描述:订阅者类** @Author crysw* @Version 1.0* @Date 2024/1/9 22:03*/
@Slf4j
public class MessageSubscriber {@Subscribepublic void handleMessageEvent(MessageEvent event) {log.info("收到消息: {}", event.getMessage());}
}
测试用例:
/*** 测试guava的发布订阅实现*/
@Test
public void test4() {// 创建 EventBus 事件实例com.google.common.eventbus.EventBus eventBus = new com.google.common.eventbus.EventBus();// 创建并注册订阅者MessageSubscriber messageSubscriber = new MessageSubscriber();eventBus.register(messageSubscriber);// 发布事件eventBus.post(new MessageEvent("Hello, EventBus!"));// 删除订阅者eventBus.unregister(messageSubscriber);// 再次发布事件(此时订阅者已取消注册,将不会收到消息)eventBus.post(new MessageEvent("Another message"));
}
在这个示例中,我们创建了一个 EventBus 实例,然后创建并注册了一个MessageSubscriber 类型的订阅者。当我们使用 eventBus.post() 方法发布一个 MessageEvent 事件时,订阅者的 handleMessageEvent 方法将被调用,并输出收到的消息。
注意,如果订阅者处理事件的方法抛出异常, EventBus 默认情况下不会对异常进行处理。如果需要处理异常,可以在创建 EventBus 实例时传入一个自定义的SubscriberExceptionHandler。
public class EventBus {public EventBus(SubscriberExceptionHandler exceptionHandler) {this("default", MoreExecutors.directExecutor(), Dispatcher.perThreadDispatchQueue(), exceptionHandler);}
} public interface SubscriberExceptionHandler {void handleException(Throwable var1, SubscriberExceptionContext var2);
}
1.5 进阶
之前讲到的实现方式,是一种同步阻塞的实现方式。观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。
如果注册接口是一个调用比较频繁的接口,对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。
1.5.1 异步非阻塞模型
创建主题接口及实现
/*** 接口描述:主题类(发布事件等)** @Author crysw* @Version 1.0* @Date 2024/1/10 21:35*/
public interface SyncObservable {/*** 注册观察者** @param observer*/void addObserver(SyncObserver observer);/*** 移除观察者** @param observer*/void removeObserver(SyncObserver observer);/*** 通知观察者** @param message*/void notifyObservers(String message);
}/*** 类描述:具体的主题实现类(异步通知观察者)** @Author crysw* @Version 1.0* @Date 2024/1/10 21:37*/
public class SyncObservableImpl implements SyncObservable {private List<SyncObserver> observers;private ExecutorService executorService;public SyncObservableImpl() {observers = new ArrayList<>();executorService = Executors.newCachedThreadPool();}/*** 消息更新后,通知所有观察者** @param message*/public void setMessage(String message) {notifyObservers(message);}@Overridepublic void addObserver(SyncObserver observer) {observers.add(observer);}@Overridepublic void removeObserver(SyncObserver observer) {observers.remove(observer);}@Overridepublic void notifyObservers(String message) {for (SyncObserver observer : observers) {// 异步执行executorService.submit(() -> observer.update(message));}}
}
创建观察者接口及实现
/*** 接口描述:观察者接口** @Author crysw* @Version 1.0* @Date 2024/1/10 21:34*/
public interface SyncObserver {void update(String message);
}/*** 类描述:** @Author crysw* @Version 1.0* @Date 2024/1/10 21:36*/
@Slf4j
public class SyncObserverImpl implements SyncObserver {private String name;public SyncObserverImpl(String name) {this.name = name;}@Overridepublic void update(String message) {log.info("{} received message: {}", name, message);}
}
测试用例:
/*** 测试异步通知*/
@Test
public void test5() {// 创建主题(发布事件)对象SyncObservableImpl observable = new SyncObservableImpl();// 创建观察者SyncObserver observer = new SyncObserverImpl("crysw");SyncObserver observer2 = new SyncObserverImpl("paanda");// 注册观察者observable.addObserver(observer);observable.addObserver(observer2);// 发布消息observable.setMessage("放假了,放假了");// 移除观察者observable.removeObserver(observer);// 再次更新消息observable.setMessage("错了,继续搬砖");
}
1.5.2 跨进程通信
不管是同步阻塞实现方式还是异步非阻塞实现方式,都是进程内的实现方式。如果用户注册成功之后,需要发送用户信息给大数据征信系统,而大数据征信系统是一个独立的系统,跟它之间的交互是跨不同进程的,那如何实现一个跨进程的观察者模式呢?
如果大数据征信系统提供了发送用户注册信息的 RPC 接口,我们仍然可以沿用之前的实现思路,在 notifyObservers() 函数中调用 RPC 接口来发送数据。但是,我们还有更加常用的一种实现方式,那就是基于消息队列(Message Queue)来实现。
当然,这种实现方式也有弊端,需要引入一个新的系统(消息队列),增加了维护成本。不过,它的好处也非常明显。在原来的实现方式中,观察者需要注册到被观察者中,被观察者需要依次遍历观察者来发送消息。而基于消息队列的实现方式,被观察者和观察者解耦更加彻底,两部分的耦合更小。被观察者完全不感知观察者,同理,观察者也完全不感知被观察者。被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。
相关文章:
观察者设计模式
行为型设计模式 行为型模式(Behavioral Patterns):这类模式主要关注对象之间的通信。它们 分别是: 职责链模式(Chain of Responsibility)命令模式(Command)解释器模式(…...
创建mysql普通用户
一、创建mysql普通用户的原因: 权限控制:MySQL的权限系统允许您为每个用户分配特定的权限。通过创建普通用户,您可以根据需要为每个用户分配特定的数据库和表权限,而不是将所有权限授予一个全局管理员用户。这有助于提高数据库的…...
基于多反应堆的高并发服务器【C/C++/Reactor】(中)完整代码
Buffer.h #pragma oncestruct Buffer {// 指向内存的指针char* data;int capacity;int readPos;int writePos; };// 初始化 struct Buffer* bufferInit(int size);// 销毁 void bufferDestroy(struct Buffer* buf);// 扩容 void bufferExtendRoom(struct Buffer* buf, int siz…...
Fluids —— Fluid sourcing
目录 FLIP Boundary: None FLIP Boundary: Velocity FLIP Boundary: Pressure Other methods SOP FLIP流体为生成粒子提供三种Boundary方式(None、Velocity、Pressure); 注,源对象必须是封闭且实体3D或体积对象,开…...
MongoDB相关问题及答案(2024)
1、MongoDB是什么,它与其他传统关系型数据库的主要区别是什么? MongoDB是一种开源文档型数据库,它属于NoSQL数据库的一个分支。NoSQL数据库提供了一种存储和检索数据的机制,这种机制的建模方式与传统的关系型数据库不同。而Mongo…...
前端系列:ES6-ES12新语法
文章目录 ECMAScript系列:简介ECMAScript系列:ES6新特性let 关键字const 关键字变量的解构赋值模板字符串简化对象写法箭头函数参数默认值rest 参数spread扩展运算符Symbol迭代器生成器PromiseSetMapclass类数值扩展对象扩展模块化 ECMAScript系列&#…...
226.【2023年华为OD机试真题(C卷)】精准核酸检测(并查集-JavaPythonC++JS实现)
🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-精准核酸检测二.解题思路三.题解代码Python题解…...
浅谈MySQL之索引
1.什么是索引 索引是一种数据结构,用于提高数据库的查询性能。它类似于书籍的目录,通过预先排序和存储一定列(或多列)的值,使数据库引擎能够更快速地定位和访问特定行的数据。索引的作用是加速数据检索的速度ÿ…...
Rust类型之字符串
字符串 Rust 中的字符串类型是String。虽然字符串只是比字符多了一个“串”字,但是在Rust中这两者的存储方式完全不一样,字符串不是字符的数组,String内部存储的是Unicode字符串的UTF8编码,而char直接存的是Unicode Scalar Value…...
Shell - 学习笔记 - 2.1 - Shell变量:Shell变量的定义、赋值和删除
第2章 Shell编程 这一章我们正式进入 Shell 脚本编程,重点讲解变量、字符串、数组、数学计算、选择结构、循环结构和函数。 Shell 的编程思想虽然和 C、Java、Python、C# 等其它编程语言类似,但是在语法细节方面差异还是比较大的,有编程经验的…...
【OCR】实战使用 - 如何提高识别文字的精准度?
实战使用 - 如何提高文字识别的精准度 我们在平常使用OCR的时候,经常会出现文字识别不精准的情况,我们改如何提高文字识别的精度呢? 以下是一些提高OCR(Optical Character Recognition,光学字符识别)文字识…...
css3浮动定位
css3浮动定位 前言浮动float的基本概念浮动的使用浮动的顺序贴靠特性浮动的元素一定能设置宽高 使用浮动实现网页布局BFC规范和浏览器差异如何创建BFCBFC的其他作用浏览器差异 清除浮动相对定位 relative绝对定位 absolute绝对定位脱离标准文档流绝对定位的参考盒子绝对定位的盒…...
Linux 上 Nginx 配置访问 web 服务器及配置 https 访问配置过程记录
目录 一、前言说明二、配置思路三、开始修改配置四、结尾 一、前言说明 最近自己搭建了个 Blog 网站,想把网站部署到服务器上面,本文记录一下搭建过程中 Nginx 配置请求转发的过程。 二、配置思路 web项目已经在服务器上面运行起来了,运行的端…...
css less sass 动态宽高
less height: ~"calc(100% - 30px)";若要需要按照某个比例固定高度可以用 min-height: e("calc(100vh - 184px)")css height: calc(100% - 50px);sass height:calc(100% - var(--height) );...
sqlserver导出数据为excel再导入到另一个数据库
要将SQL Server中的数据导出为Excel文件,然后再将该Excel文件导入到另一个数据库中,你可以按照以下步骤进行操作: 导出数据为Excel文件 echo offset SourceServer源服务器名称 set SourceDB数据库名称 set ExcelFilePath导出到的Excel文件路…...
异构微服务远程调用如何打jar包
1.服务提供方打 jar 包 RemoteUserService.java package com.finance.system.api;import com.finance.system.api.domain.dto.Enterprise; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springfra…...
赋能智慧农业生产,基于YOLOv7开发构建农业生产场景下油茶作物成熟检测识别系统
AI赋能生产生活场景,是加速人工智能技术落地的有利途径,在前文很多具体的业务场景中我们也从实验的角度来尝试性地分析实践了基于AI模型来助力生产生活制造相关的各个领域,诸如:基于AI硬件实现农业作物除草就是一个比较熟知的场景…...
Docker入门介绍
【一】从 dotCloud 到 Docker——低调奢华有内涵 1、追根溯源:dotCloud 时间倒回到两年前,有一个名不见经传的小公司,他的名字叫做:dotCloud。 dotCloud 公司主要提供的是基于 PaaS(Platform as a Service,平台及服务) 平台为开发者或开发商…...
第四站:指针的进阶-(二级指针,函数指针)
目录 二级指针 二级指针的用途 多级指针的定义和使用 指针和数组之间的关系 存储指针的数组(指针数组:保存地址值) 指向数组的指针(数组指针) 传参的形式(指针) 数组传参时会退化为指针 void类型的指针 函数指针 定义: 调用:两种方式:(*指针名)(参数地址) 或者 指针…...
浏览器渲染原理(面试重点)
一、浏览器是如何渲染页面的 常见的简洁答案: 浏览器内核拿到内容后,渲染流程大致如下:解析HTML,构建Dom树;解析CSS,构建Render树;(将CSS代码解析成树形的数据结构,与D…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
【堆垛策略】设计方法
堆垛策略的设计是积木堆叠系统的核心,直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法,涵盖基础规则、优化算法和容错机制: 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则: 大尺寸/重量积木在下…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...
