这里主要是utils下的部分文件的补充注释

文章目录

  • config.py
  • array_tool.py
  • eval_tool.py
  • vis_tool.py
  • 不熟悉的numpy,pytorch,python操作
    • data.detach()
    • data.numpy()
    • t.from_numpy(data)
      • data.reshape(1)[0]
      • data.item()
    • from collections import defaultdict
    • np.unique
    • iter(pred_bboxes)#生成迭代器
    • np.logical_not()
    • np.cumsum(match_l==1)#统计累计match_1=1的个数
    • np.nan_to_num
    • np.maximum.accumulate(mpre[::-1])[::-1]

config.py

from pprint import pprint#打印出来更美观
class Config:#datavoc_data_dir='/home/wrc/yuyijie/KITTI/VOCdevkit/VOC2007'min_size=600max_size=1000num_works=8test_num_works=8rpn_sigma=3.roi_sigma=1.# for optimizerwd=0.0005lr_decay=0.1lr=1e-3#visenv='faster-rcnn'port=8097#visdom 端口plot_every=40#presetdata='voc'pretrained_model='vgg16'epoch=14use_adam=Falseuse_chainer=Falseuse_drop=False#debugdebug_file='/tmp/debugf'test_num=10000#modelload_path=Nonecaffe_pretrain=Falsecaffe_pretrain_path='checkpoints/vgg16_caffe.pth'def _parse(self,kwargs):#解析并设置用户设定的参数state_dict=self._state_dict()#读取Config类所有参数dict{para_name:para_value}for k,v in kwargs.items():#遍历用户传来的dictif k not in state_dict:raise ValueError('Unknow option:"--%s"'%k)setattr(self,k,v)#设置参数print('=============user config=========')pprint(self._state_dict())#打印参数print('=============end=========')def _state_dict(self):return {k:getattr(self,k) for k ,_ in Config.__dict__.items() if not k.startswith('_')}#字典解析,字典解析,Config.__dict__.items() 取出类中所有的函数、全局变量以及一些内置的属性# 前面我们设定的都是全局变量(键值对:比如min_size = 600),没有函数,而系统内置属性都是_打头的,# 所以我们要not k.startswith('_')  返回结果dict{para_name0:para_value0,para_name1:para_value1,....}
opt=Config()#创建config对象

array_tool.py

import torch as t
import numpy as npdef tonumpy(data):#将数据转化为Numpyif isinstance(data,np.ndarray):#如果数据是numpy类型 直接返回return dataif isinstance(data,t.Tensor):return data.detach().cpu().numpy()#将变量从图中分离(使得数据独立,以后你再如何操作都不会对图,对模型产生影响)#如果是gpu类型还要转化为cpu,再转化为numpydef totensor(data,cuda=True):#将数据转化为Tensor或者cudaif isinstance(data,np.ndarray):tensor=t.from_numpy(data)if isinstance(data,t.Tensor):tensor=data.detach() #隔离变量if cuda:tensor=tensor.cuda()return tensordef scalar(data):#取出数据的值if isinstance(data,np.ndarray):return data.reshape(1)[0]#如果是numpy类型(必须为1个数据 几维都行) 取出这个数据的值if isinstance(data,t.Tensor):return data.item()#如果是tensor类型 调用pytorch常用的item方法 取出tensor的值

eval_tool.py

from __future__ import division
from collections import defaultdict
#这里的defaultdict(function_factory)构建的是一个类似dictionary的对象,
# 其中keys的值,自行确定赋值,但是values的类型,是function_factory的类实例,而且具有默认值。
# 比如default(int)则创建一个类似dictionary对象,里面任何的values都是int的实例,
# 而且就算是一个不存在的key, d[key] 也有一个默认值,这个默认值是int()的默认值0.
import itertools
import numpy as np
import six
from model.utils.bbox_tools import bbox_ioudef eval_detection_voc(pred_bboxes,pred_labels,pred_scores,gt_bboxes,gt_labels,gt_difficults=None,iou_thresh=0.5,use_07_metric=False):#根据PASCAL VOC evaluation#所有参数都是list# test_num张图片(图片数据来自测试数据testdata)的预测框,标签,分数,和真实的框,标签和分数。所有参数都是list# len(list)=opt.test_num(default=10000)# pred_boxes: [(A, 4), (B, 4), (C, 4)....共test_num个]# 输入源gt_数据# 经过train.predict函数预测出的结果框# pred_labels[(A,), (B,), (C,)...共test_num个]# pred_scores同pred_labels# A, B, C, D是由nms决定的个数,即预测的框个数,不确定。# gt_bboxes:[(a, 4), (b, 4)....共test_num个]# a b...是每张图片标注真实框的个数# gt_labels与gt_difficults同理prec,rec=calc_detection_voc_prec_rec(#计算每个label类别的准确率和召回率pred_bboxes,pred_labels,pred_scores,gt_bboxes,gt_labels,gt_difficults,iou_thresh=iou_thresh)ap=calc_detection_voc_ap(prec,rec,use_07_metric=use_07_metric)#根据prec和rec计算map和apreturn {'ap':ap,'map':np.nanmean(ap)}def calc_detection_voc_prec_rec(pred_bboxes,pred_labels,pred_scores,gt_bboxes,gt_labels,gt_difficults=None,iou_thresh=0.5):pred_bboxes=iter(pred_bboxes)#生成迭代器pred_labels=iter(pred_labels)#生成迭代器pred_scores=iter(pred_scores)#生成迭代器gt_bboxes=iter(gt_bboxes)#生成迭代器gt_labels=iter(gt_labels)#生成迭代器if gt_difficults is None:gt_difficults=itertools.repeat(None)#itertools.repeat生成一个重复的迭代器 None是每次迭代获得的数值else:gt_difficults=iter(gt_difficults)n_pos=defaultdict(int)#defaultdict当key不存在时,dict[key]=default(int)=0 default(list)=[]score=defaultdict(list)match=defaultdict(list)for pred_bbox,pred_label,pred_score,gt_bbox,gt_label,gt_difficult in six.moves.zip(pred_bboxes,pred_labels,pred_scores,gt_bboxes,gt_labels,gt_difficults):if gt_difficults is None:gt_difficults=np.zeros(gt_bbox.shape[0],dtype=bool)#全部设置为非difficult#遍历一边图片中,所有出现的labelfor l in np.unique(np.concatenate((pred_label,gt_label)).astype(int)):#拼接后返回无重复的从小到大排序的一维numpy 如[2,3,4,5,6]# 并遍历这个一维数组,即遍历这张图片出现过的标签数字(gt_label+pred_label)#>>> np.unique([1, 1, 2, 2, 3, 3])# array([1, 2, 3])# >>> a = np.array([[1, 1], [2, 3]])# >>> np.unique(a)# array([1, 2, 3])pred_mask_l=pred_label==l#//广播pred_mask_l=[eg. T,F,T,T,F,F,F,T..] 所有预测label中等于L的为T 否则Fpred_bbox_l=pred_bbox[pred_mask_l]#选出label=L的所有pre_boxpred_score_l=pred_scores[pred_mask_l]#label=L 对应所有pre_score#sort by scoreorder=pred_score_l.argsort()[::-1]#获得score降序排序索引pred_bbox_l=pred_bbox_l[order]pred_score_l=pred_score_l[order]gt_mask_l=gt_label==l#同理gt_bbox_l=gt_bbox[gt_mask_l]gt_difficult_l=gt_difficult[gt_mask_l]n_pos[l]+=np.logical_not(gt_difficult_l).sum()#对T,F取反求和,统计出difficult=0的个数score[l].extend(pred_score_l)#score={l:predscore_l,...} extend是针对defaultdict中是list的情况if len(pred_bbox_l)==0:#没有预测的label=L的box,即真实label有L,我们全没有预测到continue#跳过这张图片 此时没有对match字典操作,之前score[l].extend操作也为空 保持了match和score的形状一致if len(gt_bbox_l)==0:#没有真实的label=L的情况 即预测中有L 真实中没有 全都预测错了match[l].extend((0,)*pred_bbox_l.shape[0])#match{L:[0,0,0...n_pred_box个0]}# VOC evaluation follows integer typed bounding boxes.# 作者给的注释是follows integer typed bounding boxes# 但是只改变了ymax, xmax的值,重要的是这样做并不能转化为整数# pred_bbox和gt_bbox只# 参与了IOU计算且后面没有参与其他计算pred_bbox_l=pred_bbox_l.copy()pred_bbox_l[:,2:]+=1#ymax,xmax+=1gt_bbox_l=gt_bbox_l.copy()gt_bbox_l[:,2:]+=1iou=bbox_iou(pred_bbox_l,gt_bbox_l)#计算两个box的iougt_index=iou.argmax(axis=1)#有len(pred_bbox_l)个索引,第i个索引值n表示gt_box[n]与pred_box[i]iou最大#比如 4个pred_box 3 个gtbox#     gt0 gt1 gt2    最大对应用*表示   这里的gt_index输出将是[1,0,0,2] 第0个索引值1 表示gt1和pred_box[0]iou最大#  A       *#  B   *#  C   *#  D           *#要注意 这里的gt_index是会重复的 因为同一个gt会与很多pred_box拥有最大iou#    #计算iou 将会是(N,K)纬度的输出,如果所有tl都大于br的话gt_index[iou.max(axis=1)<iou_thresh]=-1#这里则是在上面的基础上把小于阈值的gt_index设置为-1#将gt_box与pred_box iou<thresh的索引值置为-1# 即针对每个pred_bbox,与每个gt_bbox IOU的最大值 如果最大值小于阀值则置为-1# 即我们预测的这个box效果并不理想 后续会将index=-1的 matchlabel=0del iouselec=np.zeros(gt_bbox_l.shape[0],dtype=bool)for gt_idx in gt_index:#遍历gt_index索引值if gt_index>=0:#即iou满足条件的bboxif gt_difficult_l[gt_idx]:#对应的gt_difficult=1即困难标记match[l].append(-1)#match[l] 后面追加一个-1else:#这里的做法可能有个问题 就是比如gt1同时根B,C两个prebox拥有超过阈值的iou这时候的排序就很关键 ,要先按照分数来进行排序if not selec[gt_idx]:#没有被选国,select[idx]=0的时候match[l].append(1)else:#对应的gt_box已经被选国一次,即已经和前面的某个pre_box iou最大match[l].append(0)selec[gt_idx]=True#一个gt被选过之后,则要设置为Trueelse:#不满足iou>threshmatch[l].append(0)#追加0 很重复匹配的结果一样#我们注意到上面为每个pred_box都打了label 0, 1, -1#len(match[l]) = len(score[l]) = len(pred_bbox_l)for iter_ in ( # 上面的 six.moves.zip遍历会在某一iter遍历到头后停止,由于pred_bboxes等是全局iter对象,#我们此时继续调用next取下一数据,如果有任一数据不为None,那么说明他们的len是不相等的 有悖常理,数据错误pred_bboxes,pred_labels,pred_scores,gt_bboxes,gt_labels,gt_difficult):if next(iter_,None) is not None:#next(iter_,None)表示调用next 如果已经遍历到了头 不抛出异常而是返回Noneraise ValueError("Length of input iterables need to be same")n_fg_class=max(n_pos.keys())+1#有n_fg_class个类prec=[None]*n_fg_classrec=[None]*n_fg_classfor l in n_pos.keys():#遍历所有labelscore_l=np.array(score[l])match_l=np.array(match[l],dtype=np.int8)order=score_l.argsort()[::-1]match_l=match_l[order]#对应match按照score由大到小排序tp=np.cumsum(match_l==1)#统计累计match_1=1的个数# 比如 match_l =[1,1,1,0,1,1] np.cumsum(match)=[1,2,3,3,4,5]# tp=[1,2,3,3,4,5] fp=[0,0,0,1,1,1] 长度是所有predbox的总数 prec[l]=[1,1,1,0.75,0.8,0.833]fp=np.cumsum(match_l==0)prec[l]=tp/(tp+fp)if n_pos[l]>0:#如果n_pos[l]=0 那么rec[l]=Nonerec[l]=tp/n_pos[l]#这里干嘛不用所有的gt[l]的总数return prec,rec
def calc_detection_voc_ap(prec,rec,use_07_metric=False):n_fg_class=len(prec)ap=np.empty(n_fg_class)for l in six.moves.range(n_fg_class):#遍历每个labelif prec[l] is None or rec[l] is None:#如果为NOne 则ap设置为np.nanap[l]=np.nancontinueif use_07_metric:# 11 point metricap[l]=0for t in np.arange(0.,1.1,0.1):#t=0 0.1 0.2...1.0if np.sum(rec[l]>=t)==0:#这个标签召回率没有大于阈值的p=0else:p=np.max(np.nan_to_num(prec[l])[rec[l]>=t])#p=(rec>=t时,对应index:prec中的最大值) np.nan_to_num是为了#让None=0一边计算ap[l]+=p/11else:mpre=np.concatenate(([0],np.nan_to_num(prec[l]),[0]))#头尾添加0mrec=np.concatenate(([0],rec[l],[1]))#头添加0 尾添加1mpre=np.maximum.accumulate(mpre[::-1])[::-1]#获得从小到大的累计最大值#>>> np.add.accumulate([2, 3, 5])# array([ 2,  5, 10])# >>> np.multiply.accumulate([2, 3, 5])# array([ 2,  6, 30])# 我们知道# 我们是按score由高到低排序的# 而且我们给box打了label# 0, 1, -1# score高时1的概率会大,所以pre是累计降序的# 而rec是累积升序的,那么此时将pre倒序再maxuim.ac# 获得累积最大值,再倒序后# 从小到大排序的累积最大值i=np.where(mrec[1:]!=mrec[:-1])[0]#差位比较,看哪里改变了recall的值,记录index(x轴)#and sum (\Delta recall)*precap[l]=np.sum((mrec[i+1]-mrec[i])*mrec[i+1])#差值*mpre_max的值,(x轴之差*ymax)return ap

vis_tool.py

import timeimport numpy as np
import matplotlib
import torch as t
import visdommatplotlib.use('Agg')
from matplotlib import pyplot as plot# from data.voc_dataset import VOC_BBOX_LABEL_NAMESVOC_BBOX_LABEL_NAMES = ('fly','bike','bird','boat','pin','bus','c','cat','chair','cow','table','dog','horse','moto','p','plant','shep','sofa','train','tv',
)
def vis_image(img,ax=None):#img是经过你标准化的0-255的rgb图像 ax是传来的matplotlib.axes,Axis对象,告诉我们画在那里if ax is None:#如果没有这个对象 创建一个(1,1,1)的fig=plot.figure()ax=fig.add_subplot(1,1,1)#CHW-》HWCimg=img.transpose(1,2,0)ax.imshow(img.astype(np.uint8))#matplotlib 支持0-1的图像显示,也支持0-255的图像显示,转成uint8是为了告诉它我们的图像是0-255的return ax #返回axis对象后续使用,vis_box会继续调用,在此图片基础上画框等def vis_bbox(img,bbox,label=None,score=None,ax=None):label_names=list(VOC_BBOX_LABEL_NAMES)+['bg']if label is not None and not len(bbox)==len(label):#框个数不等于标签个数raise ValueError('The length of label must be same as that of bbox')if score is not None and not len(bbox)==len(score):#score个数不等于标签个数raise ValueError('The length of score must be same as that of bbox')ax=vis_image(img,ax=ax)if len(bbox)==0:#没有框的话return axfor i,bb in enumerate(bbox):#遍历一张图中所有的框xy=(bb[1],bb[0])#左上角height=bb[2]-bb[0]width=bb[3]-bb[1]ax.add_patch(plot.Rectangle(xy,width,height,fill=False,edgecolor='red',linewidth=2))#画矩形caption=list()if label is not None and label_names is not None:lb=label[i]#此框对应的labelif not (-1<=lb<len(label_names)):#label名字超过边界raise ValueError('No corresponding name is given')caption.append(label_names[lb])#物体名字加入caption listif score is not None:sc=score[i] #找到此框对应分数caption.append('{:.2f}'.format(sc))#加入caption listif len(caption)>0:ax.text(bb[1],bb[0],':'.join(caption),style='italic',bbox={'facecolor':'white','alpha':0.5,'pad':0})#bbox是框住字体的框的一些参数return ax
def fig2data(fig):#将matplotlib的figure图像 转化为RGBA形式的NUmpy返回fig.canvas.draw()#画图,渲染 fig.canvas.draw()重绘所有内容。这是你的瓶颈。就你而言,你不需要重新绘制轴边界,刻度标签等等w,h=fig.canvas.get_width_height()#得到figure的w,hbuf=np.fromstring(fig.canvas.tostring_argb(),dtype=np.uint8)#得到numpy形式的argb图像buf.shape=(w,h,4)#(w,h,argb)buf=np.roll(buf,3,axis=2)#(w,h,rgba) 在第二轴水平向右滚动3个距离return buf.reshape(h,w,4)#(h,w,4)
def fig4vis(fig):#将matplotlib的figure图像转化为pytorch 常用的3通道RGB CHW 0-1图像ax=fig.get_figure()img_data=fig2data(ax).astype(np.int32)plot.close()return img_data[:,:,:3].transpose((2,0,1))/255#(H,W,RGBA)-》(H,W,RGB)-》(RGB,H,W)即CHW/255 -》0-1def visdom_bbox(*args,**kwargs):#我们要使用的函数 包含上面所有的函数调用,传入img box score 画成一张图像 以numpy的形式返回fig=vis_bbox(*args,**kwargs)data=fig4vis(fig)return dataclass Visualizer(object):  #Visdom可视化部分def __init__(self, env='default', **kwargs):self.vis = visdom.Visdom(env=env, use_incoming_socket=False, **kwargs) #visdom对象self._vis_kw = kwargs  #参数self.index = {}self.log_text = ''def reinit(self, env='default', **kwargs):#可能由于用户要改变kwargs初始化参数 而重新初始化visdom对象self.vis = visdom.Visdom(env=env, **kwargs)return selfdef plot_many(self, d):#plot#参数 d: dict (name,value) i.e. ('loss',0.11)for k, v in d.items():if v is not None:self.plot(k, v) #调用下面的plot函数 画折线图(各种损失折线图,训练map折线图)def img_many(self, d):    #参数 d: dict (name,value) i.e. ('loss',0.11)for k, v in d.items():self.img(k, v)  #调用下面的img函数 画图def plot(self, name, y, **kwargs): #画一条直线  title=namex = self.index.get(name, 0)self.vis.line(Y=np.array([y]), X=np.array([x]),win=name,opts=dict(title=name),update=None if x == 0 else 'append',**kwargs)self.index[name] = x + 1def img(self, name, img_, **kwargs): #画图片  title=nameself.vis.images(t.Tensor(img_).cpu().numpy(),win=name,opts=dict(title=name),**kwargs)def log(self, info, win='log_text'): #打印log日志  时间+infoself.log_text += ('[{time}] {info} <br>'.format(time=time.strftime('%m%d_%H%M%S'), \info=info))self.vis.text(self.log_text, win)def __getattr__(self, name): #按name获得一个属性return getattr(self.vis, name)def state_dict(self): #返回现有参数 dictreturn {'index': self.index,'vis_kw': self._vis_kw,'log_text': self.log_text,'env': self.vis.env}def load_state_dict(self, d):  #参数 d: dict (name,value) i.e. ('loss',0.11)  初始化参数self.vis = visdom.Visdom(env=d.get('env', self.vis.env), **(self.d.get('vis_kw')))self.log_text = d.get('log_text', '')self.index = d.get('index', dict())return self

不熟悉的numpy,pytorch,python操作

data.detach()

将tensor变量从图中分离(使得数据独立,以后你再如何操作都不会对图,对模型产生影响)

data.numpy()

tensor2numpy

t.from_numpy(data)

numpy2tensor

data.reshape(1)[0]

如果是numpy类型(必须为1个数据 几维都行) 取出这个数据的值

data.item()

如果是tensor类型 调用pytorch常用的item方法 取出tensor的值

from collections import defaultdict

这里的defaultdict(function_factory)构建的是一个类似dictionary的对象,
其中keys的值,自行确定赋值,但是values的类型,是function_factory的类实例,而且具有默认值。 比如default(int)则创建一个类似dictionary对象,里面任何的values都是int的实例,
而且就算是一个不存在的key, d[key] 也有一个默认值,这个默认值是int()的默认值0.
这在计算map等eval指标的时候非常有用
比如
if not selec[gt_idx]:#没有被选国,select[idx]=0的时候 match[l].append(1)
就能把所有的1都放到l对应的list中 而且即使没有对应的值,该list也会存在只是为空
{‘8’:[1,1,1,1]}这种形式

np.unique
#拼接后返回无重复的从小到大排序的一维numpy 如[2,3,4,5,6]# 并遍历这个一维数组,即遍历这张图片出现过的标签数字(gt_label+pred_label)#>>> np.unique([1, 1, 2, 2, 3, 3])# array([1, 2, 3])# >>> a = np.array([[1, 1], [2, 3]])# >>> np.unique(a)# array([1, 2, 3])
iter(pred_bboxes)#生成迭代器

把一个list可以变成迭代器,相比直接用list去做循环能节省很多内存
迭代器的节省内存的原因:
一个是生成器函数,外表看上去像是一个函数,但是没有用return语句一次性的返回整个结果对象列表,取而代之的是使用yield语句一次返回一个结果。另一个是生成器表达式,类似于上一小节的列表解析,但是方括号换成了圆括号,他们返回按需产生的一个结果对象,而不是构建一个结果列表。

生成器函数他通过yield关键字返回一个值后,还能从其退出的地方继续运行,因此可以随时间产生一系列的值。他们自动实现了迭代协议,并且可以出现在迭代环境中。运行的过程是这样的:生成器函数返回一个迭代器,for循环等迭代环境对这个迭代器不断调用next函数,不断的运行到下一个yield语句,逐一取得每一个返回值,直到没有yield语句可以运行,最终引发StopIteration异常。

np.logical_not()

输入一组bool型的数据
对T,F取反

np.cumsum(match_l==1)#统计累计match_1=1的个数

cumsum(a, axis=None, dtype=None, out=None):
Axis along which the cumulative sum is computed. The default
(None) is to compute the cumsum over the flattened array.

Return the cumulative sum of the elements along a given axis.
比如 match_l =[1,1,1,0,1,1] np.cumsum(match==1)=[1,2,3,3,4,5]
tp=[1,2,3,3,4,5] fp=[0,0,0,1,1,1] 长度是所有predbox的总数 prec[l]=[1,1,1,0.75,0.8,0.833]

Examples-------->>> a = np.array([[1,2,3], [4,5,6]])>>> aarray([[1, 2, 3],[4, 5, 6]])>>> np.cumsum(a)array([ 1,  3,  6, 10, 15, 21])>>> np.cumsum(a, dtype=float)     # specifies type of output value(s)array([  1.,   3.,   6.,  10.,  15.,  21.])>>> np.cumsum(a,axis=0)      # sum over rows for each of the 3 columnsarray([[1, 2, 3],[5, 7, 9]])>>> np.cumsum(a,axis=1)      # sum over columns for each of the 2 rowsarray([[ 1,  3,  6],[ 4,  9, 15]])"""
np.nan_to_num

Replace NaN with zero and infinity with large finite numbers

-------->>> np.nan_to_num(np.inf)1.7976931348623157e+308>>> np.nan_to_num(-np.inf)-1.7976931348623157e+308>>> np.nan_to_num(np.nan)0.0>>> x = np.array([np.inf, -np.inf, np.nan, -128, 128])>>> np.nan_to_num(x)array([ 1.79769313e+308, -1.79769313e+308,  0.00000000e+000, # may vary-1.28000000e+002,  1.28000000e+002])>>> np.nan_to_num(x, nan=-9999, posinf=33333333, neginf=33333333)array([ 3.3333333e+07,  3.3333333e+07, -9.9990000e+03, -1.2800000e+02,  1.2800000e+02])>>> y = np.array([complex(np.inf, np.nan), np.nan, complex(np.nan, np.inf)])array([  1.79769313e+308,  -1.79769313e+308,   0.00000000e+000, # may vary-1.28000000e+002,   1.28000000e+002])>>> np.nan_to_num(y)array([  1.79769313e+308 +0.00000000e+000j, # may vary0.00000000e+000 +0.00000000e+000j,0.00000000e+000 +1.79769313e+308j])>>> np.nan_to_num(y, nan=111111, posinf=222222)array([222222.+111111.j, 111111.     +0.j, 111111.+222222.j])"""
np.maximum.accumulate(mpre[::-1])[::-1]

获得从小到大的累计最大值

Simple-fasterrcnn源码学习笔记(4)相关推荐

  1. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  2. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  3. Vuex 4源码学习笔记 - 通过Vuex源码学习E2E测试(十一)

    在上一篇笔记中:Vuex 4源码学习笔记 - 做好changelog更新日志很重要(十) 我们学到了通过conventional-changelog来生成项目的Changelog更新日志,通过更新日志 ...

  4. Vuex 4源码学习笔记 - Vuex是怎么与Vue结合?(三)

    在上一篇笔记中:Vuex源码学习笔记 - Vuex开发运行流程(二) 我们通过运行npm run dev命令来启动webpack,来开发Vuex,并在Vuex的createStore函数中添加了第一个 ...

  5. jquery源码学习笔记三:jQuery工厂剖析

    jquery源码学习笔记二:jQuery工厂 jquery源码学习笔记一:总体结构 上两篇说过,query的核心是一个jQuery工厂.其代码如下 function( window, noGlobal ...

  6. 雷神FFMpeg源码学习笔记

    雷神FFMpeg源码学习笔记 文章目录 雷神FFMpeg源码学习笔记 读取编码并依据编码初始化内容结构 每一帧的视频解码处理 读取编码并依据编码初始化内容结构 在开始编解码视频的时候首先第一步需要注册 ...

  7. Apache log4j-1.2.17源码学习笔记

    (1)Apache log4j-1.2.17源码学习笔记 http://blog.csdn.net/zilong_zilong/article/details/78715500 (2)Apache l ...

  8. PHP Yac cache 源码学习笔记

    YAC 源码学习笔记 本文地址 http://blog.csdn.net/fanhengguang_php/article/details/54863955 config.m4 检测系统共享内存支持情 ...

  9. Vuex 4源码学习笔记 - 通过dispatch一步步来掌握Vuex整个数据流(五)

    在上一篇笔记中:Vuex 4源码学习笔记 - Store 构造函数都干了什么(四) 我们通过查看Store 构造函数的源代码可以看到主要做了三件事情: 初始化一些内部变量以外 执行installMod ...

  10. RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法

    前言 RecyclerView已经出来很久,现在几乎应该都会用RecyclerView代替Listview,虽然我觉得大多数人应该还是不太清楚这两者之前的区别的,或者说RecyclerView相对于L ...

最新文章

  1. 力扣(LeetCode)刷题,简单+中等题(第29期)
  2. java mobile phone games_j2me100-src Java
  3. linux shell 变量 管道,linux下shell,变量,管道,重定向等基础知识及技巧
  4. WebDriver(C#)之十点使用心得
  5. Java EE 7是最终版本。 思想,见解和进一步的指针。
  6. C语言的inline
  7. Codeforces Round #249 (Div. 2) A. Queue on Bus Stop
  8. 亚马逊靠“新闻稿”推动创新,跃居市值第一
  9. logback整合Logstash
  10. 飞秋命令行发送消息和文件
  11. wifi上行下行速度测试_测试网速_测试网速wifi在线测试
  12. flog和flag,FLAG标签和3xFLAG标签的序列
  13. Katana中设置全局变量
  14. win7用友u8安装教程_如何在win7系统中安装用友u8(图文)
  15. 使用rem,使字体大小自适应屏幕
  16. 2020知道python答案_2020知道智慧树Python程序设计答案
  17. Unity有哪些适合拿来练手的游戏项目?
  18. 国际歌 英特纳雄耐尔一定要实现
  19. java 切换系统输入法,Android7.0更换系统默认输入法
  20. tomcat的宏观架构

热门文章

  1. TP-LINK WR720N升级8M Flash 64M Ram,加USB-TTL,烧openwrt官方固件,接蓝牙适配器
  2. 换零钱程序c语言,《SICP》换零钱的递归法与迭代法
  3. 前端优化系列:DNS预获取 dns-prefetch 提升页面载入速度
  4. Latch及latch冲突
  5. 基于seq2seq的中国古诗词自动生成技术
  6. WordPress常见问题及其解决方法
  7. linux驱动 .c文件,linux驱动程序在.c文件和make文件都正确的时候为什么在键入命令mak...
  8. mysql 概念与命令总结(贼全)
  9. windows找不到文件cmd解决方法
  10. 使用click创建完美的Python命令行程序