Node2Vec spark版本采样生成序列

Node2Vec spark版本采样生成序列

前言

最近对node2vec比较感兴趣,再有源码的加持,想在生产环境复现一把,在复现过程中,发现几处bug(有向图的生成,边的起点和终点的拼接符号),本文予以修正,涉及Alias采样方法也给出了参考,每个函数和重要过程加了注释,还愣着干啥,赶紧在copy到你那里吧,记得点赞,收藏、关注哦

1. 定义图顶点和边的属性

case class EdgeAttr(var dstNeighbors: Array[Long] = Array.empty[Long],var J: Array[Int] = Array.empty[Int],var q: Array[Double] = Array.empty[Double])case class NodeAttr(var neighbors: Array[(Long, Double)] = Array.empty[(Long, Double)],var path: Array[Long] = Array.empty[Long])

2. 实现采样方法和定义有向图和无向图

2.1 原理

文章设计了一个灵活的采样策略用于平衡BFS和DFS,即利用带偏置的随机游走策略来,该方式可以BFS和DFS的方式探索邻近区域

在学习Node2Vec过程中,概率转移矩阵的计算用到了非均匀随机抽样方法,根据当前node的权重,决定下一次访问哪个邻接点

2.2 代码

import scala.collection.mutable.ArrayBufferobject GraphOps {def setupAlias(nodeWeights: Array[(Long, Double)]): (Array[Int], Array[Double]) = {val K = nodeWeights.lengthval J = Array.fill(K)(0)val q = Array.fill(K)(0.0)val smaller = new ArrayBuffer[Int]()val larger = new ArrayBuffer[Int]()val sum = nodeWeights.map(_._2).sumnodeWeights.zipWithIndex.foreach { case ((nodeId, weight), i) =>q(i) = K * weight / sumif (q(i) < 1.0) {smaller.append(i)} else {larger.append(i)}}while (smaller.nonEmpty && larger.nonEmpty) {val small = smaller.remove(smaller.length - 1)val large = larger.remove(larger.length - 1)J(small) = largeq(large) = q(large) + q(small) - 1.0if (q(large) < 1.0) smaller.append(large)else larger.append(large)}(J, q)}def setupEdgeAlias(p: Double = 1.0, q: Double = 1.0)(srcId: Long, srcNeighbors: Array[(Long, Double)], dstNeighbors: Array[(Long, Double)]): (Array[Int], Array[Double]) = {val neighbors_ = dstNeighbors.map { case (dstNeighborId, weight) =>var unnormProb = weight / qif (srcId == dstNeighborId) unnormProb = weight / pelse if (srcNeighbors.exists(_._1 == dstNeighborId)) unnormProb = weight(dstNeighborId, unnormProb)}setupAlias(neighbors_)}def drawAlias(J: Array[Int], q: Array[Double]): Int = {val K = J.lengthval kk = math.floor(math.random * K).toIntif (math.random < q(kk)) kkelse J(kk)}lazy val createUndirectedEdge = (srcId: Long, dstId: Long, weight: Double) => {Array((srcId, Array((dstId, weight))),(dstId, Array((srcId, weight))))}lazy val createDirectedEdge = (srcId: Long, dstId: Long, weight: Double) => {Array((srcId, Array((dstId, weight))))}
}

2.3 参考

Alias Method:时间复杂度O(1)的离散采样方法

Alias Method: 非均匀随机抽样算法

【数学】时间复杂度O(1)的离散采样算法—— Alias method/别名采样方法

【Graph Embedding】node2vec:算法原理,实现和应用

浅梦的学习笔记

3. 生成过程和最终结果

参考源码和issue,解决了一些bug,并且idea本地验证通过,大数据量集群验证通过

3.1 代码逻辑

  1. 加载原始数据

  2. 将原始序列,转换为原始边三元组,格式为(srcId,dstId,weight),其中srcId表示边的起点,dstId表示表的终点,weight表示边的起点和终点出现次数,计算过程使用了聚合函数reduceByKey

  3. 将原始顶点index化

  4. 将index->原始顶点转为map,并广播

  5. 生成index化的三元组边

  6. 根据index之后的三元组,格式RDD[(Long, Long, Double)],生成图的顶点和边

  7. 初始化图的顶点属性和图的边属性

  8. 随机游走,采样生成序列,bug修改,参考 https://github.com/aditya-grover/node2vec/issues/29

  9. 映射回原始的采样序列

  10. 显示采样结果

3.2 代码

/*** 配置类** @param numPartition 分区数量* @param walkLength   每个顶点采样序列长度* @param numWalks     每个顶点采样次数* @param p            返回参数* @param q            in-out参数* @param directed     是否有向图,有向图有bug,此处已经修复,参考 https://github.com/aditya-grover/node2vec/issues/29* @param degree       顶点的度* @param input        数据txt路径,没上传样例数据,不过代码中有给出例子,可以参考,序列按照逗号分隔,如v1,v2,v3,v4*/
case class Config(var numPartition: Int = 10,var walkLength: Int = 8,var numWalks: Int = 5,var p: Double = 1.0,var q: Double = 1.0,var directed: Boolean = true,var degree: Int = 30,var input: String = "./data")
package com.test.graphimport org.apache.spark.SparkContext
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.graphx.{Edge, EdgeTriplet, Graph, VertexId}
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SparkSession}import scala.collection.mutable.ArrayBufferobject Node2vec {val config: Config = Config()def main(args: Array[String]): Unit = {val spark: SparkSession = SparkSession.builder().master("local[*]").appName("Node2vec").getOrCreate()val sc = spark.sparkContextimport spark.implicits._// 1. 加载原始数据/*** 样例数据:* v1,v2,v3,v4* v3,v2,v1,v6* v1,v2,v6,v7* v1,v10,v8,v4* v1,v3,v8,v4* v1,v10,v9,v4* v1* v1,v10,v9,v11*/val sequenceRDD: RDD[String] = sc.textFile(config.input)// 2. 将原始序列,转换为原始边三元组,格式为(srcId,dstId,weight),其中srcId表示边的起点,dstId表示表的终点,weight表示边的起点和终点出现次数,计算过程使用了聚合函数reduceByKeyval rawEdges: RDD[(String, String, Double)] = sequenceProcess(sequenceRDD)// 3. 将原始顶点index化val node2Id: RDD[(String, VertexId)] = createNode2Id(rawEdges)// 4. 将index->原始顶点转为map,并广播val id2NodeMap: collection.Map[VertexId, String] = node2Id.map {case (node_id, node_index) => (node_index, node_id)}.collectAsMap()val id2NodeMapBC: Broadcast[collection.Map[VertexId, String]] = sc.broadcast(id2NodeMap)// 5. 生成index化的三元组边val inputTriplets: RDD[(VertexId, VertexId, Double)] = indexingGraph(rawEdges, node2Id)// 显示中间结果rawEdges.toDF("src_id", "dst_id", "weight").show(false)/*** +------+------+------+* |src_id|dst_id|weight|* +------+------+------+* |v1    |v3    |1.0   |* |v10   |v9    |2.0   |* |v1    |v10   |3.0   |* |v8    |v4    |2.0   |* |v2    |v6    |1.0   |* |v3    |v2    |1.0   |* |v2    |v3    |1.0   |* |v1    |v6    |1.0   |* |v3    |v4    |1.0   |* |v1    |v2    |2.0   |* |v9    |v11   |1.0   |* |v9    |v4    |1.0   |* |v10   |v8    |1.0   |* |v6    |v7    |1.0   |* |v2    |v1    |1.0   |* |v3    |v8    |1.0   |* +------+------+------+*/inputTriplets.toDF("src_index", "dst_index", "weight").show(false)/*** +---------+---------+------+* |src_index|dst_index|weight|* +---------+---------+------+* |3        |0        |2.0   |* |6        |0        |1.0   |* |7        |0        |1.0   |* |7        |1        |1.0   |* |4        |2        |1.0   |* |8        |2        |1.0   |* |5        |3        |1.0   |* |6        |3        |1.0   |* |6        |4        |1.0   |* |8        |4        |2.0   |* |8        |5        |3.0   |* |4        |6        |1.0   |* |8        |6        |1.0   |* |5        |7        |2.0   |* |4        |8        |1.0   |* |2        |9        |1.0   |* +---------+---------+------+*/// 6. 根据index之后的三元组,格式RDD[(Long, Long, Double)],生成图的顶点和边val (indexedNodes, indexedEdges) = buildGraph(inputTriplets)// 7. 初始化图的顶点属性和图的边属性val graph: Graph[NodeAttr, EdgeAttr] = initTransitionProb(indexedNodes = indexedNodes, indexedEdges = indexedEdges)// 8. 随机游走,采样生成序列,bug修改,参考 https://github.com/aditya-grover/node2vec/issues/29val indexedSequenceRDD: RDD[(VertexId, ArrayBuffer[VertexId])] = randomWalk(graph)// 9. 映射回原始的采样序列val sampledSequenceDF: DataFrame = indexedSequenceRDD.map {case (vertexId, path) => {path.map(elem => id2NodeMapBC.value.getOrElse(elem, "")).mkString(",")}}.toDF("sampled_sequence")// 10. 显示采样结果sampledSequenceDF.show(1000, false)/*** +------------------------+* |sampled_sequence         |* +------------------------+* |v9,v4                   |* |v10,v9,v4               |* |v1,v6,v7                |* |v6,v7                   |* |v2,v3,v8,v4             |* |v8,v4                   |* |v3,v2,v1,v2,v3,v4       |* |v9,v4                   |* |v10,v9,v11              |* |v3,v2,v1,v6,v7          |* |v1,v6,v7                |* |v6,v7                   |* |v8,v4                   |* |v2,v3,v4                |* |v9,v4                   |* |v6,v7                   |* |v3,v2,v1,v10,v8,v4      |* |v1,v10,v8,v4            |* |v10,v8,v4               |* |v8,v4                   |* |v2,v3,v4                |* |v9,v4                   |* |v1,v2,v3,v2,v1,v10,v9,v4|* |v6,v7                   |* |v2,v6,v7                |* |v10,v8,v4               |* |v3,v8,v4                |* |v8,v4                   |* |v10,v9,v4               |* |v1,v10,v9,v4            |* |v9,v11                  |* |v2,v1,v10,v9,v11        |* |v6,v7                   |* |v3,v8,v4                |* |v8,v4                   |* +------------------------+*/}/*** 将原始序列,转换为原始边三元组,格式为(srcId,dstId,weight),其中srcId表示边的起点,dstId表示表的终点,weight表示边的起点和终点出现次数,计算过程使用了聚合函数reduceByKey** @param sequenceRDD 用户序列,用逗号分隔* @return 返回(srcId,dstId,weight),类型RDD[(String, String, Double)]*/def sequenceProcess(sequenceRDD: RDD[String]): RDD[(String, String, Double)] = {sequenceRDD.flatMap(line => {val sequenceArray: Array[String] = line.split(",")val pairSeq = ArrayBuffer[(String, Int)]()var previousItem: String = nullsequenceArray.foreach((element: String) => {if (previousItem != null) {pairSeq.append((previousItem + ":" + element, 1))}previousItem = element})pairSeq}).reduceByKey(_ + _).map { case (pair: String, weight: Int) =>val arr: Array[String] = pair.split(":")(arr(0), arr(1), weight.toDouble)}}/*** 生成index化的三元组边** @param rawEdges 原始边三元组id,格式RDD[(String, String, Double)]* @param node2Id  每个顶点对应的索引,格式RDD[(String, VertexId)]* @return 返回index之后的三元组,格式RDD[(Long, Long, Double)]*/def indexingGraph(rawEdges: RDD[(String, String, Double)], node2Id: RDD[(String, VertexId)]): RDD[(Long, Long, Double)] = {rawEdges.map { case (src, dst, weight) =>(src, (dst, weight))}.join(node2Id).map { case (src, (edge: (String, Double), srcIndex: Long)) =>try {val (dst: String, weight: Double) = edge(dst, (srcIndex, weight))} catch {case e: Exception => null}}.filter(_ != null).join(node2Id).map { case (dst, (edge: (Long, Double), dstIndex: Long)) =>try {val (srcIndex, weight) = edge(srcIndex, dstIndex, weight)} catch {case e: Exception => null}}.filter(_ != null)}/*** 将原始顶点index化** @param rawEdges 原始边三元组id,格式RDD[(String, String, Double)]* @tparam T 泛型* @return 返回每个顶点对应的索引,格式RDD[(String, VertexId)]*/def createNode2Id[T <: Any](rawEdges: RDD[(String, String, T)]): RDD[(String, VertexId)] = rawEdges.flatMap { case (src, dst, weight) =>val strings: Array[String] = Array(src, dst)strings}.distinct().zipWithIndex()/*** 根据index之后的三元组,格式RDD[(Long, Long, Double)],生成图的顶点和边** @param inputTriplets index之后的三元组,格式RDD[(Long, Long, Double)]* @param config        图的配置信息* @return 返回图的顶点和边*/def buildGraph(inputTriplets: RDD[(VertexId, VertexId, Double)]): (RDD[(VertexId, NodeAttr)], RDD[Edge[EdgeAttr]]) = {val sc: SparkContext = inputTriplets.sparkContextval bcMaxDegree = sc.broadcast(config.degree)val bcEdgeCreator = config.directed match {case true => sc.broadcast(GraphOps.createDirectedEdge)case false => sc.broadcast(GraphOps.createUndirectedEdge)}val indexedNodes = inputTriplets.flatMap { case (srcId, dstId, weight) =>bcEdgeCreator.value.apply(srcId, dstId, weight)}.reduceByKey(_ ++ _).map { case (nodeId, neighbors: Array[(VertexId, Double)]) =>var neighbors_ = neighborsif (neighbors_.length > bcMaxDegree.value) {neighbors_ = neighbors.sortWith { case (left, right) => left._2 > right._2 }.slice(0, bcMaxDegree.value)}(nodeId, NodeAttr(neighbors = neighbors_.distinct))}.repartition(config.numPartition).cacheval indexedEdges = indexedNodes.flatMap { case (srcId, clickNode) =>clickNode.neighbors.map { case (dstId, weight) =>Edge(srcId, dstId, EdgeAttr())}}.repartition(config.numPartition).cache(indexedNodes, indexedEdges)}/*** 初始化图的顶点属性和图的边属性** @param indexedNodes 图的顶点* @param indexedEdges 图的边* @return 返回构建好的图*/def initTransitionProb(indexedNodes: RDD[(VertexId, NodeAttr)], indexedEdges: RDD[Edge[EdgeAttr]]): Graph[NodeAttr, EdgeAttr] = {val sc = indexedEdges.sparkContextval bcP = sc.broadcast(config.p)val bcQ = sc.broadcast(config.q)Graph(indexedNodes, indexedEdges).mapVertices[NodeAttr] { case (vertexId, nodeAttr) =>if (nodeAttr != null) {val (j, q) = GraphOps.setupAlias(nodeAttr.neighbors)val nextNodeIndex = GraphOps.drawAlias(j, q)nodeAttr.path = Array(vertexId, nodeAttr.neighbors(nextNodeIndex)._1)nodeAttr} else {NodeAttr()}}.mapTriplets { edgeTriplet: EdgeTriplet[NodeAttr, EdgeAttr] =>val (j, q) = GraphOps.setupEdgeAlias(bcP.value, bcQ.value)(edgeTriplet.srcId, edgeTriplet.srcAttr.neighbors, edgeTriplet.dstAttr.neighbors)edgeTriplet.attr.J = jedgeTriplet.attr.q = qedgeTriplet.attr.dstNeighbors = edgeTriplet.dstAttr.neighbors.map(_._1)edgeTriplet.attr}.cache}/*** 随机游走,采样生成序列,bug修改,参考 https://github.com/aditya-grover/node2vec/issues/29** @param graph 图* @return 返回采样生成的序列*/def randomWalk(graph: Graph[NodeAttr, EdgeAttr]): RDD[(VertexId, ArrayBuffer[VertexId])] = {var randomWalkPaths: RDD[(Long, ArrayBuffer[Long])] = nullval edge2attr = graph.triplets.map { edgeTriplet =>// 起点和终点之间加入拼接符号,解决11,13 和111,3拼接出问题(s"${edgeTriplet.srcId}->${edgeTriplet.dstId}", edgeTriplet.attr)}.repartition(config.numPartition).cachefor (iter <- 0 until config.numWalks) {var prevWalk: RDD[(Long, ArrayBuffer[Long])] = null// 保证path非空,否则后面程序出现空指针异常var randomWalk = graph.vertices.filter(_._2.path.nonEmpty).map { case (nodeId, clickNode) =>val pathBuffer = new ArrayBuffer[Long]()pathBuffer.append(clickNode.path: _*)(nodeId, pathBuffer)}.cache// 每次迭代,保存旧的RDD,当生成新的RDD后,在内存中释放掉旧的RDD,由于initTransitionProb函数将graph保存到内容中,此处将graph从内存中释放,保证每次迭代从头开始采样graph.unpersist(blocking = false)graph.edges.unpersist(blocking = false)for (walkCount <- 0 until config.walkLength) {// 每次迭代,保存旧的RDD,当生成新的RDD后,在内存中释放掉旧的RDDprevWalk = randomWalkrandomWalk = randomWalk.map { case (srcNodeId, pathBuffer) =>val prevNodeId = pathBuffer(pathBuffer.length - 2)val currentNodeId = pathBuffer.last(s"$prevNodeId->$currentNodeId", (srcNodeId, pathBuffer))}.join(edge2attr).map { case (edge, ((srcNodeId, pathBuffer), attr)) =>try {if (pathBuffer != null && pathBuffer.nonEmpty && attr.dstNeighbors != null && attr.dstNeighbors.nonEmpty) {val nextNodeIndex = GraphOps.drawAlias(attr.J, attr.q)val nextNodeId = attr.dstNeighbors(nextNodeIndex)pathBuffer.append(nextNodeId)}(srcNodeId, pathBuffer)} catch {case e: Exception => throw new RuntimeException(e.getMessage)}}.cache// 在内存中释放掉旧的RDDprevWalk.unpersist(blocking = false)}if (randomWalkPaths != null) {// 每次迭代,保存旧的RDD,当生成新的RDD后,在内存中释放掉旧的RDDval prevRandomWalkPaths = randomWalkPathsrandomWalkPaths = randomWalkPaths.union(randomWalk).cache()// 在内存中释放掉旧的RDDprevRandomWalkPaths.unpersist(blocking = false)} else {randomWalkPaths = randomWalk}}randomWalkPaths}}

3.3 参考

源码:使用spark实现node2vec算法

原理:[Graph Embedding] node2vec原理介绍及其spark实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8490.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

理论计算初学者实用软件,PWmat Windows版本

PWmat 是一款功能强大的平面波密度泛函软件&#xff0c;拥有上千用户&#xff0c;已被广泛用于材料研发之中。但对于大多数学生来说&#xff0c;由于经费的限制&#xff0c;他们无缘于PWmat 应用。为了满足广大同学要求&#xff0c;龙讯旷腾现隆重推出免费版的PWmat Microsoft …

七、Kafka源码分析之网络通信

1、生产者网络设计 架构设计图 2、生产者消息缓存机制 1、RecordAccumulator 将消息缓存到RecordAccumulator收集器中, 最后判断是否要发送。这个加入消息收集器&#xff0c;首先得从 Deque 里找到自己的目标分区&#xff0c;如果没有就新建一个批量消息 Deque 加进入 2、消…

【Windows】不要让你的win键落灰!掌握常用的组合快捷键,使用电脑更高效了

Windows 操作系统提供了丰富的键盘快捷键&#xff0c;能够大幅提高工作效率和操作便利性。在此介绍一些与 Win 键相关的常用快捷键&#xff0c;帮助你更好地利用 Windows 系统。想要在使用电脑时更高效吗&#xff1f;掌握常用的组合快捷键&#xff0c;让你的 Win 键从此不再落灰…

【Vue】水印组件

前言&#xff1a; 最近在工作中接收到了一个给页面添加水印的需求&#xff0c;在网上看到了各种各样的写法&#xff0c;但是感觉写的都比较啰嗦或者复杂&#xff0c;就想着自己写个组件&#xff0c;可以在以后得工作中经常用到&#xff0c;目前是使用Vue技术写的&#xff0c;如…

sql中on条件和where条件查询结果一样嘛?

如果使用 join不会有影响。 但是 在使用left join时&#xff0c;on和where条件的区别如下&#xff1a; on条件是在生成临时表时使用的条件&#xff0c;它不管on中的条件是否为真&#xff0c;都会返回左边表中的记录。 where条件是在临时表生成好后&#xff0c;再对临时表进行…

Java 两台服务器间使用FTP进行文件传输

背景&#xff1a;需要把服务器A中的文件拷贝至服务器B中&#xff0c;要求使用FTP进行传输&#xff0c;当文件传输未完成时文件是tmp格式的&#xff0c;传输完毕后显示为原格式&#xff08;此处是grib2&#xff09;。 package org.example;import org.apache.commons.io.FileUt…

Security+备考我想分想这几点

考试初衷 本人是一名信息安全从业者&#xff0c;听过很多信息安全方面的认证&#xff0c;如CISP、CISSP、CISA&#xff0c;但是没听过Security认证&#xff0c;偶然的机会&#xff0c;我的同事给我介绍了谷安&#xff0c;从这里我才了解到还有Security认证这么一个信息安全认证…

微服务——http客户端Feign

目录 Restemplate方式调用存在的问题 Feign的介绍 基于Feign远程调用 Feign自定义配置 修改日志方式一(基于配置文件) 修改日志方式二(基于java代码) Feign的性能优化 连接池使用方法 Feign_最佳实践分析 方式一: 方式二 实现Feign最佳实践(方式二) 两种解决方案 Re…

PostgreSql 事务

一、事务的 ACID 特性 在日常操作中&#xff0c;对于一组相关操作&#xff0c;通常需要其全部成功或全部失败。在关系型数据库中&#xff0c;将这组相关操作称为事务。事务具有的四个特性简称为 ACID。 原子性&#xff08;Atomicity&#xff09;&#xff1a;保证事务中的操作要…

通过v-for生成的input无法连续输入

部分代码&#xff1a;通过v-for循环生成el-form-item&#xff0c;生成多个描述输入框 更改之前的代码&#xff08;key绑定的是item&#xff09;&#xff1a; <el-form-item class"forminput" v-for"(item,index) in formdata.description" :key"…

centos下安装jdk

环境:centos7/openjdk-8u40-b25 openJDK页面 java二进制包下载页面 华为jdk镜像 1.下载安装包后上传到服务器上&#xff0c;运行命令解压到/opt/目录下 tar cxvf server-jre-8u271-linux-x64.tar.gz -C /opt/2.配置环境变量 vi /etc/profile source /etc/profile添加下面的…

IFNULL()COALESCE()

在 MySQL 中&#xff0c;IFNULL() 函数是可用的&#xff0c;但是请注意它不能直接用于聚合函数的结果。要在聚合函数结果可能为 NULL 的情况下返回特定值&#xff0c;应该使用 COALESCE() 函数而不是 IFNULL() 函数。 以下是代码示例&#xff1a; COALESCE(SUM(pc.CONTRACT_T…

【C语言】文件操作

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在回炉重造C语言&#xff08;2023暑假&#xff09; ✈️专栏&#xff1a;【C语言航路】 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你…

Supplementary Material for 3D-Aware Face Swapping

Supplementary Material for 3D-Aware Face Swapping 一、前言A. Additional Experimental ResultsA.1. More Qualitative ResultsA.2. Analysis on 3D-Aware Face SwappingA.3. More Results on 3D Face ReconstructionB. Broader ImpactFigures一、前言 【Project】 【Paper】…

Modbus TCP通信报文解析

一、实现了读取线圈状态和写入多个线圈的功能。代码中包含了详细的注释说明&#xff0c;可以清晰地了解每个方法的功能和使用方式。 对于读取线圈状态的方法&#xff0c;使用时需要传入从站地址、起始地址和线圈数量&#xff0c;最后会返回一个 bool 数组&#xff0c;其中每个…

StAX解析

StAX解析 StAX解析介绍 StAX解析与SAX解析类似&#xff0c;也是基于事件驱动的&#xff0c;不同之处在于StAX采用的是拉模式&#xff0c;应用程序通过调用解析器推进解析的进程&#xff0c;可以调用next()方法来获取下一个解析事件(开始文档&#xff0c;结束文档&#xff0c;开…

建立TCP连接的各个系统调用

TCP 连接的过程图 服务器 socket() 函数 socket() 返回的 sockfd 是一个描述符。socket()对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字&#xff0c;而socket()用于创建一个socket描述符&#xff08;socket descriptor&#xff09;&#xff0c;它唯一标识…

ks webdid 滑块注册

web和app其实都一样&#xff0c;主要是针对于设备进行风控&#xff0c;web设备叫webdid; webdid注册出来&#xff0c;过了ks滑块激活&#xff0c;测试了主页&#xff0c;评论等接口都可以跑&#xff0c;平均也就2s注册一个&#xff0c;如果开并发那就更快了&#xff1b; 不过一…

hdvp.

hdvp:外部函数文件&#xff0c;函数定义在hdvp中可以传输给任何hdev使用&#xff0c;即可以发给别人使用。同时允许对hdvp进行加密

OpenCV笔记

opencv读取视频操作 import cv2video cv2.VideoCapture("./1.mp4")if video.isOpened():# video.read() 一帧一帧地读取# open 得到的是一个布尔值&#xff0c;就是 True 或者 False# frame 得到当前这一帧的图像open, frame video.read() else:open Falsewhile …