热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Spark大数据分布式处理实战笔记(六):SparkGraphX

前言Spark是一种大规模、快速计算的集群平台,本公众号试图通过学习Spark官网的实战演练笔记提升笔者实操能力以及展现Spark的精彩之处。有关框架介绍和环境配置可

前言

    Spark是一种大规模、快速计算的集群平台,本公众号试图通过学习Spark官网的实战演练笔记提升笔者实操能力以及展现Spark的精彩之处。有关框架介绍和环境配置可以参考以下内容:    

    1.大数据处理框架Hadoop、Spark介绍

    2.linux下Hadoop安装与环境配置

    3.linux下Spark安装与环境配置

    本文的参考配置为:Deepin 15.11、Java 1.8.0_241、Hadoop 2.10.0、Spark 2.4.4、scala 2.11.12

    本文的目录为:

        一、属性图

            1.图计算入门

            2.属性图示例

        二、图运算符

            1.运算符列表

            2.属性运算符

            3.结构运算符

            4.Join运算符

            5.邻域聚合

        三、Pregel API

        四、图算法

            1.PageRank

            2.连接组件

            3.Triangle计数

一、属性图

    GraphX是Spark中用于图形图形并行计算的新组件。在较高的层次上,GraphX 通过引入新的Graph抽象来扩展Spark RDD:一个有向多重图,其属性附加到每个顶点和边上。为了支持图计算,GraphX公开了一组基本的操作符(例如,子图joinVerticesaggregateMessages),以及优化的Pregel API。此外,GraphX包括越来越多的图形算法和构建器集合,以简化图形分析任务。

    1.图计算入门

    首先,首先需要将Spark和GraphX导入项目,如下所示:

import org.apache.spark._
import org.apache.spark.graphx._
// 图计算也需要RDD
import org.apache.spark.rdd.RDD

    属性图是一个定向多重图形,用户定义的对象附加到每个顶点和边缘。定向多图是具有共享相同源和目标顶点的潜在多个平行边缘的有向图。支持平行边缘的能力简化了在相同顶点之间可以有多个关系(例如:同事和朋友)的建模场景。每个顶点都由唯一的 64 位长标识符(VertexId)键入。GraphX 不对顶点标识符施加任何排序约束。类似地,边缘具有对应的源和目标顶点标识符。

    属性图是通过 vertex(VD) edge(ED)类型进行参数化的。这些是分别与每个顶点和边缘相关联的对象的类型。

    在某些情况下,可能希望在同一个图形中具有不同属性类型的顶点。这可以通过继承来实现。例如,将用户和产品建模为二分图,我们可能会执行以下操作:

scala> class VertexProperty()
defined class VertexPropertyscala> case class UserProperty(val name: String) extends VertexProperty
defined class UserPropertyscala> case class ProductProperty(val name: String, val price: Double) extends VertexProperty
defined class ProductPropertyscala> var graph: Graph[VertexProperty, String] = null
graph: org.apache.spark.graphx.Graph[VertexProperty,String] = null

    像 RDD 一样,属性图是不可变的,分布式的和容错的。通过生成具有所需更改的新图形来完成对图表的值或结构的更改。请注意,原始图形的大部分(即,未受影响的结构,属性和索引)在新图表中重复使用,可降低此内在功能数据结构的成本。使用一系列顶点分割启发式方法,在执行器之间划分图形。与 RDD 一样,在发生故障的情况下,可以在不同的机器上重新创建图形的每个分区。

    逻辑上,属性图对应于一对编码每个顶点和边缘的属性的类型集合(RDD)。因此,图类包含访问图形顶点和边的成员:

class Graph[VD, ED] {val vertices: VertexRDD[VD]val edges: EdgeRDD[ED]
}

    VertexRDD[VD]EdgeRDD[ED]分别扩展了RDD[(VertexId, VD)] 和 RDD[Edge[ED]] 的优化版本。VertexRDD[VD] 和 EdgeRDD[ED] 都提供了围绕图计算和利用内部优化的附加功能。

    2.属性图示例

    假设我们要构建一个由 GraphX 项目中的各种协作者组成的属性图。顶点属性可能包含用户名和职业。我们可以用描述协作者之间关系的字符串来注释边:

    从原始文件合成生成器构建属性图有许多方法,这些在图形构建器的一节中有更详细的讨论。最普遍的方法是使用Graph对象。例如,以下代码从RDD集合中构建一个图:

// 定义顶点
scala> val Buildings: RDD[(VertexId, (String, String))] =| sc.parallelize(Array((3L, ("中南楼", "行政楼")), (7L, ("文泰楼", "教学楼")),| (5L, ("中原楼", "行政楼")), (2L, ("文波楼", "行政楼"))))
Buildings: org.apache.spark.rdd.RDD[(org.apache.spark.graphx.VertexId, (String, String))] = ParallelCollectionRDD[28] at parallelize at :32
// 定义边
scala> val relationships: RDD[Edge[String]] =| sc.parallelize(Array(Edge(3L, 7L, "不同校区不同性质"), Edge(5L, 3L, "不同校区同性质"),| Edge(2L, 5L, "同校区不同性质"), Edge(5L, 7L, "同校区不同性质")))
relationships: org.apache.spark.rdd.RDD[org.apache.spark.graphx.Edge[String]] = ParallelCollectionRDD[29] at parallelize at :32
// 定义默认楼栋
scala> val defaultBuilding = ("文添楼", "教学楼")
defaultBuilding: (String, String) = (文添楼,教学楼)
// 初始化图
scala> val graph = Graph(Buildings, relationships, defaultBuilding)
graph: org.apache.spark.graphx.Graph[(String, String),String] = org.apache.spark.graphx.impl.GraphImpl@6ec5e204

    我们可以分别使用 graph.vertices graph.edges 成员将图形解构成相应的顶点和边缘视图。

scala> graph.vertices.filter { case (id, (name, pos)) => pos == "行政楼" }.count
res7: Long = 3scala> graph.edges.filter(e => e.srcId > e.dstId).count
res8: Long = 1

    除了属性图的顶点和边缘视图之外,GraphX 还暴露了三元组视图。三元组在视图逻辑上连接顶点边缘属性,生成 RDD[EdgeTriplet[VD, ED]] 包含 EdgeTriplet 该类的实例。EdgeTriplet 类通过分别添加包含源和目标属性的 srcAttrdstAttr 成员来扩展 Edge 类。我们可以使用图形的三元组视图来渲染描述用户之间关系的字符串集合。

scala> val facts: RDD[String] = graph.triplets.map(triplet => triplet.srcAttr._1 + " 与 " + triplet.dstAttr._1+ " 是 " + triplet.attr + "的楼")
facts: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[26] at map at :32scala> facts.collect.foreach(println(_))
中南楼 与 文泰楼 是 不同校区不同性质的楼
中原楼 与 中南楼 是 不同校区同性质的楼
文波楼 与 中原楼 是 同校区不同性质的楼
中原楼 与 文泰楼 是 同校区不同性质的楼

二、图运算符

    1.运算符列表

    以下是两个定义的功能的简要摘要,但为简单起见将Graph,GraphOps 作为 Graph 的成员呈现。

// 属性图中的函数总结
class Graph[VD, ED] {// 图的基本信息变量val numEdges: Longval numVertices: Longval inDegrees: VertexRDD[Int]val outDegrees: VertexRDD[Int]val degrees: VertexRDD[Int]// 图的集合val vertices: VertexRDD[VD]val edges: EdgeRDD[ED]val triplets: RDD[EdgeTriplet[VD, ED]]// 有关缓存的函数def persist(newLevel: StorageLevel = StorageLevel.MEMORY_ONLY): Graph[VD, ED]def cache(): Graph[VD, ED]def unpersistVertices(blocking: Boolean = true): Graph[VD, ED]// 改变分区方式def partitionBy(partitionStrategy: PartitionStrategy): Graph[VD, ED]// 转换顶点和边属性def mapVertices[VD2](map: (VertexId, VD) => VD2): Graph[VD2, ED]def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2]def mapEdges[ED2](map: (PartitionID, Iterator[Edge[ED]]) => Iterator[ED2]): Graph[VD, ED2]def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]def mapTriplets[ED2](map: (PartitionID, Iterator[EdgeTriplet[VD, ED]]) => Iterator[ED2]): Graph[VD, ED2]// 修改图结构def reverse: Graph[VD, ED]def subgraph(epred: EdgeTriplet[VD,ED] => Boolean = (x => true),vpred: (VertexId, VD) => Boolean = ((v, d) => true)): Graph[VD, ED]def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED]def groupEdges(merge: (ED, ED) => ED): Graph[VD, ED]// 连接RDD和图def joinVertices[U](table: RDD[(VertexId, U)])(mapFunc: (VertexId, VD, U) => VD): Graph[VD, ED]def outerJoinVertices[U, VD2](other: RDD[(VertexId, U)])(mapFunc: (VertexId, VD, Option[U]) => VD2): Graph[VD2, ED]// 汇总有关相邻三元组的信息 def collectNeighborIds(edgeDirection: EdgeDirection): VertexRDD[Array[VertexId]]def collectNeighbors(edgeDirection: EdgeDirection): VertexRDD[Array[(VertexId, VD)]]def aggregateMessages[Msg: ClassTag](sendMsg: EdgeContext[VD, ED, Msg] => Unit,mergeMsg: (Msg, Msg) => Msg,tripletFields: TripletFields = TripletFields.All): VertexRDD[A]// 迭代图遍历计算 def pregel[A](initialMsg: A, maxIterations: Int, activeDirection: EdgeDirection)(vprog: (VertexId, VD, A) => VD,sendMsg: EdgeTriplet[VD, ED] => Iterator[(VertexId,A)],mergeMsg: (A, A) => A): Graph[VD, ED]// 基本的图算法def pageRank(tol: Double, resetProb: Double = 0.15): Graph[Double, Double]def connectedComponents(): Graph[VertexId, ED]def triangleCount(): Graph[Int, ED]def stronglyConnectedComponents(numIter: Int): Graph[VertexId, ED]
}

    2.属性运算符

    与 RDD map 运算符一样,属性图包含以下内容:

class Graph[VD, ED] {def mapVertices[VD2](map: (VertexId, VD) => VD2): Graph[VD2, ED]def mapEdges[ED2](map: Edge[ED] => ED2): Graph[VD, ED2]def mapTriplets[ED2](map: EdgeTriplet[VD, ED] => ED2): Graph[VD, ED2]
}

    这些运算符中的每一个都会产生一个新图形,该图形的顶点或边属性由用户定义的map函数修改。

    3.结构运算符

    目前GraphX只支持一套简单的常用结构运算符,我们预计将来会增加更多。以下是基本结构运算符的列表。

class Graph[VD, ED] {def reverse: Graph[VD, ED]def subgraph(epred: EdgeTriplet[VD,ED] => Boolean,vpred: (VertexId, VD) => Boolean): Graph[VD, ED]def mask[VD2, ED2](other: Graph[VD2, ED2]): Graph[VD, ED]def groupEdges(merge: (ED, ED) => ED): Graph[VD,ED]
}

  • reverse 运算符将返回逆转的所有边缘方向上的新图。这在例如尝试计算逆 PageRank 时是有用的。由于反向操作不会修改顶点或边缘属性或更改边缘数量,因此可以在没有数据移动或重复的情况下高效地实现。

  • subgraph 操作者需要的顶点和边缘的谓词,并返回包含只有满足谓词顶点的顶点的曲线图(评估为真),并且满足谓词边缘边缘 _并连接满足顶点谓词顶点_。所述 subgraph 操作员可在情况编号被用来限制图形以顶点和感兴趣的边缘或消除断开的链接。

  • mask 操作者通过返回包含该顶点和边,它们也在输入图形中发现的曲线构造一个子图。这可以与 subgraph 运算符一起使用,以便根据另一个相关图中的属性限制图形。例如,我们可以使用缺少顶点的图运行连接的组件,然后将答案限制为有效的子图。

  • groupEdges 操作符将多边形中的平行边(即,顶点对之间的重复边)合并。在许多数值应用中,可以将平行边缘(它们的权重组合)合并成单个边缘,从而减小图形的大小。

    4.Join运算符

    在许多情况下,有必要使用图形连接来自外部收集(RDD)的数据。例如,我们可能有额外的用户属性,我们要与现有的图形合并,或者我们可能希望将顶点属性从一个图形拉到另一个。这些任务可以使用 join 运算符完成。下面我们列出关键 join 运算符:

class Graph[VD, ED] {def joinVertices[U](table: RDD[(VertexId, U)])(map: (VertexId, VD, U) => VD): Graph[VD, ED]def outerJoinVertices[U, VD2](table: RDD[(VertexId, U)])(map: (VertexId, VD, Option[U]) => VD2): Graph[VD2, ED]
}

  • joinVertices 操作符将顶点与输入 RDD 相连,并返回一个新的图形,其中通过将用户定义的 map 函数应用于已连接顶点的结果而获得的顶点属性。RDD 中没有匹配值的顶点保留其原始值。

  • 除了将用户定义的 map 函数应用于所有顶点并且可以更改顶点属性类型之外,更一般的 outerJoinVertices 的行为类似于 joinVertices。因为不是所有的顶点都可能在输入 RDD 中具有匹配的值,所以 map 函数采用 Option 类型。

    5.邻域聚合

    许多图形分析任务的关键步骤是聚合关于每个顶点邻域的信息。例如,我们可能想知道每个用户拥有的关注者数量或每个用户的追随者的平均年龄。许多迭代图表算法(例如:网页级别,最短路径,以及连接成分)相邻顶点(例如:电流值的 PageRank,最短到源路径,和最小可达顶点 ID)的重复聚合性质。

    GraphX 中的核心聚合操作是 aggregateMessages(聚合消息)。该运算符将用户定义的 sendMsg 函数应用于图中的每个 边缘三元组,然后使用该 mergeMsg 函数在其目标顶点聚合这些消息。

class Graph[VD, ED] {def aggregateMessages[Msg: ClassTag](sendMsg: EdgeContext[VD, ED, Msg] => Unit,mergeMsg: (Msg, Msg) => Msg,tripletFields: TripletFields = TripletFields.All): VertexRDD[Msg]
}

    用户定义的 sendMsg 函数接受一个 EdgeContext,它将源和目标属性以及 edge 属性和函数 (sendToSrcsendToDst) 一起发送到源和目标属性。在 map-reduce 中,将 sendMsg 作为 map 函数。用户定义的 mergeMsg 函数需要两个发往同一顶点的消息,并产生一条消息。想想 mergeMsg 是 map-reduce 中的 reduce 函数aggregateMessages 运算符返回一个 VertexRDD[Msg],其中包含去往每个顶点的聚合消息(Msg类型)。没有收到消息的顶点不包括在返回的 VertexRDDVertexRDD 中。

、Pregel API

    图形是固有的递归数据结构,因为顶点的属性取决于其邻居的属性,而邻居的属性又依赖于 其 邻居的属性。因此,许多重要的图算法迭代地重新计算每个顶点的属性,直到达到一个固定点条件。已经提出了一系列图并行抽象来表达这些迭代算法。GraphX 公开了 Pregel API 的变体。在以下示例中,我们可以使用 Pregel 运算符来表达单源最短路径的计算:

import org.apache.spark.graphx.{Graph, VertexId}
import org.apache.spark.graphx.util.GraphGenerators// 带有包含距离属性的边的图
scala> val graph: Graph[Long, Double] = GraphGenerators.logNormalGraph(sc, numVertices = 100).mapEdges(e => e.attr.toDouble)
graph: org.apache.spark.graphx.Graph[Long,Double] = org.apache.spark.graphx.impl.GraphImpl@6fdc624
val sourceId: VertexId = 42 // 最终来源
// 初始化图
scala> val initialGraph = graph.mapVertices((id, _) => if (id == sourceId) 0.0 else Double.PositiveInfinity)
initialGraph: org.apache.spark.graphx.Graph[Double,Double] = org.apache.spark.graphx.impl.GraphImpl@211d223fscala> val sssp = initialGraph.pregel(Double.PositiveInfinity)(| (id, dist, newDist) => math.min(dist, newDist), // 顶点程序| triplet => { // 发送消息| if (triplet.srcAttr + triplet.attr math.min(a, b) // 合并消息| )
sssp: org.apache.spark.graphx.Graph[Double,Double] = org.apache.spark.graphx.impl.GraphImpl@5be47769scala> println(sssp.vertices.collect.mkString("\n"))
(84,1.0)
(96,2.0)
... ... ...

四、图算法

    GraphX 包括一组简化分析任务的图算法。该算法被包含在 org.apache.spark.graphx.lib 包可直接作为方法通过 GraphOps来访问 Graph。本节介绍算法及其使用方法。

    1.PageRank

    PageRank 测量在图中每个顶点的重要性,假设从边缘 u 到 v 表示的认可 v 通过的重要性 u。例如,如果 Twitter 用户遵循许多其他用户,则用户将被高度排名。

    GraphX 附带了 PageRank 的静态动态实现方法作PageRank 对象上的方法。静态 PageRank 运行固定次数的迭代,而动态 PageRank 运行直到排列收敛(即,停止改变超过指定的公差)。GraphOps 允许直接调用这些算法作为方法 Graph。

    GraphX还包括一个可以运行 PageRank 的社交网络数据集示例。给出了一组用户data/graphx/users.txt,并给出了一组用户之间的关系 data/graphx/followers.txt。我们计算每个用户的 PageRank 如下:

import org.apache.spark.graphx.GraphLoaderscala> val Graph = GraphLoader.edgeListFile(sc,"file:///usr/local/spark/data/graphx/followers.txt")
Graph: org.apache.spark.graphx.Graph[Int,Int] = org.apache.spark.graphx.impl.GraphImpl@4b05a635scala> val ranks = graph.pageRank(0.0001).vertices
ranks: org.apache.spark.graphx.VertexRDD[Double] = VertexRDDImpl[954] at RDD at VertexRDD.scala:57scala> val users = sc.textFile("data/graphx/users.txt").map { line =>| val fields = line.split(",")| (fields(0).toLong, fields(1))| }
users: org.apache.spark.rdd.RDD[(Long, String)] = MapPartitionsRDD[963] at map at :34scala> val users = sc.textFile("file:///usr/local/spark/data/graphx/users.txt").map { line =>| val fields = line.split(",")| (fields(0).toLong, fields(1))| }
users: org.apache.spark.rdd.RDD[(Long, String)] = MapPartitionsRDD[966] at map at :34scala> val ranksByUsername = users.join(ranks).map {| case (id, (username, rank)) => (username, rank)| }
ranksByUsername: org.apache.spark.rdd.RDD[(String, Double)] = MapPartitionsRDD[970] at map at :37scala> println(ranksByUsername.collect().mkString("\n"))
(justinbieber,1.1572302808266979)
(anonsys,1.3213594965245234)
(BarackObama,1.0873678421339295)
(matei_zaharia,1.0399824275230816)
(ladygaga,0.8845423166697186)
(jeresig,1.150821600247031)
(odersky,1.2192581719573445)

    2.连接组件

连接的组件算法将图中每个连接的组件与其最低编号顶点的ID进行标记。例如,在社交网络中,连接的组件可以近似群集。GraphX包含ConnectedComponents object 中算法的实现,我们从PageRank 部分计算示例社交网络数据集的连接组件如下:

import org.apache.spark.graphx.GraphLoader
// 加载PageRank官方示例文件
scala> val Graph = GraphLoader.edgeListFile(sc,"file:///usr/local/spark/data/graphx/followers.txt")
Graph: org.apache.spark.graphx.Graph[Int,Int] = org.apache.spark.graphx.impl.GraphImpl@49a28c9e
// 查找连接组件
scala> val cc = graph.connectedComponents().vertices
cc: org.apache.spark.graphx.VertexRDD[org.apache.spark.graphx.VertexId] = VertexRDDImpl[1021] at RDD at VertexRDD.scala:57
//  与带有用户名的连接组件Jion
scala> sc.textFile("file:///usr/local/spark/data/graphx/users.txt").collect()
res16: Array[String] = Array(1,BarackObama,Barack Obama, 2,ladygaga,Goddess of Love, 3,jeresig,John Resig, 4,justinbieber,Justin Bieber, 6,matei_zaharia,Matei Zaharia, 7,odersky,Martin Odersky, 8,anonsys)
scala> val users = sc.textFile("file:///usr/local/spark/data/graphx/users.txt").map {line => | val fields = line.split(",")| (fields(0).toLong, fields(1))| }
users: org.apache.spark.rdd.RDD[(Long, String)] = MapPartitionsRDD[1049] at map at :36
scala> val ccByUsername = users.join(cc).map {| case (id, (username, cc)) => (username, cc)| }
ccByUsername: org.apache.spark.rdd.RDD[(String, org.apache.spark.graphx.VertexId)] = MapPartitionsRDD[1053] at map at :39
// 输出结果
scala> println(ccByUsername.collect().mkString("\n"))
(justinbieber,0)
(anonsys,0)
(BarackObama,0)
(matei_zaharia,0)
(ladygaga,0)
(jeresig,0)
(odersky,0)

    3.Triangle计数

    顶点是三角形的一部分,当它有两个相邻的顶点之间有一个边。GraphX 在 TriangleCount 对象中实现一个三角计数算法,用于确定通过每个顶点的三角形数量,提供聚类度量。我们从PageRank 部分计算社交网络数据集的三角形数。需要注意的是 TriangleCount 边缘要处于规范方向(srcIddstId),而图形要使用 Graph.partitionBy

import org.apache.spark.graphx.{GraphLoader, PartitionStrategy}
// 以规范顺序加载边线并划分图形以进行三角形计数
scala> val graph = GraphLoader.edgeListFile(sc, "file:///usr/local/spark/data/graphx/followers.txt", true)
graph: org.apache.spark.graphx.Graph[Int,Int] = org.apache.spark.graphx.impl.GraphImpl@71efa96cscala> val graph = GraphLoader.edgeListFile(sc, "file:///usr/local/spark/data/graphx/followers.txt", true).partitionBy(PartitionStrategy.RandomVertexCut)
graph: org.apache.spark.graphx.Graph[Int,Int] = org.apache.spark.graphx.impl.GraphImpl@21f8fee3
// 找到每个顶点的三角形数
scala> val triCounts = graph.triangleCount().vertices
triCounts: org.apache.spark.graphx.VertexRDD[Int] = VertexRDDImpl[1134] at RDD at VertexRDD.scala:57
// 使用用户名Join三角形数
scala> val users = sc.textFile("file:///usr/local/spark/data/graphx/users.txt").map { line =>| val fields = line.split(",")| (fields(0).toLong, fields(1))| }
users: org.apache.spark.rdd.RDD[(Long, String)] = MapPartitionsRDD[1139] at map at :38scala> val triCountByUsername = users.join(triCounts).map { case (id, (username, tc)) => (username, tc)}
triCountByUsername: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[1143] at map at :41
// 输出结果
scala> println(triCountByUsername.collect().mkString("\n"))(justinbieber,0)
(matei_zaharia,1)
(ladygaga,0)
(BarackObama,0)
(jeresig,1)
(odersky,1)

    至此Spark GraphX图计算讲解完成,Spark大数据分布式处理实战笔记也到此结束。希望通过本系列可以使得作者的实操能力得到增强,也希望读者能够通过此系列入门大数据处理并且get到Spark的精彩之处~

    之后可能在此基础上继续延伸,更新Spark MLlib源码解析系列。此系列将机器学习算法与分布式架构结合在一起,使得AI人工智能算法在大数据场景下发挥更大的作用。

    前文笔记请参考下面的链接:

    Spark大数据分布式处理实战笔记(一):快速开始

    Spark大数据分布式处理实战笔记(二):RDD、共享变量

    Spark大数据分布式处理实战笔记(三):Spark SQL

    Spark大数据分布式处理实战笔记(四):Spark Streaming

    Spark大数据分布式处理实战笔记(五):Spark MLlib

    你可能错过了这些~

    “高频面经”之数据分析篇

    “高频面经”之数据结构与算法篇

    “高频面经”之大数据研发篇

    “高频面经”之机器学习篇

    “高频面经”之深度学习篇

我就知道你“在看”


推荐阅读
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
author-avatar
手机用户2502939543
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有