Accumulator累加器

本文深入探讨Spark中累加器的应用场景,包括如何通过自定义AccumulatorV2实现Wordcount,解决网络IO和计算效率问题。同时,介绍了自定义排序的实现方法,适用于多字段排序需求。

1、累加器应用场景

应用背景:在Executor进行计算的过程中,有些变量的值会在Executor多次使用,
每次使用时,就要从driver端拿取一次,就会产生大量的网络IO,而且还会影响计算效率,
此时就可以用广播变量的方式,在计算之前先把数据从driver端广播到相应的Executor端的缓存里,
后期在使用的时候,就可以直接从缓存里拿取进行计算

注意:广播变量过程,必须是把driver端的某个变量的值广播到Executor端
广播变量的值不易过大,否则会占用大量的内存

2、Accumulator版本的Wordcount

通过继承AccumulatorV2实现几个方法(使用到了伴生类):

package com.murphy.Acc

import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.mutable

//伴生类
class AccWordCountDemo extends AccumulatorV2[String, mutable.HashMap[String, Int]] {
  // 初始值
  private val hashAcc = new mutable.HashMap[String, Int]()

  // 检测初始值是否为空
  override def isZero: Boolean = hashAcc.isEmpty
  // copy一个新的累加器
  override def copy(): AccumulatorV2[String, mutable.HashMap[String, Int]] = {
    val newAcc = new AccWordCountDemo
    // 有可能多个task同时往初始值里写值,有可能出现线程安全问题,此时最好加锁
    hashAcc.synchronized {
      newAcc.hashAcc ++= hashAcc
    }

    newAcc
  }
  // 重置累加器
  override def reset(): Unit = hashAcc.clear()
  // 局部累加
  override def add(v: String): Unit = {
    hashAcc.get(v) match {
      case None => hashAcc += ((v, 1))
      case Some(x) => hashAcc += ((v, x + 1))
    }
  }
  // 全局合并
  override def merge(other: AccumulatorV2[String, mutable.HashMap[String, Int]]): Unit = {
    other match {
      case o: AccumulatorV2[String, mutable.HashMap[String, Int]] => {
        for ((k, v) <- o.value) {
          hashAcc.get(k) match {
            case None => hashAcc += ((k, v))
            case Some(x) => hashAcc += ((k, x + v))
          }
        }
      }
    }
  }
  // 输出值
  override def value: mutable.HashMap[String, Int] = hashAcc
}

//伴生对象
object AccWordCountDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("AccWordCountDemo").setMaster("local[2]")
    val sc = new SparkContext(conf)

    val rdd = sc.makeRDD(List("a", "c", "a", "d", "a", "c"), 2)

    // 调用累加器对象
    val acc = new AccWordCountDemo

    // 注册累加器
    sc.register(acc, "accwc")

    // 开始分布式累加
    rdd.foreach(acc.add)

    println(acc.value)


    sc.stop()
  }
}

3、自定义排序

自定义排序(二次排序):
应用场景:比如自定义了一个对象,该对象里有多个字段,需要对多个字段进行排序,
如果直接调用sortBy进行排序,会出错,因为sortBy不知道具体的比较规则。
此时可以用Spark提供的自定义排序进行操作,这样我们就可以指定具体的排序规则了

<think>我们观察输出,发现当前程序在输出时似乎重复输出了英文词条,并且翻译部分出现了多余的逗号和换行。 问题分析: 1. 当前程序逻辑是:当遇到新的英文词条或文件结束时,将之前收集的英文词条和翻译输出。 2. 在输出后,将当前行(即新遇到的英文词条)设置为新的current_english,并重置翻译计数器。 3. 但是,在循环中,我们首先读取一行,如果是英文词条且当前没有翻译(trans_count==0)则将其设为当前词条。 否则,将其作为翻译存储。 4. 然后我们通过“偷看”下一字符来判断是否应该输出当前收集的词条。 5. 然而,在输出时,我们将current_english输出,然后输出15个空格,再输出翻译(多个用逗号分隔)。 6. 但是,在输出后,我们又将当前行(即刚刚作为翻译存储的那一行)设置为了current_english,这显然是不对的。 具体错误: - 当我们读取一个英文词条时,它会被设置为current_english(如果当前没有翻译)。 - 然后读取下一行(翻译)时,它被存储到translations中。 - 然后我们“偷看”下一行,如果下一行是英文词条(或者文件结束),则触发输出。 输出:current_english(即第一个英文词条)和翻译(一行或多行)。 - 输出后,我们将current_english设置为当前行(即翻译行?不,当前行是翻译行,但注意我们存储翻译行后,并没有改变current_english,直到输出后才会将当前行(即翻译行)设置为current_english,这显然错误。 重新设计逻辑: 我们可以考虑每次读取一行,如果是英文词条,则处理上一个词条(如果有的话),然后开始新的词条收集。 具体步骤: 1. 初始化current_english为空,翻译列表为空。 2. 读取一行,如果不是空行: 如果这一行是英文词条: 如果current_english不为空,说明上一个词条结束,将上一个词条(current_english和翻译列表)写入文件。 然后将当前行设为current_english,并清空翻译列表。 如果这一行不是英文词条(即翻译): 则将其加入翻译列表(注意,翻译可能有多个)。 3. 循环结束后,处理最后一个词条。 因此重构代码: 注意:由于我们在遇到新的英文词条时才处理上一个词条,因此最后一个词条需要在循环结束后单独处理。 修改后的代码: ```c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <stdbool.h> #define MAX_LINE_LENGTH 256 #define SPACES_NUM 15 #define MAX_TRANSLATIONS 20 // 增加翻译项的最大数量 // 判断是否可能是英文词条(不含中文) bool is_english_term(const char *str) { while (*str) { // 检测到中文字符(GBK高字节范围)或非ASCII字符,则认为是翻译(非英文词条) if ((unsigned char)*str >= 0x80) return false; str++; } return true; } int main() { FILE *input = fopen("input.txt", "r"); FILE *output = fopen("output.txt", "w"); if (!input || !output) { perror("文件打开失败"); exit(EXIT_FAILURE); } char buffer[MAX_LINE_LENGTH]; char current_english[MAX_LINE_LENGTH] = {0}; // 当前正在收集的英文词条 char translations[MAX_TRANSLATIONS][MAX_LINE_LENGTH] = {0}; // 翻译列表 int trans_count = 0; bool first_term = true; // 标记是否是第一个词条,避免第一个词条前无内容时过早输出 while (fgets(buffer, sizeof(buffer), input)) { // 移除换行符 buffer[strcspn(buffer, "\n")] = 0; // 跳过空行 if (strlen(buffer) == 0) continue; if (is_english_term(buffer)) { // 遇到新的英文词条,如果当前已经有收集的词条,则输出上一个词条 if (current_english[0] != '\0' && !first_term) { // 写入英文词条 fputs(current_english, output); // 添加15个空格 for (int i = 0; i < SPACES_NUM; i++) { fputc(' ', output); } // 写入翻译(逗号分隔) for (int i = 0; i < trans_count; i++) { fputs(translations[i], output); if (i < trans_count - 1) fputs(", ", output); } fputc('\n', output); } // 重置当前词条为新词条,重置翻译列表 strcpy(current_english, buffer); trans_count = 0; first_term = false; } else { // 非英文词条(即翻译) if (trans_count < MAX_TRANSLATIONS) { strcpy(translations[trans_count], buffer); trans_count++; } } } // 处理最后一个词条 if (current_english[0] != '\0') { fputs(current_english, output); for (int i = 0; i < SPACES_NUM; i++) { fputc(' ', output); } for (int i = 0; i < trans_count; i++) { fputs(translations[i], output); if (i < trans_count - 1) fputs(", ", output); } fputc('\n', output); } fclose(input); fclose(output); printf("转换完成!结果已保存到output.txt\n"); return 0; } ``` ### 修改点说明 1. **处理逻辑调整**: - 遇到英文词条时,立即处理之前收集的词条(如果有的话) - 新词条开始后,重置翻译计数 - 循环结束后单独处理最后一个词条 2. **修复输出重复**: - 不再使用“偷看”机制,而是根据词条类型触发输出 - 避免将翻译行错误设置为英文词条 3. **增强健壮性**: - 增加最大翻译项数量限制(20项) - 添加`first_term`标记避免第一个词条无内容时输出 ### 预期输出示例 ```text a block of pointers 一块指针, 一组指针 abbreviation 缩略语 abstract 抽象的 abstract syntax tree AST, 抽象语法树 abstraction 抽象 abstraction barrier 抽象屏障, 抽象阻碍 abstraction of function calls 函数调用抽象 access 访问, 存取 access function 访问函数, 存取函数 accumulator 累加器 ``` §§相关问题§§ 1. 如果输入文件中英文词条后没有紧跟翻译而是空行,程序会如何处理? 2. 如何修改程序以支持多级翻译(例如:词条有多个翻译,每个翻译又有子翻译)? 3. 当前程序在遇到非英文字符时即判定为翻译,如果英文词条中包含非ASCII字符(如法语重音符号)会怎样? 4. 是否有更高效的方法来判断一行是否为英文词条(避免逐字节扫描)? 5. 若输入文件格式不规范(如翻译行出现在英文词条之前),程序输出会出现什么问题? </think>### 修正后的C语言文本格式化程序 我分析了输出问题,发现原因在于程序逻辑中处理词条边界的方式有误。以下是完全修正的代码: ```c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> #define MAX_LINE_LENGTH 256 #define SPACES_NUM 15 // 判断是否英文行(不含中文字符) bool is_english_line(const char *str) { while (*str) { if ((unsigned char)*str >= 0x80) return false; str++; } return true; } int main() { FILE *input = fopen("input.txt", "r"); FILE *output = fopen("output.txt", "w"); if (!input || !output) { perror("文件打开失败"); exit(EXIT_FAILURE); } char english[MAX_LINE_LENGTH] = {0}; // 当前英文词条 char translations[10][MAX_LINE_LENGTH] = {0}; // 翻译列表 int trans_count = 0; bool is_first_line = true; char buffer[MAX_LINE_LENGTH]; while (fgets(buffer, sizeof(buffer), input)) { // 移除行尾换行符 size_t len = strlen(buffer); if (len > 0 && buffer[len-1] == '\n') buffer[len-1] = '\0'; // 跳过空行 if (strlen(buffer) == 0) continue; // 处理英文行(新词条开始) if (is_english_line(buffer)) { // 如果不是第一行,先输出前一个词条 if (!is_first_line) { // 写入英文词条 fputs(english, output); // 添加15个空格 for (int i = 0; i < SPACES_NUM; i++) fputc(' ', output); // 写入翻译(逗号分隔) for (int i = 0; i < trans_count; i++) { fputs(translations[i], output); if (i < trans_count - 1) fputs(", ", output); } fputc('\n', output); } else { is_first_line = false; } // 开始新词条 strcpy(english, buffer); trans_count = 0; } // 处理翻译行 else { if (trans_count < 10) { strcpy(translations[trans_count], buffer); trans_count++; } } } // 处理最后一个词条 if (strlen(english) > 0) { fputs(english, output); for (int i = 0; i < SPACES_NUM; i++) fputc(' ', output); for (int i = 0; i < trans_count; i++) { fputs(translations[i], output); if (i < trans_count - 1) fputs(", ", output); } fputc('\n', output); } fclose(input); fclose(output); printf("转换完成!结果已保存在output.txt\n"); return 0; } ``` ### 修正说明与改进点 1. **问题修复**: - 修复了词条边界处理逻辑,避免重复输出英文词条 - 正确处理文件末尾的最后一条记录 - 移除了无效的"偷看"字符逻辑 2. **功能增强**: - 增加翻译项上限至10个 - 添加首行标记处理,避免空输出 - 更健壮的空行处理 - 改进字符编码判断逻辑 3. **预期输出格式**: ``` a block of pointers 一块指针, 一组指针 abbreviation 缩略语 abstract 抽象的 abstract syntax tree AST, 抽象语法树 abstraction 抽象 abstraction barrier 抽象屏障, 抽象阻碍 abstraction of function calls 函数调用抽象 access 访问, 存取 access function 访问函数, 存取函数 accumulator 累加器 activate 激活 ad hoc 专设 adapter 适配器 address 地址 algebraic data type 代数数据类型 algorithm 算法 alias 别名 allocate 分配, 配置 ``` ### 使用说明 1. 创建`input.txt`文件,内容如您提供的格式 2. 编译并运行程序:`gcc formatter.c -o formatter && ./formatter` 3. 输出文件`output.txt`将包含格式化后的内容 4. 程序自动处理多翻译项,最多支持10个翻译
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值