目录
一、概述
二、Java的LRU算法
1、HashMap
2、LinkedHashMap
三、Android的LRU算法
首页 Java java教程 Java和Android的LRU缓存及实现原理

Java和Android的LRU缓存及实现原理

Feb 20, 2017 am 10:29 AM
android java lru

一、概述

Android提供了LRUCache类,可以方便的使用它来实现LRU算法的缓存。Java提供了LinkedHashMap,可以用该类很方便的实现LRU算法,Java的LRULinkedHashMap就是直接继承了LinkedHashMap,进行了极少的改动后就可以实现LRU算法。

二、Java的LRU算法

Java的LRU算法的基础是LinkedHashMap,LinkedHashMap继承了HashMap,并且在HashMap的基础上进行了一定的改动,以实现LRU算法。

1、HashMap

首先需要说明的是,HashMap将每一个节点信息存储在Entry结构中。Entry中存储了节点对应的key、value、hash信息,同时存储了当前节点的下一个节点的引用。因此Entry是一个单向链表。HashMap的存储结构是一个数组加单向链表的形式。每一个key对应的hashCode,在HashMap的数组中都可以找到一个位置;而如果多个key对应了相同的hashCode,那么他们在数组中对应在相同的位置上,这时,HashMap将把对应的信息放到Entry中,并使用链表连接这些Entry

 

 static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;
        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        public final K getKey() {
            return key;
        }
        public final V getValue() {
            return value;
        }
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }
        public final int hashCode() {
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }
        public final String toString() {
            return getKey() + "=" + getValue();
        }
        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that&#39;s already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }
        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }
登录后复制

下面贴一下HashMap的put方法的代码,并进行分析

  

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
     //以上信息不关心,下面是正常的插入逻辑。
     //首先计算hashCode
        int hash = hash(key);
     //通过计算得到的hashCode,计算出hashCode在数组中的位置
        int i = indexFor(hash, table.length);
     //for循环,找到在HashMap中是否存在一个节点,对应的key与传入的key完全一致。如果存在,说明用户想要替换该key对应的value值,因此直接替换value即可返回。
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
     //逻辑执行到此处,说明HashMap中不存在完全一致的kye.调用addEntry,新建一个节点保存key、value信息,并增加到HashMap中
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
登录后复制

在上面的代码中增加了一些注释,可以对整体有一个了解。下面具体对一些值得分析的点进行说明。

<1> int i = indexFor(hash, table.length);
登录后复制

可以看一下源码:

  

static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }
登录后复制

为什么获得的hashCode(h)要和(length-1)进行按位与运算?这是为了保证去除掉h的高位信息。如果数组大小为8(1000),而计算出的h的值为10(1010),如果直接获取数组的index为10的数据,肯定会抛出数组超出界限异常。所以使用按位与(0111&1010),成功清除掉高位信息,得到2(0010),表示对应数组中index为2的数据。效果与取余相同,但是位运算的效率明显更高。

但是这样有一个问题,如果length为9,获取得length-1信息为8(1000),这样进行位运算,不但不能清除高位数据,得到的结果肯定不对。所以数组的大小一定有什么特别的地方。通过查看源码,可以发现,HashMap无时无刻不在保证对应的数组个数为2的n次方。

首先在put的时候,调用inflateTable方法。重点在于roundUpToPowerOf2方法,虽然它的内容包含大量的位相关的运算和处理,没有看的很明白,但是注释已经明确了,会保证数组的个数为2的n次方。

private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
登录后复制

其次,在addEntry等其他位置,也会使用(2 * table.length)、table.length << 1等方式,保证数组的个数为2的n次方。

<2> for (Entry<K,V> e = table[i]; e != null; e = e.next)
登录后复制

因为HashMap使用的是数组加链表的形式,所以通过hashCode获取到在数组中的位置后,得到的不是一个Entry,而是一个Entry的链表,一定要循环链表,获取key对应的value。

<3> addEntry(hash, key, value, i);
登录后复制

先判断数组个数是否超出阈值,如果超过,需要增加数组个数。然后会新建一个Entry,并加到数组中。

/**
     * Adds a new entry with the specified key, value and hash code to
     * the specified bucket.  It is the responsibility of this
     * method to resize the table if appropriate.
     *
     * Subclass overrides this to alter the behavior of put method.
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        createEntry(hash, key, value, bucketIndex);
    }
    /**
     * Like addEntry except that this version is used when creating entries
     * as part of Map construction or "pseudo-construction" (cloning,
     * deserialization).  This version needn&#39;t worry about resizing the table.
     *
     * Subclass overrides this to alter the behavior of HashMap(Map),
     * clone, and readObject.
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }
登录后复制

2、LinkedHashMap

LinkedHashMap在HashMap的基础上,进行了修改。首先将Entry由单向链表改成双向链表。增加了before和after两个队Entry的引用。

  private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }
        /**
         * Removes this entry from the linked list.
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }
        /**
         * Inserts this entry before the specified existing entry in the list.
         */
        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
        /**
         * This method is invoked by the superclass whenever the value
         * of a pre-existing entry is read by Map.get or modified by Map.set.
         * If the enclosing Map is access-ordered, it moves the entry
         * to the end of the list; otherwise, it does nothing.
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
        void recordRemoval(HashMap<K,V> m) {
            remove();
        }
    }
登录后复制

同时,LinkedHashMap提供了一个对Entry的引用header(private transient Entry header)。header的作用就是永远只是HashMap中所有成员的头(header.after)和尾(header.before)。这样把HashMap本身的数组加链表的格式进行了修改。在LinkedHashMap中,即保留了HashMap的数组加链表的数据保存格式,同时增加了一套header作为开始标记的双向链表(我们暂且称之为header的双向链表)。LinkedHashMap就是通过header的双向链表来实现LRU算法的。header.after永远指向最近最不常使用的那个节点,删除的话,就是删除这个header.after对应的节点。相对的,header.before指向的就是刚刚使用过的那个节点。

LinkedHashMap并没有提供put方法,但是LinkedHashMap重写了addEntry和createEntry方法,如下:

 /**
     * This override alters behavior of superclass put method. It causes newly
     * allocated entry to get inserted at the end of the linked list and
     * removes the eldest entry if appropriate.
     */
    void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);
        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }
    /**
     * This override differs from addEntry in that it doesn&#39;t resize the
     * table or remove the eldest entry.
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }
登录后复制

HashMap的put方法,调用了addEntry方法;HashMap的addEntry方法又调用了createEntry方法。因此可以把上面的两个方法和HashMap中的内容放到一起,方便分析,形成如下方法:

  

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }
登录后复制

同样,先判断是否超出阈值,超出则增加数组的个数。然后创建Entry对象,并加入到HashMap对应的数组和链表中。与HashMap不同的是LinkedHashMap增加了e.addBefore(header);和removeEntryForKey(eldest.key);这样两个操作。

首先分析一下e.addBefore(header)。其中e是LinkedHashMap.Entry对象,addBefore代码如下,作用就是讲header与当前对象相关联,使当前对象增加到header的双向链表的尾部

(header.before):
    private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
登录后复制

其次是另一个重点,代码如下:

  // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
登录后复制

其中,removeEldestEntry判断是否需要删除最近最不常使用的那个节点。LinkedHashMap中的removeEldestEntry(eldest)方法永远返回false,如果我们要实现LRU算法,就需要重写这个方法,判断在什么情况下,删除最近最不常使用的节点。removeEntryForKey的作用就是将key对应的节点在HashMap的数组加链表结构中删除,源码如下:

  

final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
        return e;
    }
登录后复制

removeEntryForKey是HashMap的方法,对LinkedHashMap中header的双向链表无能为力,而LinkedHashMap又没有重写这个方法,那header的双向链表要如何处理呢。

仔细看一下代码,可以看到在成功删除了HashMap中的节点后,调用了e.recordRemoval(this);方法。这个方法在HashMap中为空,LinkedHashMap的Entry则实现了这个方法。其中remove()方法中的两行代码为双向链表中删除当前节点的标准代码,不解释。

/**
         * Removes this entry from the linked list.
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }void recordRemoval(HashMap<K,V> m) {
            remove();
        }
登录后复制

以上,LinkedHashMap增加节点的代码分析完毕,可以看到完美的将新增的节点放在了header双向链表的末尾。

但是,这样显然是先进先出的算法,而不是最近最不常使用算法。需要在get的时候,更新header双向链表,把刚刚get的节点放到header双向链表的末尾。我们来看看get的源码:

  public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }
登录后复制

代码很短,第一行的getEntry调用的是HashMap的getEntry方法,不需要解释。真正处理header双向链表的代码是e.recordAccess(this)。看一下代码:

    

 /**
         * Removes this entry from the linked list.
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }
        /**
         * Inserts this entry before the specified existing entry in the list.
         */
        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
        /**
         * This method is invoked by the superclass whenever the value
         * of a pre-existing entry is read by Map.get or modified by Map.set.
         * If the enclosing Map is access-ordered, it moves the entry
         * to the end of the list; otherwise, it does nothing.
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }
登录后复制

首先在header双向链表中删除当前节点,再将当前节点添加到header双向链表的末尾。当然,在调用LinkedHashMap的时候,需要将accessOrder设置为true,否则就是FIFO算法。

三、Android的LRU算法

Android同样提供了HashMap和LinkedHashMap,而且总体思路有些类似,但是实现的细节明显不同。而且Android提供的LruCache虽然使用了LinkedHashMap,但是实现的思路并不一样。Java需要重写removeEldestEntry来判断是否删除节点;而Android需要重写LruCache的sizeOf,返回当前节点的大小,Android会根据这个大小判断是否超出了限制,进行调用trimToSize方法清除多余的节点。

Android的sizeOf方法默认返回1,默认的方式是判断HashMap中的数据个数是否超出了设置的阈值。也可以重写sizeOf方法,返回当前节点的大小。Android的safeSizeOf会调用sizeOf方法,其他判断阈值的方法会调用safeSizeOf方法,进行加减操作并判断阈值。进而判断是否需要清除节点。

Java的removeEldestEntry方法,也可以达到同样的效果。Java需要使用者自己提供整个判断的过程,两者思路还是有些区别的。

sizeOf,safeSizeOf不需要说明,而put和get方法,虽然和Java的实现方式不完全一样,但是思路是相同的,也不需要分析。在LruCache中put方法的最后,会调用trimToSize方法,这个方法用于清除超出的节点。它的代码如下:

 

 public void trimToSize(int maxSize)
  {
    while (true)
    {
      Object key;
      Object value;
      synchronized (this) {
        if ((this.size < 0) || ((this.map.isEmpty()) && (this.size != 0))) {
          throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
        }
      if (size <= maxSize) {
        break;
      }
        Map.Entry toEvict = (Map.Entry)this.map.entrySet().iterator().next();
        key = toEvict.getKey();
        value = toEvict.getValue();
        this.map.remove(key);
        this.size -= safeSizeOf(key, value);
        this.evictionCount += 1;
      }
      entryRemoved(true, key, value, null);
    }
  }
登录后复制

重点需要说明的是Map.Entry toEvict = (Map.Entry)this.map.entrySet().iterator().next();这行代码。它前面的代码判断是否需要删除最近最不常使用的节点,后面的代码用于删除具体的节点。这行代码用于获取最近最不常使用的节点。

首先需要说明的问题是,Android的LinkedHashMap和Java的LinkedHashMap在思路上一样,也是使用header保存双向链表。在put和get的时候,会更新对应的节点,保存header.after指向最久没有使用的节点;header.before用于指向刚刚使用过的节点。所以Map.Entry toEvict = (Map.Entry)this.map.entrySet().iterator().next();这行最终肯定是获取header.after节点。下面逐步分析代码,就可以看到是如何实现的了。

首先,map.entrySet(),HashMap定义了这个方法,LinkedHashMap没有重写这个方法。因此调用的是HashMap对应的方法:

  

public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> es = entrySet;
        return (es != null) ? es : (entrySet = new EntrySet());
    }
登录后复制

上面代码不需要细说,new一个EntrySet类的实例。而EntrySet也是在HashMap中定义,LinkedHashMap中没有。

  

private final class EntrySet extends AbstractSet<Entry<K, V>> {
        public Iterator<Entry<K, V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Entry))
                return false;
            Entry<?, ?> e = (Entry<?, ?>) o;
            return containsMapping(e.getKey(), e.getValue());
        }
        public boolean remove(Object o) {
            if (!(o instanceof Entry))
                return false;
            Entry<?, ?> e = (Entry<?, ?>)o;
            return removeMapping(e.getKey(), e.getValue());
        }
        public int size() {
            return size;
        }
        public boolean isEmpty() {
            return size == 0;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
登录后复制

  Iterator> newEntryIterator() { return new EntryIterator(); }
代码中很明显的可以看出,Map.Entry toEvict = (Map.Entry)this.map.entrySet().iterator().next(),就是要调用newEntryIterator().next(),就是调用(new EntryIterator()).next()。而EntryIterator类在LinkedHashMap中是有定义的。

  

private final class EntryIterator
            extends LinkedHashIterator<Map.Entry<K, V>> {
        public final Map.Entry<K, V> next() { return nextEntry(); }
    }
    private abstract class LinkedHashIterator<T> implements Iterator<T> {
        LinkedEntry<K, V> next = header.nxt;
        LinkedEntry<K, V> lastReturned = null;
        int expectedModCount = modCount;
        public final boolean hasNext() {
            return next != header;
        }
        final LinkedEntry<K, V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            LinkedEntry<K, V> e = next;
            if (e == header)
                throw new NoSuchElementException();
            next = e.nxt;
            return lastReturned = e;
        }
        public final void remove() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (lastReturned == null)
                throw new IllegalStateException();
            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
        }
    }
登录后复制

现在可以得到结论,trimToSize中的那行代码得到的就是header.next对应的节点,也就是最近最不常使用的那个节点。

 以上就是Java和Android的LRU缓存及实现原理 的内容,更多相关内容请关注PHP中文网(www.php.cn)!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
突破或从Java 8流返回? 突破或从Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

PHP:网络开发的关键语言 PHP:网络开发的关键语言 Apr 13, 2025 am 12:08 AM

PHP是一种广泛应用于服务器端的脚本语言,特别适合web开发。1.PHP可以嵌入HTML,处理HTTP请求和响应,支持多种数据库。2.PHP用于生成动态网页内容,处理表单数据,访问数据库等,具有强大的社区支持和开源资源。3.PHP是解释型语言,执行过程包括词法分析、语法分析、编译和执行。4.PHP可以与MySQL结合用于用户注册系统等高级应用。5.调试PHP时,可使用error_reporting()和var_dump()等函数。6.优化PHP代码可通过缓存机制、优化数据库查询和使用内置函数。7

PHP与Python:了解差异 PHP与Python:了解差异 Apr 11, 2025 am 12:15 AM

PHP和Python各有优势,选择应基于项目需求。1.PHP适合web开发,语法简单,执行效率高。2.Python适用于数据科学和机器学习,语法简洁,库丰富。

PHP与其他语言:比较 PHP与其他语言:比较 Apr 13, 2025 am 12:19 AM

PHP适合web开发,特别是在快速开发和处理动态内容方面表现出色,但不擅长数据科学和企业级应用。与Python相比,PHP在web开发中更具优势,但在数据科学领域不如Python;与Java相比,PHP在企业级应用中表现较差,但在web开发中更灵活;与JavaScript相比,PHP在后端开发中更简洁,但在前端开发中不如JavaScript。

PHP与Python:核心功能 PHP与Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有优势,适合不同场景。1.PHP适用于web开发,提供内置web服务器和丰富函数库。2.Python适合数据科学和机器学习,语法简洁且有强大标准库。选择时应根据项目需求决定。

PHP的影响:网络开发及以后 PHP的影响:网络开发及以后 Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

Java程序查找胶囊的体积 Java程序查找胶囊的体积 Feb 07, 2025 am 11:37 AM

胶囊是一种三维几何图形,由一个圆柱体和两端各一个半球体组成。胶囊的体积可以通过将圆柱体的体积和两端半球体的体积相加来计算。本教程将讨论如何使用不同的方法在Java中计算给定胶囊的体积。 胶囊体积公式 胶囊体积的公式如下: 胶囊体积 = 圆柱体体积 两个半球体体积 其中, r: 半球体的半径。 h: 圆柱体的高度(不包括半球体)。 例子 1 输入 半径 = 5 单位 高度 = 10 单位 输出 体积 = 1570.8 立方单位 解释 使用公式计算体积: 体积 = π × r2 × h (4

PHP:许多网站的基础 PHP:许多网站的基础 Apr 13, 2025 am 12:07 AM

PHP成为许多网站首选技术栈的原因包括其易用性、强大社区支持和广泛应用。1)易于学习和使用,适合初学者。2)拥有庞大的开发者社区,资源丰富。3)广泛应用于WordPress、Drupal等平台。4)与Web服务器紧密集成,简化开发部署。

See all articles