先说结论:
show()和df里数据分区有关
假设:show(m),df 0号分区里数据量为n
- 1.m <= n时,show取0号分区m条数据
- 2.m > n时,show取0号分区n条数据,另外取1号分区m-n条数据(不够的话以此类推)
上代码验证:
1.8个分区
val df = Seq((5,5), (6,6), (7,7), (8,8), (1,1), (2,2), (3,3), (4,4)).toDF("col1", "col2")
// above sequence is defined out of order - to make behaviour visible
// see partition structure
df.rdd.glom().collect()
/* Array(Array([5,5]), Array([6,6]), Array([7,7]), Array([8,8]), Array([1,1]), Array([2,2]), Array([3,3]), Array([4,4])) */
df.show(4, false)
/*
+----+----+
|col1|col2|
+----+----+
|5 |5 |
|6 |6 |
|7 |7 |
|8 |8 |
+----+----+
only showing top 4 rows
*/
2.2个分区
// Now let's repartition df
val df2 = df.repartition(2)
// lets see the partition structure
df2.rdd.glom().collect()
/* Array(Array([5,5], [6,6], [7,7], [8,8], [1,1], [2,2], [3,3], [4,4]), Array()) */
// lets see output
df2.show(4,false)
/*
+----+----+
|col1|col2|
+----+----+
|5 |5 |
|6 |6 |
|7 |7 |
|8 |8 |
+----+----+
only showing top 4 rows
*/
3.3个分区
val df3 = df.repartition(3)
// lets see partition structures
df3.rdd.glom().collect()
/*
Array(Array([8,8], [1,1], [2,2]), Array([5,5], [6,6]), Array([7,7], [3,3], [4,4]))
*/
// And lets see the top 4 rows this time
df3.show(4, false)
/*
+----+----+
|col1|col2|
+----+----+
|8 |8 |
|1 |1 |
|2 |2 |
|5 |5 |
+----+----+
only showing top 4 rows
*/
源码:
在Spark 2+中,show()
调用showString()
将数据格式化为字符串,然后打印出来。showString()
调用getRows()
以字符串集合的形式获取dataset的顶行。getRows()
调用take()
来获取原始行,并将它们转换为字符串。take()
只是简单地包装了head()
。head()
调用limit()
来构建并执行一个限制查询。limit()
在逻辑计划的前面添加了一个Limit(n)
节点,它实际上是一个GlobalLimit(n, LocalLimit(n))
。GlobalLimit
和LocalLimit
都是OrderPreservingUnaryNode
的子类,它们覆盖其maxRows
(在GlobalLimit
中)或maxRowsPerPartition
(在LocalLimit
中)方法。逻辑计划现在看起来像这样:
GlobalLimit n
+- LocalLimit n
+- ...
这经过了Catalyst的分析和优化,其中如果树下的某些东西产生的行数少于限制,则会删除限制,并最终在执行策略中作为CollectLimitExec(m)
(where m
<= n
),因此物理计划如下所示:
CollectLimit m
+- ...
CollectLimitExec
执行它的子计划,然后检查RDD有多少个分区。如果没有,则返回一个空数据集。如果有,它会运行mapPartitionsInternal(_.take(m))
来获取第一个m元素。如果不止一个,它使用mapPartitionsInternal(_.take(m))
在RDD中的每个分区上应用take(m)
,构建一个将结果收集到单个分区中的shuffle RDD,然后再次应用take(m)
。
换句话说,这取决于(因为优化阶段),但在一般情况下,它采用每个分区的顶行的连接的顶行,因此涉及持有数据集的一部分的所有执行器。