1.st电机库5.0完全开源了。轻车源码这对电机控制软件工程师有何影响?轻车源码
2.可动态配置的Schedule设计
3.JDK成长记7:3张图搞懂HashMap底层原理!
4.《开端》和《源代码》的轻车源码剧情是否有雷同之处?
5.表ç½ç¨åºä»£ç
st电机库5.0完全开源了。这对电机控制软件工程师有何影响?轻车源码
st电机库5.0的全面开源,对电机控制软件工程师来说,轻车源码是轻车源码易语言文件加密源码重大利好。开源意味着可以免费获取完整的轻车源码源代码,使用LL库的轻车源码直观性和便捷性提升编程效率。软件工程师们无需再为获取源代码而担忧,轻车源码只需注册并申请,轻车源码小时内即可收到批准邮件,轻车源码这极大地加快了项目进程。轻车源码百度云分享链接提供了方便的轻车源码访问途径,方便工程师们下载和使用。轻车源码
然而,轻车源码对于电机控制领域的老工程师们而言,开源的冲击尤为显著。伺服行业和电动汽车等高端应用领域要求极高,如电机参数辨识、惯量辨识等复杂功能,这些核心知识难以轻易通过开源代码获取。真正的技术创新往往需要工程师投入大量时间与精力,这些成果不愿公开,因此,开源虽然降低了入门门槛,吸引了更多新人进入电机控制领域,但并未改变高端领域技术壁垒的实质。
开源软件的普及,使得低端需求的市场更加饱和,相应产品价格下滑。而对专业度要求更高的领域,技术门槛依然存在,芯片厂商的开源代码仅能提供基础框架,真正实现高级功能仍需专业工程师深入研究。ST的开源代码,虽能为新入行者提供便利,但真正理解并利用其代码的工程师,相对于只懂得基本FOC的人,已展现出了更高的专业水平。在理解并运用开源代码的过程中,工程师不仅能够提升自身技能,也能对电机控制领域有更深入的理解。
可动态配置的Schedule设计
1.背景
定时任务是实际开发中常见的一类功能,例如每天早上凌晨对前一天的注册用户数量、渠道来源进行统计,并以邮件报表的地图采集标注源码方式发送给相关人员。相信这样的需求,每个开发伙伴都处理过。
你可以使用Linux的Crontab启动应用程序进行处理,或者直接使用Spring的Schedule对任务进行调度,还可以使用分布式调度系统,如果xxl-job等。相信你已经轻车熟路、习以为常。直到有一天你接到了一个新需求:
1.新建一组任务,周期性的执行指定SQL并将结果以邮件的方式发送给特定人群;2.比较方便的对任务进行管理,比如启动、停止,修改调度周期等;3.动态添加、移除任务,不需要频繁的修改、发布程序;
停顿几分钟,简单思考一下,有哪几种实现思路呢?
本篇文章将从以下几部分进行讨论:
1.SpringSchedule配置和使用。首先我们将介绍Demo的骨架,并基于Spring-Boot完成Schedule的配置;2.数据库定时轮询方案。使用SpringSchedule定时轮询数据库,并执行相应任务。在执行任务策略中,我们将尝试同步和异步执行两种方案,并对其优缺点进行分析;3.基于TaskScheduler动态配置方案。基于数据库轮询或配置中心两种方案动态的对SpringTaskScheduler进行配置,以实现动态管理任务的目的;4.我们进入分布式环境,利用多个冗余节点解决系统高可用问题,同时使用分布式锁保障只会有一个任务同时执行;
2.SpringScheduleSpringBoot上的Schedule的使用非常简单,无需增加新的依赖,只需简单配置即可。
1.使用@EnableScheduling启用Schedule;2.在要调度的方法上增加@Scheduled;
首先,我们需要在启动类上添加@EnableScheduling注解,该注解将启用SchedulingConfiguration配置类帮我们完成最基本的配置。
@SpringBootApplication@EnableSchedulingpublicclassConfigurableScheduleDemoApplication{ publicstaticvoidmain(String[]args){ SpringApplication.run(ConfigurableScheduleDemoApplication.class,args);}}启用Schedule配置之后,在需要被调度的方法上增加@Scheduled注解。
@ServicepublicclassSpringScheduleService{ @AutowiredprivateTaskServicetaskService;@Scheduled(fixedDelay=5*,initialDelay=)publicvoidrunTask(){ TaskConfigtaskConfig=TaskConfig.builder().name("SpringDefaultSchedule").build();this.taskService.runTask(taskConfig);}}runTask任务延迟1s进行初始化,并以5s为间隔进行调度。
Scheduled注解类的详细配置如下:
配置含义样例cronlinuxcrontab表达式@Scheduled(cron="*/5****MON-FRI")工作日,每5s调度一次fixedDelay固定间隔,上次运行结束,与下次启动运行,相隔固定时长@Scheduled(fixedDelay=)运行结束后,5S后启动一次调度fixedDelayString与fixedDelay一致fixedRate固定周期,前后两次运行相隔固定的打版公式源码时长@Scheduled(fixedRate=)前后两个任务,间隔5秒fixedRateString与fixedRate一致initialDelay第一次执行,间隔时间@Scheduled(initialDelay=,fixedRate=)第一次执行,延时1秒,以后以5秒为周期进行调度initialDelayString与initialDelay一致环境搭建完成,让我们开始第一个方案。
3.数据库定时轮询使用数据库来管理任务,通过轮询的方案,进行动态调度。首先,我们看下最简单的方案:串行执行方案。
3.1.串行执行方案整体思路非常简单,流程如下:
主要分如下几步:
1.在应用中启动一个Schedule任务(每1秒调度一次),定时从数据库中获取待执行的任务(状态为可用,下一次执行时间小于当前时间);2.根据数据库的任务配置信息,依次遍历并执行任务;3.任务执行完成后,经过计算获得下一次调度时间,将其写回到数据库;4.等待下一次任务调度。
核心代码如下:
@Scheduled(fixedDelay=,initialDelay=)publicvoidloadAndRunTask(){ Datenow=newDate();//加载需要运行的任务://1.状态为ENABLE//2.下一次运行时间小于当前时间List<TaskDefinitionV2>shouldRunTasks=loadShouldRunTasks(now);//依次遍历待运行任务,执行对于的任务for(TaskDefinitionV2task:shouldRunTasks){ //DoubleCheckif(task.shouldRun(now)){ //执行任务runTask(task);//更新任务的下一次运行时间updateNextRunTime(task,now);}}}方案简单但非常有效,那该方案存在哪些问题呢?最主要的问题就是:任务串行执行,会导致后面任务出现延时运行;同时,下一轮检查也会被delay。
例如,依次加载了待执行任务task1、task2、task3。其中task1耗时5秒,task2耗时5秒,task3耗时1秒,由于三个任务串行执行,task2将延时5秒,task3延时秒;下一轮检查距上次启动相差秒。
究其根本,核心问题是调度线程和运行线程是同一个线程,调度的运行和任务的运行相互影响。
让我们看一个改进方案:并行执行方案。
3.2.并行执行方案整体执行流程如下:
相比之前的方案,新方案引入了线程池,每一个任务对应一个线程池,避免任务间的相互影响;任务在线程池中异步处理,避免了调度线程的延时。具体流程如下:
1.步骤一不变,在应用中启动一个Schedule任务(每1秒调度一次),定时从数据库中获取待执行的任务(状态为可用,下一次执行时间小于当前时间);2.依次遍历任务,仓库管理bs源码将任务提交到专有线程池中异步执行,调度线程直接返回;3.任务在线程池中运行,结束后更新下一次的运行时间;4.调度线程重新从数据库中获取待执行任务,在将任务提交至线程池中,如果有任务正在执行,使用线程池拒绝策略,抛弃最老的任务;
核心代码如下:
Spring调度任务,每1秒运行一次:
@Scheduled(fixedDelay=,initialDelay=)publicvoidloadAndRunTask(){ Datenow=newDate();//加载所有待运行的任务//1.状态为ENABLE//2.下一次运行时间小于当前时间List<TaskDefinitionV2>shouldRunTasks=loadShouldRunTasks(now);//遍历待运行任务for(TaskDefinitionV2task:shouldRunTasks){ //1.根据TaskId获取任务对应的线程池//2.将任务提交至线程池中this.executorServiceForTask(task.getId()).submit(newTaskRunner(task.getId()));}}自定义线程池,每个线程池最多只有一个线程,空闲超过秒后,线程自动回收,线程饱和时,直接丢弃最老的任务:
privateExecutorServiceexecutorServiceForTask(LongtaskId){ returnthis.executorServiceRegistry.computeIfAbsent(taskId,id->{ BasicThreadFactorythreadFactory=newBasicThreadFactory.Builder()//指定线程池名称.namingPattern("Async-Task-"+taskId+"-Thread-%d")//设置线程为后台线程.daemon(true).build();//线程池核心配置://1.每个线程池最多只有一个线程//2.线程空闲超过秒进行自动回收//3.直接使用交互器,线程空闲进行任务交互//4.使用指定的线程工厂,设置线性名称//5.线程池饱和,自动丢弃最老的任务returnnewThreadPoolExecutor(0,1,L,TimeUnit.SECONDS,newSynchronousQueue<>(),threadFactory,newThreadPoolExecutor.DiscardOldestPolicy());});}最后,在线程池中运行的Task如下:
privateclassTaskRunnerimplementsRunnable{ privatefinalDatenow=newDate();privatefinalLongtaskId;publicTaskRunner(LongtaskId){ this.taskId=taskId;}@Overridepublicvoidrun(){ //重新加载任务,保持最新的任务状态TaskDefinitionV2task=definitionV2Repository.findById(this.taskId).orElse(null);if(task!=null&&task.shouldRun(now)){ //运行任务runTask(task);//更新任务的下一次运行时间updateNextRunTime(task,now);}}}4.TaskScheduler配置方案该方案的核心为:绕过@Schedule注解,直接对Spring底层核心类TaskScheduler进行配置。
TaskScheduler接口是Spring对调度任务的一个抽象,更是@Schedule背后默默的支持者,首先我们看下这个接口定义。
publicinterfaceTaskScheduler{ ScheduledFutureschedule(Runnabletask,Triggertrigger);ScheduledFutureschedule(Runnabletask,InstantstartTime);ScheduledFutureschedule(Runnabletask,DatestartTime);ScheduledFuturescheduleAtFixedRate(Runnabletask,InstantstartTime,Durationperiod);ScheduledFuturescheduleAtFixedRate(Runnabletask,DatestartTime,longperiod);ScheduledFuturescheduleAtFixedRate(Runnabletask,Durationperiod);ScheduledFuturescheduleAtFixedRate(Runnabletask,longperiod);ScheduledFuturescheduleWithFixedDelay(Runnabletask,InstantstartTime,Durationdelay);ScheduledFuturescheduleWithFixedDelay(Runnabletask,DatestartTime,longdelay);ScheduledFuturescheduleWithFixedDelay(Runnabletask,Durationdelay);ScheduledFuturescheduleWithFixedDelay(Runnabletask,longdelay);}满满的都是schedule接口,其他的比较简单就不过多叙述了,重点说下Trigger这个接口,首先看下这个接口的定义:
publicinterfaceTrigger{ DatenextExecutionTime(TriggerContexttriggerContext);}只有一个方法,获取下次执行的时间。在任务执行完成后,会调用Trigger的nextExecutionTime获取下一次运行时间,从而实现周期性调度。
CronTrigger是Trigger的最常见实现,以linuxcrontab的方式配置调度任务,如:
scheduler.schedule(task,newCronTrigger("-**MON-FRI"));基础部分简单介绍到这,让我们看下数据库动态配置方案。
4.1数据库动态配置方案整体设计如下:
仍旧是轮询数据库方式,详细流程如下:
1.在应用中启动一个Schedule任务(每1秒调度一次),定时从数据库中获取所有任务;2.依次遍历任务,与内存中的TaskEntry(任务与状态)进行比对,动态的向TaskScheduler中添加或取消调度任务;3.由TaskScheduler负责实际的任务调度;
核心代码如下:
@Scheduled(fixedDelay=,initialDelay=)publicvoidloadAndConfig(){ //加载所有的任务信息List<TaskDefinitionV3>tasks=repository.findAll();//遍历任务进行任务检查for(TaskDefinitionV3task:tasks){ //获取内存任务状态TaskEntrytaskEntry=this.taskEntry.computeIfAbsent(task.getId(),TaskEntry::new);if(task.isEnable()&&taskEntry.isStop()){ //任务为可用,运行状态为停止,则重新进行schedule注册ScheduledFuture<?>scheduledFuture=this.taskScheduler.scheduleWithFixedDelay(newTaskRunner(task),task.getDelay()*);taskEntry.setScheduledFuture(scheduledFuture);log.info("successtostartscheduletaskfor{ }",task);}elseif(task.isDisable()&&taskEntry.isRunning()){ //任务为禁用,运行状态为运行中,停止正在运行在任务taskEntry.stop();log.info("successtostopscheduletaskfor{ }",task);}}}核心辅助类:
@ServicepublicclassSpringScheduleService{ @AutowiredprivateTaskServicetaskService;@Scheduled(fixedDelay=5*,initialDelay=)publicvoidrunTask(){ TaskConfigtaskConfig=TaskConfig.builder().name("SpringDefaultSchedule").build();this.taskService.runTask(taskConfig);}}0有没有发现,以上方案都有一个共同的缺陷:基于数据库轮询获取任务,加大了数据库压力。主力资金追踪源码理论上,只有在配置发生变化时才有必要对任务进行更新,接下来让我们看下改进方案:基于配置中心的方案。
4.2配置中心通知方案整体设计如下:
核心流程如下:
1.应用启动时,从配置中心中获取调度的配置信息,并完成对TaskScheduler的配置;2.当配置发送变化时,配置中心会主动将配置推送到应用程序,应用程序在接收到变化通知时,动态的增加或取消调度任务;3.任务的实际调度仍旧由TaskScheduler完成。
由于手底下没有配置中心,暂时没有coding,思路很简单,有条件的同学可以自行完成。
5.分布式环境下应用以上方案,都是在单机环境下运行,如果应用程序挂掉了,任务调度也就停止了,为了避免这种情况的发生,需要提升系统的可用性,实现冗余部署和自动化容灾。
以上方案,如果部署多个节点会发生什么?是的,会出现任务被多次调度的问题,为了保障在同一时刻只有一个任务在运行,需要为任务增加一个排他锁。同时,由于排他锁的存在,当一个节点处问题后,另一个节点在调度时会自动获取锁,从而解系统的单点问题。
为了简单,我们使用Redis的分布式锁。
5.1.环境搭建Redisson是Redis的一个富客户端,提供了很多高级的数据结构。本次,我们将使用RLock对应用进行保护。
首先,在pom中引入RedissonStarter。
@ServicepublicclassSpringScheduleService{ @AutowiredprivateTaskServicetaskService;@Scheduled(fixedDelay=5*,initialDelay=)publicvoidrunTask(){ TaskConfigtaskConfig=TaskConfig.builder().name("SpringDefaultSchedule").build();this.taskService.runTask(taskConfig);}}1然后,在application.properties文件中增加Redis配置,具体如下:
@ServicepublicclassSpringScheduleService{ @AutowiredprivateTaskServicetaskService;@Scheduled(fixedDelay=5*,initialDelay=)publicvoidrunTask(){ TaskConfigtaskConfig=TaskConfig.builder().name("SpringDefaultSchedule").build();this.taskService.runTask(taskConfig);}}.2引入分布式锁最后,就可以直接使用分布式锁对任务执行进行保护了,代码如下:
@ServicepublicclassSpringScheduleService{ @AutowiredprivateTaskServicetaskService;@Scheduled(fixedDelay=5*,initialDelay=)publicvoidrunTask(){ TaskConfigtaskConfig=TaskConfig.builder().name("SpringDefaultSchedule").build();this.taskService.runTask(taskConfig);}}3备注:
Redis是典型的AP应用,而分布式锁严格意义上来说是CP。所以基于Redis的分布式锁只能使用在非严格环境中,比如我们的数据报表需求。如果设计金钱,需要使用CP实现,如Zookeeper或etcd等。
6.小结本文从Spring的Schedule出发,依次对数据库轮询方案、TaskScheduler配置方案进行详细讲解,以实现对调度任务的可配置化。最后,使用Redis分布式锁有效解决了分布式环境下任务重复调度和自动容灾问题。
仍旧是那句话,架构设计没有更好,只有最适合。同学们可以根据自己的需求自取。
References[1]源码:/litao/books/tree/master/configurable-schedule
JDK成长记7:3张图搞懂HashMap底层原理!
一句话讲, HashMap底层数据结构,JDK1.7数组+单向链表、JDK1.8数组+单向链表+红黑树。
在看过了ArrayList、LinkedList的底层源码后,相信你对阅读JDK源码已经轻车熟路了。除了List很多时候你使用最多的还有Map和Set。接下来我将用三张图和你一起来探索下HashMap的底层核心原理到底有哪些?
首先你应该知道HashMap的核心方法之一就是put。我们带着如下几个问题来看下图:
如上图所示,put方法调用了putVal方法,之后主要脉络是:
如何计算hash值?
计算hash值的算法就在第一步,对key值进行hashCode()后,对hashCode的值进行无符号右移位和hashCode值进行了异或操作。为什么这么做呢?其实涉及了很多数学知识,简单的说就是尽可能让高和低位参与运算,可以减少hash值的冲突。
默认容量和扩容阈值是多少?
如上图所示,很明显第二步回调用resize方法,获取到默认容量为,这个在源码里是1<<4得到的,1左移4位得到的。之后由于默认扩容因子是0.,所以两者相乘就是扩容大小阈值*0.=。之后就分配了一个大小为的Node[]数组,作为Key-Value对存放的数据结构。
最后一问题是,如何进行hash寻址的?
hash寻址其实就在数组中找一个位置的意思。用的算法其实也很简单,就是用数组大小和hash值进行n-1&hash运算,这个操作和对hash取模很类似,只不过这样效率更高而已。hash寻址后,就得到了一个位置,可以把key-value的Node元素放入到之前创建好的Node[]数组中了。
当你了解了上面的三个原理后,你还需要掌握如下几个问题:
还是老规矩,看如下图:
当hash值计算一致,比如当hash值都是时,Key-Value对的Node节点还有一个next指针,会以单链表的形式,将冲突的节点挂在数组同样位置。这就是数据结构中所提到解决hash 的冲突方法之一:单链法。当然还有探测法+rehash法有兴趣的人可以回顾《数据结构和算法》相关书籍。
但是当hash冲突严重的时候,单链法会造成原理链接过长,导致HashMap性能下降,因为链表需要逐个遍历性能很差。所以JDK1.8对hash冲突的算法进行了优化。当链表节点数达到8个的时候,会自动转换为红黑树,自平衡的一种二叉树,有很多特点,比如区分红和黑节点等,具体大家可以看小灰算法图解。红黑树的遍历效率是O(logn)肯定比单链表的O(n)要好很多。
总结一句话就是,hash冲突使用单链表法+红黑树来解决的。
上面的图,核心脉络是四步,源码具体的就不粘出来了。当put一个之后,map的size达到扩容阈值,就会触发rehash。你可以看到如下具体思路:
情况1:如果数组位置只有一个值:使用新的容量进行rehash,即e.hash & (newCap - 1)
情况2:如果数组位置有链表,根据 e.hash & oldCap == 0进行判断,结果为0的使用原位置,否则使用index + oldCap位置,放入元素形成新链表,这里不会和情况1新的容量进行rehash与运算了,index + oldCap这样更省性能。
情况3:如果数组位置有红黑树,根据split方法,同样根据 e.hash & oldCap == 0进行树节点个数统计,如果个数小于6,将树的结果恢复为普通Node,否则使用index + oldCap,调整红黑树位置,这里不会和新的容量进行rehash与运算了,index + oldCap这样更省性能。
你有兴趣的话,可以分别画一下这三种情况的图。这里给大家一个图,假设都出发了以上三种情况结果如下所示:
上面源码核心脉络,3个if主要是校验了一堆,没做什么事情,之后赋值了扩容因子,不传递使用默认值0.,扩容阈值threshold通过tableSizeFor(initialCapacity);进行计算。注意这里只是计算了扩容阈值,没有初始化数组。代码如下:
竟然不是大小*扩容因子?
n |= n >>> 1这句话,是在干什么?n |= n >>> 1等价于n = n | n >>>1; 而|表示位运算中的或,n>>>1表示无符号右移1位。遇到这种情况,之前你应该学到了,如果碰见复杂逻辑和算法方法就是画图或者举例子。这里你就可以举个例子:假设现在指定的容量大小是,n=cap-1=,那么计算过程应该如下:
n是int类型,java中一般是4个字节,位。所以的二进制: 。
最后n+1=,方法返回,赋值给threshold=。再次注意这里只是计算了扩容阈值,没有初始化数组。
为什么这么做呢?一句话,为了提高hash寻址和扩容计算的的效率。
因为无论扩容计算还是寻址计算,都是二进制的位运算,效率很快。另外之前你还记得取余(%)操作中如果除数是2的幂次方则等同于与其除数减一的与(&)操作。即 hash%size = hash & (size-1)。这个前提条件是除数是2的幂次方。
你可以再回顾下resize代码,看看指定了map容量,第一次put会发生什么。会将扩容阈值threshold,这样在第一次put的时候就会调用newCap = oldThr;使得创建一个容量为threshold的数组,之后从而会计算新的扩容阈值newThr为newCap*0.=*0.=。也就是说map到了个元素就会进行扩容。
除了今天知识,技能的成长,给大家带来一个金句甜点,结束我今天的分享:坚持的三个秘诀之一目标化。
坚持的秘诀除了上一节提到的视觉化,第二个秘诀就是目标化。顾名思义,就是需要给自己定立一个目标。这里要提到的是你的目标不要定的太高了。就比如你想要增加肌肉,给自己定了一个目标,每天5组,每次个俯卧撑,你看到自己胖的身形或者海报,很有刺激,结果开始前两天非常厉害,干劲十足,特别奥利给。但是第三天,你想到要个俯卧撑,你就不想起床,就算起来,可能也会把自己撅死过去......其实你的目标不要一下子定的太大,要从微习惯开始,比如我媳妇从来没有做过俯卧撑,就让她每天从1个开始,不能多,我就怕她收不住,做多了。一开始其实从习惯开始,先变成习惯,再开始慢慢加量。量太大养不成习惯,量小才能养成习惯。很容易做到才能养成,你想想是不是这个道理?
所以,坚持的第二个秘诀就是定一个目标,可以通过小量目标,养成微习惯。比如每天你可以读五分钟书或者5分钟成长记,不要多,我想超过你也会睡着了的.....
最后,大家可以在阅读完源码后,在茶余饭后的时候问问同事或同学,你也可以分享下,讲给他听听。
《开端》和《源代码》的剧情是否有雷同之处?
《开端》这部剧在播出之后,就受到了许多人的关注,无限流的设定,也让人们感觉到新鲜。不过像这些设定也让人们想起了一部非常有名的影片《源代码》,所以有人会觉得《开端》对《源代码》有所抄袭,但实际上除了剧情的梗概比较相似以外,剧情的细节上并没有什么相同的。所以说《开端》并没有抄袭**《源代码》,两个作品都是很优秀的,而且各有千秋。《开端》和《源代码》之所以会有人觉得《开端》抄袭的《源代码》 ,就是由于这两个故事发生的主体比较相似。《开端》是发生在公交车上的循环,而《源代码》则是发生在火车上的循环。另外都是由男女两个主角进入到循环并且不断的拯救车上人的故事,虽然说梗概有一些相似。但是要知道无限流的设定在很多的影视剧中都有出现,而且这种为了突出男女主的特色,所以说不断的去进行拯救他人的剧情设定也是比较常规的,所以说并没有什么抄袭的。再加上《开端》从刚开始播出到之后都没有任何剧情上的重复点,而且也非常能够吸引观众的注意力,所以说编剧在设计剧情的过程中是非常细心的,所以说两者并没有任何的抄袭情况,也能够体现网友们对于《开端》剧情的关注,所以好剧只要欣赏就够了,没有必要过分的担心。
《开端》的播出从刚开始播出的社会讨论度很高,很多人也被这些年轻。演员的演技提升和突破感到十分的吃惊。当然在这部剧中更多的人是能够看到编剧的细心,所以好剧就要体现在各个细节上以及制作特色上,这样的话才能够让更多的人可以给予好评。
总结一部好剧有相似点是很正常的,但是只要能够体现自身的特色就可以了。并且《开端》这部剧在海内外都取得了不错的播出效果,也希望有更多的人能够通过这部剧来获得观看的喜悦。
表ç½ç¨åºä»£ç
å¹´è½»çç·å¥³æå们ï¼æ天åæ¯ä¸ä¸ªç¸å½éè¦çæ¥åï¼ï¼ä¸ç¥éæ¯ä»å¥æ¶åå¼å§å ´èµ·æ¥çï¼è½ç¶å¾å¤å身ç人ä¸çå°è¿ä¸ªå æ¥å°±è§å¾é¹å¿ï¼ä½ä¹æå¾å¤§ä¸é¨åå身人士çå¾ çæ天ç好æºä¼ï¼æ¯ç«å¤©æ¶å°å©ï¼è¿ä¹å¥½çæ¥åä¸å®å¥½å¥½çæçã
表ç½çå¥è·¯å¾å¤ï¼ä½é½å°ä¸äºéè±é礼ç©ï¼ä½ä¸ºä¸ä¸ªç¨åºåï¼æä¸æç°å¨æµè¡ç泡泡æºãå°çªãéåæåºè½¦çç©å ·ï¼ä¹ä¸æ³å»è®©æå们å»éé±ç«èï¼æ¯ç«çæ æéè¦ï¼é±å°±ç©è´¨äºãæè½ç»åä½å身ç²ä¸ä»¬åçå¯è½å°±åªæå享å 个表ç½ä»£ç äºï¼å¨çµèä¸æ²ä¸å è¡ä»£ç ï¼è®©å¥¹å¨éé·çå¨ä¸å¾å°ä¸ä¸ªå¤§å¤§çæåï¼å¾ç®åï¼ä¸çå°±ä¼ï¼å¦æç°å¨ç¨ä¸å°ä¹ä¸è¦ç´§ï¼å æ¶èèµ·æ¥ï¼åæ£è¿æ ·çèæ¥å¾å¤ï¼ä»¥åç¨çæ¶åè½æ¾å°ã
ç±ç®å°ç¹ï¼å æ¥äºä¸ªã
1ãæµè§å¨æ ç¾æ³
è¿ä¸ªå ç¨ä¸å°ä»£ç ï¼ä½å´ååç®åå®ç¨çï¼é¦å æ©ä¸æ¥å°åå ¬å®¤ï¼æè è¶è¡¨ç½å¯¹è±¡ä¸æ³¨æçæ¶åå¨å¥¹ççµèä¸å®è£ chromeæµè§å¨ï¼è®¾ç½®æé»è®¤çæµè§å¨ï¼æå项çåæ¥åè½æå¼å°±å¯ä»¥äºãç¶åéæ©ä¸ä¸ªåéçæ¶æºï¼å¨ä½ ççµèä¸ç»å½è´¦å·ï¼ç¶åæ ç¾æ æ·»å ä½ æ³è¯´çè¯ï¼ä¾å¦ä¸å¾è¿æ ·ï¼
ç¶åæ ç¾å°±èªå¨åæ¥å°è¡¨ç½å¯¹è±¡çæµè§å¨ä¸äºã
2ã两个æé®ç»åæI LOVE U
è¿ä¸ªç®åçåè½æ¯å©ç¨çç½é¡µæ¥æ¾çåè½ï¼å¨ç½é¡µä¸å¤å¶ä¸è¿æ®µæåï¼
ç¶åcrtrl+fï¼å¨è¾å ¥æ¡ä¸è¾å ¥9ï¼ç¶åå车ï¼çä¸ææï¼
3ãPython表ç½ä»£ç
å¿åååå½¢æ¤åç代ç 类似ï¼é½æ¯ç±X,Yä¸çç¹ææçæ²çº¿ï¼ç¡®å®åºä¸ä¸ªè¡¨è¾¾å¼expressionï¼ç¶åå°±æ¯ä¸¤ä¸ªfor循ç¯ï¼for y in rangeï¼for x in rangeï¼ï¼ç¶åéè¡éåçå¼å§å°±è¡äºã
å½ç¶æç¹å¤ªåè°ï¼æ们å¯ä»¥è¿æ ·æä½ä¸ä¸ï¼è®©ä»å¨èµ·æ¥ï¼åè¿æ ·
print('\n'.join([''.join([('Love'[(x-y) % len('Love')] if ((x*0.)**2+(y*0.1)**2-1)**3-(x*0.)**2*(y*0.1)**3 <= 0 else ' ') for x in range(-, )]) for y in range(, -, -1)]))
å½ç¶è¿è½æ¿æ¢è¡¨æ ï¼è¿ä¸ªä»£ç å°±ä¸æ¾äºï¼éè¦çç´æ¥è·æè¦å°±è¡ã
4ãç«é ·ä¸ç¹ç
Cè¯è¨è¯ éç±ââ为TAåä¸å¿ä¸æ ï¼çæç¨åºä¼ ç»TAï¼TAç¹å¼ç¨åºå°±æä½ ã
è¿ä¸ªå¼å§çæ¶åä¼å±ç¤ºåºè¡¨ç½å¯¹è±¡çååï¼æ¥çå°±æ¯æéè¦ççè±é¨åï¼ä¸ºäºè®©çè±æ¾å¾æ´å çå®ï¼éè¦éå éåææå空æ°é»åææï¼æºä»£ç å¾å¦ä¸
å¦æéè¦æºç ççä¸é®ä¹¡å°±å¯ä»¥äºã
5ãæ¶æä¸ç¹ç
è¿ä¸ªéåå ³ç³»å·²ç»ä¸éçé£ç§äºï¼ççææ
æºç ç´æ¥å
æååæ¾1个代ç 表ç½çå¾ï¼åæ ·ä»£ç æ æ³æ¾åºæ¥ï¼ä½æ¯å¯ä»¥ç´æ¥ç¨ï¼éè¦çæåä¸æ¹çè¨ã
è¿äºä»£ç å¨ç¨åºåæåç¼éä¸å®æ¯å¾ç®åçï¼èä¸ç¨åºåä¸è¬é½ä¸ä¼ç¨ï¼å 为ä»ä»¬ç对象ä¸éè¦è¡¨ç½ï¼NEWä¸ä¸ªå°±å¯ä»¥ãæ°æå¯ä»¥æ ¢æ ¢å¦ï¼æææå°±æäºï¼ä¹ä¼å¾æåå¦ã