Dubbo 源码学习系列(二) 动手写dubbo核心原理

 

前言

       我觉得只有学习源码,才能获取到与大师交流的机会,优秀的设计能让我在编程思想上得到锻炼提升,也会让我去更注重自己的代码质量 !

一、 Dubbo 架构详解 

      理解Dubbo前,最好先手动画一下dubbo的架构图,画图理解架构是最清晰有效地方式。

各模块的职责:

  • 注册中心:  提供服务发现与注册功能, 如果服务发生变动通过watch机制通知服务消费方。
  • 服务消费者:  服务的调用方,在启动的时候会从注册中心拉取到服务地址列表通过Map缓存到本地。
  • 服务提供者:  服务的提供方,在启动的时候会将服务注册到远程注册中心,例如zookeeper, 同时也本地注册,提供接口方法的实现。
  • 监视器:  监视并统计服务提供者和服务消费者在调用时的一些参数数据。

二、  动手写Dubbo

       在写dubbo前,我们一定要先理清他的实现逻辑,最好画一个流程图,或者是通过顺序罗列出执行顺序,然后再去写代码。     

       我自己写了四个版本, 每个版本都有迭代,后续会继续完善,使用的Java版本是 1.8, 每个版本的实现步骤描述如下: 

v1.0 
服务提供者和服务消费者都使用http协议, 服务提供者使用tomcat容器处理请求,服务消费者通过httpClient将请求发送出去。
1. 服务提供者
     服务提供者要做的事情可以总结为如下两点: 
    1)  本地注册到registry, 注册接口名和实现类的Class。
    2)  使用remotemap 替代zookeeper 注册中心,注册接口名和服务地址列表
    3)  使用http 协议启动tomcat容器接收请求,处理请求。
         DispatcherServlet 接收请求-----> HttpServerHandler-----> response。


2. 服务消费者
    1)  使用动态代理模式获取接口的代理对象, 使用代理对象调用目标方法
         通过cglib代理模式生成目标对象的代理对象
            执行invoker()方法,invoke()方法在调用目标方法时执行
                   根据接口名从使用remotemap拿到url列表,然后通过负载均衡策略选一个url进行调用
                     将Url和invocation发送到服务提供者处理。
                         返回result打印调用结果。



v 2.0
   使用zookeeper注册中心缓存服务地址列表。
   1. 服务提供者
       在服务启动时将接口的Class Name和服务地址列表注册到zookeeper
       
   2. 在invoke()方法里先通过zookeeper 获取到服务地址列表,然后通过负载均衡策略选一个url。

v 3.0 
   实现dubbo协议,使用netty server 来处理请求。
   
v 4.0 
   利用工厂设计模式优化代码结构。

 

原则:

       1. 服务消费者只管接口调用,不管实现细节。

       2. 服务提供者只提供服务实现,在调用的时候才去处理请求。

 版本 v1.0: 

        1. 服务提供者能提供服务,服务消费者能够消费服务。

        2. 使用本地文件替代zookeeper注册中心。

项目目录结构:

 项目依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example.dubbo</groupId>
    <artifactId>dubbo-project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>dubbo-project</name>
    <description>project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.45</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
        <!-- java 工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.5.7</version>
        </dependency>
        <!-- apache common工具包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.openhtmltopdf</groupId>
            <artifactId>openhtmltopdf-core</artifactId>
            <version>0.0.1-RC9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 

 1. 模拟服务提供者

      

           本地注册中心

                本地注册的用处是在启动的时候用来缓存,在处理请求的时候会用到,根据接口的全限定名获取到实现类的Class,  当服务消费方把请求的目标接口的方法名和方法参数列表发过来后就可以获取到method对象, 然后通过method对象在服务提供方执行目标方法。

package com.example.dubbo.provider;

import com.example.dubbo.framework.URL;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author bingbing
 * @date 2021/4/29 0029 10:53
 * 本地注册中心
 */
public class LocalRegistry {


    private static Map<String, Class> clazzMap = new HashMap<>();


    public static void regist(String interfacename, Class implClazz) {
        clazzMap.put(interfacename, implClazz);
    }

    public static Class getClass(String interfacename) {
        return clazzMap.get(interfacename);
    }

}

         本地模拟zookeeper注册中心

             理解了dubbo的服务消费模块后,我们就知道服务提供者在启动应用时会将本地的接口信息注册到zookeeper注册中心。

             本地可以使用map去替代zookeeper缓存接口的全限定名和服务地址列表,为什么缓存这两个值?  是因为在调用时如果有多实例的情况下,服务消费端调用接口达到负载均衡的效果。

package com.example.dubbo.registry;

import com.example.dubbo.framework.URL;
import com.sun.org.apache.regexp.internal.RE;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author bingbing
 * @date 2021/4/29 0029 10:53
 * 暂时替代远程注册中心 zookeeper
 * 使用map缓存地址列表
 */
public class RemoteRegistry {


    private static Map<String, List<URL>> remotemap = new HashMap<>();

    private static final String filePath = "/temp.text";

    public static void registry(String interfacename, URL url) {
        List<URL> lists = new ArrayList<>();
        if (remotemap == null) {
            remotemap = new HashMap<>();
        }
        lists.add(url);
        remotemap.put(interfacename, lists);
        saveFile();
    }

    public static List<URL> get(String interfacename) {
        Map<String, List<URL>> map = getFile();
        return map.get(interfacename);
    }


    public static void saveFile() {
        File file = new File(filePath);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 将对象序列化后保存到文件里
        try {
            FileOutputStream fos = new FileOutputStream(filePath);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(remotemap);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 从文件中读取
    public static Map<String, List<URL>> getFile() {
        try {
            FileInputStream fis = new FileInputStream(filePath);
            ObjectInputStream ois = new ObjectInputStream(fis);
            Object obj = ois.readObject();
            return (Map<String, List<URL>>) obj;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

     注:   此处有坑!

         坑一: 

               我在invoke()方法里获取到服务地址列表时, List<URL> lists = RemoteRegistry.get(interfaceclass.getName());  从RemoteRegistry获取到的lists 值竟然为null,我明明在服务提供者写了启动时自动将服务地址列表注册到 RemoteRegistry呀! 然后我找到原因,因为两个不同的应用是不能直接通信的,相当于两个不同的环境,每个应用的RemoteRegistry是独立的,所以拿不到服务提供者的map。

       解决方法: 

               在注册时,将map写入到文件里,服务消费者在调用的时, 每次get前去拿getFile(), 这样数据就能达到共享的目的了!

       坑二:

               将map对象在写入到文件时报错: URL不能被序列化。

      解决方法:

               继承Serializable 接口并添加唯一标识。

            

              

 

    基于Http协议设置tomcat启动相关参数

           设置一个tomcat容器,作为请求处理的载体,另外需要一个DispatcherServlet接收和分发请求,写好后执行一下main方法,看能不能正常启动, 可以在启动完成后,访问localhost:8080。

package com.example.dubbo.protocol.http;

import com.example.dubbo.framework.URL;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;

/**
 * @author bingbing
 * @date 2021/4/29 0029 11:00
 */
public class HttpServer {


    public void start(URL url) {
        Tomcat tomcat = new Tomcat();
        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(url.getPort());


        Engine engine = new StandardEngine();
        engine.setDefaultHost(url.getHost());


        Host host = new StandardHost();
        host.setName(url.getHost());
        String contextpah = "";


        Context context = new StandardContext();

        context.setPath(contextpah);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        //配置dispatcherServlet 来处理请求
        tomcat.addServlet(contextpah, "dispatcher", new DispatcherServlet());
        // 配置mapping
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
            tomcat.getServer().await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        URL url = new URL("localhost", 8080);
        HttpServer server = new HttpServer();
        server.start(url);
    }
}

      DispatcherServlet

         分发并处理请求

package com.example.dubbo.protocol.http;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author bingbing
 * @date 2021/4/29 0029 11:00
 */
public class DispatcherServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        new HttpServerHandler().handler(req, resp);
    }
}

       HttpServerHandler,  万事具备,只欠东风! 在此方法里method对象通过反射执行目标接口方法。

package com.example.dubbo.protocol.http;

import com.alibaba.fastjson.JSONObject;
import com.example.dubbo.framework.Invocation;
import com.example.dubbo.provider.LocalRegistry;
import com.example.dubbo.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author bingbing
 * @date 2021/4/29 0029 11:00
 */
public class HttpServerHandler {

    Logger logger = LoggerFactory.getLogger(HttpServerHandler.class);

    /**
     * 处理请求,解析invocation对象
     *
     * @param req
     * @param resp
     */
    public void handler(HttpServletRequest req, HttpServletResponse resp) {
        try {
            logger.debug("收到一个请求,开始处理....");
            // 1. 反序列化,解析对象
            Invocation invocation = JSONObject.parseObject(req.getInputStream(), Invocation.class);
//            invocation.setParamTypes(new Class[]{String.class});
            logger.debug("接收到invocation{}", invocation.toString());
            // 2. 根据接口名获取到接口实现类的Class
            Class impl = LocalRegistry.getClass(invocation.getInterfaceName());
            // 3. 获取反射调用的method对象, 通过方法名和参数列表类型获取方法method对象
            Method method = impl.getMethod(invocation.getMethodName(), invocation.getParamTypes());
            // 4. 执行方法调用
            Object obj = method.invoke(impl.newInstance(), invocation.getParams());
            logger.debug("返回结果:{}", obj);
            resp.setCharacterEncoding("utf-8");
            resp.setContentType("*/*;charset=UTF-8");
            IOUtils.write(obj, resp.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}

 

  2. 测试服务提供者

     写好服务提供者后,首先使用postman测一下看能不能获取到数据,如果不能就先检查一下代码是否写的有问题。

          postman测试技巧: 

            1)  将参数指定为invocation对象对应的属性值。

            2)  接口名为接口所在的全限定名,即所在包名路径.接口名

            3)  参数类型的Classy 用java.lang.String 字符串。

{
    "interfaceName": "com.example.dubbo.provider.api.UserInterface",
    "methodName": "sayHello",
    "paramTypes": [
        "java.lang.String"
    ],
    "params": [
        "bingbing"
    ]
}

 

      

              

    服务提供者可以调通后, 就可以开始写服务消费者了! 

3. 模拟服务消费者

           消费方不管实现,只知道接口即可,屏蔽实现细节,因此在调用的接口的时候应该达到尽可能地精简, 通过代理工厂,达到只需要传接口的Class即可获取到代理对象,然后调用目标方法传递参数即可!

package com.example.dubbo.consumer;

import com.example.dubbo.framework.ProxyFactory;
import com.example.dubbo.provider.api.UserInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author bingbing
 * @date 2021/4/29 0029 10:17
 */
public class ConsumerApplication {

    public static Logger logger = LoggerFactory.getLogger(ConsumerApplication.class);

    public static void main(String[] args) {
        // 消费方不管实现,只知道接口即可,屏蔽实现细节
        UserInterface userInterface = ProxyFactory.getProxy(UserInterface.class);
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String result = userInterface.sayHello("bingbing");
            System.out.println("执行成功,返回结果:{}" + result);
        }

    }
}

          如果你还不了解动态代理,那么可以先补一下cglib动态代理和jdk动态代理模式。

          画个图分析一下执行流程:

       

对应的代码如下: 

           

package com.example.dubbo.framework;

import com.example.dubbo.protocol.http.HttpClient;
import com.example.dubbo.provider.LocalRegistry;
import com.example.dubbo.registry.RemoteRegistry;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Random;

/**
 * @author bingbing
 * @date 2021/4/29 0029 10:46
 */
public class ProxyFactory<T> {


    public static <T> T getProxy(final Class interfaceclass) {

        return (T) Proxy.newProxyInstance(interfaceclass.getClassLoader(), new Class[]{interfaceclass}, new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                //  可以自己定义mock, 通过Mock实现服务降级

                // 2. 获取服务地址列表
                List<URL> lists = RemoteRegistry.get(interfaceclass.getName());
                // 3. 负载均衡策略选择一个url进行使用。
                URL url = LoadBalance.random(lists);
                // 3. 发送http 请求
                HttpClient client = new HttpClient();
                Invocation invocation = new Invocation(interfaceclass.getName(), method.getName(), objects, method.getParameterTypes());
                Object obj = client.send(url, invocation);
                return obj;
            }
        });
    }

}

  最终实现http请求调用的地方是Object obj = client.send(url, invocation);

package com.example.dubbo.protocol.http;

import com.alibaba.fastjson.JSONObject;
import com.example.dubbo.framework.Invocation;
import com.example.dubbo.framework.URL;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

/**
 * @author bingbing
 * @date 2021/4/29 0029 11:00
 */
public class HttpClient {


    public Object send(URL url, Invocation invocation) {

        // 1.将invocation对象序列化转换成json对象
        // 2. 通过post方式发起http请求。
        String jsonInvocation = JSONObject.toJSONString(invocation);

        // 3. 获取响应结果并返回。
        String urlDetail = url.getHost() + ":" + url.getPort();
        try {
            return doPostData(urlDetail, jsonInvocation);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static Object doPostData(String url, String json) throws Exception {
        DefaultHttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost("http://" + url);
        String result = "";
        HttpResponse res = null;
        try {
            StringEntity s = new StringEntity(json, "UTF-8");
            s.setContentType("application/json");
            post.setHeader("Accept", "application/json");
            post.setHeader("Content-type", "application/json; charset=utf-8");
            post.setEntity(s);
            res = client.execute(post);
            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                result = EntityUtils.toString(res.getEntity());
                return result;
            }
        } catch (Exception e) {
            if (res == null) {
                return "HttpResponse 为 null!";
            }
            throw new RuntimeException(e);
        }
        if (res == null || res.getStatusLine() == null) {
            return "无响应";
        }
        return result;
    }

}

 

    先启动服务提供者,再启动服务消费者

       服务提供者控制台:

       服务消费者控制台: 

 

4. 总结 

        v1.0版本实现了服务消费者能够调用服务提供者的目标接口方法,通过文件实现共享服务地址列表。

5. 源码地址

       https://gitee.com/bingbing-123456/dubbo-rpc.git

热门文章

暂无图片
编程学习 ·

C语言二分查找详解

二分查找是一种知名度很高的查找算法&#xff0c;在对有序数列进行查找时效率远高于传统的顺序查找。 下面这张动图对比了二者的效率差距。 二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较&#xff0c;从而确定目标数是在中间数的左边还是右边&#xff0c;将查…
暂无图片
编程学习 ·

GMX 命令分类列表

建模和计算操作命令&#xff1a; 1.1 . 创建拓扑与坐标文件 gmx editconf - 编辑模拟盒子以及写入子组(subgroups) gmx protonate - 结构质子化 gmx x2top - 根据坐标生成原始拓扑文件 gmx solvate - 体系溶剂化 gmx insert-molecules - 将分子插入已有空位 gmx genconf - 增加…
暂无图片
编程学习 ·

一文高效回顾研究生课程《数值分析》重点

数值分析这门课的本质就是用离散的已知点去估计整体&#xff0c;就是由黑盒子产生的结果去估计这个黑盒子。在数学里这个黑盒子就是一个函数嘛&#xff0c;这门课会介绍许多方法去利用离散点最大化地逼近这个函数&#xff0c;甚至它的导数、积分&#xff0c;甚至微分方程的解。…
暂无图片
编程学习 ·

在职阿里5年,一个28岁女软测工程师的心声

简单的先说一下&#xff0c;坐标杭州&#xff0c;14届本科毕业&#xff0c;算上年前在阿里巴巴的面试&#xff0c;一共有面试了有6家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; ​ 编辑切换为居中…
暂无图片
编程学习 ·

字符串左旋c语言

目录 题目&#xff1a; 解题思路&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 总代码&#xff1a; 题目&#xff1a; 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符…
暂无图片
编程学习 ·

设计模式--观察者模式笔记

模式的定义与特点 观察者&#xff08;Observer&#xff09;模式的定义&#xff1a;指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式&#xf…
暂无图片
编程学习 ·

睡觉突然身体动不了,什么是睡眠痽痪症

很多朋友可能有这样的体验&#xff0c;睡觉过程中突然意识清醒&#xff0c;身体却动弹不了。这时候感觉非常恐怖&#xff0c;希望旁边有一个人推自己一下。阳光以前也经常会碰到这样的情况&#xff0c;一年有一百多次&#xff0c;那时候很害怕晚上到来&#xff0c;睡觉了就会出…
暂无图片
编程学习 ·

深入理解C++智能指针——浅析MSVC源码

文章目录unique_ptrshared_ptr 与 weak_ptrstd::bad_weak_ptr 异常std::enable_shared_from_thisunique_ptr unique_ptr 是一个只移型别&#xff08;move-only type&#xff0c;只移型别还有std::mutex等&#xff09;。 结合一下工厂模式&#xff0c;看看其基本用法&#xff…
暂无图片
编程学习 ·

@TableField(exist = false)

TableField(exist false) //申明此字段不在数据库存在&#xff0c;但代码中需要用到它&#xff0c;通知Mybatis-plus在做写库操作是忽略它。,.
暂无图片
编程学习 ·

Java Web day15

第十二章文件上传和下载 一、如何实现文件上传 要实现Web开发中的文件上传功能&#xff0c;通常需要完成两步操作&#xff1a;一.是在Web页面中添加上传输入项&#xff1b;二是在Servlet中读取上传文件的数据&#xff0c;并保存到本地硬盘中。 需要使用一个Apache组织提供一个…
暂无图片
编程学习 ·

【51nod 2478】【单调栈】【前缀和】小b接水

小b接水题目解题思路Code51nod 2478 小b接水 题目 输入样例 12 0 1 0 2 1 0 1 3 2 1 2 1输出样例 6解题思路 可以发现最后能拦住水的都是向两边递减高度&#xff08;&#xff1f;&#xff09; 不管两个高积木之间的的积木是怎样乱七八糟的高度&#xff0c;最后能用来装水的…
暂无图片
编程学习 ·

花了大半天写了一个UVC扩展单元调试工具

基于DIRECTSHOW 实现的&#xff0c;用的是MFC VS2019. 详见&#xff1a;http://www.usbzh.com/article/detail-761.html 获取方法 加QQ群:952873936&#xff0c;然后在群文件\USB调试工具&测试软件\UVCXU-V1.0(UVC扩展单元调试工具-USB中文网官方版).exe USB中文网 USB中文…
暂无图片
编程学习 ·

贪心(一):区间问题、Huffman树

区间问题 例题一&#xff1a;区间选点 给定 N 个闭区间 [ai,bi]请你在数轴上选择尽量少的点&#xff0c;使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入格式 第一行包含整数 N&#xff0c;表示区间数。 接下来 …
暂无图片
编程学习 ·

C语言练习实例——费氏数列

目录 题目 解法 输出结果 题目 Fibonacci为1200年代的欧洲数学家&#xff0c;在他的着作中曾经提到&#xff1a;「若有一只免子每个月生一只小免子&#xff0c;一个月后小免子也开始生产。起初只有一只免子&#xff0c;一个月后就有两只免子&#xff0c;二个月后有三只免子…
暂无图片
编程学习 ·

Android开发(2): Android 资源

个人笔记整理 Android 资源 Android中的资源&#xff0c;一般分为两类&#xff1a; 系统内置资源&#xff1a;Android SDK中所提供的已经定义好的资源&#xff0c;用户可以直接拿来使用。 用户自定义资源&#xff1a;用户自己定义或引入的&#xff0c;只适用于当前应用的资源…
暂无图片
编程学习 ·

零基础如何在短时间内拿到算法offer

​算法工程师是利用算法处理事物的职业 算法&#xff08;Algorithm&#xff09;是一系列解决问题的清晰指令&#xff0c;也就是说&#xff0c;能够对一定规范的输入&#xff0c;在有限时间内获得所要求的输出。 如果一个算法有缺陷&#xff0c;或不适合于某个问题&#xff0c;执…
暂无图片
编程学习 ·

人工智能:知识图谱实战总结

人工智能python&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习人工智能&#xff1a;知识图谱实战前言一、实体建模工具Protegepython&#xff0c;NLP&#xff0c;知识图谱&#xff0c;机器学习&#xff0c;深度学习 人工智能&#xff1a;知识图…
暂无图片
编程学习 ·

【无标题】

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…