Spring中的设计模式--观察者模式
spring在容器中使用了观察者模式:
一、spring事件:ApplicationEvent,该抽象类继承了EventObject类,jdk建议所有的事件都应该继承自EventObject。
二、spring事件监听器:ApplicationLisener,该接口继承了EventListener接口,jdk建议所有的事件监听器都应该继承EventListener。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
三、spring事件发布:ApplicationEventPublisher 。 ApplicationContext继承了该接口,在ApplicationContext的抽象类AbstractApplicationContext中做了实现。
package org.springframework.context;
public interface ApplicationEventPublisher {
/**
* Notify all <strong>matching</strong> listeners registered with this
* application of an application event. Events may be framework events
* (such as RequestHandledEvent) or application-specific events.
* @param event the event to publish
* @see org.springframework.web.context.support.RequestHandledEvent
*/
void publishEvent(ApplicationEvent var1);
void publishEvent(Object var1);
}

AbstractApplicationContext类中publishEvent方法实现:
/**
* Publish the given event to all listeners.
* <p>Note: Listeners get initialized after the MessageSource, to be able
* to access it within listener implementations. Thus, MessageSource
* implementations cannot publish events.
* @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent})
*/
@Override
public void publishEvent(Object event) {
publishEvent(event, null);
} /**
* Publish the given event to all listeners.
* @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent})
* @param eventType the resolved event type, if known
* @since 4.2
*/
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
} // Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
}
} // Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//事件广播委托给ApplicationEventMulticaster来进行
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
} // Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
由上代码可知,AbstractApplicationContext类并没有具体的做事件广播,而是委托给ApplicationEventMulticaster来进行,ApplicationEventMulticaster的multicastEvent()方法实现如下:
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
ErrorHandler errorHandler = this.getErrorHandler();
if (errorHandler != null) {
try {
listener.onApplicationEvent(event);
} catch (Throwable var6) {
errorHandler.handleError(var6);
}
} else {
try {
listener.onApplicationEvent(event);
} catch (ClassCastException var5) {
LogFactory.getLog(this.getClass()).debug("Non-matching event type for listener: " + listener, var5);
}
}
}
获得listener集合,遍历listener触发事件Executor接口有多个实现类,可以支持同步或异步广播事件。
问题:spring容器是怎么根据事件去找到事件对应的事件监听器呢?
一、入口
private ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:/spring/applicationContext.xml");
二、生成Spring上下文ApplicationContext
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
三、调用spring容器初始化方法
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
//初始化一个事件注册表
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// 初始化事件监听器
// Check for listener beans and register them.
registerListeners();
// 实例化所有单例对象,其中包括默认注册表
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// 发布事件
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
3.1 initApplicationEventMulticaster()方法代码
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
//先查找BeanFactory配置文件中是否有ApplicationEventMulticaster
if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
} else {// 如果beanFactory中没有,则创建一个SimpleApplicationEventMulticaster
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [" + this.applicationEventMulticaster + "]");
}
}
}
spring先从beanFactory中获取ApplicationEventMulticaster,如果没有自定义,则创建一个SimpleApplicationEventMulticaster。
ApplicationEventMulticaster包含以下属性:defaultRetriever即为注册表,注册监听事件的相关消息; retrieverCache用来做defaultRetriever的缓存。
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
private final AbstractApplicationEventMulticaster.ListenerRetriever defaultRetriever = new AbstractApplicationEventMulticaster.ListenerRetriever(false);
final Map<AbstractApplicationEventMulticaster.ListenerCacheKey, AbstractApplicationEventMulticaster.ListenerRetriever> retrieverCache = new ConcurrentHashMap(64);
private ClassLoader beanClassLoader;
private BeanFactory beanFactory;
private Object retrievalMutex;
}
ListenerRetriever的数据结构如下:applicationListeners用来存放监听事件, applicationListenerBeans为存放监听事件的类名称。
private class ListenerRetriever {
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet();
public final Set<String> applicationListenerBeans = new LinkedHashSet();
private final boolean preFiltered;
ListenerCacheKey的数据结构如下:eventType是事件类型,sourceType是事件的源类型,即为事件的构造函数的参数类型。
private static class ListenerCacheKey {
private final ResolvableType eventType;
private final Class<?> sourceType;
3.2 registerListeners()方法代码
初始化注册表以后,则把事件注册到注册表中,registerListeners()
protected void registerListeners() {
//获取所有的listener的迭代器
Iterator var1 = this.getApplicationListeners().iterator();
while(var1.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var1.next();
//把获取所有的listener, 把事件的bean放到ApplicationEventMulticaster中的ApplicationListener
this.getApplicationEventMulticaster().addApplicationListener(listener);
}
String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);
String[] var7 = listenerBeanNames;
int var3 = listenerBeanNames.length;
for(int var4 = 0; var4 < var3; ++var4) {
String listenerBeanName = var7[var4];
//把事件的名称放到ApplicationListenerBean里去
this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
Iterator var9 = earlyEventsToProcess.iterator();
while(var9.hasNext()) {
ApplicationEvent earlyEvent = (ApplicationEvent)var9.next();
this.getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
3.3 finishBeanFactoryInitialization(beanFactory) 具体会执行到下面的方法,会把AbstractApplicationEventMulticaster的defaultRetriever属性赋值。 执行PostProcessorRegistrationDelegate类的postProcessAfterInitialization()方法:
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.applicationContext != null && bean instanceof ApplicationListener) {
Boolean flag = (Boolean)this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
this.applicationContext.addApplicationListener((ApplicationListener)bean);
} else if (flag == null) {
if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " + "but is not reachable for event multicasting by its containing ApplicationContext " + "because it does not have singleton scope. Only top-level listener beans are allowed " + "to be of non-singleton scope.");
}
this.singletonNames.put(beanName, Boolean.FALSE);
}
}
return bean;
}
执行AbstractApplicationContext类的addApplicationListener()方法:
public void addApplicationListener(ApplicationListener<?> listener) {
if (this.applicationEventMulticaster != null) {
this.applicationEventMulticaster.addApplicationListener(listener);
} else {
this.applicationListeners.add(listener);
}
}
执行AbstractApplicationEventMulticaster类的addApplicationListener()方法
public void addApplicationListener(ApplicationListener<?> listener) {
Object var2 = this.retrievalMutex;
synchronized(this.retrievalMutex) {
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}
【spring根据反射机制,通过方法getBeansOfType()获取所有继承了ApplicationListener接口的监听器,然后把监听器全放到注册表里,所以我们可以在spring配置文件中配置自定义的监听器,在spring初始化的时候会把监听器自动注册到注册表中。】
3.4 finishRefresh()
里面执行发布事件。
protected void finishRefresh() {
this.initLifecycleProcessor();
this.getLifecycleProcessor().onRefresh();
this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
LiveBeansView.registerApplicationContext(this);
}
在applicationContext发布事件的时候。
public void publishEvent(ApplicationEvent event) {
this.publishEvent(event, (ResolvableType)null);
}
public void publishEvent(Object event) {
this.publishEvent(event, (ResolvableType)null);
}
protected void publishEvent(Object event, ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (this.logger.isTraceEnabled()) {
this.logger.trace("Publishing event in " + this.getDisplayName() + ": " + event);
}
Object applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent)event;
} else {
applicationEvent = new PayloadApplicationEvent(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
// 调用ApplicationEventMulticaster的multicastEvent()方法
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
AbstractApplicationContext类并没有具体的做事件广播,而是委托给ApplicationEventMulticaster来进行。
ApplicationEventMulticaster的方法multicastEvent()为:
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Iterator var4 = this.getApplicationListeners(event, type).iterator();
while(var4.hasNext()) {
final ApplicationListener<?> listener = (ApplicationListener)var4.next();
Executor executor = this.getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
public void run() {
SimpleApplicationEventMulticaster.this.invokeListener(listener, event);
}
});
} else {
this.invokeListener(listener, event);
}
}
}
根据事件和类型获取所有的监听器方法: getApplicationListeners()
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
Class<?> sourceType = source != null ? source.getClass() : null;
AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey = new AbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType);
AbstractApplicationEventMulticaster.ListenerRetriever retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey);
//从缓存里查找ListenerRetriever
if (retriever != null) {
return retriever.getApplicationListeners();
} else if (this.beanClassLoader == null || ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader))) {
Object var7 = this.retrievalMutex;
synchronized(this.retrievalMutex) {
retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
} else {
//如果缓存里不存在,则去获得
retriever = new AbstractApplicationEventMulticaster.ListenerRetriever(true);
Collection<ApplicationListener<?>> listeners = this.retrieveApplicationListeners(eventType, sourceType, retriever);
this.retrieverCache.put(cacheKey, retriever);
return listeners;
}
}
} else {
return this.retrieveApplicationListeners(eventType, sourceType, (AbstractApplicationEventMulticaster.ListenerRetriever)null);
}
}
private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, Class<?> sourceType, AbstractApplicationEventMulticaster.ListenerRetriever retriever) {
LinkedList<ApplicationListener<?>> allListeners = new LinkedList();
Object var7 = this.retrievalMutex;
LinkedHashSet listeners;
LinkedHashSet listenerBeans;
synchronized(this.retrievalMutex) {
//获取注册表里所有的listener, defaultRetriever在前面已被赋值
listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans);
}
Iterator var14 = listeners.iterator();
while(var14.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var14.next();
//根据事件类型,事件源类型,获取所需要的监听事件
if (this.supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListeners.add(listener);
}
allListeners.add(listener);
}
}
if (!listenerBeans.isEmpty()) {
BeanFactory beanFactory = this.getBeanFactory();
Iterator var16 = listenerBeans.iterator();
while(var16.hasNext()) {
String listenerBeanName = (String)var16.next();
try {
Class<?> listenerType = beanFactory.getType(listenerBeanName);
if (listenerType == null || this.supportsEvent(listenerType, eventType)) {
ApplicationListener<?> listener = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class);
if (!allListeners.contains(listener) && this.supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListenerBeans.add(listenerBeanName);
}
allListeners.add(listener);
}
}
} catch (NoSuchBeanDefinitionException var13) {
;
}
}
}
AnnotationAwareOrderComparator.sort(allListeners);
return allListeners;
}
配合上面的注解,即可理解,根据事件和事件类型找到对应的监听器,那么如何根据事件类型找到对应的监听器呢?
上面方法中的supportsEvent(listener, eventType, sourceType)方法实现了根据事件类型查找对应的监听器,代码具体实现为:
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
GenericApplicationListener smartListener = listener instanceof GenericApplicationListener ? (GenericApplicationListener)listener : new GenericApplicationListenerAdapter(listener);
return ((GenericApplicationListener)smartListener).supportsEventType(eventType) && ((GenericApplicationListener)smartListener).supportsSourceType(sourceType);
}
如上可知:上面方法的返回结果跟方法smartListener.supportsEventType(eventType)和smartListener.supportsSourceType(sourceType)有关。
smartListener.supportsEventType(eventType)方法实现为:
public boolean supportsEventType(ResolvableType eventType) {
if (this.delegate instanceof SmartApplicationListener) {
Class<? extends ApplicationEvent> eventClass = eventType.getRawClass();
return ((SmartApplicationListener)this.delegate).supportsEventType(eventClass);
} else {
return this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType);
}
}
该方法主要的逻辑就是根据事件类型判断是否和监听器参数泛型的类型是否一致。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
在定义自己的监听器要明确指定参数泛型,表明该监听器支持的事件,如果不指明具体的泛型,则没有监听器监听事件。
smartListener.supportsSourceType(sourceType)方法的实现为:
public boolean supportsSourceType(Class<?> sourceType) {
return this.delegate instanceof SmartApplicationListener ? ((SmartApplicationListener)this.delegate).supportsSourceType(sourceType) : true;
}
以上是spring的事件体系。
四、自定义事件和监听器
我们可以使用spring的事件广播体系,自定义自己的事件:
自定义事件,继承ApplicationEvent:
public class DIYEvent extends ApplicationEvent {
private static final long serialVersionUID = 7099057708183571977L;
public DIYEvent(String source) {
super(source);
}
}
自定义listener,继承ApplicationListener
@Component
public class DIYListener implements ApplicationListener<DIYEvent> {
@Override
public void onApplicationEvent(DIYEvent diyEvent) {
System.out.println("自定义监听器执行");
System.out.println(diyEvent.getSource());
}
}
测试触发事件:
public class DIYTest{
private ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:/spring/applicationContext.xml");
@Test
public void diyTest(){
applicationContext.publishEvent(new DIYEvent("测试数据"));
}
}
获取ApplicationContext,发布事件。
调试结果:
自定义监听器执行
测试数据
Spring中的设计模式--观察者模式的更多相关文章
- JDK和Spring中的设计模式
创建型 1)工厂方法 Collection.iterator() 由具体的聚集类来确定使用哪一个Iterator 2)单例模式 Runtime.getRuntime() 3)建造者模式 StringB ...
- Spring中的设计模式
[Spring中的设计模式] http://www.uml.org.cn/j2ee/201301074.asp [详解设计模式在Spring中的应用] [http://www.geek521.c ...
- spring 中的设计模式
https://mp.weixin.qq.com/s?__biz=MzU0MDEwMjgwNA==&mid=2247485205&idx=1&sn=63455d2313776d ...
- [Head First设计模式]山西面馆中的设计模式——观察者模式
系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 引言 不知不自觉又将设计模式融入生活了,吃个饭也不得安生,也发现生活中的很多场景,都可以用设计模式来模拟.原来设计模式就在 ...
- Spring中的设计模式学习
Spring提供了一种Template的设计哲学,包含了很多优秀的软件工程思想. 1. 简单工厂 又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一. ...
- Spring中的设计模式:模板模式
导读 模板模式在是Spring底层被广泛的应用,比如事务管理器的实现,JDBC模板的实现. 文章首发于作者的微信公众号[码猿技术专栏] 今天就来谈谈「什么是模板模式」.「模板模式的优缺点」.「模板模式 ...
- Spring中的设计模式2
Spring设计模式分析 工厂模式和单态模式 工厂模式:可以将java对象对象的调用者从被调用者的实现逻辑中分离.调用者只关心被调用者必须满足的某种规则,这种规则我们看做是接口,不必关心实例的具体 ...
- Spring中的设计模式:工厂方法模式
导读 工厂方法模式是所有设计模式中比较常用的一种模式,但是真正能搞懂用好的少之又少,Spring底层大量的使用该设计模式来进行封装,以致开发者阅读源代码的时候晕头转向. 文章首发于微信公众号[码猿技术 ...
- [工作中的设计模式]观察者模式observer
一.模式解析 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己. 观察者模式又叫订阅发布模式, ...
随机推荐
- OpenGL中的数据——Buffer
OpenGL中主要包括了两种数据——Buffer和Texture. Buffer用于储存线性数无类型据块,可以看成普通的内存块,而Texture则用于储存多维数据,一般储存图像或者其他数据. Buff ...
- 48. Rotate Image (matrix retation, transpose) Amazon problem
You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees (clockwise). ...
- Locust性能测试2 分布式运行
locust分布式可以是本机多进程,也可以是本机作为master,其他机器作slave. 试一下本机的多进程运行: 1 控制台输入 locust -f 脚本路径 --master 2 打开另一个控 ...
- 设计模式——模板方法模式(TemplateMethod Pattern)
模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. UML图: 抽象模板: package com.cnblo ...
- c++互斥锁的实现
class IMyLock { public: virtual ~IMyLock(){} ; ; }; class Mutex : public IMyLock { public: Mutex(); ...
- 【[AHOI2012]树屋阶梯】
卡特兰数! 至于为什么是卡特兰数,就稍微说那么一两句吧 对于一个高度为\(i\)的阶梯,我们可以在左上角填一个高度为\(k\)的阶梯,右下角填一个高度为\(i-1-k\)的阶梯剩下的我们用一个大的长方 ...
- [APIO/CTSC 2007]数据备份
嘟嘟嘟 这竟然是一道贪心题,然而我在不看题解之前一直以为是dp. 首先最优的配对一定是相邻两个建筑物配对,所以我们求出差分数组,就变成了在n - 1个数中选出不相邻的k个数,使这k个数的和最小. 贪心 ...
- http://codeforces.com/gym/100623/attachments E题
http://codeforces.com/gym/100623/attachments E题第一个优化它虽然是镜像对称,但它毕竟是一一对称的,所以可以匹配串和模式串都从头到尾颠倒一下第二个优化,与次 ...
- Android学习笔记_72_Spinner的用法
一.普通 1. <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android= ...
- CALayer层的属性(转)
一.position和anchorPoint 1.简单介绍 CALayer有2个非常重要的属性:position和anchorPoint position: (1)用来设置CALayer在父层中的 ...