Bevezetés a Guava Memoizer-be

1. Áttekintés

Ebben az oktatóanyagban a Google Guava könyvtárának emlékeztető funkcióit tárjuk fel.

A memória egy olyan technika, amely elkerüli a számítási szempontból drága függvény ismételt végrehajtását azáltal, hogy a függvény első végrehajtásának eredményét gyorsítótárba helyezi.

1.1. Memória és gyorsítótár

A memória tárolás szempontjából hasonló a gyorsítótárhoz. Mindkét technika megkísérli növelje a hatékonyságot a számítási szempontból drága kódra történő hívások számának csökkentésével.

Azonban, míg a gyorsítótárazás egy általánosabb kifejezés, amely osztályproblémák, objektumok visszakeresése vagy tartalom visszakeresése szintjén foglalkozik a problémával, a memoizálás megoldja a problémát a módszer / függvény végrehajtásának szintjén.

1.2. Guava Memoizer és Guava Cache

A guava támogatja az emlékeztetést és a gyorsítótárat is. A memorizálás argumentum nélküli függvényekre vonatkozik (Támogató) és pontosan egy argumentummal működik (Funkció). Támogató és Funkció itt a Guava funkcionális interfészekre utalunk, amelyek a Java 8 Functional API azonos nevű alfalak közvetlen alosztályai.

A 23.6-os verziótól kezdve a Guava nem támogatja a függvények feljegyzését egynél több argumentummal.

Igény szerint hívhatjuk a memoization API-kat, és meghatározhatunk egy olyan kilakolási házirendet, amely ellenőrzi a memóriában tárolt bejegyzések számát, és megakadályozza a használatban lévő memória ellenőrizetlen növekedését azáltal, hogy kiírnak / eltávolítunk egy bejegyzést a gyorsítótárból, amint az megfelel a házirend feltételének.

A memorizálás a Guava gyorsítótárat használja; A guava gyorsítótárral kapcsolatos további információkért olvassa el a guava gyorsítótár cikkünket.

2. Támogató Memoizálás

Két módszer létezik a Beszállítók osztály, amely lehetővé teszi a memoázást: emlékezzen, és memoizeWithExpiration.

Amikor a memoizált módszert akarjuk végrehajtani, egyszerűen felhívhatjuk a kap módszer a visszaküldött Támogató. Attól függően, hogy a módszer visszatérési értéke létezik-e a memóriában, a kap A metódus vagy visszaadja a memóriában lévő értéket, vagy végrehajtja az emlékeztető módszert, és a visszatérési értéket továbbítja a hívónak.

Fedezzük fel a Támogató’Emlékeztető.

2.1. Támogató Memoizálás kilakoltatás nélkül

Használhatjuk a Beszállítókemlékezzen módszert, és adja meg a delegáltakat Támogató mint módszer referencia:

Szállító memoizedSupplier = Beszállítók.memoize (CostlySupplier :: generateBigNumber);

Mivel nem írtunk ki kilakoltatási szabályzatot, egyszer a kap metódust hívják meg, a visszaadott érték megmarad a memóriában, amíg a Java alkalmazás még fut. Bármely hívás kap az első hívás után visszaadja a memóriában szereplő értéket.

2.2. Támogató Memorizálás a kilakoltatással az élettől időig (TTL)

Tegyük fel, hogy csak a visszaadott értéket akarjuk megtartani a Támogató a feljegyzésben egy bizonyos ideig.

Használhatjuk a BeszállítókmemoizeWithExpiration módszert, és adja meg a lejárati időt a megfelelő időegységgel (pl. másodperc, perc), a delegált mellett Támogató:

Szállító memoizedSupplier = Suppliers.memoizeWithExpiration (CostlySupplier :: generatorBigNumber, 5, TimeUnit.SECONDS);

Miután a megadott idő letelt (5 másodperc), a gyorsítótár kiirtja a Támogató emlékezetből és minden későbbi felhívás a kap metódus újra végrehajtódik generálniBigNumber.

Részletesebb információkért kérjük, olvassa el a Javadoc-ot.

2.3. Példa

Szimuláljunk egy számítási szempontból drága nevű módszert generálniBigNumber:

public class CostlySupplier {private static BigInteger geneBigNumber () {try {TimeUnit.SECONDS.sleep (2); } catch (InterruptedException e) {} return new BigInteger ("12345"); }}

A példamódszerünk végrehajtása 2 másodpercet vesz igénybe, majd visszatér a BigInteger eredmény. Feljegyezhetnénk a emlékezzen vagy memoizeWithExpiration API-k.

Az egyszerűség kedvéért kihagyjuk a kilakoltatási politikát:

@Test public void givenMemoizedSupplier_whenGet_thenSubsequentGetsAreFast () {Beszállító memoizedSupplier; memoizedSupplier = Beszállítók.memoize (CostlySupplier :: geneBigNumber); BigInteger várható érték = new BigInteger ("12345"); assertSupplierGetExecutionResultAndDuration (memoizedSupplier, várható érték, 2000D); assertSupplierGetExecutionResultAndDuration (memoizedSupplier, várható érték, 0D); assertSupplierGetExecutionResultAndDuration (memoizedSupplier, várható érték, 0D); } private void assertSupplierGetExecutionResultAndDuration (Szállítói beszállító, T várható érték, duplán várhatóDurationInMs) {Azonnali indítás = Azonnali.now (); T érték = szállító.get (); Long durationInMs = Duration.between (start, Instant.now ()). ToMillis (); dupla marginOfErrorInMs = 100D; assertThat (érték, van (egyenlőTo (várható érték))); assertThat (durationInMs.doubleValue (), is (closeTo (várhatóDurationInMs, marginOfErrorInMs))); }

Az első kap metódus hívása két másodpercet vesz igénybe, amint azt a generálniBigNumber módszer; azonban, későbbi hívások kap() lényegesen gyorsabban fog futni, mivel a generálniBigNumber eredményt feljegyezték.

3. Funkció Memorizálás

Egy olyan módszer memoizálása, amely egyetlen argumentumot vesz fel épít egy LoadingCache térkép segítségével CacheLoader’S tól től módszer, amellyel az építtető rendelkezhet guavai módszerünkkel Funkció.

LoadingCache egyidejű térkép, amelynek értékeit automatikusan betöltötte CacheLoader.CacheLoader a térkép kiszámításával töltse fel a térképet Funkció -ban meghatározott tól től módszer, és a visszatérő értéket a LoadingCache. Részletesebb információkért kérjük, olvassa el a Javadoc-ot.

LoadingCache’Kulcsa az FunkcióArgumentuma / bevitele, míg a térkép értéke az FunkcióVisszatérített értéke:

LoadingCache memo = CacheBuilder.newBuilder () .build (CacheLoader.from (FibonacciSequence :: getFibonacciNumber));

Mivel LoadingCache egyidejű térkép, nem engedélyez semmilyen kulcsot vagy értéket. Ezért biztosítanunk kell, hogy a Funkció nem támogatja a null argumentumként, vagy nem ad vissza null értékeket.

3.1. Funkció Memoizálás a kilakoltatási politikákkal

Különféle Guava Cache kilakoltatási politikáját alkalmazhatjuk, amikor megjegyezzük a Funkció amint azt a Guava Cache cikk 3. szakasza említi.

Például kiiktathatjuk azokat a bejegyzéseket, amelyek 2 másodpercig tétlenek:

LoadingCache memo = CacheBuilder.newBuilder () .expireAfterAccess (2, TimeUnit.SECONDS) .build (CacheLoader.from (Fibonacci :: getFibonacciNumber));

Ezután vessünk egy pillantást két felhasználási esetre Funkció feljegyzés: Fibonacci-sorrend és faktoriális.

3.2. Fibonacci szekvencia példa

Rekurzív módon kiszámolhatunk egy Fibonacci számot egy adott számból n:

public static BigInteger getFibonacciNumber (int n) {if (n == 0) {return BigInteger.ZERO; } else if (n == 1) {return BigInteger.ONE; } else {return getFibonacciNumber (n - 1) .add (getFibonacciNumber (n - 2)); }}

Feljegyzés nélkül, ha a bemeneti érték viszonylag magas, a függvény végrehajtása lassú lesz.

A hatékonyság és a teljesítmény javítása érdekében feljegyezhetjük getFibonacciNumber felhasználásával CacheLoader és CacheBuilder, szükség esetén a kilakoltatási politika meghatározása.

A következő példában eltávolítjuk a legrégebbi bejegyzést, ha a jegyzet mérete eléri a 100 bejegyzést:

public class FibonacciSequence {private static LoadingCache memo = CacheBuilder.newBuilder () .maximumSize (100) .build (CacheLoader.from (FibonacciSequence :: getFibonacciNumber)); public static BigInteger getFibonacciNumber (int n) {if (n == 0) {return BigInteger.ZERO; } else if (n == 1) {return BigInteger.ONE; } else {return memo.getUnchecked (n - 1) .add (memo.getUnchecked (n - 2)); }}}

Itt használjuk getUnchecked metódus, amely visszaadja az értéket, ha létezik, anélkül, hogy egy ellenőrzött kivételt dobna.

Ebben az esetben a megadáskor nem kell kifejezetten kezelnünk a kivételt getFibonacciNumber módszer referencia a CacheLoader’S tól től módszer hívása.

Részletesebb információkért kérjük, olvassa el a Javadoc-ot.

3.3. Faktoriális példa

Ezután van egy másik rekurzív módszerünk, amely kiszámítja az adott bemeneti érték faktoriáját, n:

public static BigInteger getFactorial (int n) {if (n == 0) {return BigInteger.ONE; } else {return BigInteger.valueOf (n) .multiply (getFactorial (n - 1)); }}

Növelhetjük ennek a megvalósításnak a hatékonyságát az emlékeztetés alkalmazásával:

public class Factorial {privát statikus LoadingCache memo = CacheBuilder.newBuilder () .build (CacheLoader.from (Factorial :: getFactorial)); public static BigInteger getFactorial (int n) {if (n == 0) {return BigInteger.ONE; } else {return BigInteger.valueOf (n) .multiply (memo.getUnchecked (n - 1)); }}}

4. Következtetés

Ebben a cikkben azt láttuk, hogy a Guava miként biztosítja az API-kat az emlékeztetés végrehajtásához Támogató és Funkció mód. Megmutattuk azt is, hogy miként adhatjuk meg a tárolt függvény eredményének kilakoltatási politikáját a memóriában.

Mint mindig, a forráskód megtalálható a GitHubon.