欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Groovy的規(guī)則腳本引擎實(shí)例解讀

 更新時(shí)間:2023年03月12日 16:04:34   作者:qq_1757537040  
這篇文章主要介紹了Groovy的規(guī)則腳本引擎實(shí)例解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

序言

因?yàn)橹霸陧?xiàng)目中使用看groovy對(duì)業(yè)務(wù)進(jìn)行一些抽象,效果比較好,過(guò)程中踩過(guò)一些坑,所以簡(jiǎn)單記錄分享一下自己如何一步一步去實(shí)現(xiàn)的

為什么用groovy作為規(guī)則引擎

互聯(lián)網(wǎng)時(shí)代隨著業(yè)務(wù)的飛速發(fā)展,迭代和產(chǎn)品接入的速度越來(lái)越快,需要一些靈活的配置。

辦法通常有如下幾個(gè)方面:

1、最為傳統(tǒng)的方式是java程序直接寫死提供幾個(gè)可調(diào)節(jié)的參數(shù)配置然后封裝成為獨(dú)立的業(yè)務(wù)模塊組件,在增加參數(shù)或簡(jiǎn)單調(diào)整規(guī)則后,重新調(diào)上線。

2、使用開源方案,例如drools規(guī)則引擎,此類引擎適合業(yè)務(wù)較復(fù)雜的系統(tǒng)

3、使用動(dòng)態(tài)腳本引擎:groovy,simpleEl,QLExpress

引入規(guī)則腳本對(duì)業(yè)務(wù)進(jìn)行抽象可以大大提升效率,例如:

在貸款審核系統(tǒng)中,貸款的訂單在收單后會(huì)經(jīng)過(guò)多個(gè)流程扭轉(zhuǎn):收單后需要根據(jù)風(fēng)控系統(tǒng)給出結(jié)果決定訂單的流程,而不同的產(chǎn)品訂單的扭轉(zhuǎn)是不一致的,每接入一個(gè)新產(chǎn)品,碼農(nóng)都要寫一堆對(duì)于此產(chǎn)品的流程邏輯;現(xiàn)有的產(chǎn)品規(guī)則也經(jīng)常需要更換。

所以想利用腳本引擎動(dòng)態(tài)解析執(zhí)行,到使用規(guī)則腳本將流程的扭轉(zhuǎn)抽象出來(lái),提升效率

groovy的優(yōu)勢(shì):

1.歷史悠久,使用范圍大,坑少

2.和java兼容性強(qiáng):可以無(wú)縫銜接,即使不懂groovy語(yǔ)法也沒(méi)有關(guān)系

3.語(yǔ)法糖

4.項(xiàng)目周期短,上線時(shí)間急

項(xiàng)目流轉(zhuǎn)的抽象:

因?yàn)椴煌臉I(yè)務(wù)在流轉(zhuǎn)的過(guò)程中對(duì)于邏輯處理是不一致的,我們先考慮一種簡(jiǎn)單的情況:

本身的項(xiàng)目在業(yè)務(wù)上會(huì)對(duì)不同的貸款訂單進(jìn)行流程扭轉(zhuǎn),例如訂單可以從流程a扭轉(zhuǎn)到流程b或者流程c,取決于每一個(gè)Strategy unit(策略單位)的執(zhí)行情況:每個(gè)strategy unit執(zhí)行后都會(huì)返回一個(gè)boolean值,具體邏輯可以自己定義,在這里我們假設(shè):如果滿足所有的strategy unit a的條件(既每個(gè)執(zhí)行單元都返回true),那么訂單就會(huì)扭轉(zhuǎn)到Scenario B;如果滿足所有的strategy unit b的條件那么訂單就會(huì)扭轉(zhuǎn)到scenario c。

那為什么要設(shè)計(jì)成多個(gè)strategy unit呢?是因?yàn)槲业捻?xiàng)目中,為了方便配置,將整個(gè)流程的strategyunit的配置展示在ui上,可讀性強(qiáng),修改時(shí)也只需要修改某一個(gè)unit中的執(zhí)行邏輯

每個(gè)strategy unit執(zhí)行時(shí)依賴的數(shù)據(jù)我們可以抽象成為一個(gè)context,context中包含兩部分?jǐn)?shù)據(jù):

一部分是業(yè)務(wù)上的數(shù)據(jù):例如訂單的產(chǎn)品,訂單依賴的風(fēng)控?cái)?shù)據(jù)等

一部分是規(guī)則執(zhí)行的數(shù)據(jù):包括當(dāng)前執(zhí)行的node、所屬的策略組信息、當(dāng)前的流程、下一個(gè)流程等

這一部分規(guī)則引擎執(zhí)行數(shù)據(jù)的context可以根據(jù)不同的業(yè)務(wù)進(jìn)行設(shè)計(jì),設(shè)計(jì)時(shí)主要考慮斷點(diǎn)重跑,策略組等:比如可以設(shè)計(jì)不同的策略組與產(chǎn)品進(jìn)行關(guān)聯(lián),這一部分業(yè)務(wù)耦合性比較大,本文主要focus在groovy上

可以把Context理解為Strategy Unit的輸入和輸出,Strategy Unit在Groovy中進(jìn)行執(zhí)行,我們可以對(duì)每一個(gè)執(zhí)行的Strategy Unit進(jìn)行可配置化的展示和配置。執(zhí)行過(guò)程中可以根據(jù)context中含有的不同的信息進(jìn)行邏輯判斷,也可以改變context對(duì)象中的值。

基于流程將Groovy與Java的集成

基于上面的設(shè)計(jì),groovy腳本的執(zhí)行本質(zhì)上只是接受context對(duì)象,并且基于context對(duì)象中的關(guān)鍵信息進(jìn)行邏輯判斷,輸出結(jié)果,而結(jié)果也保存在context中。

 先看看Groovy與java集成的方式:

1.GroovyClassLoader

用 Groovy 的 GroovyClassLoader ,它會(huì)動(dòng)態(tài)地加載一個(gè)腳本并執(zhí)行它。GroovyClassLoader是一個(gè)Groovy定制的類裝載器,負(fù)責(zé)解析加載Java類中用到的Groovy類。

2.GroovyShell

GroovyShell允許在Java類中(甚至Groovy類)求任意Groovy表達(dá)式的值。您可使用Binding對(duì)象輸入?yún)?shù)給表達(dá)式,并最終通過(guò)GroovyShell返回Groovy表達(dá)式的計(jì)算結(jié)果。

3.GroovyScriptEngine

GroovyShell多用于推求對(duì)立的腳本或表達(dá)式,如果換成相互關(guān)聯(lián)的多個(gè)腳本,使用GroovyScriptEngine會(huì)更好些。GroovyScriptEngine從您指定的位置(文件系統(tǒng),URL,數(shù)據(jù)庫(kù),等等)加載Groovy腳本,并且隨著腳本變化而重新加載它們。如同GroovyShell一樣,GroovyScriptEngine也允許您傳入?yún)?shù)值,并能返回腳本的值。

現(xiàn)在我們以GroovyClassLoader為例,展示一下如何實(shí)現(xiàn)與java的集成:

例如:我們假設(shè)申請(qǐng)金額大于20000的訂單進(jìn)入流程B

1.在SpringBoot項(xiàng)目中maven中引入

<dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.10</version>
</dependency>

2.定義groovy執(zhí)行的java接口

public interface EngineGroovyModuleRule {
    boolean run(Object context);
}

3.抽象出一個(gè)Groovy模板文件,放在resource下面以便加載:

import com.groovyexample.groovy.*
class %s implements EngineGroovyModuleRule {
    boolean run(Object context){
        %s //業(yè)務(wù)執(zhí)行邏輯:可配置化
    }
}

4.解析groovy的模板文件,可以將模板文件緩存起來(lái),解析通過(guò)spring的PathMatchingResourcePatternResolver進(jìn)行

下面的Strategy Unit這個(gè)String就是具體的業(yè)務(wù)規(guī)則的邏輯,把這一部分的邏輯進(jìn)行一個(gè)配置化

        //解析Groovy模板文件
        ConcurrentHashMap<String,String> concurrentHashMap = new ConcurrentHashMap(128);
        final String path = "classpath*:*.groovy_template";
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Arrays.stream(resolver.getResources(path))
                .parallel()
                .forEach(resource -> {
                    try {
                        String fileName = resource.getFilename();
                        InputStream input = resource.getInputStream();
                        InputStreamReader reader = new InputStreamReader(input);
                        BufferedReader br = new BufferedReader(reader);
                        StringBuilder template = new StringBuilder();
                        for (String line; (line = br.readLine()) != null; ) {
                            template.append(line).append("\n");
                        }
                        concurrentHashMap.put(fileName, template.toString());
                    } catch (Exception e) {
                        log.error("resolve file failed", e);
                    }
                });
        String scriptBuilder = concurrentHashMap.get("ScriptTemplate.groovy_template");
        String scriptClassName = "testGroovy";
        //這一部分String的獲取邏輯進(jìn)行可配置化
        String StrategyLogicUnit = "if(context.amount>=20000){\n" +
                "            context.nextScenario='A'\n" +
                "            return true\n" +
                "        }\n" +
                "        ";
        String fullScript = String.format(scriptBuilder, scriptClassName, StrategyLogicUnit);
    GroovyClassLoader classLoader = new GroovyClassLoader();
    Class<EngineGroovyModuleRule> aClass = classLoader.parseClass(fullScript);
    Context context = new Context();
    context.setAmount(30000);
    try {
        EngineGroovyModuleRule engineGroovyModuleRule = aClass.newInstance();
        log.info("Groovy Script returns:{} "+engineGroovyModuleRule.run(context));
        log.info("Next Scenario is {}"+context.getNextScenario());
    }
    catch (Exception e){
       log.error("error...")
    }

5.執(zhí)行上述代碼:

Groovy Script returns: true
Next Scenario is A

關(guān)鍵的部分是Strategy Unit這個(gè)部分的可配置化,我們是通過(guò)管理端UI上展示不同產(chǎn)品對(duì)應(yīng)的StrategyLogicUnit,并可進(jìn)行CRUD,為了方便配置同時(shí)引進(jìn)了策略組、產(chǎn)品策略復(fù)制關(guān)聯(lián)、一鍵復(fù)制模板等功能。

集成過(guò)程中的坑和性能優(yōu)化

項(xiàng)目在測(cè)試時(shí)就發(fā)現(xiàn)隨著收單的數(shù)量增加,進(jìn)行頻繁的Full GC,測(cè)試環(huán)境復(fù)現(xiàn)后查看日志顯示:

[Full GC (Metadata GC Threshold) [PSYoungGen: 64K->0K(43008K)] 
[ParOldGen: 3479K->3482K(87552K)] 3543K->3482K(130560K), 
[Metaspace: 15031K->15031K(1062912K)], 0.0093409 secs] 
[Times: user=0.03 sys=0.00, real=0.01 secs] 

日志中可以看出是mataspace空間不足,并且無(wú)法被full gc回收。 通過(guò)JVisualVM可以查看具體的情況:

發(fā)現(xiàn)class太多了,有2326個(gè),導(dǎo)致metaspace滿了。我們先回顧一下metaspace ##metaspace和permgen 這是jdk在1.8中才有的東西,并且1.8講將permgen去除了,其中的方法區(qū)移到non-heap中的Metaspace。

 這個(gè)區(qū)域主要存放:存儲(chǔ)類的信息、常量池、方法數(shù)據(jù)、方法代碼等。 分析主要問(wèn)題有兩方面:

問(wèn)題1:Class數(shù)量問(wèn)題:可能是引入groovy導(dǎo)致加載的類過(guò)多了,但實(shí)際上項(xiàng)目只配置了10個(gè)StrategyLogicUnit,不同的訂單執(zhí)行同一個(gè)StrategyLogicUnit時(shí)應(yīng)該對(duì)應(yīng)同一個(gè)class。class的數(shù)量過(guò)于異常。

問(wèn)題2:就算Class數(shù)量過(guò)多,F(xiàn)ull GC為何沒(méi)有辦法回收?

GroovyClassLoader的加載

我們先分析Groovy執(zhí)行的過(guò)程,最關(guān)鍵的代碼是如下幾部分:

 GroovyClassLoader classLoader = new GroovyClassLoader();
 Class<EngineGroovyModuleRule> aClass = classLoader.parseClass(fullScript);
 EngineGroovyModuleRule engineGroovyModuleRule = aClass.newInstance();
engineGroovyModuleRule.run(context)

GroovyClassLoader是一個(gè)定制的類裝載器,在代碼執(zhí)行時(shí)動(dòng)態(tài)加載groovy腳本為java對(duì)象。

大家都知道classloader的雙親委派,我們先來(lái)分析一下這個(gè)GroovyClassloader,看看它的祖先分別是啥:

def cl = this.class.classLoader  
while (cl) {  
    println cl  
    cl = cl.parent  
}  

輸出:

groovy.lang.GroovyClassLoader$InnerLoader@13322f3  
groovy.lang.GroovyClassLoader@127c1db  
org.codehaus.groovy.tools.RootLoader@176db54  
sun.misc.Launcher$AppClassLoader@199d342  
sun.misc.Launcher$ExtClassLoader@6327fd  

從而得出:

    Bootstrap ClassLoader  
             ↑  
sun.misc.Launcher.ExtClassLoader      // 即Extension ClassLoader  
             ↑  
sun.misc.Launcher.AppClassLoader      // 即System ClassLoader  
             ↑  
org.codehaus.groovy.tools.RootLoader  // 以下為User Custom ClassLoader  
             ↑  
groovy.lang.GroovyClassLoader  
             ↑  
groovy.lang.GroovyClassLoader.InnerLoader  

查看關(guān)鍵的GroovyClassLoader.parseClass方法,發(fā)現(xiàn)如下代碼:

    public Class parseClass(String text) throws CompilationFailedException {
        return parseClass(text, "script" + System.currentTimeMillis() +
                Math.abs(text.hashCode()) + ".groovy");
    }
    protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
        InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
            public InnerLoader run() {
                return new InnerLoader(GroovyClassLoader.this);
            }
        });
        return new ClassCollector(loader, unit, su);
    }

這兩處代碼的意思是: groovy每執(zhí)行一次腳本,都會(huì)生成一個(gè)腳本的class對(duì)象,這個(gè)class對(duì)象的名字由 "script" + System.currentTimeMillis() + Math.abs(text.hashCode()組成,對(duì)于問(wèn)題1:每次訂單執(zhí)行同一個(gè)StrategyLogicUnit時(shí),產(chǎn)生的class都不同,每次執(zhí)行規(guī)則腳本都會(huì)產(chǎn)品一個(gè)新的class。

接著看問(wèn)題2InnerLoader部分: groovy每執(zhí)行一次腳本都會(huì)new一個(gè)InnerLoader去加載這個(gè)對(duì)象,而對(duì)于問(wèn)題2,我們可以推測(cè):InnerLoader和腳本對(duì)象都無(wú)法在fullGC的時(shí)候被回收,因此運(yùn)行一段時(shí)間后將PERM占滿,一直觸發(fā)fullGC。

為什么需要有innerLoader呢?

結(jié)合雙親委派模型,由于一個(gè)ClassLoader對(duì)于同一個(gè)名字的類只能加載一次,如果都由GroovyClassLoader加載,那么當(dāng)一個(gè)腳本里定義了C這個(gè)類之后,另外一個(gè)腳本再定義一個(gè)C類的話,GroovyClassLoader就無(wú)法加載了。

由于當(dāng)一個(gè)類的ClassLoader被GC之后,這個(gè)類才能被GC。

如果由GroovyClassLoader加載所有的類,那么只有當(dāng)GroovyClassLoader被GC了,所有這些類才能被GC,而如果用InnerLoader的話,由于編譯完源代碼之后,已經(jīng)沒(méi)有對(duì)它的外部引用,除了它加載的類,所以只要它加載的類沒(méi)有被引用之后,它以及它加載的類就都可以被GC了。

Class回收的條件(摘自《深入理解JVM虛擬機(jī)》)

JVM中的Class只有滿足以下三個(gè)條件,才能被GC回收,也就是該Class被卸載(unload): 

1、該類所有的實(shí)例都已經(jīng)被GC,也就是JVM中不存在該Class的任何實(shí)例。 

2、加載該類的ClassLoader已經(jīng)被GC。 

3、該類的java.lang.Class 

對(duì)象沒(méi)有在任何地方被引用,如不能在任何地方通過(guò)反射訪問(wèn)該類的方法. 

第一點(diǎn)被排除

查看GroovyClassLoader.parseClass()代碼,總結(jié):Groovy會(huì)把腳本編譯為一個(gè)名為Scriptxx的類,這個(gè)腳本類運(yùn)行時(shí)用反射生成一個(gè)實(shí)例并調(diào)用它的MAIN函數(shù)執(zhí)行,這個(gè)動(dòng)作只會(huì)被執(zhí)行一次,在應(yīng)用里面不會(huì)有其他地方引用該類或它生成的實(shí)例;

第二點(diǎn)被排除:

關(guān)于InnerLoader:Groovy專門在編譯每個(gè)腳本時(shí)new一個(gè)InnerLoader就是為了解決GC的問(wèn)題,所以InnerLoader應(yīng)該是獨(dú)立的,并且在應(yīng)用中不會(huì)被引用;

只剩下第三種可能:

該類的Class對(duì)象有被引用,繼續(xù)查看代碼:

    /**
     * sets an entry in the class cache.
     *
     * @param cls the class
     * @see #removeClassCacheEntry(String)
     * @see #getClassCacheEntry(String)
     * @see #clearCache()
     */
    protected void setClassCacheEntry(Class cls) {
        synchronized (classCache) {
            classCache.put(cls.getName(), cls);
        }
    }

可以復(fù)現(xiàn)問(wèn)題并查看原因:具體思路是無(wú)限循環(huán)解析腳本,jmap -clsstat查看classloader的情況,并結(jié)合導(dǎo)出dump查看引用關(guān)系。

所以總結(jié)原因是:每次groovy parse腳本后,會(huì)緩存腳本的Class,下次解析該腳本時(shí),會(huì)優(yōu)先從緩存中讀取。這個(gè)緩存的Map由GroovyClassLoader持有,key是腳本的類名,value是class,class對(duì)象的命名規(guī)則為

"script" + System.currentTimeMillis() + Math.abs(text.hashCode()) + ".groovy"

因此,每次編譯的對(duì)象名都不同,都會(huì)在緩存中添加一個(gè)class對(duì)象,導(dǎo)致class對(duì)象不可釋放,隨著次數(shù)的增加,編譯的class對(duì)象將PERM區(qū)撐滿。

解決方案

大多數(shù)的情況下,Groovy都是編譯后執(zhí)行的,實(shí)際在本次的應(yīng)用場(chǎng)景中,雖然是腳本是以參數(shù)傳入,但其實(shí)大多數(shù)腳本的內(nèi)容是相同的。

解決方案就是在項(xiàng)目啟動(dòng)時(shí)通過(guò)InitializingBean接口對(duì)于 parseClass 后生成的 Class 對(duì)象進(jìn)行緩存,key 為 groovyScript 腳本的md5值,并且在配置端修改配置后可進(jìn)行緩存刷新。 這樣做的好處有兩點(diǎn):

1、解決metaspace爆滿的問(wèn)題

2、因?yàn)椴恍枰谶\(yùn)行時(shí)編譯加載,所以可以加快腳本執(zhí)行的速度

總結(jié)

1.Groovy適合在業(yè)務(wù)變化較多、較快的情況下進(jìn)行一些可配置化的處理

2.它容易上手:其本質(zhì)上也是運(yùn)行在jvm的java代碼,我們?cè)谑褂脮r(shí)需了解清楚它的類加載機(jī)制,對(duì)于內(nèi)存存儲(chǔ)的基礎(chǔ)爛熟于心,并通過(guò)緩存解決一些潛在的問(wèn)題同時(shí)提升性能

3.適合規(guī)則數(shù)量相對(duì)較小的且不會(huì)頻繁更新規(guī)則的規(guī)則引擎。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 圖解Java經(jīng)典算法冒泡選擇插入希爾排序的原理與實(shí)現(xiàn)

    圖解Java經(jīng)典算法冒泡選擇插入希爾排序的原理與實(shí)現(xiàn)

    冒泡排序是一種簡(jiǎn)單的排序算法,它也是一種穩(wěn)定排序算法。其實(shí)現(xiàn)原理是重復(fù)掃描待排序序列,并比較每一對(duì)相鄰的元素,當(dāng)該對(duì)元素順序不正確時(shí)進(jìn)行交換。一直重復(fù)這個(gè)過(guò)程,直到?jīng)]有任何兩個(gè)相鄰元素可以交換,就表明完成了排序
    2022-09-09
  • Java8中的default關(guān)鍵字詳解

    Java8中的default關(guān)鍵字詳解

    這篇文章主要介紹了Java8中的default關(guān)鍵字詳解,在實(shí)現(xiàn)某個(gè)接口的時(shí)候,需要實(shí)現(xiàn)該接口所有的方法,這個(gè)時(shí)候default關(guān)鍵字就派上用場(chǎng)了。通過(guò)default關(guān)鍵字定義的方法,集成該接口的方法不需要去實(shí)現(xiàn)該方法,需要的朋友可以參考下
    2023-08-08
  • Java異常的幾個(gè)謎題_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java異常的幾個(gè)謎題_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    本文給大家收藏整理java異常的幾個(gè)謎題,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-06-06
  • JAVA隨機(jī)打亂數(shù)組順序的方法

    JAVA隨機(jī)打亂數(shù)組順序的方法

    這篇文章主要介紹了JAVA隨機(jī)打亂數(shù)組順序的方法,包含了隨機(jī)數(shù)的應(yīng)用及數(shù)組的排序等操作,是Java操作數(shù)組的典型應(yīng)用,需要的朋友可以參考下
    2014-11-11
  • Java 讀取網(wǎng)絡(luò)圖片存儲(chǔ)到本地并生成縮略圖

    Java 讀取網(wǎng)絡(luò)圖片存儲(chǔ)到本地并生成縮略圖

    用Java做開發(fā)經(jīng)常需要處理圖片。本文就來(lái)看一下如何保存圖片到本地并生成縮略圖
    2021-05-05
  • Java8 將一個(gè)List<T>轉(zhuǎn)為Map<String,T>的操作

    Java8 將一個(gè)List<T>轉(zhuǎn)為Map<String,T>的操作

    這篇文章主要介紹了Java8 將一個(gè)List<T>轉(zhuǎn)為Map<String, T>的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-02-02
  • idea創(chuàng)建spring?boot項(xiàng)目時(shí)javaversion只能選擇17和21解決辦法

    idea創(chuàng)建spring?boot項(xiàng)目時(shí)javaversion只能選擇17和21解決辦法

    這篇文章主要給大家介紹了關(guān)于idea創(chuàng)建spring?boot項(xiàng)目時(shí)javaversion只能選擇17和21的解決辦法,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-01-01
  • 深入Java冒泡排序與選擇排序的區(qū)別詳解

    深入Java冒泡排序與選擇排序的區(qū)別詳解

    本篇文章是對(duì)Java冒泡排序與選擇排序的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • 關(guān)于在Java中反轉(zhuǎn)數(shù)組的4種詳細(xì)方法

    關(guān)于在Java中反轉(zhuǎn)數(shù)組的4種詳細(xì)方法

    這篇文章主要介紹了關(guān)于在Java中反轉(zhuǎn)數(shù)組的4種詳細(xì)方法,數(shù)組是一個(gè)固定長(zhǎng)度的存儲(chǔ)相同數(shù)據(jù)類型的數(shù)據(jù)結(jié)構(gòu),數(shù)組中的元素被存儲(chǔ)在一段連續(xù)的內(nèi)存空間中,今天我們來(lái)學(xué)習(xí)一下如何反轉(zhuǎn)數(shù)組
    2023-05-05
  • 為何Java8需要引入新的日期與時(shí)間庫(kù)

    為何Java8需要引入新的日期與時(shí)間庫(kù)

    這篇文章主要給大家介紹了關(guān)于Java8為什么需要引入新的日期與時(shí)間庫(kù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11

最新評(píng)論