深度剖析 -- 时间轮算法(TimingWheel)是如何实现的?

article/2025/7/28 0:55:24

前言

        时间轮的应用场景很多,在 Netty、Akka、Quartz、ZooKeeper 、Kafka等组件中都存在时间轮的踪影。我们下面讲解的时间轮的实现以JRaft中的为例子进行讲解,因为JRaft这部分的代码是参考Netty的,所以大家也可以去Netty中去寻找源码实现。

时间轮用来解决什么问题?

如果一个系统中存在着大量的调度任务,而大量的调度任务如果每一个都使用自己的调度器来管理任务的生命周期的话,浪费cpu的资源并且很低效。

时间轮是一种高效来利用线程资源来进行批量化调度的一种调度模型。把大批量的调度任务全部都绑定到同一个的调度器上面,使用这一个调度器来进行所有任务的管理(manager),触发(trigger)以及运行(runnable)。能够高效的管理各种延时任务,周期任务,通知任务等等。

不过,时间轮调度器的时间精度可能不是很高,对于精度要求特别高的调度任务可能不太适合。因为时间轮算法的精度取决于,时间段“指针”单元的最小粒度大小,比如时间轮的格子是一秒跳一次,那么调度精度小于一秒的任务就无法被时间轮所调度。

时间轮结构

如图,JRaft中时间轮(HashedWheelTimer)是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表(HashedWheelBucket),HashedWheelBucket是一个环形的双向链表,链表中的每一项表示的都是定时任务项(HashedWheelTimeout),其中封装了真正的定时任务(TimerTask)。

时间轮由多个时间格组成,每个时间格代表当前时间轮的基本时间跨度(tickDuration)。时间轮的时间格个数是固定的,可用 wheel.length 来表示。

时间轮还有一个表盘指针(tick),用来表示时间轮当前指针跳动的次数,可以用tickDuration * (tick + 1)来表示下一次到期的任务,需要处理此时间格所对应的 HashedWheelBucket 中的所有任务。

时间轮运行逻辑

时间轮在启动的时候会记录一下当前启动的时间赋值给startTime。时间轮在添加任务的时候首先会计算延迟时间(deadline),比如一个任务的延迟时间为24ms,那么会将当前的时间(currentTime)+24ms-时间轮启动时的时间(startTime)。然后将任务封装成HashedWheelTimeout加入到timeouts队列中,作为缓存。

时间轮在运行的时候会将timeouts中缓存的HashedWheelTimeout任务取10万个出来进行遍历。
然后需要计算出几个参数值:

  1. HashedWheelTimeout的总共延迟的次数:将每个任务的延迟时间(deadline)/tickDuration 计算出tick需要总共跳动的次数;
  2. 计算时间轮round次数:根据计算的需要走的(总次数- 当前tick数量)/ 时间格个数(wheel.length)。比如tickDuration为1ms,时间格个数为20个,那么时间轮走一圈需要20ms,那么添加进一个延时为24ms的数据,如果当前的tick为0,那么计算出的轮数为1,指针没运行一圈就会将round取出来减一,所以需要转动到第二轮之后才可以将轮数round减为0之后才会运行
  3. 计算出该任务需要放置到时间轮(wheel)的槽位,然后加入到槽位链表最后

将timeouts中的数据放置到时间轮wheel中之后,计算出当前时针走到的槽位的位置,并取出槽位中的链表数据,将deadline和当前的时间做对比,运行过期的数据。

源码分析

构造器

public HashedWheelTimer(ThreadFactory threadFactory, long tickDuration, TimeUnit unit, int ticksPerWheel,long maxPendingTimeouts) {if (threadFactory == null) {throw new NullPointerException("threadFactory");}//unit = MILLISECONDSif (unit == null) {throw new NullPointerException("unit");}if (tickDuration <= 0) {throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);}if (ticksPerWheel <= 0) {throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);}// Normalize ticksPerWheel to power of two and initialize the wheel.// 创建一个HashedWheelBucket数组// 创建时间轮基本的数据结构,一个数组。长度为不小于ticksPerWheel的最小2的n次方wheel = createWheel(ticksPerWheel);// 这是一个标示符,用来快速计算任务应该呆的格子。// 我们知道,给定一个deadline的定时任务,其应该呆的格子=deadline%wheel.length.但是%操作是个相对耗时的操作,所以使用一种变通的位运算代替:// 因为一圈的长度为2的n次方,mask = 2^n-1后低位将全部是1,然后deadline&mast == deadline%wheel.length// java中的HashMap在进行hash之后,进行index的hash寻址寻址的算法也是和这个一样的mask = wheel.length - 1;// Convert tickDuration to nanos.//tickDuration传入是1的话,这里会转换成1000000this.tickDuration = unit.toNanos(tickDuration);// Prevent overflow.// 校验是否存在溢出。即指针转动的时间间隔不能太长而导致tickDuration*wheel.length>Long.MAX_VALUEif (this.tickDuration >= Long.MAX_VALUE / wheel.length) {throw new IllegalArgumentException(String.format("tickDuration: %d (expected: 0 < tickDuration in nanos < %d", tickDuration, Long.MAX_VALUE/ wheel.length));}//将worker包装成threadworkerThread = threadFactory.newThread(worker);//maxPendingTimeouts = -1this.maxPendingTimeouts = maxPendingTimeouts;//如果HashedWheelTimer实例太多,那么就会打印一个error日志if (instanceCounter.incrementAndGet() > INSTANCE_COUNT_LIMIT&& warnedTooManyInstances.compareAndSet(false, true)) {reportTooManyInstances();}
}

在这个构造器中有几个细节需要注意:

  1. 调用createWheel方法创建的wheel数组一定是2次方数,比如传入的ticksPerWheel是6,那么初始化的wheel长度一定是8。这样做是为了让mask & tick 来计算出槽位
  2. tickDuration用的是纳秒
  3. 在构造里面并不会里面启动时间轮,而是要等到有第一个任务加入到时间轮的时候才启动。在构造器里面会将工作线程worker封装成workerThread

放入任务到时间轮中

public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {if (task == null) {throw new NullPointerException("task");}if (unit == null) {throw new NullPointerException("unit");}long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {pendingTimeouts.decrementAndGet();throw new RejectedExecutionException("Number of pending timeouts (" + pendingTimeoutsCount+ ") is greater than or equal to maximum allowed pending "+ "timeouts (" + maxPendingTimeouts + ")");}// 如果时间轮没有启动,则启动start();// Add the timeout to the timeout queue which will be processed on the next tick.// During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket.long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;// Guard against overflow.//在delay为正数的情况下,deadline是不可能为负数//如果为负数,那么说明超过了long的最大值if (delay > 0 && deadline < 0) {deadline = Long.MAX_VALUE;}// 这里定时任务不是直接加到对应的格子中,而是先加入到一个队列里,然后等到下一个tick的时候,// 会从队列里取出最多100000个任务加入到指定的格子中HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);//Worker会去处理timeouts队列里面的数据timeouts.add(timeout);return timeout;
}

 

  1. 如果时间轮没有启动,那么就调用start方法启动时间轮,启动时间轮之后会为startTime设置为当前时间
  2. 计算延迟时间deadline
  3. 将task任务封装到HashedWheelTimeout中,然后添加到timeouts队列中进行缓存

start

private final CountDownLatch                                     startTimeInitialized   = new CountDownLatch(1);public void start() {//workerState一开始的时候是0(WORKER_STATE_INIT),然后才会设置为1(WORKER_STATE_STARTED)switch (workerStateUpdater.get(this)) {case WORKER_STATE_INIT://使用cas来获取启动调度的权力,只有竞争到的线程允许来进行实例启动if (workerStateUpdater.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) {//如果成功设置了workerState,那么就调用workerThread线程workerThread.start();}break;case WORKER_STATE_STARTED:break;case WORKER_STATE_SHUTDOWN:throw new IllegalStateException("cannot be started once stopped");default:throw new Error("Invalid WorkerState");}// 等待worker线程初始化时间轮的启动时间// Wait until the startTime is initialized by the worker.while (startTime == 0) {try {//这里使用countDownLauch来确保调度的线程已经被启动startTimeInitialized.await();} catch (InterruptedException ignore) {// Ignore - it will be ready very soon.}}
}

start方法会根据当前的workerState状态来启动时间轮。并且用了startTimeInitialized来控制线程的运行,如果workerThread没有启动起来,那么newTimeout方法会一直阻塞在运行start方法中。如果不阻塞,newTimeout方法会获取不到startTime。

启动时间轮

时间轮的启动在HashedWheelTimer的内部类Worker中。调用workerThread#start方法会调用Worker的run方法启动时间轮。

下面我们看时间轮启动做了什么,下面的分析不考虑任务被取消的情况。

Worker

run

public void run() {// Initialize the startTime.startTime = System.nanoTime();if (startTime == 0) {// We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.startTime = 1;}//HashedWheelTimer的start方法会继续往下运行// Notify the other threads waiting for the initialization at start().startTimeInitialized.countDown();do {//返回的是当前的nanoTime- startTime//也就是返回的是 每 tick 一次的时间间隔final long deadline = waitForNextTick();if (deadline > 0) {//算出时间轮的槽位int idx = (int) (tick & mask);//移除cancelledTimeouts中的bucket// 从bucket中移除timeoutprocessCancelledTasks();HashedWheelBucket bucket = wheel[idx];// 将newTimeout()方法中加入到待处理定时任务队列中的任务加入到指定的格子中transferTimeoutsToBuckets();bucket.expireTimeouts(deadline);tick++;}//    校验如果workerState是started状态,那么就一直循环} while (workerStateUpdater.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);// Fill the unprocessedTimeouts so we can return them from stop() method.for (HashedWheelBucket bucket : wheel) {bucket.clearTimeouts(unprocessedTimeouts);}for (;;) {HashedWheelTimeout timeout = timeouts.poll();if (timeout == null) {break;}//如果有没有被处理的timeout,那么加入到unprocessedTimeouts对列中if (!timeout.isCancelled()) {unprocessedTimeouts.add(timeout);}}//处理被取消的任务processCancelledTasks();
}

  1. 时间轮运行的时候首先会记录一下启动时间(startTime),然后调用startTimeInitialized释放外层的等待线程;
  2. 进入dowhile循环,调用waitForNextTick睡眠等待到下一次的tick指针的跳动,并返回当前时间减去startTime作为deadline
  3. 由于mask= wheel.length -1 ,wheel是2的次方数,所以可以直接用tick & mask 计算出此次在wheel中的槽位
  4. 调用processCancelledTasks将cancelledTimeouts队列中的任务取出来,并将当前的任务从时间轮中移除
  5. 调用transferTimeoutsToBuckets方法将timeouts队列中缓存的数据取出加入到时间轮中
  6. 运行目前指针指向的槽位中的bucket链表数据

时间轮指针跳动

waitForNextTick

//sleep, 直到下次tick到来, 然后返回该次tick和启动时间之间的时长
private long waitForNextTick() {//tickDuration这里是100000//tick表示总tick数long deadline = tickDuration * (tick + 1);for (;;) {final long currentTime = System.nanoTime() - startTime;// 计算需要sleep的时间, 之所以加999999后再除10000000,前面是1所以这里需要减去1,// 才能计算准确,还有通过这里可以看到 其实线程是以睡眠一定的时候再来执行下一个ticket的任务的,//这样如果ticket的间隔设置的太小的话,系统会频繁的睡眠然后启动,//其实感觉影响部分的性能,所以为了更好的利用系统资源步长可以稍微设置大点long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;//sleepTimeMs小于零表示走到了下一个时间轮位置if (sleepTimeMs <= 0) {if (currentTime == Long.MIN_VALUE) {return -Long.MAX_VALUE;} else {return currentTime;}}// Check if we run on windows, as if thats the case we will need// to round the sleepTime as workaround for a bug that only affect// the JVM if it runs on windows.//// See https://github.com/netty/netty/issues/356if (Platform.isWindows()) {sleepTimeMs = sleepTimeMs / 10 * 10;}try {Thread.sleep(sleepTimeMs);} catch (InterruptedException ignored) {if (workerStateUpdater.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {return Long.MIN_VALUE;}}}
}

可以想象一下在时钟的秒钟上面秒与秒之间的时间是需要等待的,那么waitForNextTick这个方法就是根据当前的时间计算出跳动到下个时间的间隔时间,并进行sleep操作,然后返回当前时间距离时间轮启动时间的时间段。

转移任务到时间轮中

在调用时间轮的方法加入任务的时候并没有直接加入到时间轮中,而是缓存到了timeouts队列中,所以在运行的时候需要将timeouts队列中的任务转移到时间轮数据的链表中

transferTimeoutsToBuckets

private void transferTimeoutsToBuckets() {// transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just// adds new timeouts in a loop.// 每次tick只处理10w个任务,以免阻塞worker线程for (int i = 0; i < 100000; i++) {HashedWheelTimeout timeout = timeouts.poll();if (timeout == null) {// all processedbreak;}//已经被取消了;if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {// Was cancelled in the meantime.continue;}//calculated = tick 次数long calculated = timeout.deadline / tickDuration;// 计算剩余的轮数, 只有 timer 走够轮数, 并且到达了 task 所在的 slot, task 才会过期timeout.remainingRounds = (calculated - tick) / wheel.length;//如果任务在timeouts队列里面放久了, 以至于已经过了执行时间, 这个时候就使用当前tick, 也就是放到当前bucket, 此方法调用完后就会被执行final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.算出任务应该插入的 wheel 的 slot, slotIndex = tick 次数 & mask, mask = wheel.length - 1int stopIndex = (int) (ticks & mask);HashedWheelBucket bucket = wheel[stopIndex];//将timeout加入到bucket链表中bucket.addTimeout(timeout);}
}

在这个转移方法中,写死了一个循环,每次都只转移10万个任务。

然后根据HashedWheelTimeout的deadline延迟时间计算出时间轮需要运行多少次才能运行当前的任务,如果当前的任务延迟时间大于时间轮跑一圈所需要的时间,那么就计算需要跑几圈才能到这个任务运行。

最后计算出该任务在时间轮中的槽位,添加到时间轮的链表中。

运行时间轮中的任务

当指针跳到时间轮的槽位的时间,会将槽位的HashedWheelBucket取出来,然后遍历链表,运行其中到期的任务。

expireTimeouts

// 过期并执行格子中的到期任务,tick到该格子的时候,worker线程会调用这个方法
//根据deadline和remainingRounds判断任务是否过期
public void expireTimeouts(long deadline) {HashedWheelTimeout timeout = head;// process all timeouts//遍历格子中的所有定时任务while (timeout != null) {// 先保存next,因为移除后next将被设置为nullHashedWheelTimeout next = timeout.next;if (timeout.remainingRounds <= 0) {//从bucket链表中移除当前timeout,并返回链表中下一个timeoutnext = remove(timeout);//如果timeout的时间小于当前的时间,那么就调用expire执行taskif (timeout.deadline <= deadline) {timeout.expire();} else {//不可能发生的情况,就是说round已经为0了,deadline却>当前槽的deadline// The timeout was placed into a wrong slot. This should never happen.throw new IllegalStateException(String.format("timeout.deadline (%d) > deadline (%d)",timeout.deadline, deadline));}} else if (timeout.isCancelled()) {next = remove(timeout);} else {//因为当前的槽位已经过了,说明已经走了一圈了,把轮数减一timeout.remainingRounds--;}//把指针放置到下一个timeouttimeout = next;}
}

HashedWheelBucket是一个链表,所以我们需要从head节点往下进行遍历。如果链表没有遍历到链表尾部那么就继续往下遍历。

获取的timeout节点节点,如果剩余轮数remainingRounds大于0,那么就说明要到下一圈才能运行,所以将剩余轮数减一;

如果当前剩余轮数小于等于零了,那么就将当前节点从bucket链表中移除,并判断一下当前的时间是否大于timeout的延迟时间,如果是则调用timeout的expire执行任务。

时间轮使用示例

import io.netty.util.HashedWheelTimer;import io.netty.util.Timeout;import io.netty.util.Timer;import io.netty.util.TimerTask;import java.util.concurrent.TimeUnit;public class HashedWheelTimerTest {static class MyTimerTask implements TimerTask{boolean flag;public MyTimerTask(boolean flag){this.flag = flag;}public void run(Timeout timeout) throws Exception {// TODO Auto-generated method stubSystem.out.println("要去数据库删除订单了。。。。");this.flag =false;}}public static void main(String[] argv) {MyTimerTask timerTask = new MyTimerTask(true);Timer timer = new HashedWheelTimer();timer.newTimeout(timerTask, 5, TimeUnit.SECONDS);int i = 1;while(timerTask.flag){try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(i+"秒过去了");i++;}}}


http://chatgpt.dhexx.cn/article/mP6i3kg2.shtml

相关文章

Kafka原理--时间轮(延时操作)

原文网址&#xff1a;Kafka原理--时间轮(延时操作)_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Kafka的时间轮的原理。 Kafka没有延迟队列功能供用户使用&#xff0c;本文介绍的延时操作仅仅是Kafka内部的使用&#xff0c;用户无法使用。 Kafka延时操作 Kafka中存在大量的…

时间轮算法HashedWheelTimer

文章目录 一.HashedWheelTimer是什么?二.能干什么?为什么需要这个东西?优点适用场景 三.怎么用?使用步骤1.引入pom2.使用举例 四.时间轮原理五.使用注意点1.一个HashedWheelTimer对象只有一个worker线程2.每次添加的任务只会执行一次3.时间轮的参数非常重要4.所有的任务都是…

时间轮-理论篇

1. 引言 定时任务再开发过程中无处不在&#xff0c;定时发送消息&#xff0c;定时更新数据库表的状态&#xff0c;Linux系统定时执行脚本等等。这些操作都离不开定时任务&#xff0c;那么这些定时任务是怎么实现的是否又去想过。如果让开发者自己去实现一个定时任务又该怎么实…

定时任务的实现原理:时间轮算法

文章目录 前言时间轮定时使用方式时间轮定时内部原理时间轮定时源码剖析构造方法添加任务工作线程启动工作线程run方法指针跳动将队列任务放入时间轮中链表任务遍历定时任务执行 前言 最近在思考实现定时任务的几种方式&#xff0c;比如 quartz&#xff0c;delay queue&#xf…

时间轮-Java实现篇

在前面的文章《时间轮-理论篇》讲了时间轮的一些理论知识&#xff0c;然后根据理论知识。我们自己来实现一个简单的时间轮。 1. 理论抽象 将时间轮的理论进行抽象&#xff0c;主要有两个方面&#xff1a; 时间轮的转动每一个时间间隔任务的处理&#xff0c;从时间间隔的Buke…

时间轮timewheel算法

时间轮是个不太常见&#xff0c;但在部分场景有较高使用价值的工具。 时间轮常用于延时任务&#xff0c;在Netty、akka、Quartz、Zookeeper等高性能组件中都存在时间轮定时器的踪影。 从定时任务说起 自然界中定时任务无处不在&#xff0c;太阳每天东升西落&#xff0c;候鸟的…

定时器实现原理——时间轮

时间轮 时间轮算法是通过一个时间轮去维护定时任务&#xff0c;按照一定的时间单位对时间轮进行划分刻度。然后根据任务延时计算任务落在该时间轮的第几个刻度上&#xff0c;如果任务时长超出了刻度数量&#xff0c;则需要增加一个参数记录时间轮需要转动的圈数。 简单时间轮…

时间轮算法

一、时间轮算法 1. 时间轮的基本概念 Kafka中存在大量的延时操作&#xff0c;如延时生产、延时消费等&#xff1b;而JDK中自带的 Timer 和 DelayQueue 的插入和删除操作的平均复杂度为 O&#xff08;nlogn&#xff09;&#xff0c;无法满足 Kafka 的高性能要求&#xff0c;因…

时间轮算法(TimingWheel)

时间轮算法的应用非常广泛&#xff0c;在 Dubbo、Netty、Kafka、ZooKeeper、Quartz 的组件中都有时间轮思想的应用&#xff0c;甚至在 Linux 内核中都有用到。 其思想理论基础可以参考论文&#xff1a;http://www.cs.columbia.edu/~nahum/w6998/papers/ton97-timing-wheels.pd…

那些惊艳的算法们(三)—— 时间轮

从定时任务说起 自然界中定时任务无处不在&#xff0c;太阳每天东升西落&#xff0c;候鸟的迁徙&#xff0c;树木的年轮&#xff0c;人们每天按时上班&#xff0c;每个月按时发工资、交房租&#xff0c;四季轮换&#xff0c;潮涨潮落&#xff0c;等等&#xff0c;从某种意义上…

1、时间轮

一、什么是时间轮&#xff1f; 作为一个粗人&#xff0c;咱不扯什么高级的词汇&#xff0c;直接上图&#xff1a; 上面是一张时间轮的示意图&#xff0c;可以看到&#xff0c;这个时间轮就像一个钟表一样&#xff0c;它有刻度&#xff0c;图中画了9个格子&#xff0c;每个格子…

浅谈时间轮算法

浅谈时间轮算法 基于队列的定时任务执行模型缺陷 在计算机世界中&#xff0c;只有待解决的问题变得大规模后&#xff0c;算法的价值才能够最大化的体现。时间轮算法可以将插入和删除操作的时间复杂度都降为 O(1)&#xff0c;在大规模问题下还能够达到非常好的运行效果。 如果…

时间轮(TimingWheel)算法总结

通过阅读篇文章您可以很容易理解平时所使用的开源框架是如何进行任务调度的。而且对于以后业务上碰到需要做时间任务调度的需求&#xff0c;也可以尝试着用实践论算法去实现。 一、时间轮的应用 其实早在1987年&#xff0c;时间轮算法的论文已经发布。论文名称&#xff1a;Hash…

时间轮 (史上最全)

缓存之王 Caffeine 中&#xff0c;涉及到100w级、1000W级、甚至亿级元素的过期问题&#xff0c;如何进行高性能的定时调度&#xff0c;是一个难题。 注&#xff1a; 本文从 对 海量调度任务场景中&#xff0c; 高性能的时间轮算法&#xff0c; 做了一个 系统化、由浅入深的 穿透…

时间轮(Timing Wheel)案例和原理

什么是时间轮(Timing Wheel) 时间轮(Timing Wheel)是George Varghese和Tony Lauck在1996年的论文Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility实现的&#xff0c;它在Linux内核中使用广泛&#xff0c;是Linux内核定时器…

时间轮(TimingWheel)

一、什么是时间轮 时间轮其实就是一种环形的数据结构&#xff0c;可以想象成时钟&#xff0c;分成很多格子&#xff0c;一个格子代表一段时间&#xff08;这个时间越短&#xff0c;Timer的精度越高&#xff09;。并用一个双向链表存储放在该格子上的延时任务&#xff0c;同时一…

高性能定时器3——时间轮

​ 在网络程序中我们通常要处理三种事件&#xff0c;网络I/O事件、信号以及定时事件&#xff0c;我们可以使用I/O复用系统调用&#xff08;select、poll、epoll&#xff09;将这三类事件进行统一处理。我们通常使用定时器来检测一个客户端的活动状态&#xff0c;服务器程序通常…

C#,斐波那契数列(Fibonacci Sequence)的八种算法与源代码

一、莱昂纳多斐波那契&#xff08;Leonardo Fibonacci&#xff09; 斐波那契公元1170年生于意大利比萨&#xff0c;卒于1250年&#xff0c;被人称作“比萨的莱昂纳多”&#xff0c;是一名闻名于欧洲的数学家&#xff0c;其主要的著作有《算盘书》、《实用几何》和《四艺经》等。…

matlab斐波那契数列画图,斐波拉契数列 斐波那契数列 matlab程序

斐波那契数列数列从第3项开始,每一项都等于前两项之和。 例子:数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368 应用: 生活斐波那契 斐波那契数列中的斐波脾气有点大,表面温和到别人误以为很好欺…

快速计算斐波那契数列(Fibonacci数列)

本文最后更新于 619 天前&#xff0c;其中的信息可能已经有所发展或是发生改变。 题目描述 输入一个正整数n&#xff0c;求Fibonacci数列的第n个数。Fibonacci数列的特点&#xff1a;第1,2个数为1,1。从第3个数开始&#xff0c;概述是前面两个数之和。即&#xff1a; 要求输入的…