Task not serializable:java.io.NotSerializableExceptionon

本文探讨了在使用Spark时遇到的序列化问题,特别是在处理类内部函数时出现的`java.io.NotSerializableException`异常。文章提供了两种解决方案:一是对整个类进行序列化处理,二是通过Lambda表达式替换函数,确保Spark能够正确地进行序列化。

异常信息

这里关于调用外部的closure时出现了一些错误,当函数是一个对象时一切正常,当函数是一个类时则出现如下报错:

Task not serializable: java.io.NotSerializableException: testing

下面是能正常工作的代码示例:

 object working extends App {
    val list = List(1,2,3)

    val rddList = Spark.ctx.parallelize(list)
    //calling function outside closure 
    val after = rddList.map(someFunc(_))

    def someFunc(a:Int)  = a+1

    after.collect().map(println(_))
  }

不能正常工作的代码:

object NOTworking extends App {
     new testing().doIT
  }
  //adding extends Serializable wont help
  class testing {

    val list = List(1,2,3)

    val rddList = Spark.ctx.parallelize(list)

    def doIT =  {
      //again calling the fucntion someFunc 
      val after = rddList.map(someFunc(_))
      //this will crash (spark lazy)
      after.collect().map(println(_))
    }

    def someFunc(a:Int) = a+1

  }

解决方案

Spark是一个分布式计算引擎,它抽象的提出一种弹性分布式数据集(RDD),RDD可以被看作是一个分布式集合。RDD的元素是在集群的节点分区,但Spark将用户抽象的运用到这里,让用户使用RDD(集合)事感到就是在本地进行交互。
第二个例子不能正常运行的原因是从map中定义一个类—testing,这样调用函数的方法是不对的,Spark检测到了这一点,一旦没有对自己进行序列化的方法,Spark会尝试序列化整个testing类。这样当在另一个JVM中执行代码仍然可以运行。在这里有两种可能性:
首先你可以做一个类的序列化测试,以确保整个类都可以被Spark进行序列化:

import org.apache.spark.SparkContext

object Spark {
  val ctx = new SparkContext("local", "test")
}

object NOTworking extends App {
  new Test().doIT
}

class Test extends java.io.Serializable {
  val rddList = Spark.ctx.parallelize(List(1,2,3))

  def doIT() =  {
    val after = rddList.map(someFunc)
    after.collect().foreach(println)
  }

  def someFunc(a: Int) = a + 1
}

或者用函数someFunc来替代的方法,来确保Spark能够进行序列化:

import org.apache.spark.SparkContext

object Spark {
  val ctx = new SparkContext("local", "test")
}

object NOTworking extends App {
  new Test().doIT
}

class Test {
  val rddList = Spark.ctx.parallelize(List(1,2,3))

  def doIT() =  {
    val after = rddList.map(someFunc)
    after.collect().foreach(println)
  }

  val someFunc = (a: Int) => a + 1
}

参考链接:
http://stackoverflow.com/questions/22592811/task-not-serializable-java-io-notserializableexception-when-calling-function-ou

这个错误 `java.io.NotSerializableException: android.graphics.Paint` 表明你尝试序列化一个包含 `Paint` 对象的类,但 `Paint` 类本身没有实现 `Serializable` 接口,因此无法直接序列化。 ### 原因分析: 1. **`Paint` 不可序列化**: - `android.graphics.Paint` 是 Android 图形绘制工具类,但未实现 `Serializable` 或 `Parcelable`。 - 如果你的类(如 `FloorElement`)包含 `Paint` 字段,并尝试通过 `Serializable` 序列化,就会抛出此异常。 2. **常见场景**: - 将包含 `Paint` 的对象通过 `Intent` 传递(`putExtra` + `getSerializableExtra`)。 - 将对象保存到磁盘(如 `ObjectOutputStream`)。 - 跨进程通信(如 `Binder` 或 `Bundle`)。 --- ### 解决方法: #### 方案 1:将 `Paint` 标记为 `transient` 如果 `Paint` 的状态不需要序列化(例如,可以在目标端重新创建),可以将其标记为 `transient`: ```java public class FloorElement implements Serializable { private transient Paint paint; // 不会被序列化 private int color; // 可序列化的字段 // 其他代码... } ``` **恢复 `Paint` 对象**: 在反序列化后(如 `readObject` 方法中),重新初始化 `Paint`: ```java private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 反序列化其他字段 this.paint = new Paint(); // 重新创建 Paint this.paint.setColor(this.color); // 恢复状态 } ``` #### 方案 2:提取 `Paint` 的可序列化属性 如果 `Paint` 的某些属性需要保存(如颜色、样式等),可以单独存储这些属性: ```java public class FloorElement implements Serializable { private int paintColor; private float strokeWidth; // 其他可序列化字段... public Paint toPaint() { Paint paint = new Paint(); paint.setColor(paintColor); paint.setStrokeWidth(strokeWidth); return paint; } public void fromPaint(Paint paint) { this.paintColor = paint.getColor(); this.strokeWidth = paint.getStrokeWidth(); } } ``` #### 方案 3:使用 `Parcelable` 替代 `Serializable` 如果类需要频繁跨进程传递,建议实现 `Parcelable`(但 `Paint` 仍不可直接传递,需类似处理): ```java public class FloorElement implements Parcelable { private transient Paint paint; private int color; // 写入 Parcel(跳过 Paint) @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(color); } // 从 Parcel 读取 protected FloorElement(Parcel in) { this.color = in.readInt(); this.paint = new Paint(); // 重新创建 this.paint.setColor(color); } public static final Creator<FloorElement> CREATOR = ...; } ``` #### 方案 4:避免序列化 `Paint` 如果 `Paint` 仅用于临时绘制,可以重构代码,将其排除在序列化范围之外。 --- ### 关键点总结: 1. **`Paint` 不可序列化**:它是 Android 系统类,未实现 `Serializable`。 2. **`transient` 关键字**:跳过不可序列化的字段,但需手动恢复对象状态。 3. **属性拆分**:提取 `Paint` 的关键属性(如颜色、粗细)单独存储。 4. **设计建议**:避免在需要序列化的类中直接使用 `Paint`,改用基本类型或可序列化对象。 --- ### 示例修复代码: ```java public class FloorElement implements Serializable { private transient Paint paint; private int color; private float strokeWidth; public FloorElement(Paint paint) { this.paint = paint; this.color = paint.getColor(); this.strokeWidth = paint.getStrokeWidth(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.paint = new Paint(); this.paint.setColor(color); this.paint.setStrokeWidth(strokeWidth); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); } } ``` --- ### 常见问题扩展: 1. **为什么 `Bitmap` 也常导致序列化问题?如何解决?** 2. **`Parcelable` 和 `Serializable` 在序列化 `Paint` 时的区别是什么?** 3. **如何调试 `NotSerializableException` 的根本原因?** 4. **是否可以通过反射强制序列化 `Paint`?有什么风险?** 5. **在跨进程通信中传递 `Paint` 的最佳实践是什么?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值