主頁 > 後端開發 > 深度思考:老生常談的雙親委派機制,JDBC、Tomcat是怎么反其道而行之的?

深度思考:老生常談的雙親委派機制,JDBC、Tomcat是怎么反其道而行之的?

2021-09-07 08:10:29 後端開發

要說雙親委派機制,還得從類加載器的型別談起

一、類加載器的型別

類加載器有以下種類:

  • 啟動類加載器(Bootstrap ClassLoader)
  • 擴展類加載器(Extension ClassLoader)
  • 應用類加載器(Application ClassLoader)

啟動類加載器

內嵌在JVM內核中的加載器,由C++語言撰寫(因此也不會繼承ClassLoader),是類加載器層次中最頂層的加載器,用于加載java的核心類別庫,即加載jre/lib/rt.jar里所有的class,由于啟動類加載器涉及到虛擬機本地實作細節,我們無法獲取啟動類加載器的參考,

擴展類加載器

它負責加載JRE的擴展目錄,jre/lib/ext或者由java.ext.dirs系統屬性指定的目錄中jar包的類,父類加載器為啟動類加載器,但使用擴展類加載器呼叫getParent依然為null,

應用類加載器

又稱系統類加載器,可用通過 java.lang.ClassLoader.getSystemClassLoader()方法獲得此類加載器的實體,系統類加載器也因此得名,應用類加載器主要加載classpath下的class,即用戶自己撰寫的應用編譯得來的class,呼叫getParent回傳擴展類加載器,

擴展類加載器與應用類加載器繼承結構如圖所示:

可以看到除了啟動類加載器,其余的兩個類加載器都繼承于ClassLoader,我們自定義的類加載器,也需要繼承ClassLoader,

值得注意的是,啟動類、擴展類與應用類加載器之間的父子關系,并不是通過繼承來實作的,而是通過組合,即使用parent變數來保存“父加載器”的參考,


二、雙親委派機制

當一個類加載器收到了一個類加載請求時,它自己不會先去嘗試加載這個類,而是把這個請求轉交給父類加載器,每一個層的類加載器都是如此,因此所有的類加載請求都應該傳遞到最頂層的啟動類加載器中,只有當父類加載器在自己的加載范圍內沒有搜尋到該類時,并向子類反饋自己無法加載后,子類加載器才會嘗試自己去加載,

加載標準類別庫與用戶代碼,會有不同的方式:

ClassLoader內的loadClass方法,就很好的解釋了雙親委派的加載程序:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //檢查該class是否已經被當前類加載器加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
              //此時該class還沒有被加載
                try {
                    if (parent != null) {
                      //如果父加載器不為null,則委托給父類加載
                        c = parent.loadClass(name, false);
                    } else {
                       //如果父加載器為null,說明當前類加載器已經是啟動類加載器,直接時候用啟動類加載器去加載該class
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {
                    //此時父類加載器都無法加載該class,則使用當前類加載器進行加載
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    ...
                }
            }
            //是否需要連接該類
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

三、雙親委派存在的意義

為什么要使用雙親委派機制呢?

假設用戶自己定義了java.lang.Object類,由于雙親委派機制的存在,最侄訓委托到啟動類加載器去加載,即回傳rt.jar中的Object類,并不會加載用戶撰寫的Object類,

大家上班摸魚刷的LeetCode,本質上自定義了一個類加載器,重寫了findClass方法,會從網路中加載位元組碼,生成Class物件,最終通過loadClass定義的雙親委派機制進行加載,如果這個時候,我定義了一個惡意java.lang.Object類,在沒有雙親委派機制的情況下,可能會對jvm產生安全風險,

雙親委派機制存在的意義,就是為了防止findClass與defineclass生成的Class物件覆寫掉標準類別庫中的基礎類,避免產生安全風險,


四、如何自定義類加載器

我們整理ClassLoader里面的流程

  1. loadclass:雙親委派機制,子加載器委托父加載器加載,父加載器都加載失敗時,子加載器通過findclass自行加載
  2. findclass:當前類加載器根據路徑以及class檔案名稱加載位元組碼,從class檔案中讀取位元組陣列,然后使用defineClass
  3. defineclass:根據位元組陣列,回傳Class物件

我們在ClassLoader里面找到findClass方法,發現該方法直接拋出例外,應該是留給子類實作的,

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

到這里,我們應該明白,loadClass方法使用了模版方法模式,主線邏輯是雙親委派,但如何將class檔案轉化為Class物件的步驟,已經交由子類去實作,對模版方法模式不熟悉的同學,可以先參考我的另外一篇文章模版方法模式

其實原始碼中,已經有一個自定義類加載的樣例代碼,在注釋中:

      class NetworkClassLoader extends ClassLoader {
          String host;
          int port;
 
          public Class findClass(String name) {
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);
          }
 
          private byte[] loadClassData(String name) {
              // load the class data from the connection
             
          }
      }

看得出來,如果我們需要自定義類加載器,只需要繼承ClassLoader,并且重寫findClass方法即可,

現在有一個簡單的樣例,class檔案依然在檔案目錄中:

package com.yang.testClassLoader;

import sun.misc.Launcher;

import java.io.*;

public class MyClassLoader extends ClassLoader {

    /**
     * 類加載路徑,不包含檔案名
     */
    private String path;


    public MyClassLoader(String path) {
        super();
        this.path = path;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = getBytesFromClass(name);
        assert bytes != null;
        //讀取位元組陣列,轉化為Class物件
        return defineClass(name, bytes, 0, bytes.length);
    }

    //讀取class檔案,轉化為位元組陣列
    private byte[] getBytesFromClass(String name) {
        String absolutePath = path + "/" + name + ".class";
        FileInputStream fis = null;
        ByteArrayOutputStream bos = null;
        try {
            fis = new FileInputStream(new File(absolutePath));
            bos = new ByteArrayOutputStream();
            byte[] temp = new byte[1024];
            int len;
            while ((len = fis.read(temp)) != -1) {
                bos.write(temp, 0, len);
            }
            return bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != bos) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoader classLoader = new MyClassLoader("C://develop");
        Class test = classLoader.loadClass("Student");
        test.newInstance();
    }
}

Student類:

public class Student {
    public Student() {
        System.out.println("student classloader is" + this.getClass().getClassLoader().toString());
    }
}

注意,這個Student類千萬不要加包名,idea報錯不管他即可,然后使用javac Student.java編譯該類,將生成的class檔案復制到c://develop下即可,

運行MyClassLoader的main方法后,可以看到輸出:

看得出來,Student.class確實是被我們自定義的類加載器給加載了,


五、雙親委派機制能被破壞嗎

從上面的自定義類加載器的內容中,我們應該可以猜到了,破壞雙親委派直接重寫loadClass方法就完事了,事實上,我們確實可以重寫loadClass方法,畢竟這個方法沒有被final修飾,雙親委派既然有好處,為什么jdk對loadClass開放重寫呢?這要從雙親委派引入的時間來看:

雙親委派模型是在JDK1.2之后才被引入的,而類加載器和抽象類java.lang.ClassLoader則在JDK1.0時代就已經存在,面對已經存在的用戶自定義類加載器的實作代碼,Java設計者引入雙親委派模型時不得不做出一些妥協,在此之前,用戶去繼承java.lang.ClassLoader的唯一目的就是為了重寫loadClass()方法,jdk為了向前兼容,不得已開放對loadClass的重寫操作,

當然,破壞也不止這一次,jdbc與tomcat也破壞了雙親委派,


六、JDBC對雙親委派的破壞

還記得,我們第一次學jdbc的時候,是怎么連接資料庫的嗎?

先參考一個mysql-connector-java的jar包,這里的版本是5.0.8

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.0.8</version>
        </dependency>
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

這段代碼,真的是勾起了我好多回憶啊~ 想起了那年在夕陽下奔跑的時光,那是我逝去的青春

首先要說明的是,該版本的jdbc并沒有去打破雙親委派,或者說jdbc4.0前沒有破壞雙親委派

資料庫這么多,jdk為了統一管理資料庫驅動,在java.sql下定義了Driver介面,具體的實作由資料庫廠商去做,

mysql對Driver介面的實作類是com.mysql.jdbc.Driver類,位于我們新引入的jar包中,

我們進入Class.forName中,發現最侄訓使用應用類加載器去加載com.mysql.jdbc.Driver類,

而該Driver位于引入的jar包中,確實是應該被應用類加載器加載,

接著進入到com.mysql.jdbc包下的Driver類中,它實作了rt.jar中的java.sql.Driver介面,

Class.forName會初始化該類,初始化的時候會執行靜態方法,

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            //將mysql的Driver注冊進驅動管理器中
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

所以,整個程序是:

  1. Class.forName會使用應用類加載器加載Driver實作類
  2. 加載Driver實作類需要執行靜態方法,即將mysql的Driver注冊進驅動管理器中,那么此時需要加載DriverManager類
  3. 應用類加載器去加載DriverManager類,而DriverManager位于rt.jar中,便一直向上委托到啟動類加載器完成加載

這個程序確實沒有破壞雙親委派

那么jdbc4.0后的情況呢?

為了使用該特性,我們需要引入高版本的mysql-connector-java,這里引入的版本是5.1.8

此時完全可以拋棄第一行的Class.forName陳述句了,使用以下陳述句來進行實驗

        Enumeration<Driver> en = DriverManager.getDrivers();
        while (en.hasMoreElements()) {
            java.sql.Driver driver = en.nextElement();
            System.out.println(driver);
        }

輸出為:

看來記憶體中已經存在mysql的Driver了,這到底是怎么做的呢?

應用類加載器逐層委托到啟動類加載器去加載DriverManager時,會同時執行它的靜態方法

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

loadInitialDrivers內部核心的代碼這有這兩句

    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    Iterator<Driver> driversIterator = loadedDrivers.iterator();

看到ServiceLoader,大家想到了什么,這不是jdk spi機制的核心嗎?

spi機制在我的這篇文章SpringBoot的自動裝配原理、自定義starter與spi機制,一網打盡有詳細的一個介紹,并且對比了SpringBoot與JDK中spi機制的異同,

既然使用到了spi機制,那么mysql-connector-java的jar包在META-INF目錄下必然有services目錄,內容如下,

啟動類加載DriverManager,之后需要通過spi機制去加載jar包中的Driver類,而該Driver理應被應用類加載器加載,這個時候就需要啟動類加載器去通知應用類加載器,這明顯違背了雙親委派機制

那么,啟動類加載器是怎么去通知應用類加載器的呢?

我們繼續進入到ServiceLoader.load方法中

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

Thread.currentThread().getContextClassLoader()是執行緒背景關系類加載器,看來最終使用的是執行緒背景關系類加載器去加載的Driver實作類,

而在sun.misc.Launcher類中,將應用類加載器設定進了執行緒背景關系類加載器中,所以可以理解為,通過執行緒背景關系類加載器,我們可以拿到應用類加載器的參考,

    public Launcher() {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        Thread.currentThread().setContextClassLoader(this.loader);
    }

在jdbc4.0的情況下,梳理一下整個程序:

  1. 應用類加載器逐層委托到啟動類加載器去加載DriverManager類
  2. 啟動類加載器加載DriverManager類時,會執行其靜態方法,即通過spi機制去加載jar包中的Driver實作類
  3. 此時啟動類加載器需要委托應用類加載器加載Driver實作類,具體做法是通過執行緒背景關系類加載器拿到應用類加載器的參考

確實是破壞了雙親委派!


七、Tomcat對雙親委派的破壞

tomcat有兩個最基礎的知識點,一個是應用打包放在webapps目錄下就可以運行,另外一個是修改jsp會實時生效,

那這里拋出幾個問題,來猜想一下Tomcat中類加載器的一個結構,

(1)jsp實時生效是怎么做的?

首先,在jvm中,如何去確定類的唯一性呢?是由類加載器實體+全限定名一起確定的,全限定名相同,類加載器不同,則會被認定為不同的類,

jsp檔案被修改后,會被重新編譯成Servlet,全限定名肯定是不變的,如果這個時候不去卸載加載該Servlet的類加載器,那么新jsp是無論如何都不會被加載進來的,因此,我們可以得知,每一個jsp檔案都會對應一個類加載器實體

(2)每個webapps下的應用依賴的類別庫是否會互相影響?

顯然是不會影響的,應用A依賴低版本的Spring,而應用B依賴高版本的Spring,都是允許的,雖然Spring的版本不同,但某些類的全限定名是完全一致的,如果應用A與應用B采用同一個類加載器,是不會允許Spring版本不一樣的,這里,我們猜想webapps下的每一個應用都會對應一個不同的類加載器實體,用以保持應用間的隔離,

從以上的兩個問題,我們可以了解到:每一個jsp(或者說servlet)都對應一個不同的類加載器實體,每個webapp應用也是,

其實,tomcat5版本(以下如果沒有另外宣告版本,那么都是以該版本為例)的類加載器結構為:

其中各個加載器加載的范圍為:

  • Common ClassLoader:主要加載common目錄下的資源
  • Catalina ClassLoader:主要加載server目錄下的資源
  • Shared ClassLoader:主要加載shared目錄下的資源
  • Webapp ClassLoader:每一個應用會對應與該型別的一個實體,主要加載該應用下的WEB-INF下的資源
  • JasperLoader:每一個jsp檔案會對應于該型別的一個實體,就是為了修改jsp能及時生效

(前三個類加載器在tomcat6中已經合并了,合并之后的加載器加載lib目錄下的資源)

它們在Bootstrap類中有過宣告:

    protected ClassLoader commonLoader = null;
    protected ClassLoader catalinaLoader = null;
    protected ClassLoader sharedLoader = null;

    private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

其中createClassLoader方法會從container\catalina\src\conf\catalina.properties配置中讀取每個加載器加載的范圍:

common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar

server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar

shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar

catalina.home是安裝目錄,catalina.base是每個tomcat實體的作業目錄,在只用一個tomcat的情況下,兩個目錄是一樣的,

在了解了加載器的型別與范圍之后,那么tomcat到底是怎么打破雙親委派機制的呢?

前面說過,雙親委派機制被定義在ClassLoader中的loadClass方法中,如果某個自定義的類加載想要打破雙親委派,那么重新loadClass方法即可,

Tomcat中的WebappClassLoader就是自定義類加載器,它的loadClass方法為:

    public Class loadClass(String name) throws ClassNotFoundException {
        return (loadClass(name, false));
    }
    
    public Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException {

        if (log.isDebugEnabled())
            log.debug("loadClass(" + name + ", " + resolve + ")");
        Class clazz = null;

        // Log access to stopped classloader
        if (!started) {
            try {
                throw new IllegalStateException();
            } catch (IllegalStateException e) {
                log.info(sm.getString("webappClassLoader.stopped", name), e);
            }
        }

        //1、從自己的本地快取中查找,本地快取的資料結構為ResourceEntry
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }

        //2、從jvm的快取中查找
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }

        //3、如果快取中都找不到,則利用系統類加載器加載
        try {
            clazz = system.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        if (securityManager != null) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                try {
                    securityManager.checkPackageAccess(name.substring(0,i));
                } catch (SecurityException se) {
                    String error = "Security Violation, attempt to use " +
                        "Restricted Class: " + name;
                    log.info(error, se);
                    throw new ClassNotFoundException(error, se);
                }
            }
        }

        boolean delegateLoad = delegate || filter(name);

        //4、開啟代理的話,則使用父加載器加載
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }

        //5、自行加載
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            ;
        }

        //如果自己也加載不了,那就只能讓父加載器加載了
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }

        throw new ClassNotFoundException(name);
    }

loadClass內部的邏輯整理如下:

  1. 先從WebappClassLoader的ResourceEntry快取中查找
  2. 從jvm快取中查找,比如去元資料區查找
  3. 利用系統類(應用類)加載器加載,避免webapp中的類覆寫掉標準類別庫中的類,
  4. 開啟代理的話,則使用父加載器加載,這個默認沒開啟的,
  5. webappClassLoader自行去加載
  6. 自己也沒加載成功的話,最后只能讓父加載器去加載

這里有一個問題,對于一些非基礎類別庫,為什么要先讓webappClassLoader先去加載呢?

假設應用a依賴1.0版本的x.jar,而應用b依賴2.0版本的x.jar,為了保證兩個應用的隔離性,首先要做的就是保證兩個應用各自對應不同的webappClassLoader實體,如果這兩個webappClassLoader實體在加載x.jar的時候,直接向上委托,那么最終只會加載一個版本的x.jar,

從上面,我們可以了解到:

對于一些標準類別庫中的類,比如Object類,會讓系統類加載器加載,然后一直委托到啟動類加載器,這個程序是沒有違背雙親委派的

而對于webapp中獨有的類,則是webappClassLoader自行去加載,加載失敗才讓父加載器加載,明顯是違背雙親委派的,


八、總結

雙親委派機制,核心是子加載器委托父加載器,能夠避免java核心類別庫被篡改,增加了安全性,

但發展會帶來創新,創新就會帶來變革,jdbc與tomcat打破了這個自古相傳的機制,

在jdbc中,父加載器委托子加載器,即利用執行緒背景關系類加載器,讓啟動類加載器得以委托應用類加載器,去加載jar中的資料庫驅動,

在tomcat中,子加載器優先于父加載器加載,即為了實作各個webapp的隔離性,webappClassLoader會先于父加載器加載,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298103.html

標籤:java

上一篇:老寇云-java技術堆疊進階-武俠篇-位運算之異或運算

下一篇:java基礎-陣列(回顧)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more