在PullConsumer中,有关消息的拉取RocketMQ提供了很多API,但总的来说分为两种,同步消息拉取和异步消息拉取

同步消息拉取
以同步方式拉取消息都是通过DefaultMQPullConsumerImpl的pullSyncImpl方法:

 private PullResult pullSyncImpl(MessageQueue mq, SubscriptionData subscriptionData, long offset, int maxNums, boolean block,
     long timeout)
     throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
     this.makeSureStateOK();

     if (null == mq) {
         throw new MQClientException("mq is null", null);
     }

     if (offset < 0) {
         throw new MQClientException("offset < 0", null);
     }

     if (maxNums <= 0) {
         throw new MQClientException("maxNums <= 0", null);
     }

     this.subscriptionAutomatically(mq.getTopic());

     int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);

     long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;

     boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
     PullResult pullResult = this.pullAPIWrapper.pullKernelImpl(
         mq,
         subscriptionData.getSubString(),
         subscriptionData.getExpressionType(),
         isTagType ? 0L : subscriptionData.getSubVersion(),
         offset,
         maxNums,
         sysFlag,
         0,
         this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
         timeoutMillis,
         CommunicationMode.SYNC,
         null
     );
     this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData);
     if (!this.consumeMessageHookList.isEmpty()) {
         ConsumeMessageContext consumeMessageContext = null;
         consumeMessageContext = new ConsumeMessageContext();
         consumeMessageContext.setConsumerGroup(this.groupName());
         consumeMessageContext.setMq(mq);
         consumeMessageContext.setMsgList(pullResult.getMsgFoundList());
         consumeMessageContext.setSuccess(false);
         this.executeHookBefore(consumeMessageContext);
         consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString());
         consumeMessageContext.setSuccess(true);
         this.executeHookAfter(consumeMessageContext);
     }
     return pullResult;
 }

首先通过subscriptionAutomatically方法检查Topic是否订阅

 public void subscriptionAutomatically(final String topic) {
     if (!this.rebalanceImpl.getSubscriptionInner().containsKey(topic)) {
         try {
             SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(this.defaultMQPullConsumer.getConsumerGroup(),
                 topic, SubscriptionData.SUB_ALL);
             this.rebalanceImpl.subscriptionInner.putIfAbsent(topic, subscriptionData);
         } catch (Exception ignore) {
         }
     }
 }

若是没有就新建一条订阅数据保存在rebalanceImpl的subscriptionInner中

之后调用pullKernelImpl方法:

 public PullResult pullKernelImpl(
     final MessageQueue mq,
     final String subExpression,
     final String expressionType,
     final long subVersion,
     final long offset,
     final int maxNums,
     final int sysFlag,
     final long commitOffset,
     final long brokerSuspendMaxTimeMillis,
     final long timeoutMillis,
     final CommunicationMode communicationMode,
     final PullCallback pullCallback
 ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
     FindBrokerResult findBrokerResult =
         this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
             this.recalculatePullFromWhichNode(mq), false);
     if (null == findBrokerResult) {
         this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
         findBrokerResult =
             this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),
                 this.recalculatePullFromWhichNode(mq), false);
     }

     if (findBrokerResult != null) {
         {
             // check version
             if (!ExpressionType.isTagType(expressionType)
                 && findBrokerResult.getBrokerVersion() < MQVersion.Version.V4_1_0_SNAPSHOT.ordinal()) {
                 throw new MQClientException("The broker[" + mq.getBrokerName() + ", "
                     + findBrokerResult.getBrokerVersion() + "] does not upgrade to support for filter message by " + expressionType, null);
             }
         }
         int sysFlagInner = sysFlag;

         if (findBrokerResult.isSlave()) {
             sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);
         }

         PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();
         requestHeader.setConsumerGroup(this.consumerGroup);
         requestHeader.setTopic(mq.getTopic());
         requestHeader.setQueueId(mq.getQueueId());
         requestHeader.setQueueOffset(offset);
         requestHeader.setMaxMsgNums(maxNums);
         requestHeader.setSysFlag(sysFlagInner);
         requestHeader.setCommitOffset(commitOffset);
         requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);
         requestHeader.setSubscription(subExpression);
         requestHeader.setSubVersion(subVersion);
         requestHeader.setExpressionType(expressionType);

         String brokerAddr = findBrokerResult.getBrokerAddr();
         if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {
             brokerAddr = computPullFromWhichFilterServer(mq.getTopic(), brokerAddr);
         }

         PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(
             brokerAddr,
             requestHeader,
             timeoutMillis,
             communicationMode,
             pullCallback);

         return pullResult;
     }

     throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
 }

首先通过findBrokerAddressInSubscribe方法查找关于消息队列的Broker信息

这里的recalculatePullFromWhichNode方法:

 public long recalculatePullFromWhichNode(final MessageQueue mq) {
     if (this.isConnectBrokerByUser()) {
         return this.defaultBrokerId;
     }

     AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
     if (suggest != null) {
         return suggest.get();
     }

     return MixAll.MASTER_ID;
 }

根据消息队列,在pullFromWhichNodeTable查找其对应的Broker的ID
pullFromWhichNodeTable记录了消息对了和BrokerID的映射

 private ConcurrentMap<MessageQueue, AtomicLong/* brokerId */> pullFromWhichNodeTable =
         new ConcurrentHashMap<MessageQueue, AtomicLong>(32);

(master的BrokerID为0,slave的BrokerID大于0)

findBrokerAddressInSubscribe方法:

 public FindBrokerResult findBrokerAddressInSubscribe(
     final String brokerName,
     final long brokerId,
     final boolean onlyThisBroker
 ) {
     String brokerAddr = null;
     boolean slave = false;
     boolean found = false;

     HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
     if (map != null && !map.isEmpty()) {
         brokerAddr = map.get(brokerId);
         slave = brokerId != MixAll.MASTER_ID;
         found = brokerAddr != null;

         if (!found && !onlyThisBroker) {
             Entry<Long, String> entry = map.entrySet().iterator().next();
             brokerAddr = entry.getValue();
             slave = entry.getKey() != MixAll.MASTER_ID;
             found = true;
         }
     }

     if (found) {
         return new FindBrokerResult(brokerAddr, slave, findBrokerVersion(brokerName, brokerAddr));
     }

     return null;
 }

这里就根据brokerAddrTable表查找该BrokerID对应的Broker的地址信息,以及是否是slave
封装为FindBrokerResult返回

若是没有找到Broker的路由信息,则通过updateTopicRouteInfoFromNameServer方法向NameServer请求更新,更新完成后再调用findBrokerAddressInSubscribe方法查找

之后会根据相应的信息封装请求消息头PullMessageRequestHeader

然后调用pullMessage方法:

 public PullResult pullMessage(
     final String addr,
     final PullMessageRequestHeader requestHeader,
     final long timeoutMillis,
     final CommunicationMode communicationMode,
     final PullCallback pullCallback
 ) throws RemotingException, MQBrokerException, InterruptedException {
     RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);

     switch (communicationMode) {
         case ONEWAY:
             assert false;
             return null;
         case ASYNC:
             this.pullMessageAsync(addr, request, timeoutMillis, pullCallback);
             return null;
         case SYNC:
             return this.pullMessageSync(addr, request, timeoutMillis);
         default:
             assert false;
             break;
     }

     return null;
 }

这里就可以看出我前面说的两种类型,同步拉取和异步拉取

pullMessageSync方法:

 private PullResult pullMessageSync(
     final String addr,
     final RemotingCommand request,
     final long timeoutMillis
 ) throws RemotingException, InterruptedException, MQBrokerException {
     RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
     assert response != null;
     return this.processPullResponse(response);
 }

这里其实就是通过invokeSync方法,由Netty进行同步发送,将请求发送给Broker
关于消息的发送详见:

【RocketMQ中Producer消息的发送源码分析】

在收到响应后由processPullResponse方法处理
processPullResponse方法:

 private PullResult processPullResponse(
     final RemotingCommand response) throws MQBrokerException, RemotingCommandException {
     PullStatus pullStatus = PullStatus.NO_NEW_MSG;
     switch (response.getCode()) {
         case ResponseCode.SUCCESS:
             pullStatus = PullStatus.FOUND;
             break;
         case ResponseCode.PULL_NOT_FOUND:
             pullStatus = PullStatus.NO_NEW_MSG;
             break;
         case ResponseCode.PULL_RETRY_IMMEDIATELY:
             pullStatus = PullStatus.NO_MATCHED_MSG;
             break;
         case ResponseCode.PULL_OFFSET_MOVED:
             pullStatus = PullStatus.OFFSET_ILLEGAL;
             break;

         default:
             throw new MQBrokerException(response.getCode(), response.getRemark());
     }

     PullMessageResponseHeader responseHeader =
         (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class);

     return new PullResultExt(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(),
         responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody());
 }

根据响应的状态,设置PullStatus状态

然后通过decodeCommandCustomHeader方法,将响应中的信息解码
最后由PullResultExt封装消息信息

 public class PullResultExt extends PullResult {
     private final long suggestWhichBrokerId;
     private byte[] messageBinary;

     public PullResultExt(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset,
         List<MessageExt> msgFoundList, final long suggestWhichBrokerId, final byte[] messageBinary) {
         super(pullStatus, nextBeginOffset, minOffset, maxOffset, msgFoundList);
         this.suggestWhichBrokerId = suggestWhichBrokerId;
         this.messageBinary = messageBinary;
     }
     ......
 }

 public class PullResult {
     private final PullStatus pullStatus;
     private final long nextBeginOffset;
     private final long minOffset;
     private final long maxOffset;
     private List<MessageExt> msgFoundList;

     public PullResult(PullStatus pullStatus, long nextBeginOffset, long minOffset, long maxOffset,
         List<MessageExt> msgFoundList) {
         super();
         this.pullStatus = pullStatus;
         this.nextBeginOffset = nextBeginOffset;
         this.minOffset = minOffset;
         this.maxOffset = maxOffset;
         this.msgFoundList = msgFoundList;
     }
     ......
 }

拉取到的消息可能是多条,具体内容在PullResult 中的msgFoundList保存,MessageExt是Message的超类

回到pullSyncImpl方法,在拉取到消息后,调用processPullResult方法:

 public PullResult processPullResult(final MessageQueue mq, final PullResult pullResult,
     final SubscriptionData subscriptionData) {
     PullResultExt pullResultExt = (PullResultExt) pullResult;

     this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId());
     if (PullStatus.FOUND == pullResult.getPullStatus()) {
         ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary());
         List<MessageExt> msgList = MessageDecoder.decodes(byteBuffer);

         List<MessageExt> msgListFilterAgain = msgList;
         if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode()) {
             msgListFilterAgain = new ArrayList<MessageExt>(msgList.size());
             for (MessageExt msg : msgList) {
                 if (msg.getTags() != null) {
                     if (subscriptionData.getTagsSet().contains(msg.getTags())) {
                         msgListFilterAgain.add(msg);
                     }
                 }
             }
         }

         if (this.hasHook()) {
             FilterMessageContext filterMessageContext = new FilterMessageContext();
             filterMessageContext.setUnitMode(unitMode);
             filterMessageContext.setMsgList(msgListFilterAgain);
             this.executeHook(filterMessageContext);
         }

         for (MessageExt msg : msgListFilterAgain) {
             String traFlag = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
             if (traFlag != null && Boolean.parseBoolean(traFlag)) {
                 msg.setTransactionId(msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
             }
             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MIN_OFFSET,
                 Long.toString(pullResult.getMinOffset()));
             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_MAX_OFFSET,
                 Long.toString(pullResult.getMaxOffset()));
         }

         pullResultExt.setMsgFoundList(msgListFilterAgain);
     }

     pullResultExt.setMessageBinary(null);

     return pullResult;
 }

首先调用updatePullFromWhichNode方法:

 public void updatePullFromWhichNode(final MessageQueue mq, final long brokerId) {
    AtomicLong suggest = this.pullFromWhichNodeTable.get(mq);
     if (null == suggest) {
         this.pullFromWhichNodeTable.put(mq, new AtomicLong(brokerId));
     } else {
         suggest.set(brokerId);
     }
 }

这里就会将pullFromWhichNodeTable中记录的消息队列和BrokerID的映射,更新为Broker发送过来的建议ID
结合上一篇博客来看,若是采用集群模式,就完成了消费者端的负载均衡

在PullStatus.FOUND情况下,会调用MessageDecoder的decodes方法,将CommitLog格式的消息数据进行解码,转化为真正可读的消息

之后会对Tag进行判断,设置了Tag,添加Tag消息记录

之后,在设置了FilterMessageHook钩子情况下,通过executeHook方法执行FilterMessageHook钩子的filterMessage方法:

 public void executeHook(final FilterMessageContext context) {
     if (!this.filterMessageHookList.isEmpty()) {
         for (FilterMessageHook hook : this.filterMessageHookList) {
             try {
                 hook.filterMessage(context);
             } catch (Throwable e) {
                 log.error("execute hook error. hookName={}", hook.hookName());
             }
         }
     }
 }

然后对消息进行属性设置

processPullResult完成后,若是设置了ConsumeMessageHook钩子,调用executeHookBefore和executeHookAfter方法,分别执行钩子中的consumeMessageBefore和consumeMessageAfter方法:

 public void executeHookBefore(final ConsumeMessageContext context) {
     if (!this.consumeMessageHookList.isEmpty()) {
         for (ConsumeMessageHook hook : this.consumeMessageHookList) {
             try {
                 hook.consumeMessageBefore(context);
             } catch (Throwable ignored) {
             }
         }
     }
 }

 public void executeHookAfter(final ConsumeMessageContext context) {
     if (!this.consumeMessageHookList.isEmpty()) {
         for (ConsumeMessageHook hook : this.consumeMessageHookList) {
             try {
                 hook.consumeMessageAfter(context);
             } catch (Throwable ignored) {
             }
         }
     }
 }

PullConsumer消息的同步拉取到此结束

异步消息拉取

异步拉取的API都通过pullAsyncImpl方法实现:

 private void pullAsyncImpl(
     final MessageQueue mq,
     final SubscriptionData subscriptionData,
     final long offset,
     final int maxNums,
     final PullCallback pullCallback,
     final boolean block,
     final long timeout) throws MQClientException, RemotingException, InterruptedException {
     this.makeSureStateOK();

     if (null == mq) {
         throw new MQClientException("mq is null", null);
     }

     if (offset < 0) {
         throw new MQClientException("offset < 0", null);
     }

     if (maxNums <= 0) {
         throw new MQClientException("maxNums <= 0", null);
     }

     if (null == pullCallback) {
         throw new MQClientException("pullCallback is null", null);
     }

     this.subscriptionAutomatically(mq.getTopic());

     try {
         int sysFlag = PullSysFlag.buildSysFlag(false, block, true, false);

         long timeoutMillis = block ? this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() : timeout;

         boolean isTagType = ExpressionType.isTagType(subscriptionData.getExpressionType());
         this.pullAPIWrapper.pullKernelImpl(
             mq,
             subscriptionData.getSubString(),
             subscriptionData.getExpressionType(),
             isTagType ? 0L : subscriptionData.getSubVersion(),
             offset,
             maxNums,
             sysFlag,
             0,
             this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis(),
             timeoutMillis,
             CommunicationMode.ASYNC,
             new PullCallback() {

                 @Override
                 public void onSuccess(PullResult pullResult) {
                     pullCallback
                         .onSuccess(DefaultMQPullConsumerImpl.this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData));
                 }

                 @Override
                 public void onException(Throwable e) {
                     pullCallback.onException(e);
                 }
             });
     } catch (MQBrokerException e) {
         throw new MQClientException("pullAsync unknow exception", e);
     }
 }

相比同步,参数多了个PullCallback,用于处理异步拉取后的回调

过程基本上个同步拉取类似,只不过在调用pullKernelImpl方法时,会创建一个PullCallback
在onSuccess和onException中,实际上调用了pullCallback的相应方法,这样就完成了异步的回调

在onSuccess回调的参数中,同同步方式类似,会通过processPullResult方法,对结果进一步加工

之后的pullKernelImpl方法和同步一样

只不过最后调用了pullMessageAsync方法:

 private void pullMessageAsync(
     final String addr,
     final RemotingCommand request,
     final long timeoutMillis,
     final PullCallback pullCallback
 ) throws RemotingException, InterruptedException {
     this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
         @Override
         public void operationComplete(ResponseFuture responseFuture) {
             RemotingCommand response = responseFuture.getResponseCommand();
             if (response != null) {
                 try {
                     PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response);
                     assert pullResult != null;
                     pullCallback.onSuccess(pullResult);
                 } catch (Exception e) {
                     pullCallback.onException(e);
                 }
             } else {
                 if (!responseFuture.isSendRequestOK()) {
                     pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
                 } else if (responseFuture.isTimeout()) {
                     pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
                         responseFuture.getCause()));
                 } else {
                     pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
                 }
             }
         }
     });
 }

这里实际上也是通过Netty完成异步发送
详见:

【RocketMQ中Producer消息的发送源码分析】

由于是异步发送,这里又设置了一个回调InvokeCallback
当请求发送完成,收到响应后,就会执行InvokeCallback的operationComplete方法,

在operationComplete方法中,和同步一样,执行processPullResponse方法,处理响应
之后调用pullCallback的onSuccess方法,也就是刚才创建的回调接口,进而执行用户传入的回调接口的方法

消息异步拉取也就到此结束

RocketMQ中PullConsumer的消息拉取源码分析的更多相关文章

  1. java中的==、equals()、hashCode()源码分析(转载)

    在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...

  2. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

  3. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  4. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  5. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  6. Android中Handler的消息处理机制以及源码分析

    在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui.我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui.而一些耗时操作,比如数据库,网络请 ...

  7. java中的==、equals()、hashCode()源码分析

    转载自:http://www.cnblogs.com/xudong-bupt/p/3960177.html 在Java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际 ...

  8. HashMap中的TreeNode,红黑树源码分析

    在看HashMap的源码时候看到了TreeNode.因此需要对其进行一个了解.是一个红黑树.可以百度一下红黑树的数据结构.分析了下源码,还是比较枯燥的 红黑树的性质:本身是一个二叉查找树(所有左节点的 ...

  9. Django中CBV(Class Base Views)模型源码分析

    在view文件中编写一个类,并配置好路由 class Test(View): def get(self, request, *args, **kwargs): return HttpResponse( ...

  10. [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现

    ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...

随机推荐

  1. CC2540的使用入门

    目录 1. 介绍 2. 开发环境 3. SDCC 1. 介绍 CC2540是一款2.4GHz Bluetooth® low energy SOC,基于8051 MCU 首先,你需要硬件设备 笔者的开发 ...

  2. MVC1

  3. java中如何操作数据库(增删改查)

    EntityManager 是用来对实体Bean 进行操作的辅助类.他可以用来产生/删除持久化的实体Bean,通过主键查找实体bean,也可以通过EJB3 QL 语言查找满足条件的实体Bean.实体B ...

  4. C#7.0之ref locals and returns (局部变量和引用返回,之前欠大家的,现在补上)

    废话不多说,直接进入正题. 首先我们知道 ref关键字是将值传递变为引用传递 那么我们先来看看ref locals(ref局部变量) 列子代码如下: static void Main(string[] ...

  5. Android Studio常用快捷键使用

    以下是我在编程中实际用上的Android Studio快捷键,基于Windows系统,在使用过程中会不断添加不断完善,OSX版本的在另外一篇博客 Ctrl+Alt+L 格式化代码,编写完成项目来一下, ...

  6. C语言指针2(空指针,野指针)

    //最近,有朋友开玩笑问 int *p  *是指针还是p是指针还是*p是指针,当然了,知道的都知道p是指针 //野指针----->>>指没有指向一个地址的指针(指针指向地址请参考上一 ...

  7. find()用法

    >>> str = '编程改变世界'>>> str.find('编')0>>> str.find('程')1>>> str.fi ...

  8. 树莓派3B+(三)

    上一篇中,我们配置好了基本的raspbain系统,接下来我们可以用xrdp或者vnc在Windows上远程连接树莓派. 一.安装xrdp xrdp和vnc是两种常见的远程桌面协议,可以进行可视化界面远 ...

  9. SQL反模式学习笔记17 全文搜索

    目标:全文搜索 使用SQL搜索关键字,同时保证快速和精确,依旧是相当地困难. SQL的一个基本原理(以及SQL所继承的关系原理)就是一列中的单个数据是原子性的. 反模式:模式匹配 使用Like 或者正 ...

  10. js_2_逻辑分支