• [技术干货] django中显示字符串的实例方法
    1、quicktool/view.py文件修改视图函数index(),渲染一个home.html模板,在视图中传递一个字符串名称是 string 到模板 home.html123def index(request):  string = u'这是一个由Django2.1.7版本写出的视图内容!'  return render(request, 'home.html',{'string':string})2、quicktool(应用app)下创建一个templates文件夹(模板),在templates下创建一个home.html1234567891011<!DOCTYPE html><html><head>  <title>学习Django</title></head><body>  {{ string }}  </body></html>知识点扩展:在django中,带有HTML标签的字符串,如str = "aa a",我们通过template展示在html页面中时,django会自动转义为可显示的html字符串,即将html标签的尖括号等字符转义。如字符串中的空格会显示为  ,尖括号会显示为 <、 >等等。当我们不需要此自动转换,如上述str在html页面直接显示为aa a时,可通过多种方法禁止django进行自动转义。1. 对于单个变量,可以在其后面加上safe过滤器,告诉Django这个字符串不用进行HTML转义。比如:12This will be escaped: {{ data }}This will not be escaped: {{ data|safe }}2.对于一段模板内容可以使用autoescape标签,比如:123{% autoescape off %}Hello {{ name }}{% endautoescape %}
  • [其他] 【故障】列存表并发更新时时出现锁等待问题
    【案例描述】现场在做业务测试,开启transaction,执行updata的语句执行成功,不执行commit或rollback;再开启另一个窗口,执行upadate语句,有时失败(报错:锁等待超时),有时成功,但是如果对于上一个窗口执行rollback,此窗口update可以执行成功。【案例根因】现场测试使用的是列存表,在事务中执行update操作时,是以CU为单位进行加锁的,所以在事务未提交时并发更新同一CU的其他数据时会出现锁等待的情况,等待超时时会出现报错【机制原理】1. CU为压缩单元(Compress Unit),列存储的最小单位,导入数据时生成,生成后数据固定不可更改,单个CU最多存储1列60000行数据。同一列的CU连续存储在一个文件中,当大于1G时,切换到新文件中。其中的Ctid字段标识列存表的一行,由cu_id和CU内行号(cu_id, offset)组成;一次性写入的多条的数据位于同一CU。2.  为了防止页面同一个元组被两个事务同时更新,在进行update时,都会加上行级锁,对于行存来说是对一行数据加锁,对于列存来说就是对一个CU加锁,当一个事务update未提交时,其他事务是无法同时去更新同一CU的数据的。3. 进行update操作后,旧元组被标记为deleted,新元组会写到一个新的CU中【案例分析】1.根据现场的报错信息,可以确定是并发更新报错;进行update时,会申请行级锁,在申请行级锁之前会申请transactionid锁,等待超时后报错信息为:waiting for ShareLock on transaction xxx after ..ms2. 客户反应更新的并不是同一条数据,id不同,询问客户后得知出现问题的是列存表,查询更新的数据是否处于同一CU。查询后发现处于同一CU,符合预期。3. 本地复现场景后向客户解释:起事务执行update操作:事务未提交并发更新数据出现等待:查询后发现两条数据位于同一cu:【相关问题】1.一线咨询为什么update成功一次之后,下一次update就不会互相等锁了  这是因为update成功之后,旧数据被标记为deleted,新数据写入新的CU,这两条数据不再是同一个CU了,也就不存在这种锁冲突2.客户反馈有两条数据不是同一批次入库的,也出现了锁等待现象通过查出这两条数据的ctid和xmin,可以确认是同一批入库,在同一CU,客户反馈有误。【处理方案】列存表不适合频繁的update场景,列存频繁的update容易触发并发更新等锁超时,并且会导致小CU过多,而每个CU都会扩展至8192字节进行对齐,从而导致磁盘空间迅速膨胀;频繁的点查或频繁update场景建议使用行存表
  • [技术干货] Django URL和View的关系说明
    URL和View的关系1、每次用户发来一个HttpRequest请求,Django会用URL_pattern与请求进行匹配,匹配到第一个URL_pattern就会把请求转向对应的view2、view用来响应request,并返回response,response里可以包含网页文件呀,图片等等。所以项目中绝大部分代码均在此编写。view 可以引入通用模板来减少代码量,具体查看文档1Use generic views3、view引入HTML文件的方法:在一个app下建立一个templates文件夹,将html文件放进去该文件夹这里其实不一定要把HTML文件放在这个templates里,可以随意定义不过,需要在setting.py里,找到templates,修改DIRS:[ os.path.join (BASE_DIR,'你定义的html存放的文件夹')]在app下 view.py 文件,在里面写好view函数def 函数名(request):1return render(request,'index.html')render(渲染)需要三个变量, 第一个变量是request请求,第二个是模版所在目录,第三个是一个字典(可选),字典用于对应模板里设计的变量,可以用locals()将函数里设计的变量自动转换为字典4、在对于app下创建一个urls.py ,写入如下内容12345from django.urls import pathfrom . import viewsurlpatterns = [  path('', views.index, name='index'),]5、在项目文件夹里找到 urls.py 加入如下内容123456from django.urls import include, pathurlpatterns = [ path('polls/', include('polls.urls')), path('admin/', admin.site.urls),]对于<a>标签里的 href 可以用 {% url 'url名' %}来代替,url名指的是 urls.py 里的path('login.html/', views.my_login, name='url名'),Django 中通常使用 get_object_or_404()来捕捉 404 错误,而不用自己写 try...except123def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question})补充:django中url与view配置方法django中url与view配置方法(一)url.py12345from django.conf.urls import urlfrom . import viewsurlpatterns = [ url(r'^$', views.showAssets, name='show_assets'),]views.py12345# -*- coding: utf-8 -*-from django.shortcuts import render_to_response# Create your views here.def showAssets(request): return render_to_response('assets_index.html', {})django中url与view配置方法(二)url.py123456# -*- coding: utf-8 -*-from django.conf.urls import urlfrom app001.views import IndexViewurlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', IndexView.as_view(), name="index"), ]views.py1234567# -*- coding: utf-8 -*-from django.views.generic.base import Viewfrom django.shortcuts import render# Create your views here.class IndexView(View): def get(self, request):  return render(request, 'index.html', {})
  • 项目实战案例解析:B样条曲线问题
    【案例】我们今天就以B样条曲线问题,作为今天的测试样例。B样条(B-Spline)曲线是最重要的自由曲线曲面类型之一。我们可以在在二维平面内,根据指定的控制点坐标,在指定的精度下,按照指定幂次,绘制与这组控制点对应的B样条曲线的函数。代码如下:          void drawBSpline( GLint order, GLfloat v[][2], GLint ptsNum, GLint numofU, GLint node[] )          {              glColor3f(0.0,1.0,0.0); // 设置B样条曲线绘制颜色为绿色              glLineWidth(3.0); // 设置绘制线宽              for(int i=0; i<ptsNum-order; i++){// 对每段曲线,有numofU个点                  glBegin(GL_LINE_STRIP); // 开始绘制的标志                  GLfloat tempU=node[0]; // 对tempU赋初值for(int k=0; k<numofU; k++)  {// 对每个点,有Bj, k(u)*Vj个取值,j=i-k, i-k+1, ..., i                      GLfloat tempx = 0.0, tempy = 0.0;                      GLint jj = i;                      for(int j=-order; j<1; j++){// 计算对应B样条基函数的值,即Bi, k(u)                          GLfloat tempB = calcBaseFunc( j, order, tempU, node );                          tempx += v[jj][0] * tempB;                          tempy += v[jj][1] * tempB;                          jj++; // 顶点是不断变化的,但B样条基函数其实是不变的                      }                      glVertex2f(tempx, tempy); // 不记录曲线点,直接绘制B-spline曲线上的点                      tempU+=1.0/(numofU-1); // 更新tempU,作为下次循环的初值                  }                  glEnd(); // 结束绘制的标志}          }  【解析】我们可以将每层循环涉及的循环变量、初值、终值和循环累加量的情况,做一个简单的罗列。 ptsNum、oder、numofU均为取值不确定的变量,并满足如下的约束条件:(1)order≥0,曲线幂次不应小于零;(2)ptsNum≥2,控制点的个数至少应能生成一条直线段;(3)numofU≥2,绘制B样条曲线的点数应保证至少包含起点和终点。 针对这样的边界条件,我们应结合循环次数的边界组合,来进行测试设计。这样,我们就可以得到内容:①9个(21.4%)是永远测不到的情况;②18个(42.9%)是不符合约束条件的情况。这种情况,我们只需要选择其中几个进行测试即可;③剩下的15个(35.7%)测试用例中,只有3个是可以得到光滑曲线的,其他均对应测试的是特殊的曲线形式或特殊的显示效果。因此,如果仅根据循环的类型(串联、嵌套等),B样条曲线如何随着幂次的变化而变化,曲线形状如何随着绘制精度的变化而变化,以及B样条控制多边形的形状和绘制精度对得到的B样条曲线形状具有怎样的影响?通过上述分析,我们不难看出,实际上,B样条曲线的绘制函数是一种特殊情况,它所涉及的数值在计算上较为复杂。如果我们要检验函数计算的正确性,就必须根据B样条曲线点的计算公式,通过手动方式来计算其正确结果。这样做其实非常烦琐。所以,我们一般地处理方法是,通过直观观察的方式,对程序计算结果进行初步检验。如果曲线生成符合一般规律,则初步认为计算过程是正确的。
  • [问题求助] 【GIS超图】超图GIS的addElement只能添加html,不能添加marker
    【功能模块】【操作步骤&问题现象】1、2、触发addElement Action后,出现了添加上去的html,但marker没有添加成功【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [技术干货] 自建开源Cassandra与GaussDB(for Cassandra)性能对比测试报告
    自建开源Cassandra与GaussDB(for Cassandra)性能对比测试报告1 概述本文为自建开源Cassandra与GaussDB(for Cassandra)性能对比测试报告,完整描述了整个测试的环境、测试模型和详细的测试步骤,作为性能的参考。2 环境描述2.1 开源Cassandra测试环境名称Cassandra版本集群节点数操作系统版本ECS规格开源Cassandra集群3.11.53Centos7.4通用增强型4vCPUs | 16GB通用增强型8vCPUs |32GB通用增强型16vCPUs | 64GB通用增强型32vCPUs | 128GB        2.2 GaussDB(for Cassandra)测试环境 名称Region集群节点数可用区ECS规格开源Cassandra集群华东-上海二3可用区三4vCPUs | 16GB8vCPUs |32GB16vCPUs | 64GB32vCPUs | 128GB2.3 压测工具环境2.3.1  压测机器规格名称CPU内存操作系统测试客户端ECS16vCPUs64GCentos7.4          2.3.2  压测工具信息测试工具版本下载地址Ycsb0.12.0https://github.com/brianfrankcooper/YCSB3 测试模型业务模型编号业务模型负载描述S2_read95_update5读95%,更新5%S3_update50_read50更新50%,读50%S4_read65_update25_insert10读65%,更新25%,写10%S5_insert90_read10写90%,读10%4  测试步骤4.1 开源Cassandra测试详细步骤4.1.1  购买机器步骤一:登录弹性云服务器页面,点击右侧的购买弹性云服务器按钮,并按照相关提示,完成ecs服务器购买,其中一些关键参数的取值区域:华东-上海二;可用区:可用区3,规格:通用增强型|c6.xlarge.4,镜像:公共镜像-CentOS-CentOS 7.6 64bit(40GB);数据盘:选择超高IO,大小为200G;网络:选择任意vpc和子网;其他参数:根据提示设置即可,非必选参数可不填。                             步骤二:等待ecs 服务器创建完成之后,我们登录ecs服务器,可以用控制台的远程登录或者通过xshell工具用弹性ip连接;步骤三:登录五台ecs云服务器,安装jre,执行yum install jre,按照提示,完成安装。步骤四:我们把五台ecs分别命名为Cassandra-1(192.168.0.15),Cassandra-2(192.168.0.240),Cassandra-3(192.168.0.153),Cassandra-4(192.168.0.175),ycsb-Cassandra(192.168.0.60),其中,Cassandra-1,Cassandra-2,Cassandra-3,为我们的初始化Cassandra集群,Cassandra-4为我们后面扩容用的扩展服务器,ycsb-Cassandra作为压测服务器;步骤五:登录五台ecs云服务器,安装Cassandra服务,并创建数据目录,按顺序执行下列命令:下载Cassandra安装包,执行命令:wget http://mirror-hk.koddos.net/apache/Cassandra/3.11.5/apache-Cassandra-3.11.5-bin.tar.gz ;解压安装包,tar -zxvf apache-Cassandra-3.11.5-bin.tar.gz -C /root/更改安装目录:mv /root/apache-Cassandra-3.11.5 /usr/local/Cassandra配置环境变量:echo “export PATH=/usr/local/Cassandra/bin:$PATH” >> /etc/profile使环境变量生效:source /etc/profile创建数据目录:mkdir /data执行如下命令,回显如下,则表示安装成功:cqlsh4.1.2  开源Cassandra集群配置操作手册步骤一:配置Cassandra集群,并启动;登录Cassandra-1(192.168.0.15),Cassandra-2(192.168.0.240),Cassandra-3(192.168.0.153),进入/usr/local/Cassandra/conf 目录,修改Cassandra-topology.properties文件,三个节点,这个配置文件,保持一致即可。修改内容如下,注释掉方框中内容,增加圆圈中的内容:修改Cassandra.yaml文件,涉及以下几个参数:data_file_directories:     - /datacommitlog_directory: /usr/local/Cassandra/commitlogsaved_caches_directory: /usr/local/Cassandra/saved_cachesseed_provider:    # Addresses of hosts that are deemed contact points.    # Cassandra nodes use this list of hosts to find each other and learn    # the topology of the ring.  You must change this if you are running    # multiple nodes!    - class_name: org.apache.Cassandra.locator.SimpleSeedProvider      parameters:          # seeds is actually a comma-delimited list of addresses.          # Ex: "<ip1>,<ip2>,<ip3>"          - seeds: "192.168.0.153,192.168.0.240,192.168.0.15"       ##填写集群三个节点的iplisten_address: 192.168.0.153       #各节点的ip地址                   rpc_address: 192.168.0.153                          #各节点的ip地址修改完以上两个配置之后,我们现在可以启动Cassandra集群了,分别在三个节点执行:Cassandra –R &集群的配置和启动,到此就结束了。 4.1.3  开源Cassandra集群扩容节点步骤一:登录Cassandra-4节点,进入/usr/local/cassnadra/conf目录,编辑Cassandra-topology.properties文件,注释方框内容,添加圆圈中内容修改Cassandra.yaml文件,关键参数如下:data_file_directories:     - /datacommitlog_directory: /usr/local/Cassandra/commitlogsaved_caches_directory: /usr/local/Cassandra/saved_cachesseed_provider:    # Addresses of hosts that are deemed contact points.    # Cassandra nodes use this list of hosts to find each other and learn    # the topology of the ring.  You must change this if you are running    # multiple nodes!    - class_name: org.apache.Cassandra.locator.SimpleSeedProvider      parameters:          # seeds is actually a comma-delimited list of addresses.          # Ex: "<ip1>,<ip2>,<ip3>"          - seeds: "192.168.0.153,192.168.0.240,192.168.0.15"       ##填写集群三个seed节点的ip,和步骤一填写的值保持一致listen_address: 192.168.0.175       #各节点的ip地址                   rpc_address: 192.168.0.175                          #各节点的ip地址登录Cassandra-1节点,停止所有节点的压缩,执行命令: nodetool disableautocompaction登录Cassandra-1节点,停止正在执行的压缩,执行命令: nodetool stop COMPACTION登录Cassandra-1节点,限制节点的迁移流量(这里我们设置为32MB/S,避免对业务造成太大的影响),执行命令: nodetool setstreamthroughput 32登录Cassandra-4节点,启动Cassandra服务,执行: Cassandra –R &步骤二:登录Cassandra-1,扩容过程中(这里请记录开始扩容的时间点t1),每30s执行一次nodetool status,当Cassandra-4节点的状态为UJ时,说明还在数据迁移,直到状态为UN时,迁移才算完成。迁移中如下图:4.2  GaussDB(for Cassandra)测试步骤4.2.1  购买Cassandra集群步骤一:创建Cassandra实例,选择上海二区域,云数据库GaussDB(for Cassandra)服务,点击购买数据库实例,按照提示完成Cassandra实例购买,其中一些参数选择如下;       性能规格:4核|16GB       存储空间:200G       节点数量:3       虚拟私有云,内网安全组:和创建的弹性云服务器保持一致步骤二:等待Cassandra实例创建完成,进行性能测试; 4.2.2  GaussDB(for Cassandra)集群扩容节点步骤一:登录GaussDB(for Cassandra)控制台,点开实例详情页面,点击途中的”+”按钮,跳转到添加节点页面,选择添加1个节点,点击提交;       步骤二:等待扩容完成,操作完成,观察在扩容过程中QPS 的浮动情况,可以看到在GaussDB(for Cassandra)场景下,对实例进行节点扩容,仅有十秒左右的qps小幅下降,对业务几乎无影响,扩容操作从下发到扩容完成,大约十分钟左右。在扩容操作完成之后,也可以跳回步骤八九,对测试过程数据进行分析。下面仅提供4.3  ycsb测试详细步骤登录ycsb-Cassandra,下载ycsb工具并安装,curl -O --location https://github.com/brianfrankcooper/YCSB/releases/download/0.12.0/ycsb-0.12.0.tar.gz使用终端工具如xshell通过客户端ECS实例弹性公网IP连接。ycsb具体使用方法: https://github.com/brianfrankcooper/YCSB/tree/master/Cassandra5 测试结果5.1  性能结果5.2 测试结论1. GaussDB(for Cassandra)扩容较快,影响业务的时间较为短暂,为10s,且不涉及参数调整,扩容过程十分钟。2. 社区版根据数据量的大小,参数的设置,整个扩容流程比较长,对性能的影响也参差不齐,50G预置数据,在实验场景中,扩容会超过30分钟。3. 计算公式:最快迁移速度=(nodetool setstreamthroughput 32设置的迁移流量限制,默认为200Mbps)*原节点数。4. 本测试过程中,最快的迁移速度=32Mbps/s*3=12MB/S=720MB/min=0.703GB/min,计算可得50GB数据在此场景下迁移的最快时间是:50/0.703=71.1分钟。各性能模型下,只要有读操作,同规格的GaussDB(for Cassandra)集群性能远高于社区版集群。社区版Cassandra对读非常不友好,GaussDB(for Cassandra)在读时延的性能表现上,要优于社区版数十倍。在写性能表现上,GaussDB(for Cassandra)与社区版基本保持一致。社区版和GaussDB(for Cassandra)在节点扩容过程中,对业务都有轻度的影响。
  • [调优工具] 【Hyper Tuner调优实践 03】基于Java性能分析工具的内存泄露调优实践
    1      调优概述华为鲲鹏性能优化工具是一款针对鲲鹏平台的性能调优工具,包含系统性能优化工具和Java性能优化工具。本文使用Java性能优化工具对运行中的Java程序进行了基于Profiling的GC和内存分析、结合Sampling分析,找到程序问题,并根据分析结果进行优化修改,从而实现Java程序最佳运行。2      环境要求项目说明服务器TaiShan 200 服务器(型号2280)CPU鲲鹏920 4826处理器OSCentOS 7.6应用Java应用程序性能分析工具Hyper Tuner 2.2.T2.SPC1003      前提条件1.    服务器和操作系统正常运行。2.    PC端已经安装SSH远程登录工具3.    需要优化的Java程序4      调优思路1、  检查Java程序的基础参数;2、  使用Java性能优化工具对Java进程进行GC分析以及内存分析;3、  使用Java性能优化工具对Java进程进行Sampling分析4、  针对性能的瓶颈点进行性能优化;5、  观察优化后的Java程序,判断性能是否提升5      调优过程5.1      检查Java程序的基础参数步骤1     登录Java性能优化工具步骤2     创建Guardian (说明:部署的需要优化的Java进程的服务器)步骤3     启动Profiling分析获取到的Java程序的基础参数如下图:只配置最大堆大小可继续通过工具其他功能分析5.2      查看Profiling概览概览分析:通过概览功能页面查看Java进程发现内存占用很高、GC活动也频繁5.3      查看Profiling GC步骤1     点击页面上方的【GC】页签步骤2     查看GC的活动详情GC分析:发现在几分钟内多次出现old GC,后续old GC频繁old GC 频繁的原因分析:5.4      查看Profiling内存转储该Java进程的内存在old GC之后回收的内存较少,且频繁GC,继续使用工具其他功能分析步骤1     页面上方的【内存转储】页签步骤2     等待执行内存转储的结果内存转储分析:下图为内存转储的【直方图】,展示了Java进程中堆内存分配,可以发现第一行的Object[] 与其他数据的浅堆大小不在一个数量级,此时需要结合支配树继续分析 支配树分析:下图中的第一行非常直观的可以发现该ScheduleTask类的浅堆大小占据了整个堆的98.27%,继续查看该类的下级支配数据,最终发现该类支配了大量的Object[]类,Object[]类下支配最多的即Student类5.5      优化 Java程序分析:通过上述四步骤的Profiling分析,可以发现堆内存被大量的ScheduleTask类支配的Student类所占据,因此回归Java程序本身发现该ScheduleTask类是一个作为缓存功能的类,自己检视代码发现该类使用的HashMap作为缓存对象,在构建代码时忽略了缓存的过期时间,导致历史的数据一直在该缓存对象中导致内存泄露,GC无法回收对象。优化:       优化代码,加入缓存过期策略;5.6      优化后的 Java程序优化后的程序通过概览页即可发现内存使用正常、GC活动正常6      实践总结本次实践中,对一Java程序进行了性能分析,通过Profiling GC分析和内存转储分析发现了程序中的内存泄漏点,进而优化程序中的内存泄漏点达到优化程序性能的目的,解决了程序中内存持续占满且频繁old GC的问题。在进行其他程序调优时,需要根据华为鲲鹏性能优化工具采集分析的实际结果和对应的优化建议进行调优操作。具体的调优思路可以参考本次实践。
  • [技术干货] Vue 中用 HTML + CSS 实现导航按钮动画绘制
    先看效果:默认 按钮1 亮鼠标指针滑动到 按钮2 按钮2 字体颜色改变鼠标指针点击 按钮3, 按钮3亮起下面是实现过程:用的 float 布局,tabButtonActive 的 class 和 style 绑定,有需要的小伙伴可以参考<template><div class="tabBarArea">    <div class="tabButton" :class="[tabName=='按钮1'?'tabButtonActive':'']"         @click="tabName='按钮1'">按钮1</div>    <div class="tabButton" :class="[tabName=='按钮2'?'tabButtonActive':'']"         @click="tabName='按钮2'">按钮2</div>    <div class="tabButton" :class="[tabName=='按钮3'?'tabButtonActive':'']"         @click="tabName='按钮3'">按钮3</div>    <div class="tabButton" :class="[tabName=='按钮4'?'tabButtonActive':'']"         @click="tabName='按钮4'">按钮4</div></div></template><script>export default {  name: 'buttonTab',  data() {    return {      tabName: '按钮1',    };  },};</script><style scoped>  /*清除浮动*/  .tabBarArea:after{    display: block;    content: '';    clear: both;  }  .tabButton {    float: left;    margin-left: 30px;    border-radius: 10px;    cursor: pointer;    padding: 2px 10px;  }  .tabButton:hover {    color: #326BE8;  }  .tabButtonActive {    background-color: #326BE8;    color: #ffffff;    transition: all .5s;  }  .tabButtonActive:hover {    color: #ffffff;  }</style>
  • [技术干货] 初识Webpack
    1. 摘要Webpack是一种前端资源构建工具,一个静态模块打包器。在Webpack看来,前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理,当Webpack处理应用程序时,它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源。Webpack打包流程图如图1-1所示。图1-1 Webpack打包流程图2. Webpack五个核心概念2.1 Entry入口(Entry)指示Webpack以哪个文件作为入口起点分析构建内部依赖图并进行打包。2.2 Output输出(Output)指示Webpack打包后的资源bundles输出到哪里去,以及如何命名。2.3 LoaderLoader让Webpack能够去处理那些非JavaScript语言的文件,Webpack本身只能理解JavaScript。2.4 Plugins插件(Plugins)可以用于执行范围更广的任务,插件的范围包括从打包和压缩,一直到重新定义环境中的变量等。2.5 Mode模式(Mode)指示Webpack使用相应模式的配置。分为development和production两种模式,下面分别进行简述。development: 开发模式,能让代码本地运行的环境,会将process.env.NODE_ENV的值设为development,同时启用NamedChunksPlugin和NamedModulesPlugin插件;production: 生产模式,能让代码优化运行的环境,会将process.env.NODE_ENV的值设为production,同时启用FlagDependencyUsagePlugin、FlagIncludedChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OccurrenceOrderPlugin、SideEffectsFlagPlugin和UglifyJsPlugin插件。3. Wbepack配置3.1 webpack.config.js文件webpack.config.js是webpack的配置文件,用来指示webpack工作,运行webpack指令时,会加载里面的配置,所有构建工具都是基于nodejs平台运行的,默认采用commonjs模块化。webpack.config.js基础配置如图3-1所示。图3-1 webpack.config.js基础配置3.2 devServer配置开发服务器(devServer)用来实现自动化(自动编译、自动打开浏览器、自动刷新浏览器),只会在内存中编译打包,不会有任何文件输出,本地安装webpack-dev-server后,通过npx webpack-dev-server命令启动devServer,核心代码如图3-2所示。图3-2 devServer配置核心代码3.3 打包html/样式/图片/其它资源打包不同的资源会使用不同的loader和插件,打包html/样式/图片/其它资源的流程如下所述。3.3.1 打包html资源1.下载html-webpack-plugin插件;2.引入html-webpack-plugin插件;3.使用html-webpack-plugin插件,并进行相应配置。3.3.2 打包样式资源不同的样式文件需要配置不同的loader1.下载loader;2.配置loader,css样式文件使用css-loader和style-loader,less文件使用less-loader、css-loader和style-loader。其中css-loader的作用是将css文件变成commonjs模块加载到js文件中,style-loader的作用是创建style标签,将js中的样式资源插入进去,添加到head中生效。3.3.3 打包图片资源1.下载url-loader,file-loader2.配置loader3.3.4 打包其它资源1.下载file-loader2. 配置loader,配置该loader作用于不为html/css/less/js的其他文件3.4 提取css成单独文件/css兼容性处理/压缩css3.4.1  提取css成单独文件样式文件打包后会默认和js文件一起输出,可以通过插件将打包后的css文件单独输出,流程如下所述。1.下载mini-css-extract-plugin插件2.引用该插件3.配置3.4.2 css兼容性处理1.下载postcss-loader和postcss-preset-env2.在package.json中browsetslist属性中分别对开发环境和生产环境进行兼容性配置,设置支持样式的浏览器版本3.通过postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式。3.4.3 压缩css1.下载optimize-css-assets-webpack-plugin插件2.引用该插件3.使用该插件3.5 js语法检查eslint/js兼容性处理/js压缩3.5.1 js语法检查eslint1.下载eslint-loader和eslint2.在package.json中的eslintConfig中进行配置3.配置eslint-loader,其中只需检测js文件并要排除第三方库,只检测自己写的源代码,同时可在options配置中设置fix:true,自动修复eslint的错误。3.5.2 js兼容性处理    1.下载babel-loader、@babel/core、@babel/preset-env,通过@babel/preset-env做基本的js兼容性处理,然后通过corejs做前面无法实现的兼容性处理,并实现按需加载    2. 配置loaderjs兼容性处理核心代码如图3-3所示图3-3 js兼容性处理核心代码3.5.3 js压缩mode设置为production生产环境时会自动压缩js代码。4. webpack性能优化可以从开发环境和生产环境分别对webpack进行性能优化。其中开发环境主要考虑从打包构建速度和代码调试两个方面进行优化,生产环境主要考虑从打包构建速度和代码运行性能这两个方面进行优化。下面简单介绍下开发环境上通过HMR提升构建速度。4.1 HMR    HMR(热模块替换),作用是一个模块发生变化后,只会更新打包这一个模块而不是所有模块,通过在devServer中设置hot:true属性启动HMR功能。其中对于样式文件,可以使用HMR功能,因为style-loader内部实现了;对于js文件,默认不能使用HMR功能,解决方法:修改入口文件js代码,添加支持HMR功能的代码,另外HMR只能处理非入口js文件的其他文件,对入口文件并不能生效,因为一旦入口文件更新,入口文件引入的其他文件一定会被重新加载;对于html文件,默认不能使用HMR功能,同时会导致html文件不能热更新,解决方法:修改entry入口文件,将html文件引入,只能解决html文件不能热更新的问题。js文件支持HMR功能的核心代码如图4-1所示。图4-1 js文件支持HMR功能核心代码4.2  HMR效果在入口index.js文件中引入print.js文件,运行npx webpack-devserver后,页面如图4-2所示。4-2 初始页面修改print.js文件后,只会重新加载print.js文件,而不会重新加载index.js文件,HMR效果如图4-3所示。4-3 HMR效果图
  • [技术干货] [转载]Python Collections模块
    在内置数据类型(dict、list、set、tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter、deque、defaultdict、namedtuple和OrderedDict等。1.namedtuple: 生成可以使用名字来访问元素内容的tuple2.deque: 双端队列,可以快速的从另外一侧追加和推出对象3.Counter: 计数器,主要用来计数4.OrderedDict: 有序字典5.defaultdict: 带有默认值的字典1、namedtuple我们知道tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:>>> p=(2,6)但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。这时,namedtuple就派上了用场:from collections import namedtuple point = namedtuple("point", ['x', 'y']) print(point) p_obj = point(6, 8) print(p_obj.x) print(p_obj.y)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py <class '__main__.point'>68Process finished with exit code 0类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:# namedtuple('名称', [属性list]):Circle = namedtuple("Circle", ['x', 'y', 'r']) cir = Circle(8, 6, 10) print(cir.x) print(cir.y) print(cir.r)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py8610Process finished with exit code 02、deque使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:from collections import deque q = deque(['a', 'b', 'c']) q.append('d') print(q) q.appendleft('e') print(q) q.pop() print(q) q.popleft() print(q)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py deque(['a', 'b', 'c', 'd']) deque(['e', 'a', 'b', 'c', 'd']) deque(['e', 'a', 'b', 'c']) deque(['a', 'b', 'c']) Process finished with exit code 0deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。3、OrderedDict使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict:from collections import OrderedDict lis = [('a', 21), ('b', 55), ('c', 86)] dic = dict(lis)  # dict的Key是无序的print(dic) ord_dic = OrderedDict(lis)  # OrderedDict的Key是有序的print(ord_dic)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py {'a': 21, 'b': 55, 'c': 86}OrderedDict([('a', 21), ('b', 55), ('c', 86)])Process finished with exit code 0注意,OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:ord_dic = OrderedDict() ord_dic['z'] = 99ord_dic['y'] = 110ord_dic['z'] = 666print(ord_dic.keys())  # 按照插入的Key的顺序返回结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py odict_keys(['z', 'y']) Process finished with exit code 04、defaultdict 有如下值集合 [11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。即: {'k1': 大于66 , 'k2': 小于66}lis = [11, 22, 33, 55, 77, 66, 88, 99] dic = {}for value in lis:     if value > 66:         if dic.has_key('k1'):             dic['k1'].append(value)         else:             dic['k1'] = [value]     else:         if dic.has_key('k2'):             dic['k2'].append(value)         else:             dic['k2'] = [value]from collections import defaultdictlis = [11, 22, 33, 55, 77, 66, 88, 99]my_dic = defaultdict(lis)for value in lis:    if value > 66:        my_dic['k1'].append(value)    else:        my_dic['k2'].append(value)print(my_dic)使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:from collections import defaultdict dd = defaultdict(lambda: 'N/A') dd['key1'] = 'abc'  # key1存在print(dd['key1']) print(dd['key2'])  # key2不存在,返回默认值print(dd['key3'])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py abc N/A N/A Process finished with exit code 05、CounterCounter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bags或multisets很相似。from collections import Counter coun = Counter("absdsdsdadsdsa") print(coun)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo005.py Counter({'s': 5, 'd': 5, 'a': 3, 'b': 1}) Process finished with exit code 0创建下面的代码说明了Counter类创建的四种方法:from collections import Counterc = Counter()  # 创建一个空的Counter类print(c)c = Counter('g***d')  # 从一个可iterable对象(list、tuple、dict、字符串等)创建print(c)c = Counter({'a': 4, 'b': 2})  # 从一个字典对象创建print(c)c = Counter(a=4, b=2)  # 从一组键值对创建print(c)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter() Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1}) Counter({'a': 4, 'b': 2}) Counter({'a': 4, 'b': 2}) Process finished with exit code 0计数值的访问与缺失的键当所访问的键不存在时,返回0,而不是KeyError;否则返回它的计数。计数值的访问from collections import Counter cou = Counter("hello world") print(cou["l"]) print(cou["o"]) print(cou["a"])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py320Process finished with exit code 0计数器的更新(update和subtract)可以使用一个iterable对象或者另一个Counter对象来更新键值。计数器的更新包括增加和减少两种。其中,增加使用update()方法:计数器的更新(update)from collections import Counter cou = Counter("hello world") cou.update("which")  # 使用另一个iterable对象更新print(cou["h"]) c = Counter("watch") cou.update(c)  # 使用另一个Counter对象更新print(cou["h"])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py34Process finished with exit code 0减少则使用subtract()方法:计数器的更新(subtract)from collections import Counter c = Counter('which') print(c["h"]) c.subtract('witch')  # 使用另一个iterable对象更新print(c['h']) d = Counter('watch') c.subtract(d)  # 使用另一个Counter对象更新print(c['a'])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py21-1Process finished with exit code 0键的修改和删除当计数值为0时,并不意味着元素被删除,删除元素应当使用del。 键的删除from collections import Counterc = Counter("abcdcba")print(c)c["b"] = 0print(c) del c["a"]print(c)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Counter({'a': 2, 'c': 2, 'd': 1, 'b': 0}) Counter({'c': 2, 'd': 1, 'b': 0}) Process finished with exit code 0elements()返回一个迭代器。元素被重复了多少次,在该迭代器中就包含多少个该元素。元素排列无确定顺序,个数小于1的元素不被包含。elements()方法 from collections import Counterc = Counter(a=4, b=2, c=0, d=-2)print(list(c.elements()))结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py ['a', 'a', 'a', 'a', 'b', 'b'] Process finished with exit code 0most_common([n])返回一个TopN列表。如果n没有被指定,则返回所有元素。当多个元素计数值相同时,排列是无确定顺序的。most_common()方法from collections import Counterc = Counter('abracadabra')print(c)print(c.most_common())print(c.most_common(3))结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}) [('a', 5), ('b', 2), ('r', 2), ('c', 1), ('d', 1)] [('a', 5), ('b', 2), ('r', 2)] Process finished with exit code 0浅拷贝copy浅拷贝copyfrom collections import Counterc = Counter("abcdcba")print(c) d = c.copy()print(d)结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Counter({'a': 2, 'b': 2, 'c': 2, 'd': 1}) Process finished with exit code 0算术和集合操作+、-、&、|操作也可以用于Counter。其中&和|操作分别返回两个Counter对象各元素的最小值和最大值。需要注意的是,得到的Counter对象将删除小于1的元素。Counter对象的算术和集合操作from collections import Counterc = Counter(a=3, b=1) d = Counter(a=1, b=2)print(c + d)  # c[x] + d[x]print(c - d)  # subtract(只保留正数计数的元素)print(c & d)  # 交集:  min(c[x], d[x])print(c | d)  # 并集:  max(c[x], d[x])结果:D:\YuchuanProjectData\PythonProject\venv\Scripts\python.exe D:/YuchuanProjectData/PythonProject/YuchuanDemo006.py Counter({'a': 4, 'b': 3}) Counter({'a': 2}) Counter({'a': 1, 'b': 1}) Counter({'a': 3, 'b': 2}) Process finished with exit code 0其他常用操作下面是一些Counter类的常用操作,来源于Python官方文档Counter类常用操作sum(c.values())  # 所有计数的总数c.clear()  # 重置Counter对象,注意不是删除list(c)  # 将c中的键转为列表set(c)  # 将c中的键转为setdict(c)  # 将c中的键值对转为字典c.items()  # 转为(elem, cnt)格式的列表Counter(dict(list_of_pairs))  # 从(elem, cnt)格式的列表转换为Counter类对象c.most_common()[:-n:-1]  # 取出计数最少的n个元素c += Counter()  # 移除0和负值
  • [技术干货] Python基础学习之邮件操作(实用版,复制可直接使用)
    1. HTML格式邮件的发送以下实例为发送HTML格式的邮件,我们只需要变更调用函数时候的参数,即可完成邮件的发送;#!/usr/bin/env python# -*- coding: utf-8 -*-# author:Zhang Kai time:2020/10/12# 使用其他邮件服务商的 SMTP 访问import smtplibfrom email.mime.text import MIMETextfrom email.header import Headerdef sendMail(sender, mail_pass, receivers, mail_msg, mail_host='smtp.163.com'):  # 定义发送邮件的函数;     '''     :param mail_user: 字符串格式:kai.zhang@unisemicon.com 发件人的邮箱     :param mail_pass: 字符串格式:发件人邮箱密码     :param receivers: 收件人列表;格式:['123@qq.com','234@qq.com','678@qq.com']     :param mail_msg: 邮件的内容,HTML 的字符串格式     :param mail_host: 设置服务器; 默认163邮箱; 举例:qq:smtp.qq.com; 163:smtp.163.com; 等     :return: 返回值如下:         1:代表邮件发送成功;         2:代表邮件发送失败;     '''     # 第三方 SMTP 服务     mail_host = "smtp.qq.com"  # 设置服务器,qq:smtp.qq.com; 163:smtp.163.com; 等     sender = sender  # 用户名     mail_pass = mail_pass  # 密码     # 设置发件人 & 收件人     sender = sender  # 发件人,这里设置成与用户名一致     receivers = receivers  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱     # 邮件的内容,HTML 格式     mail_msg = mail_msg     message = MIMEText(mail_msg, 'HTML', 'utf-8')     # 三个参数:第一个为邮件内容;第二个 设置文本格式 plain为纯文本,HTML为HTML格式;第三个 utf-8 设置编码     message['From'] = sender     message['To'] = ','.join(receivers)     subject = 'Python SMTP 邮件测试'     message['Subject'] = Header(subject, 'utf-8')     try:         smtpObj = smtplib.SMTP()         smtpObj.connect(mail_host, 25)  # 25 为 SMTP 端口号         smtpObj.login(sender, mail_pass)         smtpObj.sendmail(sender, receivers, message.as_string())         return 1  # 邮件发送成功     except Exception:      print(e.__class__.__name__)   # 打印异常的名字         print(e)    # 打印异常的详细信息         return 0  # "Error: 其他异常"# 定义邮件内容,HTML格式mail_msg = """     <p>Python 邮件发送测试...</p>     <p><a href="http://172.21.21.8:8000/">这是一个内部查询系统的链接</a></p>     """# 调用函数res = sendMail(sender="794532995@qq.com", mail_pass="12345678", receivers=['794532995@qq.com'],                mail_msg=mail_msg, mail_host="smtp.unisemicon.com")# 打印返回值print(res)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364652. 文本格式邮件的发送只需要将上述代码中的 message = MIMEText(mail_msg, 'HTML', 'utf-8') 中的HTML 变为plain ,则邮件内容变为纯文本;即修改为:message = MIMEText(mail_msg, 'plain', 'utf-8')3. 包含附件邮件的发送发送带附件的邮件,首先要创建MIMEMultipart()实例,然后构造附件,如果有多个附件,可依次构造,最后利用smtplib.smtp发送;只有mail_msg这个参数需要重新构造,其余无变化;代码参考如下:#!/usr/bin/env python# -*- coding: utf-8 -*-# author:Zhang Kai time:2020/10/12import smtplibfrom email.mime.text import MIMETextfrom email.header import Headerfrom email.mime.multipart import MIMEMultipart mail_msg = """     <p>Python 邮件发送测试...</p>     <p><a href="http://172.21.21.8:8000/">这是一个内部查询系统的链接</a></p>     """sender="794532995@qq.com"mail_pass="pyelx****lkbfhj"receivers=['kai.zhang@unisemicon.com']mail_msg=mail_msg attach_file = 'test.txt'mail_host="smtp.qq.com"# 第三方 SMTP 服务mail_host = "smtp.qq.com"  # 设置服务器,qq:smtp.qq.com; 163:smtp.163.com; 等sender = sender  # 用户名mail_pass = mail_pass  # 密码# 设置发件人 & 收件人sender = sender  # 发件人,这里设置成与用户名一致receivers = receivers  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱# 创建一个带附件的邮件内容实例;message=MIMEMultipart()message['From'] = sender message['To'] = ','.join(receivers)subject = 'Python SMTP 邮件测试'message['Subject'] = Header(subject, 'utf-8')# 增加邮件正文的内容,HTML 格式mail_msg = mail_msg message.attach(MIMEText(mail_msg, 'HTML', 'utf-8'))    # 三个参数:第一个为邮件内容;第二个 设置文本格式 plain为纯文本,HTML为HTML格式;第三个 utf-8 设置编码# 构造附件1,传入当前目录下的test.txt 文件,当前目录下必须要有test.txt文件;attach_file =attach_file att1 = MIMEText(open(attach_file, 'rb').read(), 'base64', 'utf-8')att1["Content-Type"] = 'application/octet-stream'# 这里的filename可以任意写,写什么名字,邮件中显示什么名字att1["Content-Disposition"] = 'attachment; filename="test.txt"'message.attach(att1)# 构造附件2,按照附件1 的方式,可以添加很多个附件;try:     smtpObj = smtplib.SMTP()     smtpObj.connect(mail_host, 25)  # 25 为 SMTP 端口号     smtpObj.login(sender, mail_pass)     smtpObj.sendmail(sender, receivers, message.as_string())     print('邮件发送成功')except Exception as e:     print(e.__class__.__name__)   #     print(e)    #     print('邮件发送失败')123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566备注:邮件一般在垃圾邮箱;4. 包含附件邮件的发送#!/usr/bin/env python# -*- coding: utf-8 -*-# author:Zhang Kai time:2020/10/13import smtplibfrom email.mime.image import MIMEImagefrom email.mime.multipart import MIMEMultipartfrom email.mime.text import MIMETextfrom email.header import Header sender="794532995@qq.com"mail_pass="pyelxrvvgclkbfhj"    # 密码receivers=['kai.zhang@unisemicon.com']    # 设置收件人attach_file = 'test.txt'mail_host="smtp.qq.com"# 创建一个带图片的邮件内容实例:message;message=MIMEMultipart()    # msgRoot = MIMEMultipart('related')message['From'] = sender   # 给实例添加发件人message['To'] = ','.join(receivers)    # 给实例添加收件人subject = 'Python SMTP 邮件测试'message['Subject'] = Header(subject, 'utf-8')   # 给实例添加标题msgAlternative = MIMEMultipart('alternative')    # 创建带图片的内容message.attach(msgAlternative)    # 将内容添加到实例mail_msg = """     <p>Python 邮件发送测试...</p>     <p><a href="http://172.21.21.8:8000/">这是一个内部查询系统的链接</a></p>     <p>图片如下:</p>     <p><img src="cid:image1"></p>     <p><img src="cid:image2"></p> """msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))    # 给内容增加邮件内容# 指定图片为当前目录fp = open('test.jpg', 'rb')msgImage = MIMEImage(fp.read())    # 将图片赋值给msgImage;fp.close()fp = open('test2.jpg', 'rb')msgImage2 = MIMEImage(fp.read())    # 将图片2赋值给msgImage;fp.close()# 定义图片 ID,在 HTML 文本中引用msgImage.add_header('Content-ID', '<image1>')    # 给图片添加标题message.attach(msgImage)     # 给实例添加图片msgImage2.add_header('Content-ID', '<image2>')    # 给图片添加标题message.attach(msgImage2)     # 给实例添加图片try:     smtpObj = smtplib.SMTP()     smtpObj.connect(mail_host, 25)  # 25 为 SMTP 端口号     smtpObj.login(sender, mail_pass)     smtpObj.sendmail(sender, receivers, message.as_string())     print('邮件发送成功')except Exception as e:     print(e.__class__.__name__)   #     print(e)    #     print('邮件发送失败')5. 发生邮件的步骤总结5.1 导入对应的包;一般以下几个就完全够用了;import smtplibfrom email.mime.image import MIMEImagefrom email.mime.multipart import MIMEMultipartfrom email.mime.text import MIMETextfrom email.header import Header123455.2 建立smtplib.SMTP()对象;smtpObj = smtplib.SMTP()    # 创建邮件对象15.3 连接服务器 & 登陆邮箱; QQ邮箱密码参考本章的 ‘3. QQ邮箱异常解决’mail_host = "smtp.qq.com"  # 取决于发送邮件的邮箱是什么;smtpObj.connect(mail_host, 25)  # 25 为 SMTP 端口号smtpObj.login(sender, mail_pass)  # sender:发件人的邮箱; mail_pass:邮箱对应的密码;QQ邮箱密码参考本章的 3. QQ邮箱异常解决5.4 发送邮件;发送邮件的语句如下,包括三个参数:sender, receivers, message.as_string();构建完成message参数后,使用以下语句就可以实现发送邮件的功能;smtpObj.sendmail(sender, receivers, message.as_string())1参数解释:sender: 就是发件人,直接定义即可,例如:sender="794532995@qq.com"receivers: 就是收件人,列表直接定义即可,例如:receivers=['kai.zhang@unisemicon.com','794532995@qq.com']message: 就是邮件内容;邮件内容稍微复杂,需要先创建邮件对象实例,然后向实例中添加:发件人,收件人,主题,正文,附件;5.4.0 创建邮件对象实例:message=MIMEMultipart()   # 创建邮件对象实例,然后向实例中添加发件人,收件人,主题,正文,附件;15.4.1 邮件对象中的发件人:message['From'] = '794532995@qq.com'15.4.2 邮件对象中的收件人:message['To'] = ','.join(receivers)# 其中:receivers=['kai.zhang@unisemicon.com','794532995@qq.com'] 是一个收件人列表;125.4.3 邮件对象中的主题:subject = 'Python SMTP 邮件测试'message['Subject'] = Header(subject, 'utf-8')   # 给实例添加标题125.4.4 邮件对象中的正文:# 生成一个只有文本(或者HTML)的正文对象,然后添加到message中;mail_msg = """     <p>Python 邮件发送测试...</p>     <p><a href="http://172.21.21.8:8000/">这是一个内部查询系统的链接</a></p>     """message.attach(MIMEText(mail_msg, 'HTML', 'utf-8'))1234565.4.5 邮件对象中的包含图片的正文:# 创建一个带图片的邮件内容,然后添加到message中;msgAlternative = MIMEMultipart('alternative')    # 创建带图片的内容message.attach(msgAlternative)    # 将内容添加到实例mail_msg = """     <p>Python 邮件发送测试...</p>     <p><a href="http://172.21.21.8:8000/">这是一个内部查询系统的链接</a></p>     <p>图片如下:</p>     <p><img src="cid:image1"></p>     <p><img src="cid:image2"></p> """msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8'))    # 给内容增加邮件HTML文本# 指定图片为当前目录,然后打开图片文件,赋值给 msgImage;fp = open('test.jpg', 'rb')msgImage = MIMEImage(fp.read())    # 将图片1 赋值给msgImage;fp.close()msgImage.add_header('Content-ID', '<image1>')    # 给图片1添加标题message.attach(msgImage)     # 给实例添加图片123456789101112131415161718195.4.6 邮件对象中的附件:构造附件对象,然后添加到message中;# 构造附件1,传入当前目录下的test.txt 文件attach_file = 'test.txt'att1 = MIMEText(open(attach_file, 'rb').read(), 'base64', 'utf-8')att1["Content-Type"] = 'application/octet-stream'# 下面的filename可以任意写,写什么名字,邮件中显示什么名字att1["Content-Disposition"] = 'attachment; filename="test.txt"'message.attach(att1)
  • [技术干货] 操作DOM对象(节点信息、操作节点、属性、获取元素样式)
    JavaScript操作DOM对象一、DOM概述Document Object Model,即文档对象模型DOMDOM CoreCSS-DOMHTML-DOM节点与节点的关系二、访问节点使用getElement系列方法访问指定节点getElementById() 通过id访问节点getElementsByName() 通过类名访问节点getElementsByTagName() 通过标签访问节点根据层次关系访问节点节点属性parentNode 返回节点的父节点childNodes 返回子节点集合,childNodes[i]firstChild 返回节点的第一个子节点,最普遍的用法是访问该元素的文本节点lastChild 返回节点的最后一个子节点nextSibling 下一个节点previousSibling 上一个节点三、element属性firstElementChild 返回节点的第一个子节点,最普遍的用法是访问该元素的文本节点lastElementChild 返回节点的最后一个子节点nextElementSibling 下一个节点previousElementSibling 上一个节点<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>Title</title></head><body><ul id="nodeList">    <li>aaa</li>    <li>bbb</li>    <li>ccc</li></ul><script>    var nodes = document.getElementById("nodeList");    var node = nodes.firstElementChild;    console.log(nodes);    console.log(node);</script></body></html>运行结果:四、节点信息nodeName:节点名称nodeValue:节点值nodeType:节点类型元素element 1属性attr 2文本text 3注释comments 8文档document 9<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>Title</title></head><body><input type="text" value="请输入" id="a"><script>    var name = document.getElementById("a").nodeName;    var value = document.getElementById("a").nodeValue;    var type = document.getElementById("a").nodeType;    console.log(name);    console.log(value);    console.log(type);</script></body></html>运行结果:五、操作节点属性getAttribute(“属性名”)setAttribute(“属性名”,“属性值”)<!DOCTYPE html><html><head><meta charset="utf-8"><title>Title</title></head><body><input value="OK"><p id="demo">点击下面的按钮来设置按钮的类型属性。</p><button onclick="myFunction()">点我</button><script>function myFunction(){document.getElementsByTagName("INPUT")[0].setAttribute("type","button"); };</script><p>Internet Explorer 8 及更早的版本不支持 setAttribute 方法。</p></body></html>运行结果:点击后:六、创建和插入节点createElement( tagName) 创建一个标签名为tagName的新元素节点A.appendChild( B) 把B节点追加至A节点的末尾insertBefore( A,B ) 把A节点插入到B节点之前cloneNode(deep) 复制某个指定的节点<!DOCTYPE html><html><head><meta charset="utf-8"><title>Title</title></head><body><p id="demo">单击按钮创建有文本的按钮</p><button onclick="myFunction()">点我</button><script>function myFunction(){var btn=document.createElement("BUTTON");var t=document.createTextNode("CLICK ME");btn.appendChild(t);document.body.appendChild(btn);};</script></body></html>运行结果:七、删除和替换节点removeChild( node) 删除指定的节点replaceChild( newNode, oldNode)属性attr 用其他的节点替换指定的节点<!DOCTYPE html><html><head><meta charset="utf-8"><title>Title</title></head><body><ul id="myList"><li>Coffee</li><li>Tea</li><li>Milk</li></ul><p id="demo">单击按钮替换列表中的第一项。</p><button onclick="myFunction()">点我</button><script>function myFunction(){var textnode=document.createTextNode("Water");var item=document.getElementById("myList").childNodes[0];item.replaceChild(textnode,item.childNodes[0]);}</script><p>首先创建一个文本节点。<br>然后替换第一个列表中的第一个子节点。</p><p><strong>注意:</strong>这个例子只将文本节点的文本节点“Coffee”替换为“Water”,而不是整个LI元素,这也是替换节点的一种备选。</p></body></html>运行结果:点击后:八、style属性语法:HTML元素.style.样式属性="值"document.getElementById("titles").style.color="#ff0000"; document.getElementById("titles").style.fontSize="25px ";1九、className属性语法:HTML元素.className=“样式名称”function over(){      document.getElementById("cart").className="cartOver"; document.getElementById("cartList").className="cartListOver"; }  1234十、获取元素样式语法:HTML元素.style.样式属性;不支持IE浏览器document.defaultView.getComputedStyle(元素,null).属性;兼容IE浏览器HTML元素. currentStyle.样式属性;十一、HTML中元素属性offsetLeft 返回当前元素左边界到它上级元素的左边界的距离,只读属性offsetTop 返回当前元素上边界到它上级元素的上边界的距离,只读属性offsetHeight 返回元素的高度offsetWidth 返回元素的宽度offsetParent 返回元素的偏移容器,即对最近的动态定位的包含元素的引用scrollTop 返回匹配元素的滚动条的垂直位置scrollLeft 返回匹配元素的滚动条的水平位置clientWidth 返回元素的可见宽度clientHeight 返回元素的可见高度标准浏览器应用属性案例document.documentElement.scrollTop;  document.documentElement.scrollLeft;Chrome应用属性案例document.body.scrollTop;  document.body.scrollLeft;
  • [交流分享] 编译
    编译(compile)是指将代码某一种语言转化为另一种语言,如机器码,java的字节码,甚至c代码(flex/bison)。解释(interpret)是指将按照代码所表达的逻辑进行特定的操作。编译器是指专门用来进行编译这一动作的程序或组件。解释器是指专门用来进行解释这一动作的程序或组件。从编译的行为我们会发现这通常是一个一次性的行为(有特殊情况,比如glsl,比如有JIT功能的解释器),所以并没有关闭编译器这一说法(硬要说的话是指当编译器正在编译的时候,杀掉编译器这个进程)。你在F12中打开的调试界面中会发现网页的代码主要是有3种:html、js和css。简单的说,html提供了网页的骨架,js提供了网页的交互,css提供了网页的外貌。理论上如果浏览器足够模块化,是可以禁用js或者css或者html,但是我所了解的这几乎没有意义。html、js、css由浏览器内部的解释组件解释运行,并不会将其转化为其他某种语言,因此没有它们的编译组件。前端的组成多为这3种代码。但后端可以非常多样。对于cgi来说,其既可以是c、c++、java的程序、也可以是php、python、js(nodejs)的脚本。服务器有若干条规则告诉它对于不同的文件应该如何处理,比如程序就执行,脚本就调用相应的程序解释(也可能由服务器直接解释)。注意这里的服务器完全可以在内部建立处理请求的方法(比如由nodejs中的express直接搭建的服务器),在这种情况下我们可以认为是服务器直接解释了脚本(脚本和服务器写在一起了)。一个常见的网络请求的通俗流程如下:浏览器向服务器发送请求服务器调用一个cgi程序/脚本服务器将其输出返回给浏览器浏览器显示(html/css)运行(js)而禁用某个解释器会导致第2步无法进行。注意这里的禁用其实是指去禁止服务器处理这类脚本的方法(比如删掉了处理php文件的规则,导致由php构建的网页失效),导致服务器无法处理请求。转自:https://www.zhihu.com/question/368003317/answer/987083150
  • [公告] 国家广电总局发布4份5G高新视频系列技术白皮书(含下载地址)
    国家广播电视总局近日发布了4份5G高新视频白皮书,官方的表述中提到“为深化广播电视和网络视听供给侧结构性改革,培育打造更高技术格式、更新应用场景、更美视听体验的5G高新视频新产品新服务新业态”。DVBCN笔者结合目前已知的关于广电5G的顶层设计层面内容与个人的小拙见而形成了本文。    一、5G高新视频系列白皮书内容聚焦     何为“高新视频”?     这个概念从去年广电总局领导的多次会谈中便已经被提及,根据DVBCN的相关报道,在2019年8月26日时,“中国广电·青岛5G高新视频实验园区”通过政府引导、部省市三方共建的形式首次将“高新视频”映入大众眼帘;随后在2020年3月31日,湖南“5G高新视频多场景应用国家广播电视总局重点实验室”通过部省联合的方式正式挂牌。     按照官方确定的定义,5G高新视频是指5G环境下具有“更高技术格式、更新应用场景、更美视听体验”的视频。其中,“高”是指视频融合4K/8K、3D、VR/AR/MR、高帧率(HFR)、高动态范围(HDR)、广色域(WCG)等高技术格式;“新”是指具有新奇的影像语言和视觉体验的创新应用场景,能够吸引观众兴趣并促使其产生消费。         1、《5G高新视频—互动视频技术白皮书(2020)》    关键词:网络切片、智能编码     互动视频作为高新视频业态的重要组成部分,是指以“非线性视频”内容为主线,在“非线性视频”内容上开展的可支持时间域互动、空间域互动、事件型互动的内容互动视频业务,该业务具有分支剧情选择、视角切换、画面互动等交互能力,能够为用户带来强参与感、强沉浸度的互动观看体验。  下载链接:http://sme.miit.gov.cn/cms/document/attach_manager!download.action?id=8a8900c973ea2035017446cdfaad176a    2、《5G高新视频—沉浸式视频技术白皮书(2020)》     关键词:全场景、三维图像映射、三维声、DOCSIS组网     沉浸式视频作为高新视频业态的重要组成部分,是指一种采用裸眼观看方式获得身临其境感受,呈现画面覆盖人眼至少120°(水平)×70°(垂直)视场角的视频系统及具备三维声的音频系统。沉浸式视频通过播放器、投影幕或LED自显屏、多声道扬声器、播控系统等构建出超大视角、超高沉浸感的视听呈现系统,使观众能够同时获得周围多方位的视听信息,带来单一平面视频无法展示出的强大沉浸感,让观众真正有身临其境的感觉,应用场景丰富且形式多样。  下载链接:http://sme.miit.gov.cn/cms/document/attach_manager!download.action?id=8a8900c973ea2035017446ce2d69176b    3、《5G高新视频—VR视频技术白皮书(2020)》     关键词:AVSVR视频编码、6DoF     VR视频作为高新视频业态的重要组成部分,是指全景视频,如水平360°×垂直360°全景视频、水平180°×垂直180°全景视频等,用户可借助VR眼镜等虚拟现实设备观看全景视频,并获得身临其境的视觉感受。  下载链接:http://sme.miit.gov.cn/cms/document/attach_manager!download.action?id=8a8900c973ea2035017446ce7c4d176c    4、《5G高新视频—云游戏技术白皮书(2020)》     关键词:云计算、GPU、边缘计算     云游戏作为高新视频新型业务的重要组成部分,是指以云计算为基础的游戏方式,游戏在云端服务器上运行,并将渲染完毕后的游戏画面或指令压缩后通过网络传送给用户,用户可通过输入设备对游戏进行实时操作,获得全新的在线游戏娱乐体验。  下载链接:http://sme.miit.gov.cn/cms/document/attach_manager!download.action?id=8a8900c973ea2035017446cea679176d    二、广电5G前期部署频频展开,为规模化打好基础     中国广电5G受制于广电网络多方面的短板(如技术、基建、历史、资金、组织形式等),在实践层面上仍是无法快速启动商用的,根据中国移动老总的说法,双方还在就合作内容细节进行协商,2020年底仍无法启动规模建网。现阶段的进展,实际上还是以一些“小打小闹”似的5G试验基站、试验网等为主,大体已在湖北、贵州、陕西、北京、河北、湖南等省份的数座城市进行了小范围的部署测试。     在频谱方面,中国广电仍是目前世界上唯一具备2x30MHz以上700MHz频谱资源的5G网络运营商,凭借其覆盖广、成本低、绕射能力强等优势,可按照FDD模式实现在多场景的服务。未来将通过深挖700MHz频谱资源潜力,通过“700M广覆盖+4.9G容量覆盖+未来毫米波”的形式,依靠部署SA独立组网以支撑全业务服务,打造出一张广域连续覆盖的优质5G网络。     从2019年起,中国广电便成功将700MHz支持2*30M大载频带宽正式写入进了Rel-16标准,这也是全球首个5G低频段(Sub-1GHz)大频宽标准。随着R-16的冻结完成,中国广电还在推动R-17的标准立项,以期望支持5GNR广播特性。此外,中国广电在3GPPRAN4#93会议上,还联合了美国T-Mobile提交了5G600/700MHz终端四天线接收提案,将确保700MHz大尺寸终端可实现DLRx4天线,届时其接入速率及信号稳定性将提升近一倍,以实现系统性能突破。     在700MHz资源的探索中,中国广电已完成了如全球首个5G低频段(Sub-1GHz)大带宽标准、全球首个700MHz2x30MHz5G试验网、全球首个700MHz2x30MHz5G基带芯片网络能力演示、全球首个700MHz2x30MHz5G手机、我国首批通过型号核准的700MHz5G手机、CPE终端、工业模组、基站设备等成果。     除了700MHz频段外,中国广电也具备3.3GHz、4.9GHz频段的使用资质,通过与中国移动的合作,将能获得2.6GHz频段的补充,届时通过连续多段频谱能满足未来5G新场景业务需要。     在实践方面,中国广电5G实验室积极组织了相关技术验证、产品测试及融合业务开发等工作。如完成了全球首个700M2x30MHz终端性能验证,完成了业界首个基于700M5GVoNR下的语音通话,完成了700M+3.3G+4.9G协同组网测试,建成中国广电5G融合视频APP以及完成了广电5G网络测试平台的开发等。     广电5G商用的开展,更离不开前期产业链的支撑,因此中国广电已经组织完成了我国首批5G700MHz终端及系统设备的型号核准工作,首批获得工信部核准的5G700MHz设备已经公示,合作厂商涉及华为、中兴、vivo、爱立信、高通、联发科等产业伙伴,产品类别包括700MHz频段的基站设备、5G手机、CPE终端、工业模组等。其中,基站型号4个、手机型号10个、CPE终端型号2个、工业模组型号3个;芯片涉及了华为海思、高通、联发科等主流厂商。     三、广电5G的先期业务探索:高新视频娱乐场景     中国广电5G的架构及发展路径是以有线无线协同、广电通信协同、传播监管协同,进而推进实现差异化的发展。以中国广电云为基础,以中国广电互联互通平台为承载,努力打造移动终端、大屏终端以及新的工业互联网终端为主的协同发展模式,包括可管可控的安全网络、中国广电宽带电视等,重点将突出广电特色优势,推动制播升级、台网融合,以高新内容构建常态化直播内容。     随着泛在化的5G需求,正为行业的发展提供了新的动力,以消费电子终端(如智能手机、智能穿戴设备、智能家居等)、超高清视频(如4K/8K、VR/AR等)、云服务(以云计算为基础的服务方式)、车联网、工业互联网为代表的新兴业务形态将成为5G的可期支持场景。娱乐场景其实会先成为可涉足的领域,而车联网、工业物联、远程医疗等还需更长的周期得以实现。     据DVBCN的了解,其实在广电5G的目标定位中,就提到了要建立起包括“高新视频”在内的全新融合应用体系,未来将共同成为构建广电5G新场景应用的重要一环。广电总局确定5G高新视频四份技术白皮书,其实也意味着广电5G业务形态上将抢先聚焦的是娱乐场景,这也就回归了广电业务的内容本分,“内容为王”依然是广播电视与网络视听行业的亘古不变核心命题。      需注意的是,中国广电5G的策略中,将坚持在资产、经营和业务三个方面,推动5G赋能全国有线电视网络转型升级。统筹规划业务产品,协同布局市场体系,着力开展全国性业务孵化;从视频内容和科创文创出发,做好大小屏融合、多屏/跨屏服务,提供超高清4K/8K、虚拟/增强现实、物联网等新业务新业态,着力推动TV大屏与移动小屏的业务协同模式,促进广播电视及移动通信的深度融合和产业生态发展,推动广播电视人人通、终端通,促进从看电视模式到用电视模式的转变。突破传统电视形态,就更要确立起5G高新视频的技术体系,以满足可期的场景化需求。     四、高新视频可成为广电5G差异化业务场景实践     如前文中所提及的部分内容,中国广电引导下的全国有线电视网络运营商存在着诸多的短板,注定了其无法快速开展5G规模化部署及商用。三级基础电信企业,在这么多年的风风雨雨中,趁着数字红利的大背景,同时响应国家“三网融合”“宽带中国”“网络强国”等政策,无论是固网还是无线数据网络均取得了极为广阔的稳定市场份额,3G/4G红利驱动着移动上网用户渗透率向着极高的水平迈进。因此,广电入局移动通信注定是条颇为艰难的道路。     根据预计,2020年底国内将计划建设5G基站80万座,5G终端款数预计可超过500款,运营商们正在加快探索各类的5GC端及B端应用场景。广电5G尚未正式部署,未来对于策略的选择也至关重要,要从比较稳固的通讯运营商手中抢夺个人用户,还是较为困难的。而高新视频场景,则集结了沉浸式视频、VR、云游戏、互动场景等新的娱乐体验,通过构筑差异化的路线可以避开运营商的锋芒,具体表现于以下层面。     广电5G未来需要以内容服务为核心,通过5G技术下其独有的直播禀赋亦可作为差异化发展的主方向。广电网络经历了“制播分离”“台网分离”后,其运营能力在互联网的当下底气愈加不足。但广电的诸如新闻、重大赛事、综艺等内容制作的人才优势还在,未来仍可紧抓广电直播优势,从内容产出方面重新找准定位。     由于直播节目的时效性价值度颇高,5G的随时随地化服务是保证时效性的利器;5G的超大带宽也将使能新的特性,如多视角下的直播体验、直播与VR/AR等的结合等,将颠覆观看者的体验,进而进一步提升直播业务的价值。做好直播将能成为构建广电5G核心业务的竞争力。     除了内容,广电若面向C端用户,云游戏、VR、AR等是较为热点的5G业务。由于云游戏的本质实际上也是算力上云的过程,包括交互性在线视频流都将对云化的需求提升,未来更能打破终端的限制,实现用户流量聚合。初期,云游戏的订阅收入占比较低,但由于其弱化了渠道作用,诞生出了云游戏运营商的新角色,将有望改变现有的流量产业链。     而VR/AR业务,目前仍处于市场的培育阶段,广电有望从自身的优势制作领域切入,逐步在多个环节进行布局。起步期阶段,可以独有的版权音像资源切入内容制作环节,如将历史、文化、教育、旅游等优质资源进行二次创作,乃至实现内容出海;而在成熟期,可利用内容制作环节优势向产业链下游布局,如借用制作、内容运营两环节打通产业链其他环节。     未来的商用阶段,广电5G还要建立包括多量纲计费、切片计费、API计费、场景计费等模式,以满足不同层次客户的需求。5G切片运营也是广电5G需要不断探索的技术手段,通过构建5G切片的模板定义、创建、开通、计费体系等,进而实现基于切片的运营。当然,广电5G实际上也已明确了5G专网、边缘计算云台等构建体系,在实践层面上积极与多方协作,还是可以实现差异化发展的。  来源:DVBCN作者:张晓宝
  • [技术干货] AV1解码器模型
    为什么编解码器需要解码器模型   大多数现代视频编解码器都具有某种形式的解码器模型。在MPEG-2中,它被称为视频缓冲验证器(VBV);在H.264 / AVC和HEVC / H.265中,它可以称为假设参考解码器(HRD)。解码器模型提高了互操作性。解码器模型允许确认一个比特流是否可以被一个特定的解码器解码。这些模型还可以向解码器提供关于何时开始解码帧以能够及时显示它的指令。通常来说,视频解码器声明支持某个配置文件和级别。配置文件可以指定有关比特深度和色度二次采样的视频格式,以及解码器需要支持的以解码比特流的一组编码工具。级别描述了视频比特流的定量特征,例如分辨率,帧速率和比特率。对于视频编解码器生态系统而言至关重要的一点是,表明支持某个级别的解码器是否能够解码符合该级别要求的任何比特流,并且内容提供商和编码器制造商可以检查其生成的流是否符合这些要求。为了实现这些目标,由开放媒体联盟(AOM)开发的AV1规范定义了与配置文件和级别系统耦合的解码器模型。AV1解码器模型包括平滑/位流缓冲区,解码过程以及对解码后的帧缓冲区的操作。这篇文章可以作为AV1规范中与解码器型号和级别有关的部分的简介。本文的其余部分描述了一些AV1基本概念,AV1解码器模型,并提供了开发它时做出决策的原因。有关解码器模型的更多详细信息,请阅读AV1规范。 AV1比特流的高级结构   在更高级别上,AV1结构以开放比特流单元(OBU)打包。每个OBU都有一个标头,该标头提供标识其有效负载的信息(请参见图1)。可以在AV1视频比特流中出现的OBU类型的示例是序列头OBU,帧头OBU,元数据OBU,时间定界符OBU和图块组OBU。帧OBU由打包到一个OBU中的帧头和图块组OBU组成,以提供一种通用结构的更有效表示,其中帧头数据后紧跟着帧或图块组数据。 根据语法元素show_existing_frame的值,AV1帧头可以分为两种主要类型。  show_existing_frame等于0的帧头是需要解码的常规帧。show_existing_frame等于1的帧头指定了在该帧头中指定的显示时间显示先前解码的帧(由frame_to_show_map_idx表示)的命令。当解码顺序与显示顺序不同时,该机制有助于帧重新排序。另一个AV1概念是时间单元(TU),它由时间定界符OBU和在此之后且在下一个时间定界符OBU之前的所有OBU组成。TU始终遵循递增的显示顺序。如果未使用可伸缩性,则TU仅包含一个显示帧,即show_existing_frame等于1或show_frame等于1的帧。如果使用了可伸缩性,则TU中来自不同可伸缩层的所有显示帧都对应于相同的呈现时间。  一个TU也可以包含show_frame标志等于0的帧。此类帧会被解码但不会立即显示。它们用于支持如上所述的帧重排序。类似地,也可以发送覆盖帧,该覆盖帧会对先前解码的帧(称为替代参考帧(ARF))与源帧之间的差异进行编码。AV1比特流的这一方面类似于VP9编解码器中的超帧。在图2中显示出了将比特流划分为时间单元的示例。在该图中,帧编号按照显示顺序编号。比特流使用具有三个时间层的4帧的双向层级预测结构。show_frame等于0的帧显示为青色框,show_frame等于1的帧显示为深绿色框。FrameHdr 2是show_existing_frame标志等于1的帧头,该帧指向先前解码的Frame 2。 平滑缓冲 平滑缓冲   平滑缓冲器是AV1解码器的一部分,用于存储AV1比特流,直到压缩数据被解码器解码完毕为止。缓冲区由所谓的“漏桶”模型构成。漏桶的类比和编码器的操作有关,在压缩器中,压缩帧被分块转储到缓冲区中,并且数据以恒定速率连续离开缓冲区。解码器缓冲区是编码器之一的对应部分。注意,平滑缓冲器是解码器内部的。通常来说,解码系统会在更高级别上具有其他缓冲区,这些缓冲区不在AV1规范的范围内。从解码器模型的角度来看,可以将较高级别的缓冲区视为传输通道中造成总延迟的一部分。例如,从解码器的角度来看,与自适应流式传输有关的缓冲将被视为传输通道的一部分,在本文中不再讨论。而且,可能经常出现预先准备编码的比特流,而这会使延迟相当长。但是,对于模型而言,这样的长延迟通常不是问题,因为它在方程式中被抵消了。平滑缓冲区可确保解码器具有足够的内部存储器来存储到达(或读取)的位流的数据。当解码器需要时,它还确保下一帧的压缩数据在缓冲区中。平滑缓冲器的大小限制了瞬时比特率的变化,并限制了帧数据消耗的时序。AV1解码器模型仅支持可变比特率(VBR)操作模式,而不支持恒定比特率(CBR)模式。解码器模型的VBR模式是一种抽象模式,其中速率在最大级别比特率和零之间交替。听起来可能有限制。但是,此模型足以确保在最坏的情况下确保比特流与解码器功能匹配。平滑缓冲区充满度随时间变化的示意图如图3所示。时钟从与帧0有关的第一个比特的到达开始。斜线的斜率与比特到达的速率相对应。Removal 对应于从缓冲区中删除帧i的数据并开始解码帧i的时刻。注意,可能会有一段时间没有新的比特到达,例如Removal [1]之后的时间。这与编码器没有要发送的位(即编码器缓冲区为空)的时间段匹配。   帧i的removal 是根据两种解码模式之一来定义的。在解码调度模式下,这些值在比特流中用信号发送。在资源可用性模式下,根据解码器操作导出Removal 。解码的开始,即Removal [0],由两种模式中的变量decoder_buffer_delay确定。在时间Removal 时从解码缓冲区中删除的比特属于可解码帧组(DFG)i,即与帧i − 1相关的最后一个OBU的末尾与与帧i相关的最后一个OBU的末尾之间的所有OBU 。DFG中的OBU可以包括序列头OBU,帧和图块组OBU,帧头OBU和元数据OBU。DFG i的第一位到达平滑缓冲区由FirstBitArrival 确定,该值如下所示: FirstBitArrival[ i ] = max ( LastBitArrival[ i − 1 ], LatestArrivalTime[ i ] ). Respectively, arrival of the last bit of the DFG i is found asLastBitArrival[ i ] = FirstBitArrival[ i ] + CodedBits[ i ] ÷ BitRate. Finally, LatestArrivalTime[ i ] is determined asLatestArrivalTime[ i ] = ScheduledRemoval[ i ] − ( encoder_buffer_delay + decoder_buffer_delay ) ÷ 90 000.关于后一个表达式中coder_buffer_delay和decoder_buffer_delay之间关系以及其他有用的信息可以从Ribas-Corbera et al, 2003中找到很好的解释。该模型假设一个编码器具有一个以恒定速率发送比特的平滑缓冲器,并且一个解码器带有一个以该比特率接收比特的平滑缓冲器。通常来说,encoder_buffer_delay和decoder_buffer_delay的作用是确定帧的编码和解码之间的延迟,因此限制了比特流存储在解码器缓冲区中的“窗口”(通过网络/信道进行的传输是排除在外的)。由于缓冲区大小设置为比特流在最大级别比特率下的1秒,因此建议不要将这两个变量的总和超过90 000,这相当于时钟频率的1秒。当low_delay_mode标志等于1时,解码器在低延迟模式下运行,在该模式下,帧数据在预定的移除时间可能还不在缓冲区,在这种情况下,移除时间会延迟,直到数据到达缓冲区。除非处于低延迟模式,否则平滑缓冲区不应下溢。平滑缓冲区也不应溢出。这些限制适用于所有一致的比特流。解码帧缓冲区  帧缓冲器用于存储解码后的帧,以便可以将它们用于帧间预测或之后的显示。AV1定义了一个缓冲池,该缓冲池代表帧缓冲区的存储区域。AV1帧缓冲区的管理示意图如图4所示。AV1规范要求解码器支持10个物理帧缓冲区。帧缓冲器的时隙应能够以对应级别的最大分辨率存储帧。虚拟缓冲器索引(VBI)用于指向图片间预测中的帧。VBI可以在帧缓冲池中存储8个帧索引。并且允许不同的VBI条目指向同一缓冲区。空的VBI条目值为-1。当前帧缓冲区索引(cfbi)将索引存储到正在解码当前帧的帧缓冲区。注意,有一个“额外的”物理帧缓冲区,可用于保存帧以用于显示。  数组DecoderRefCount和PlayerRefCount(图4中的前两行)分别跟踪解码和显示过程是否仍需要帧缓冲区。DecoderRefCount跟踪对VBI中的帧缓冲区的引用数,并由语法元素refresh_frame_flags更新,而当帧在上次演示时已显示时,PlayerRefCount设置为0。空帧缓冲区和相应的计数器在图4中显示为白色方块。帧缓冲器对视频帧的解码和表示施加了限制,从而限制了编码器可以使用哪些预测结构和帧的重新排序。通常来说,10个帧缓冲区允许支持相当复杂的预测结构。解码器模型在应显示该帧时会验证该帧是否可用,并且在应解码一帧时在缓冲池中有一个空闲位置。解码过程  AV1解码器模型的解码过程将对平滑缓冲区和解码器帧缓冲区的操作联系在一起。特别地,解码器模型确定何时开始帧解码以及从平滑缓冲器中移除帧比特,这立即使平滑缓冲器的饱和度降低了相应的量。解码器模型还会计算解码何时完成,并将解码后的帧添加到帧缓冲区。它还确定何时为显示输出帧并将其从缓冲区中移除。AV1的一个特点是广泛使用替代参考帧(ARF),即用作预测参考但从未显示过的帧。此外,AV1在主配置文件中支持参考图片的缩放和可伸缩性。这意味着该模型应适应帧解码所需的不同时间,并支持不同的帧解码和显示速率。请注意,即使H.264和HEVC允许显示不可显示的图片,但这并不是这些编解码器的典型用法,而在AV1中,这是一种典型的使用情况,需要解码器模型很好地支持。   图5中展示了使用ARF进行编码的示例。该图显示了sub-GOP大小为4的双向层级结构编码。可显示的帧显示为灰色矩形。不显示的替代参考帧(ARF),用白色矩形表示。通常,该帧是在相同时间位置的帧的滤波版本,这为帧间预测带来了优势。由于对ARF进行了低通滤波,因此可以使用ARF作为预测因子对覆盖帧(图5中的OL)进行编码。覆盖帧会添加高频和纹理信息。为了支持替代参考帧和不同分辨率的帧,AV1解码器模型引入了以下功能:l在解码器中使用不同数量的时间单位并显示时钟节拍的可能性。注意,图5中的显示时钟节拍(DispCT)和解码时钟节拍(DecCT)具有不同的长度,因为解码和显示速率不同。解码器和显示刻度均使用相同的时标,并且时钟已同步l帧不会立即解码,并且根据帧分辨率和其他因素,可以有不同的时间可以看到,图5中的解码和显示时间轴使用了不同的时钟节拍。显然,在显示帧之前需要完成每个帧的解码。为了确保将来有可用的帧,编码器可以使用initial_display_delay_minus_1,该参数对应已解码的帧数减去在显示第一帧之前帧缓冲区中应可用的帧数。此参数相对于解码偏移了显示过程。如果未发信号,则将initial_display_delay_minus_1的值推断为BUFFER_POOL_MAX_SIZE −1。总的显示延迟包括coder_buffer_delay,它与图3中的变量相同,是从第一个比特到达到开始解码帧0之间的时间,即Removal [0]。解码帧i所需的时间确定为:TimeToDecode = lumaSamples ÷MaxDecodeRate,其中,MaxDecodeRate以样本/秒为单位进行测量,并由每个解码器级别指定。依次为帧内预测帧计算lumaSamples,如下所示:lumaSamples = UpscaledWidth * FrameHeight 。UpscaledWidth是使用可选的超分辨率工具后的帧的宽度。对于帧间预测帧,在参考图片重采样的情况下,考虑到来自分辨率更高的帧的可能运动补偿,可以确定此数量,如下所示lumaSamples = max_frame_width * max_frame_height。在可伸缩比特流中,将lumaSamples确定为当前可伸缩层的最大宽度和高度的乘积。除了知道帧解码需要花费多长时间之外,解码器模型还需要确定何时开始解码以及从平滑缓冲区中删除压缩帧,即Removal 。关于如何计算Removal ,AV1具有两种不同的模式。这两种模式是以下描述的资源可用性模式和解码调度模式。   资源可用性模式  在资源可用性模式中,如果在解码的帧缓冲区中有可用的空闲位置,则在完成前一帧解码之后立即解码一帧。否则,在一个位置释放后对帧进行解码。如果比特流低于解码器的最大级别限制,则逐帧解码这些帧,直到它们填满所有可用的帧缓冲区,此后解码速度会减慢。然后,仅在解码的帧缓冲区释放后,才进行下一帧的解码。帧0的删除时间由decoder_buffer_delay确定:Removal[ 0 ] = decoder_buffer_delay ÷ 90 000要使用资源可用性模式,应在比特流中设置以下参数:Timing_info_present_flag = 1,decoder_model_info_present_flag = 0,并且equal_picture_interval =1。标志equal_picture_interval等于1表示使用了恒定的帧速率,并且不发送显示时间。而是从帧速率和initial_display_delay_minus_1得出显示时间。解码定时Removal 由解码的帧缓冲器可用时的时刻来决定,并且也不发信号通知。一些解码器模型参数在资源可用性模式下采用默认值,例如,encoder_buffer_delay = 20 000,decoder_buffer_delay = 70 000,low_delay_mode_flag = 0。解码调度模式  在解码调度模式下,除了帧显示时间之外,还在视频比特流中用信号发送解码时间Removal 。该模型灵活地定义了何时从平滑缓冲区中删除帧并对其进行解码,以及何时显示该帧。除了使用恒定的帧速率外,该模型还可以通过显式发送帧表示时间来支持变化的帧速率。除此之外,解码器时钟节拍DecCT以及decoder_buffer_delay,encoder_buffer_delay和ScheduledRemovalTiming 也以这种解码模式发送信号。在这种模式下,帧i的计划删除时间如下所示。ScheduledRemovalTiming [0] = encoder_buffer_delay÷90 000。ScheduledRemovalTiming = ScheduledRemovalTiming [PrevRap] + buffer_removal_time * DecCT,其中PrevRap是先前的随机访问点(RAP)。如果帧i对应于RAP,但不是比特流中的第一帧,则PrevRAP对应于先前的RAP。这里的随机访问点是指比特流中的一个位置,可以从中解码该比特流。它通常对应于一个关键帧,并且应包含所有开始解码位流所需的信息,包括序列头。除非解码器在低延迟模式下运行,否则删除时间与计划的删除时间一致Removal = ScheduledRemovalTiming 。为了支持可伸缩性,解码器模型针对每个工作点(OP)单独发出信号。工作点与某个可伸缩层的解码及其解码所需的较低可伸缩层有关。比特流中较高的工作点可能需要使用符合较高级别的解码器。  解码器模型的两种模式之间的差异  可以注意到,解码调度模式下的解码器操作是资源可用性模式下的解码器操作的超集。编码器应该有可能用信号通知在资源可用性模式中可能已经导出的相同Removal 。解码时间表模式也可以用于控制帧解码时间表。图6示出了当比特流需求低于最大等级能力时的情形。在资源可用性模式下,将帧依次解码,并且当帧缓冲区中没有剩余空闲时隙时,解码速度会变慢。在解码调度模式下,可以以恒定速度解码比特流。注意,当解码器接近其最大能力工作时(例如,比特流接近于等级限制的分辨率和帧率),两种模式下的解码器操作是相似的。   另外,可以使用解码调度模式来更好地控制平滑缓冲区的饱和度(见图7)。该图说明了平滑缓冲区充满度如何随时间变化,取决于参数coder_buffer_delay和decoder_buffer_delay的值。该图使用1920×1080的视频,每秒24帧,编码为4.0级AV1比特流。选择符合8帧分层预测结构的帧大小;该示例已构建,并不代表任何特定的视频编码。最大的平滑缓冲区容量由水平虚线显示。  图7(a)显示了encoder_buffer_delay = 20 000,decoder_buffer_delay = 70 000时随时间变化的缓冲区充满度,它们等于资源可用性模式中使用的默认值。  通过减少coder_buffer_delay,可以更早开始解码,这在图7(b)中通过使用encoder_buffer_delay和decoder_buffer_delay均等于45 000进行了演示。请注意,encoder_buffer_delay与decoder_buffer_delay的总和等于90 000,这对应于1秒,即平滑缓冲区可以保持的最大级别比特率下的比特流持续时间。  通过使用参数coder_buffer_delay = 10000,decoder_buffer_delay = 45000,也可以将缓冲区充满度保持在较低水平,如图7(c)所示。 显示时间   AV1的显示时间通过frame_presentation_time语法元素发出信号。实际的显示时间还取决于InitialPresentationDelay,其计算方式如下:PresentationTime [0] = InitialPresentationDelay,PresentationTime [j] = PresentationTime [PrevPresent] + frame_presentation_time [j] * DispCT,其中,如果前一个RAP是关键帧RAP,则PrevPresent对应于与最后一个关键帧随机接入点(RAP)关联的索引;如果前一个RAP是延迟RAP,则PrevPresent对应于延迟恢复点(即对应于前向关键帧/open-GOP)。延迟的恢复点对应于open-GOP中的关键帧的显示时间。InitialPresentationDelay依次确定如下:InitialPresentationDelay =Removal[initial_display_delay_minus_1] + TimeToDecode [initial_display_delay_minus_1]。换句话说,InitialPresentationDelay是帧缓冲区中存在initial_display_delay_minus_1 + 1个解码帧的时间。当equal_picture_interval等于1时,使用恒定帧率模式,并且大于0的帧j的显示时间推导如下: PresentationTime [j] = PresentationTime [j − 1] +(num_ticks_per_picture_minus_1 + 1)* DispCT,其中PresentationTime [j-1]指的是显示顺序中的前一帧。如上导出PresentationTime [0]。  解码器模型信令  解码器模型参数主要在序列和帧级别上发出信号。序列标头可以包括Timing_info()结构,该结构包含显示时序信息。基本的解码器模型信息位于decoder_model_info()结构中。除此之外,还可以在序列头中用信号发送一个或多个操作点(OP),以实现可伸缩的比特流。每个OP对应于解码该OP所必需的解码器级别,并且可以可选地被分配一组解码器模型参数。Timing_info()结构包含时间刻度和显示刻度号num_units_in_display_tick中的时间单位数,而coder_model_info()结构包含解码器刻度号num_units_in_decoding_tick中的单位数以及其他解码器模型语法元素的长度。这两个语法元素将DispCT和DecCT变量的持续时间定义为:DispCT = num_units_in_display_tick÷time_scale,DecCT = num_units_in_decoding_tick÷time_scale。operating_parameters_info()结构包含用于操作点的 encoder_buffer_delay 和decoder_buffer_delay 以及低延迟模式标志。如果使用解码器模型,则可以在帧头中为选定的工作点发信号通知以解码时钟节拍为单位的buffer_removal_time。帧头中的temporal_point_info()结构包含frame_presentation_time语法元素,该元素以显示时钟节拍表示信号的显示时间。 AV1等级  在撰写本文时,AV1规范定义了2.0到6.3级,该级别大致涵盖了将视频从426×240 @ 30fps解码到7680×4320 @ 120fps所需的解码器功能。解码器模型将比特流和解码器一致性统一到了一定水平。AV1级别声明支持某种帧分辨率(一帧中的样本数),解码以及显示的采样率。与解码器模型相关的其他级别参数包括最大比特率和帧头速率。级别可以属于两个级别(主级别和高级级别)之一,其中高级级别具有比主级别更高的最大比特率,并且面向专业和特殊应用。最大比特率直接定义了平滑缓冲区的大小,该平滑缓冲区应能够以最大级别的比特率保持最多1秒的压缩流。由于对一致的比特流不允许缓冲区上溢或下溢,因此这对峰值比特率施加了限制。除此之外,还规定了帧的最小压缩率。声称符合某个级别的比特流,如果通过解码器模型,则不应违反约束。顺便说一句,相应的解码器应能够解码相同或更低级别的任何顺应性比特流,只要该比特流符合AV1规范(包括通过相应级别的解码器模型测试)即可。可以在此Wikipedia链接上找到AV1级别的表,尽管通常推荐的来源是AV1规范。  进一步阅读  这篇文章介绍了AV1解码器模型。它还提供了一些有关在开发时进行的设计选择的背景。在AV1规范(https://aomediacodec.github.io/av1-spec/av1-spec.pdf)中可以找到AV1解码器模型的完整描述和更多细节。      来自:livevideostack链接:https://www.livevideostack.cn/news/wechat0724/
总条数:160 到第 页
上滑加载中