【面试系列】详细拆解Java、Spring、Dubbo三者SPI机制的原理

news2024/9/24 7:21:43

什么是SPI

SPI全称为Service Provider Interface,是一种动态替换发现的机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。

简单来说SPI是一种非常优秀的设计思想,它的核心就是解耦、方便扩展。

Java SPI机制–ServiceLoader

ServiceLoader是Java提供的一种简单的SPI机制的实现,Java的SPI实现约定了以下两件事:

  • 文件必须放在META-INF/services/目录底下
  • 文件名必须为接口的全限定名,内容为接口实现的全限定名

这样就能够通过ServiceLoader加载到文件中接口的实现。

demo展示

接口类

public interface LoadBalance {
}

实现类,这里引用了googleauto-service,可以在编译的时候生成配置文件com.charles.spi.LoadBalance,内容是com.charles.spi.RandomLoadBalance

import com.google.auto.service.AutoService;

@AutoService(LoadBalance.class)
public class RandomLoadBalance implements LoadBalance {

    public RandomLoadBalance() {
        System.out.println("random init ...");
    }
}

测试效果

public class SpiTest {

    public static void main(String[] args) {
        ServiceLoader<LoadBalance> serviceLoader = ServiceLoader.load(LoadBalance.class);
        Iterator<LoadBalance> iterator = serviceLoader.iterator();
        while (iterator.hasNext()){
            LoadBalance next = iterator.next();
            System.out.println("获取到负载均衡策略:" + next);
        }
    }
}

源码解析

ServiceLoader#load(),初始化了LazyIterator

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

ServiceLoader.LazyIterator#next,迭代器获取,根据接口名称获取配置文件,解析每一行数据,使用自定义的类加载器加载。

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

    private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

优缺点

  • 浪费资源,会对每一个实现类进行实例化
  • 无法区分具体的实现,做不到按需加载

Spring SPI机制–SpringFactoriesLoader

pring的SPI机制的约定如下:

  • 配置文件必须在META-INF/目录下,文件名必须为spring.factories
  • 文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名,键和值可以没有任何类与类之间的关系,当然也可以有实现的关系。

demo展示

META-INF/目录下创建spring.factories文件,LoadBalance为键,RandomLoadBalance为值。

com.charles.spi.LoadBalance=com.charles.spi.RandomLoadBalance

测试效果

public class SpringSpiTest {

    public static void main(String[] args) {
        List<LoadBalance> loadBalances = SpringFactoriesLoader.loadFactories(LoadBalance.class, null);
//        List<String> factoryNames = SpringFactoriesLoader.loadFactoryNames(LoadBalance.class, null);
        System.out.println(loadBalances);
    }
}

源码解析

SpringFactoriesLoader#loadFactories,获取factoryType的类名。加载META-INF/spring.factories目录下的文件,用Properties来接收数据,用map做缓存,避免重复加载。获取到指定接口的全部实现类的类名称,调用SpringFactoriesLoader#instantiateFactory进行实例化。

	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

优缺点

  • Spring的SPI机制对Java的SPI机制对进行了一些简化,Java的SPI每个接口都需要对应的文件,而Spring的SPI机制只需要一个spring.factories文件。
  • Java的SPI机制文件内容必须为接口的实现类,而Spring的SPI并不要求键值对必须有什么关系,更加灵活。

Dubbo SPI机制–ExtensionLoader

ExtensionLoader是dubbo的SPI机制的实现类。每一个接口都会有一个自己的ExtensionLoader实例对象,这点跟Java的SPI机制是一样的。

同样地,Dubbo的SPI机制也做了以下几点约定:

  • 接口必须要加@SPI注解
  • 配置文件可以放在META-INF/services/META-INF/dubbo/internal/ META-INF/dubbo/META-INF/dubbo/external/这四个目录底下,文件名也是接口的全限定名
  • 内容为键值对,键为短名称(可以理解为spring中Bean的名称),值为实现类的全限定名

demo展示

引入依赖

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.4.1</version>
        </dependency>

接口类

@SPI
public interface LoadBalance {
}

实现类

public class RandomLoadBalance implements LoadBalance {

    public RandomLoadBalance() {
        System.out.println("random init ...");
    }
}

实现类

@Adaptive
public class RoundLoadBalance implements LoadBalance{
}

META-INF\dubbo创建文件名为com.charles.spi.LoadBalance的文件

random=com.charles.spi.RandomLoadBalance
round=com.charles.spi.RoundLoadBalance

测试效果

public class DubboSpiTest {

    public static void main(String[] args) {
        ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
        LoadBalance random = extensionLoader.getExtension("random");
        LoadBalance adaptiveExtension = extensionLoader.getAdaptiveExtension();
        System.out.println(random);
        System.out.println(adaptiveExtension);
    }
}

通过ExtensionLoadergetExtension方法,传入短名称,这样就可以精确地找到短名称对的实现类。Dubbo的SPI机制解决了前面提到的无法获取指定实现类的问题。

源码解析

ExtensionLoader#getExtension,根据名称获取实现类名,进行初始化。

    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

    private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

ExtensionLoader#loadExtensionClasses,加载指定目录下面的文件。

    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

ExtensionLoader#loadDirectory,加载指定目录下的文件(META-INF/dubbo/com.charles.spi.LoadBalance

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

ExtensionLoader#loadResource,读取文件。

    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

ExtensionLoader#loadClass,加载类

    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

ExtensionLoader#cacheAdaptiveClass,判断是否Adaptive注解,将类赋值给cachedAdaptiveClass

    private void cacheAdaptiveClass(Class<?> clazz) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            throw new IllegalStateException("More than 1 adaptive class found: "
                    + cachedAdaptiveClass.getName()
                    + ", " + clazz.getName());
        }
    }

ExtensionLoader#saveInExtensionClass,保存结果在extensionClasses

    private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
        Class<?> c = extensionClasses.get(name);
        if (c == null) {
            extensionClasses.put(name, clazz);
        } else if (c != clazz) {
            String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
            logger.error(duplicateMsg);
            throw new IllegalStateException(duplicateMsg);
        }
    }

获取自适应实现类,如果没有,进行创建。

    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }

            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }

        return (T) instance;
    }

    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

ExtensionLoader#getAdaptiveExtensionClass,执行getExtensionClasses进行类加载,获取cachedAdaptiveClass

  private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

在这里插入图片描述

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

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

相关文章

面向开发人员的 ChatGPT 提示语教程 - ChatGPT Prompt Engineering for Developers

面向开发人员的 ChatGPT 提示语教程 - ChatGPT Prompt Engineering for Developers 1. 指南1-1. 提示的准则1-2. 配置1-3. 提示语原则原则 1: 写出清晰而具体的指示(原文: Write clear and specific instructions)技巧 1: 使用分隔符来清楚地表明输入的不同部分(原文: Use deli…

2022年NOC大赛编程马拉松赛道初赛图形化高年级A卷-正式卷,包含答案

目录 选择题: 下载打印文档做题: 2022NOC-图形化初赛高年级A卷正式卷 选择题: 1、答案:B 俄罗斯方块是一款风靡全球的益智小游戏,玩家通过移动、旋转和摆放不同造型的方块,使其排列成完整的一行或多行。请问如何旋转图中的蓝色方块,可以使它刚好放入虚线框中,消灭方块…

设计模式——责任链模式

是什么? 场景案例&#xff1a;假设我们现在在公司里面需要请假&#xff0c;那么如果请假的天数比较少&#xff0c;可以直接找组长请假&#xff0c;但是如果是一个星期这种假的话就还需要去找部门主管&#xff0c;如果是半个月以上的假的话就还需要去找副总经理甚至总经理请假…

win10安装Anaconda巨详细[更新于2023.5.7]

目录 一、Anaconda下载&#xff08;官网和清华源,更推荐清华源&#xff09; 1.1、Anaconda官网首页地址 1.2、清华源Anaconda地址 二、Anaconda安装 三、测试Anaconda是否安装配置成功 一、Anaconda下载&#xff08;官网和清华源,更推荐清华源&#xff09; 1.1、Anaconda…

烽火HG680-J_Hi3798MV100_内有普通版和高安版-当贝桌面-卡刷强刷固件包

烽火HG680-J_Hi3798MV100_内有普通版和高安版-当贝桌面-卡刷强刷固件包-内有短接图和教程 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精…

树【二叉树】与森林的相互转化与遍历

一、树与森林的相互转换 预备知识&#xff1a;孩子兄弟表示法。 代码编写出来&#xff1a; typedef struct CSNode{int data;struct CSNode *firstchild,*nextS; }CSNode; 解释&#xff1a;该结点的链域分别指向它的第一个孩子和它同级的兄弟。 &#xff08;一&#xff09;森…

【场景方案】我所遇到的有关前端文件上传的知识点归纳,欢迎大家来补充

文章目录 前言前后端传输的文件格式主要有哪些base64formData 前端上传方案input标签获取文件HTML5的API 切片上传大文件blob数据转成base64未来不间断补充 前言 本文章总结了本人在网上和实际公司项目中遇到的有关前端文件上传功能的知识点&#xff0c;如有更好的方案或者发现…

【Windows】【Audio】Windows 11 声音配置

目录 一. 问题 二. 步骤 三. 配置 3.1 候选列表 3.2 程序事件 3.2.1 Windows 3.2.2 文件资源管理器 3.2.3 Windows 语音识别 一. 问题 印象中记得 Windows XP 启动和关机&#xff0c;还有平常点击的过程中有声音来着&#xff0c;Windows 11 咋没有&#xff1f; 折腾了折…

智能优化算法:浣熊优化算法-附代码

智能优化算法&#xff1a;浣熊优化算法 文章目录 智能优化算法&#xff1a;浣熊优化算法1.浣熊优化算法1.1 初始化1.2 阶段一&#xff1a;狩猎和攻击&#xff08;探索阶段&#xff09; 2.实验结果3.参考文献4. Matlab 摘要&#xff1a;浣熊优化算法&#xff08;Coati Optimizat…

Mysql的可重复读解决了幻读问题吗

针对快照读&#xff08;普通 select 语句&#xff09;&#xff0c;是通过 MVCC 方式解决了幻读&#xff0c;因为可重复读隔离级别下&#xff0c;事务执行过程中看到的数据&#xff0c;一直跟这个事务启动时看到的数据是一致的&#xff0c;即使中途有其他事务插入了一条数据&…

开关电源基础02:基本开关电源拓扑(3)-拓扑分析

说在开头&#xff1a;关于薛定谔的波动方程&#xff08;1&#xff09; 当年毛头小子海森堡在哥廷根求学的时候&#xff0c;埃尔文.薛定谔已经是瑞士苏黎世大学的著名教授了。跟其他那些小天才&#xff08;定位精度&#xff1a;25岁5岁&#xff09;相比&#xff0c;薛定谔只能用…

使用 Python 查找本月的最后一天

文章目录 使用 Python 中的日历库查找月份的最后一天使用 Python 中的 DateTime 模块查找该月的最后一天从每个月的第一天减去一天以找到该月的最后一天使用存储在数组中的预加载日期使用 for 循环查找该月的最后一天打印日历年所有月份的最后一天以查找该月的最后一天 使用 Ar…

Kafka生产者原理

消息发送流程介绍 Producer创建时&#xff0c;会创建⼀个sender线程并设置为守护线程。⽣产消息时&#xff0c;内部其实是异步的&#xff1b;⽣产的消息先经过拦截器->序列化器->分区器&#xff0c;然后将消息缓存在缓冲区&#xff08;该缓冲区也是在Producer创建时创建…

关于clash退出后,华硕电脑连不上网了

关于clash退出后&#xff0c;华硕电脑连不上网了 问题记录 问题记录 昨天因为overleaf老断网&#xff0c;然后我就挂了一下clash&#xff08;第一次用&#xff0c;不怎么懂&#xff09;&#xff0c;后来直接关闭退出了。 今天早上突然浏览器的网页&#xff08;微软自带、华硕…

挑战14天学完Python----初识Python语法

往期文章 Java继承与组合 你知道为什么会划分数据类型吗?—JAVA数据类型与变量 10 &#xff1e; 20 && 10 / 0 0等于串联小灯泡?—JAVA运算符 你真的知道怎样用java敲出Hello World吗&#xff1f;—初识JAVA 目录 往期文章前言1.温度转换实例2. 程序格式框架2.1 高…

ATT汇编快速学习

说明 文档来源 https://flint.cs.yale.edu/cs421/papers/x86-asm/asm.html 使用AT&T语法; 原文档是intel语法翻译过来的; 内容 基于32位, x86的硬件环境; 指令仅仅介绍常用, 即还有很大一部分的指令并没有支持; 编译器(汇编器) GAS(GNU assembler: 即gnu组织提供; 使…

《斯坦福数据挖掘教程·第三版》读书笔记(英文版)Chapter 5 Link Analysis

来源&#xff1a;《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT Chapter 5 Link Analysis Terms: words or other strings of characters other than white space. An inverted index is a data structure that makes it easy, given a term, to find (pointers to) a…

【数码】收音机,德生PL380使用教程与注意事项

文章目录 1、主界面功能介绍&#xff08;注意闹钟和自动关机&#xff09;2、电池和电池模式的匹配3、收音机天线与信号&#xff0c;耳机与噪音F、参考资料 1、主界面功能介绍&#xff08;注意闹钟和自动关机&#xff09; 红色的按钮&#xff1a;power 按一下开机&#xff0c;按…

DJYOS开源往事一:djyos爱好者大南山相聚写实

前言&#xff1a;DJYOS开源社区成立于2009年&#xff0c;后因为专注技术方向和垂直产业化等原因&#xff0c;2015年后关闭开源社区。斗转星移&#xff0c;DJYOS开源社区虽然关闭&#xff0c;但是DJYOS开源和精神依然在&#xff0c;转眼DJYOS发布十四年了。记录一下DJYOS开源往事…