2012-09-05 4 views
1

제가 알기론 Kryo는 writeObjectclassName < ->numberID 맵을 생성합니다. 이지도가 너무 좁습니다. 객체 모델에서 인스턴스는 동일한 클래스에 속하기 때문에 writeObject는 비슷한 맵을 다시 만들고 다시 직렬화합니다. 지도를 수동으로 등록하여지도를 공유 할 수 있다는 것을 알고 있지만 지루한 수동 하드 코딩입니다. 첫 번째 객체 쓰기에 의해 맵이 시작되고 정상적으로 처리되지만, 세션의 모든 후속 쓰기가 재사용 및 확장됩니다. 이렇게하면 런타임에 자동으로 등록이 실행되고 추가 런타임 오버 헤드가 발생하지 않고 자주 사용되는 객체가 자연스럽게 낮은 ID 번호를 수신하게됩니다. 지도는 나중에 첨부 파일로 별도로 해독 키로 저장할 수 있습니다. 디시리얼라이저는이 맵을로드하여 시작합니다. 어떻게 아이디어가 좋고, 어떻게 구현 될 수 있습니까?Kryo의 자동 클래스 등록

내 질문은 Strategy for registering classes with kryo과 비슷하지만 사용자는 List를 사용하여 단일 writeObject 아래에서 모든 쓰기를 결합 할 수 있습니다. 내가 제안한대로 별도로지도를 저장하는 것보다 훨씬 간단합니다. 그러나 그는 그렇게하기를 원치 않습니다. 필자의 경우, 그러한 조합은 큰 자바 모델 때문에 가능하지 않다. 조각으로 직렬화하여 메모리에 전체적으로 보관하는 것을 피한다. 내 시나리오에서는 사용자가 프로젝트를 열고 변경 한 다음 플러시합니다. 따라서이 프로젝트는 클래스 맵을 유지하고 모든 직렬화에 사용할 수 있습니다.

업데이트! 클래스/객체 등록자와 autoReset이 있다는 것을 깨달았습니다. 그들은 바로 그 일을 위해 창작 된 것 같습니다. 그러나, 나는이 것들이 그것을 해결하는 방법을 보지 못한다. Autoreset=false은 두 번째 쓰기를 실제로 더 작게 만듭니다. 그러나, 나는이 경우 객체를 비 직렬화하지 못한다. 당신이 예에서 보듯이, 두 번째 역 직렬화 실패 :

public class A { 
    String f1; 
    A(String a) { 
     f1 = a; 
    } 
    List list = new ArrayList(); 
    public String toString() { 
     return "f1 = " + f1 + ":" + f1.getClass().getSimpleName(); 
    } 

    public static void main(String[] args) { 
     test(true); 
     test(false); 
    } 


    static void write(String time, Kryo kryo, ByteArrayOutputStream baos, Object o) { 
     Output output = new Output(baos); 
     kryo.writeClassAndObject(output, o); 
     output.close(); 
     System.err.println(baos.size() + " after " + time + " write"); 
    } 

    private static void test(boolean autoReset) { 
     Kryo kryo = new Kryo(); 
     kryo.setAutoReset(autoReset); 
     kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); 
     System.err.println("-------\ntesting autoreset = " + autoReset); 
     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
     A a = new A("a"), b = new A("b"); 
     write("first", kryo, baos, a); 
     write("second", kryo, baos, b); 
     A o1 = restore("first", baos, kryo); 
     A o2 = restore("second", baos, kryo); // this fails 
     System.err.println((o1.f1.equals(o2.f1)) ? "SUCCESS" : "FAILURE"); 

    } 

    private static A restore(String time, ByteArrayOutputStream baos, Kryo k) { 
     ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray()); 
     Input input = new Input(in); 
     A o = (A) k.readClassAndObject(input); 
     System.err.println("reading object " + time + " time, got " + o); 
     return o; 
    } 

출력은 또한 자동 리셋 = false를 기록 클래스 이름에 추가하여 객체가 참조 발생할 수 있습니다 갱신 2

------- 
testing autoreset = true 
41 after first write 
82 after second write 
reading object first time, got f1 = a:String 
reading object second time, got f1 = a:String 
SUCCESS 
------- 
testing autoreset = false 
41 after first write 
52 after second write 
reading object first time, got f1 = a:String 
reading object second time, got null 
Exception in thread "main" java.lang.NullPointerException 
    at kryo_test.AutoresetDemo.test(AutoresetDemo.java:40) 
    at kryo_test.AutoresetDemo.main(AutoresetDemo.java:18) 

입니다. 실제로 자동 리셋하는 것이 가치가 있습니다.

업데이트 3 등록에 kryo 개체를 참조하고 일부 상태를 유지하는 serializer가 포함되어 있기 때문에 클래스 맵 (즉, 클래스 -> 등록)을 직렬화하기가 어렵다는 것을 발견했습니다. 그런 다음 많은 kryo 개체간에지도를 공유하기가 어렵습니다.

답변

2

좋아, 여기에 스트림을 발생하는 클래스 이름의 깨끗한 kryo - 2.20

public class GlobalClassKryo extends Kryo { 

    public static class ExternalizableClassResolver implements ClassResolver { 

     //local serializers 
     final Map<Class, Registration> fromClass = new HashMap(); 
     final Map<Integer, Registration> fromId = new HashMap(); 

     public static class GlobalRegistration { int id; Class type; Class<? extends Serializer> serializer; } 

     public final Map<Integer, GlobalRegistration> globalIds; 
     public final Map<Class, GlobalRegistration> globalClasses; 

     // I synchronize because I have one reader and one writer thread and 
     // writer may break the reader when adds something into the map. 
     public ExternalizableClassResolver() {this (
       Collections.synchronizedMap(new HashMap()), 
       Collections.synchronizedMap(new HashMap()) 
      ) ;} 

     public ExternalizableClassResolver(Map<Integer, GlobalRegistration> ids, Map<Class, GlobalRegistration> classes) { 
      globalIds = ids; 
      globalClasses = classes; 
     } 

     public ExternalizableClassResolver (DataInput in) throws ClassNotFoundException, IOException { 
      this(); 
      int id; 
      while ((id = in.readInt()) != -1) { 
       GlobalRegistration e = new GlobalRegistration(); 
       globalIds.put(e.id = id, e); 
       e.type = Class.forName(in.readUTF()); 
       e.serializer = (Class<? extends Serializer>) Class.forName(in.readUTF()); 
       globalClasses.put(e.type, e); 
      } 
     } 

     public void save(DataOutput out) throws IOException { 
      for (GlobalRegistration entry : globalIds.values()) { 
        out.writeInt(entry.id); 
        out.writeUTF(entry.type.getName()); 
        out.writeUTF(entry.serializer.getName()); 
      } 
      out.writeInt(-1); 
     } 

     static final boolean TRACE = false; 
     void log(String msg) { 
      System.err.println(kryo != null ? Utils.fill(kryo.getDepth(), ' ') + msg : msg); 
     } 
     @Override 
     public Registration writeClass(Output output, Class type) { 
      if (type == null) {output.writeInt(0, true); return null;} 
      Registration registration = kryo.getRegistration(type); 
      output.writeInt(registration.getId(), true); 
      return registration; 
     } 
     @Override 
     public Registration readClass(Input input) { 
      int classID = input.readInt(true); 
      if (classID == 0) return null; 
      Registration registration = fromId.get(classID); 
      if (registration == null) { 
       registration = tryGetFromGlobal(globalIds.get(classID), classID + ""); 
      } 
      if (registration == null) throw new KryoException("Encountered unregistered class ID: " + classID); 
      return registration; 
     } 

     public Registration register(Registration registration) { 
      throw new KryoException("register(registration) is not allowed. Use register(type, serializer)"); 
     } 

     public Registration getRegistration(int classID) { 
      throw new KryoException("getRegistration(id) is not implemented"); 
     } 

     Registration tryGetFromGlobal(GlobalRegistration globalClass, String title) { 
      if (globalClass != null) { 
       Serializer serializer = kryo.newSerializer(globalClass.serializer, globalClass.type); 
       Registration registration = register(globalClass.type, serializer, globalClass.id, "local"); 
       if (TRACE) log("getRegistration(" + title + ") taken from global => " + registration); 
       return registration; 
      } else 
       if (TRACE) log("getRegistration(" + title + ") was not found"); 
      return null; 
     } 
     public Registration getRegistration(Class type) { 
      Registration registration = fromClass.get(type); 
      if (registration == null) { 
       registration = tryGetFromGlobal(globalClasses.get(type), type.getSimpleName()); 
      } else 
       if (TRACE) log("getRegistration(" + type.getSimpleName() + ") => " + registration); 

      return registration; 
     } 

     Registration register(Class type, Serializer serializer, int id, String title) { 
      Registration registration = new Registration(type, serializer, id); 
      fromClass.put(type, registration); 
      fromId.put(id, registration); 

      if (TRACE) log("new " + title + " registration, " + registration); 

      //why dont' we put into fromId? 
      if (registration.getType().isPrimitive()) fromClass.put(getWrapperClass(registration.getType()), registration); 
      return registration; 
     } 

     int primitiveCounter = 1; // 0 is reserved for NULL 
     static final int PRIMITIVE_MAX = 20; 

     //here we register anything that is missing in the global map. 
     // It must not be the case that something available is registered for the second time, particularly because we do not check this here 
     // and use registered map size as identity counter. Normally, check is done prior to callig this method, in getRegistration 
     public Registration register(Class type, Serializer serializer) { 

      if (type.isPrimitive() || type.equals(String.class)) 
       return register(type, serializer, primitiveCounter++, "primitive"); 

      GlobalRegistration global = globalClasses.get(type); 

      if (global != null) 
        throw new RuntimeException("register(type,serializer): we have " + type + " in the global map, this method must not be called"); 

      global = new GlobalRegistration(); 
      globalIds.put(global.id = globalClasses.size() + PRIMITIVE_MAX, global); 
      globalClasses.put(global.type = type, global); 
      global.serializer= serializer.getClass(); 

      return register(global.type, serializer, global.id, "global"); 
     } 

     public Registration registerImplicit(Class type) { 
      throw new RuntimeException("registerImplicit is not needed since we register missing automanically in getRegistration"); 
     } 

     @Override 
     public void reset() { 
      // super.reset(); //no need to reset the classes 
     } 

     Kryo kryo; 
     public void setKryo(Kryo kryo) { 
      this.kryo = kryo; 
     } 
    } 




    public ExternalizableClassResolver ourClassResolver() { 
     return (ExternalizableClassResolver) classResolver; 
    } 

    public GlobalClassKryo(ClassResolver resolver) { 
     super(resolver, new MapReferenceResolver()); 
     setInstantiatorStrategy(new StdInstantiatorStrategy()); 
     this.setRegistrationRequired(true); 
    } 
    public GlobalClassKryo() { 
     this(new ExternalizableClassResolver()); 
    } 

    @Override 
    public Registration getRegistration (Class type) { 
     if (type == null) throw new IllegalArgumentException("type cannot be null."); 

     if (type == memoizedClass) return memoizedClassValue; 
     Registration registration = classResolver.getRegistration(type); 
     if (registration == null) { 
      if (Proxy.isProxyClass(type)) { 
       // If a Proxy class, treat it like an InvocationHandler because the concrete class for a proxy is generated. 
       registration = getRegistration(InvocationHandler.class); 
      } else if (!type.isEnum() && Enum.class.isAssignableFrom(type)) { 
       // This handles an enum value that is an inner class. Eg: enum A {b{}}; 
       registration = getRegistration(type.getEnclosingClass()); 
      } else if (EnumSet.class.isAssignableFrom(type)) { 
       registration = classResolver.getRegistration(EnumSet.class); 
      } 
      if (registration == null) { 
       //registration = classResolver.registerImplicit(type); 
       return register(type, getDefaultSerializer(type)); 
      } 
     } 
     memoizedClass = type; 
     memoizedClassValue = registration; 
     return registration; 
    } 

    public Registration register(Class type, Serializer serializer) { 
     return ourClassResolver().register(type, serializer);} 

    public Registration register(Registration registration) { 
     throw new RuntimeException("only register(Class, Serializer) is allowed");} 

    public Registration register(Class type) { 
     throw new RuntimeException("only register(Class, Serializer) is allowed");} 

    public Registration register(Class type, int id) { 
     throw new RuntimeException("only register(Class, Serializer) is allowed");} 

    public Registration register(Class type, Serializer serializer, int id) { 
     throw new RuntimeException("only register(Class, Serializer) is allowed"); 
    } 

    static void write(String title, Kryo k, ByteArrayOutputStream baos, Object obj) { 
     Output output = new Output(baos); 
     k.writeClassAndObject(output, obj); 
     output.close(); 
     System.err.println(baos.size() + " bytes after " + title + " write"); 
    } 
    static class A { 
     String field = "abcABC"; 
     A a = this; 
     //int b = 1; // adds 1 byte to serialization 
     @Override 
     public String toString() { 
      return field 
        + " " + list.size() 
        //+ ", " + b 
        ; 
     } 

     // list adds 3 bytes to serialization, two 3-byte string items add additionally 10 bytes in total 
     ArrayList list = new ArrayList(100); // capacity is trimmed in serialization 
     { 
      list.add("LLL"); 
      list.add("TTT"); 

     } 
    } 

    private static void test() throws IOException, ClassNotFoundException { 
     GlobalClassKryo k = new GlobalClassKryo(); 

     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 

     write("first", k, baos, new A()); // write takes 24 byts 

     //externalize the map 
     ByteArrayOutputStream mapOut = new ByteArrayOutputStream(); 
     DataOutputStream dataOut = new DataOutputStream(mapOut); 
     k.ourClassResolver().save(dataOut); 
     dataOut.close(); 

     //deserizalize the map 
     DataInputStream serialized = new DataInputStream(new ByteArrayInputStream(mapOut.toByteArray())); 
     ExternalizableClassResolver resolver2 = new ExternalizableClassResolver(serialized); 

     //use the map 
     k = new GlobalClassKryo(resolver2); 
     write("second", k, baos, new A()); // 24 bytes 

     Input input = new Input(new ByteArrayInputStream(baos.toByteArray())); 
     Object read = k.readClassAndObject(input); 
     System.err.println("output " + read); 
    } 

    public static void main(String[] args) throws IOException, ClassNotFoundException { 
     Kryo k = new Kryo(); 
     ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
     write("first", k, baos, new A()); // write takes 78 bytes 
     write("second", k, baos, new A()); // +78 bytes 
     System.err.println("----------------"); 

     test(); 
    } 
} 

위한 솔루션입니다. 불행하게도 Kryo는 스트림이 훨씬 더 밀집되어 있지만 기본 Java 직렬화 (2x 이상)에 비해 너무 느리다. Kryo만으로도 샘플 샘플 직렬화가 거의 10 배 더 작아졌습니다. 이 답안에 제시된 해결책은 추가 요인 3 배를 추가하는 것을 볼 수 있습니다. 그러나 필자가 메가 바이트를 직렬화하는 분야에서는 Kryo로 디스크에 저장할 때 java serialization 및 2x slowdown과 관련하여 단지 2 배 압축만을 얻고 있습니다.

+2

Glad Kryo는 당신이 원하는 것을 유연하게 처리했습니다. 속도는 제대로 들리지 않지만, 경기 둔화가 어디 있는지 프로필을 작성 했습니까? – NateS