主頁 > 後端開發 > Java8特性詳解 lambda運算式(三):原理篇

Java8特性詳解 lambda運算式(三):原理篇

2021-12-15 06:16:14 後端開發

?

Java為什么需要lambda運算式?

能夠提升代碼簡潔性、提高代碼可讀性,

例如,在平時的開發程序中,把一個串列轉換成另一個串列或map等等這樣的轉換操作是一種常見需求,
在沒有lambda之前通常都是這樣實作的,

List<Long> idList = Arrays.asList(1L, 2L, 3L);
List<Person> personList = new ArrayList<>();
for (long id : idList) {
    personList.add(getById(id));
}

代碼重復多了之后,大家就會對這種常見代碼進行抽象,形成一些類別庫便于復用,
上面的需求可以抽象成:對一個串列中的每個元素呼叫一個轉換函式轉換并輸出結果串列,

interface Function {
    <T, R> R fun(T input);
}
<T, R> List<R> map(List<T> inputList, Function function) {
    List<R> mappedList = new ArrayList<>();
    for (T t : inputList) {
        mappedList.add(function.fun(t));
    }
    return mappedList;
}

有了這個抽象,最開始的代碼便可以”簡化”成

List<Long> idList = Arrays.asList(1L, 2L, 3L);
List<Person> personList = map(idList, new Function<Long, Person>() {
    @Override
    public Person fun(Long input) {
        return getById(input);
    }
});

雖然實作邏輯少了一些,但是同樣也遺憾地發現,代碼行數還變多了,
因為Java語言中函式并不能作為引數傳遞到方法中,函式只能寄存在一個類中表示,為了能夠把函式作為引數傳遞到方法中,我們被迫使用了匿名內部類實作,需要加相當多的冗余代碼,
在一些支持函式式編程的語言(Functional Programming Language)中(例如Python, Scala, Kotlin等),函式是一等公民,函式可以成為引數傳遞以及作為回傳值回傳,
例如在Kotlin中,上述的代碼可以縮減到很短,代碼只包含關鍵內容,沒有冗余資訊,

val personList = idList.map { id -> getById(id) }

這樣的撰寫效率差距也導致了一部分Java用戶流失到其他語言,不過最終終于在JDK8也提供了Lambda運算式能力,來支持這種函式傳遞,

List<Person> personList = map(idList, input -> getById(input));

Lambda運算式只是匿名內部類的語法糖嗎?

如果要在Java語言中實作lambda運算式,初步觀察,通過javac把這種箭頭語法還原成匿名內部類,就可以輕松實作,因為它們功能基本是等價的(IDEA中經常有提示),

但是匿名內部類有一些缺點,

  1. 每個匿名內部類都會在編譯時創建一個對應的class,并且是有檔案的,因此在運行時不可避免的會有加載、驗證、準備、決議、初始化的類加載程序,
  2. 每次呼叫都會創建一個這個匿名內部類class的實體物件,無論是有狀態的(capturing,從背景關系中捕獲一些變數)還是無狀態(non-capturing)的內部類,

invokedynamic介紹

如果有一種函式參考、指標就好了,但JVM中并沒有函式型別表示,
Java中有表示函式參考的物件嗎,反射中有個Method物件,但它的問題是性能問題,每次執行都會進行安全檢查,且引數都是Object型別,需要boxing等等,

還有其他表示函式參考的方法嗎?MethodHandle,它是在JDK7中與invokedynamic指令等一起提供的新特性,

但直接使用MethodHandle來實作,由于沒有簽名資訊,會遇不能多載的問題,并且MethodHandle的invoke方法性能不一定能保證比位元組碼呼叫好,

invokedynamic出現的背景

JVM上的動態語言(JRuby, Scala等),要實作dynamic typing動態型別,是比較麻煩的,
這里簡單解釋一下什么是dynamic typing,與其相對的是static typing靜態型別,
static typing: 所有變數的型別在編譯時都是確定的,并且會進行型別檢查,
dynamic typing: 變數的型別在編譯時不能確定,只能在運行時才能確定、檢查,

例如如下動態語言的例子,a和b的型別都是未知的,因此a.append(b)這個方法是什么也是未知的,

def add(val a, val b)
    a.append(b)

而在Java中a和b的型別在編譯時就能確定,

SimpleString add(SimpleString a, SimpleString b) {
    return a.append(b);
}

編譯后的位元組碼如下,通過invokevirtual明確呼叫變數a的函式簽名為(LSimpleString;)LSimpleString;的方法,

0: aload_1
1: aload_2
2: invokevirtual #2 // Method SimpleString.append:(LSimpleString;)LSimpleString;
5: areturn

關于方法呼叫的位元組碼指令,JVM中提供了四種,
invokestatic - 呼叫靜態方法
invokeinterface - 呼叫介面方法
invokevirtual - 呼叫實體非介面方法的public方法
invokespecial - 其他的方法呼叫,private,constructor, super
這幾種方法呼叫指令,在編譯的時候就已經明確指定了要呼叫什么樣的方法,且均需要接收一個明確的常量池中的方法的符號參考,并進行型別檢查,是不能隨便傳一個不滿足型別要求的物件來呼叫的,即使傳過來的型別中也恰好有一樣的方法簽名也不行,

invokedynamic功能

這個限制讓JVM上的動態語言實作者感到很艱難,只能暫時通過性能較差的反射等方式實作動態型別,
這說明在位元組碼層面無法支持動態分派,該怎么辦呢,又用到了大家熟悉的”All problems in computer science can be solved by another level of indirection”了,
要實作動態分派,既然不能在編譯時決定,那么我們把這個決策推遲到運行時再決定,由用戶的自定義代碼告訴給JVM要執行什么方法,

在jdk7,Java提供了invokedynamic指令來解決這個問題,同時搭配的還有java.lang.invoke包,
這個指令大部分用戶不太熟悉,因為不像invokestatic等指令,它在Java語言中并沒有和它相關的直接概念,

關鍵的概念有如下幾個

  1. invokedynamic指令: 運行時JVM第一次到這里的時候會進行linkage,會呼叫用戶指定的bootstrap method來決定要執行什么方法,之后便不需要這個決議步驟,這個invokedynamic指令出現的地方也叫做dynamic call site
  2. Bootstrap Method: 用戶可以自己撰寫的方法,實作自己的邏輯最侄訓傳一個CallSite物件,
  3. CallSite: 負責通過getTarget()方法回傳MethodHandle
  4. MethodHandle: MethodHandle表示的是要執行的方法的指標

再串聯起來梳理下

invokedynamic在最開始時處于未鏈接(unlinked)狀態,這時這個指令并不知道要呼叫的目標方法是什么,
當JVM要第一次執行某個地方的invokedynamic指令的時候,invokedynamic必須先進行鏈接(linkage),
鏈接程序通過呼叫一個boostrap method,傳入當前的呼叫相關資訊,bootstrap method會回傳一個CallSite,這個CallSite中包含了MethodHandle的參考,也就是CallSite的target,
invokedynamic指令便鏈接到這個CallSite上,并把所有的呼叫delegate到它當前的targetMethodHandle上,根據target是否需要變換,CallSite可以分為MutableCallSiteConstantCallSiteVolatileCallSite等,可以通過切換target MethodHandle實作動態修改要呼叫的方法,

invokedynamic?

lambda運算式真正是如何實作的

下面直接看一下目前java實作lambda的方式

以下面的代碼為例

public class RunnableTest {
    void run() {
        Function<Integer, Integer> function = input -> input + 1;
        function.apply(1);
    }
}

編譯后通過javap查看生成的位元組碼

void run();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
         5: astore_1
         6: aload_1
         7: iconst_1
         8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: invokeinterface #4,  2            // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
        16: pop
        17: return
      LineNumberTable:
        line 12: 0
        line 13: 6
        line 14: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      18     0  this   Lcom/github/liuzhengyang/invokedyanmic/RunnableTest;
            6      12     1 function   Ljava/util/function/Function;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            6      12     1 function   Ljava/util/function/Function<Ljava/lang/Integer;Ljava/lang/Integer;>;

private static java.lang.Integer lambda$run$0(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #5                  // Method java/lang/Integer.intValue:()I
         4: iconst_1
         5: iadd
         6: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         9: areturn
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0 input   Ljava/lang/Integer;

對應Function<Integer, Integer> function = input -> input + 1;這一行的位元組碼為

0: invokedynamic #2,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
5: astore_1

這里再復習一下invokedynamic的步驟,

  1. JVM第一次決議時,呼叫用戶定義的bootstrap method
  2. bootstrap method會回傳一個CallSite
  3. CallSite中能夠得到MethodHandle,表示方法指標
  4. JVM之后呼叫這里就不再需要重新決議,直接系結到這個CallSite上,呼叫對應的target MethodHandle,并能夠進行inline等呼叫優化

第一行invokedynamic后面有兩個引數,第二個0沒有意義固定為0 第一個引數是#2,指向的是常量池中型別為CONSTANT_InvokeDynamic_info的常量,

#2 = InvokeDynamic      #0:#32         // #0:apply:()Ljava/util/function/Function;

這個常量對應的#0:#32中第二個#32表示的是這個invokedynamic指令對應的動態方法的名字和方法簽名(方法型別)

#32 = NameAndType        #43:#44        // apply:()Ljava/util/function/Function;

第一個#0表示的是bootstrap method在BootstrapMethods表中的索引,在javap結果的最后看到是

BootstrapMethods:
  0: #28 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #29 (Ljava/lang/Object;)Ljava/lang/Object;
      #30 invokestatic com/github/liuzhengyang/invokedyanmic/RunnableTest.lambda$run$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
      #31 (Ljava/lang/Integer;)Ljava/lang/Integer;

再看下BootstrapMethods屬性對應JVM虛擬機規范里的說明,

BootstrapMethods_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 num_bootstrap_methods;
    {   u2 bootstrap_method_ref;
        u2 num_bootstrap_arguments;
        u2 bootstrap_arguments[num_bootstrap_arguments];
    } bootstrap_methods[num_bootstrap_methods];
}

bootstrap_method_ref
The value of the bootstrap_method_ref item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_MethodHandle_info structure

bootstrap_arguments[]
Each entry in the bootstrap_arguments array must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_String_info, CONSTANT_Class_info, CONSTANT_Integer_info, CONSTANT_Long_info, CONSTANT_Float_info, CONSTANT_Double_info, CONSTANT_MethodHandle_info, or CONSTANT_MethodType_info structure

CONSTANT_MethodHandle_info The CONSTANT_MethodHandle_info structure is used to represent a method handle

這個BootstrapMethod屬性可以告訴invokedynamic指令需要的boostrap method的參考以及引數的數量和型別,

28對應的是bootstrap_method_ref,為

#28 = MethodHandle       #6:#40         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

按照JVM規范,BootstrapMethod接收3個標準引數和一些自定義引數,標準引數如下

  1. MethodHandles.$Lookup型別的caller引數,這個物件能夠通過類似反射的方式拿到在執行invokedynamic指令這個環境下能夠調動到的方法,比如其他類的private方法是呼叫不到的,這個引數由JVM來入堆疊
  2. String型別的invokedName引數,表示invokedynamic要實作的方法的名字,在這里是apply,是lambda運算式實作的方法名,這個引數由JVM來入堆疊
  3. MethodType型別的invokedType引數,表示invokedynamic要實作的方法的型別,在這里是()Function,這個引數由JVM來入堆疊

29,#30,#31是可選的自定義引數型別

#29 = MethodType         #41            //  (Ljava/lang/Object;)Ljava/lang/Object;
#30 = MethodHandle       #6:#42         // invokestatic com/github/liuzhengyang/invokedyanmic/RunnableTest.lambda$run$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
#31 = MethodType         #21            //  (Ljava/lang/Integer;)Ljava/lang/Integer;

通過java.lang.invoke.LambdaMetafactory#metafactory的代碼說明下

public static CallSite metafactory(MethodHandles.Lookup caller,
        String invokedName,
        MethodType invokedType,
        MethodType samMethodType,
        MethodHandle implMethod,
        MethodType instantiatedMethodType)

前面三個介紹過了,剩下幾個為
MethodType samMethodType: sam(SingleAbstractMethod)就是#29 = MethodType #41 // (Ljava/lang/Object;)Ljava/lang/Object;,表示要實作的方法物件的型別,不過它沒有泛型資訊,(Ljava/lang/Object;)Ljava/lang/Object;
MethodHandle implMethod: 真正要執行的方法的位置,這里是com.github.liuzhengyang.invokedyanmic.Runnable.lambda$run$0(Integer)Integer/invokeStatic,這里是javac生成的一個對lambda解語法糖之后的方法,后面進行介紹
MethodType instantiatedMethodType: 和samMethod基本一樣,不過會包含泛型資訊,(Ljava/lang/Integer;)Ljava/lang/Integer;

private static java.lang.Integer lambda$run$0(java.lang.Integer);這個方法是有javac把lambda運算式desugar解語法糖生成的方法,如果lambda運算式用到了背景關系變數,則為有狀態的,這個運算式也叫做capturing-lambda,會把變數作為這個生成方法的引數傳進來,沒有狀態則為non-capturing,
另外如果使用的是java8的MethodReference,例如Main::run這種語法則說明有可以直接呼叫的方法,就不需要再生成一個中間方法,

繼續看5: astore_1這條指令,表示把當前運算元堆疊的物件參考保存到index為1的區域變數表中,即賦值給了function變數,
說明前面執行完invokedynamic #2, 0 后,在運算元堆疊中插入了一個型別為Function的物件,
這里的程序需要繼續看一下LambdaMetafactory#metafactory的實作,

mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                        invokedName, samMethodType,
                                        implMethod, instantiatedMethodType,
                                        false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();

創建了一個InnerClassLambdaMetafactory,然后呼叫buildCallSite回傳CallSite

看一下InnerClassLambdaMetafactory是做什么的: Lambda metafactory implementation which dynamically creates an inner-class-like class per lambda callsite.

怎么回事!饒了一大圈還是創建了一個inner class!先不要慌,先看完,最后分析下和普通inner class的區別,

創建InnerClassLambdaMetafactory的程序大概是引數的一些賦值和初始化等
再看buildCallSite,這個復雜一些,方法描述說明為Build the CallSite. Generate a class file which implements the functional interface, define the class, if there are no parameters create an instance of the class which the CallSite will return, otherwise, generate handles which will call the class' constructor.

創建一個實作functional interface的的class檔案,define這個class,如果是沒有引數non-capturing型別的創建一個類實體,CallSite可以固定回傳這個實體,否則有狀態,CallSite每次都要通過建構式來生成新物件,
這里相比普通的InnerClass,有一個記憶體優化,無狀態就使用一個物件,

方法實作的第一步是呼叫spinInnerClass(),通過ASM生成一個function interface的實作類位元組碼并且進行類加載回傳,

只保留關鍵代碼
cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, lambdaClassName, null, JAVA_LANG_OBJECT, interfaces);
for (int i = 0; i < argDescs.length; i++) {
    FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argDescs[i], null, null);
    fv.visitEnd();
}
generateConstructor();
if (invokedType.parameterCount() != 0) {
    generateFactory();
}
// Forward the SAM method
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, samMethodType.toMethodDescriptorString(), null, null);
mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
new ForwardingMethodGenerator(mv).generate(samMethodType);

byte[] classBytes = cw.toByteArray();

return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);

生成方法為

  1. 宣告要實作的介面
  2. 創建保存引數用的各個欄位
  3. 生成建構式,如果有引數,則生成一個static Factory方法
  4. 實作function interface里的要實作的方法,forward到implMethodName上,也就是javac生成的方法或者MethodReference指向的方法
  5. 生成完畢,通過ClassWrite.toByteArray拿到class位元組碼陣列
  6. 通過UNSAFE.defineAnonymousClass(targetClass, classBytes, null) define這個內部類class,這里的defineAnonymousClass比較特殊,它創建出來的匿名類會掛載到targetClass這個宿主類上,然后可以用宿主類的類加載器加載這個類,但是不會但是并不會放到SystemDirectory里,SystemDirectory是類加載器物件+類名字到kclass地址的映射,沒有放到這個Directory里,就可以重復加載了,來方便實作一些動態語言的功能,并且能夠防止一些記憶體泄露情況,

這些比較抽象,直觀的看一下生成的結果

// $FF: synthetic class
final class RunnableTest$Lambda$1 implements Function {
    private RunnableTest$Lambda$1() {
    }

    @Hidden
    public Object apply(Object var1) {
        return RunnableTest.lambda$run$0((Integer)var1);
    }
}

如果有引數的情況呢,例如從外部類中使用了一個非靜態欄位,并使用了一個外部區域變數

private int a;
void run() {
    int b = 0;
    Function<Integer, Integer> function = input -> input + 1 + a + b;
    function.apply(1);
}

對應的結果為

final class RunnableTest$Lambda$1 implements Function {
    private final RunnableTest arg$1;
    private final int arg$2;

    private RunnableTest$Lambda$1(RunnableTest var1, int var2) {
        this.arg$1 = var1;
        this.arg$2 = var2;
    }

    private static Function get$Lambda(RunnableTest var0, int var1) {
        return new RunnableTest$Lambda$1(var0, var1);
    }

    @Hidden
    public Object apply(Object var1) {
        return this.arg$1.lambda$run$0(this.arg$2, (Integer)var1);
    }
}

創建完inner class之后,就是生成需要的CallSite了, 如果沒有引數,則生成這個inner class的一個function interface物件示例,創建一個固定回傳這個物件的MethodHandle,再包裝成ConstantCallSite回傳,
如果有引數,則回傳一個需要每次呼叫Factory方法產生function interface的物件實體的MethodHandle,包裝成ConstantCallSite回傳,

這樣就完成了bootstrap的程序,invokedynamic鏈接完之后,后面的呼叫就直接呼叫到對應的MethodHandle了,具體是實作就是回傳固定的內部類物件,或每次創建新內部類物件,

再次對比通過invokedynamic相對于直接匿名內部類語法糖的優勢

我們再想一下,Java8實作這一套騷操作的原因是什么, 既然lambda運算式又不需要什么動態分派(調動哪個方法是明確的), 為什么要用invokedynamic呢?
JVM虛擬機的一個基本保證就是低版本的class檔案也是能夠在高版本的JVM上運行的,并且JVM虛擬機通過版本升級,是在不斷優化和提升性能的,

直接轉換成內部類實作,固然簡單,但編譯后的二進制位元組碼(包括第三方jar包等)內容就固定了,實作固定為創建內部類物件+invoke{virtual, static, special, interface}呼叫,
未來提升性能只能靠提升創建類物件、invoke指令呼叫這幾個地方的優化,換個熟悉點的說法就是這里寫死了,
如果通過invokedynamic呢,javac編譯后把足夠的資訊保留了下來,在JVM執行時能夠動態決定如何實作lambda,也就能不斷優化lambda運算式的實作,并保持兼容性,給未來留下了更多可能,

總結

本文是我學習lambda的一些總結,介紹了lambda運算式出現的原因、實作方法以及不同實作思路上的對比, 對lambda知識也只是略看了一些代碼、資料,如有錯誤或不明確的地方還請大家無情指出,

?

微信公眾號【程式員黃小斜】作者是前螞蟻金服Java工程師,專注分享Java技術干貨和求職成長心得,不限于BAT面試,演算法、計算機基礎、資料庫、分布式、spring全家桶、微服務、高并發、JVM、Docker容器,ELK、大資料等,關注后回復【book】領取精選20本Java面試必備精品電子書,

?

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

標籤:Java

上一篇:Redis持久化RDB和AOF的優缺點決議

下一篇:30個類手寫Spring核心原理之AOP代碼織入(5)

標籤雲
其他(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