얼마 전 RPC 프레임워크를 작성할 때 Kryo, Hessian, Protostuff라는 세 가지 직렬화 방법을 사용했습니다. 하지만 당시에는 기능 구현에 열중했기 때문에 이 세 가지 직렬화 방법을 사용하는 방법만 간략하게 살펴봤을 뿐, 각각의 특징과 장단점에 대해서는 자세히 다루지 않았습니다. 이제 RPC 프레임워크 작성이 끝났다는 것을 알고 나면 마음을 진정시키고 세 가지 방법을 비교하고 요약하는 시간을 가졌습니다.
Kryo, Hessain 및 Protostuff는 모두 타사 오픈 소스 직렬화/역직렬화 프레임워크입니다. 각각의 특성을 이해하려면 먼저 직렬화/역직렬화가 무엇인지 알아야 합니다.
직렬화: 그것이 바로 변환 프로세스입니다. 객체를 바이트 시퀀스로 변환합니다.
역직렬화: 바이트 시퀀스를 객체로 변환하는 프로세스에 관한 것입니다.
직렬화 직렬화: 객체를 전송을 용이하게 하는 형식으로 변환합니다. 일반적인 직렬화 형식: 바이너리 형식, 바이트 배열, json 문자열, xml 문자열.
역직렬화 역직렬화: 직렬화된 데이터를 객체로 복원하는 프로세스
학생들이 직렬화와 관련된 개념을 잘 모르는 경우 Meituan 기술팀의 직렬화 및 역직렬화를 참조할 수 있습니다.
먼저 새로운 Maven 프로젝트를 생성합니다
그런 다음 종속성을 가져옵니다
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <!-- 代码简化 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <!--kryo--> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo-shaded</artifactId> <version>4.0.2</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> <!--protostuff--> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.7.2</version> </dependency> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.7.2</version> </dependency> <!--hessian2--> <dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.62</version> </dependency>
Tool 클래스:
kryo
package cuit.pymjl.utils; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import org.apache.commons.codec.binary.Base64; import org.objenesis.strategy.StdInstantiatorStrategy; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; /** * @author Pymjl * @version 1.0 * @date 2022/4/18 20:07 **/ @SuppressWarnings("all") public class KryoUtils { private static final String DEFAULT_ENCODING = "UTF-8"; //每个线程的 Kryo 实例 private static final ThreadLocal<Kryo> KRYO_LOCAL = new ThreadLocal<Kryo>() { @Override protected Kryo initialValue() { Kryo kryo = new Kryo(); /** * 不要轻易改变这里的配置!更改之后,序列化的格式就会发生变化, * 上线的同时就必须清除 Redis 里的所有缓存, * 否则那些缓存再回来反序列化的时候,就会报错 */ //支持对象循环引用(否则会栈溢出) kryo.setReferences(true); //默认值就是 true,添加此行的目的是为了提醒维护者,不要改变这个配置 //不强制要求注册类(注册行为无法保证多个 JVM 内同一个类的注册编号相同;而且业务系统中大量的 Class 也难以一一注册) kryo.setRegistrationRequired(false); //默认值就是 false,添加此行的目的是为了提醒维护者,不要改变这个配置 //Fix the NPE bug when deserializing Collections. ((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()) .setFallbackInstantiatorStrategy(new StdInstantiatorStrategy()); return kryo; } }; /** * 获得当前线程的 Kryo 实例 * * @return 当前线程的 Kryo 实例 */ public static Kryo getInstance() { return KRYO_LOCAL.get(); } //----------------------------------------------- // 序列化/反序列化对象,及类型信息 // 序列化的结果里,包含类型的信息 // 反序列化时不再需要提供类型 //----------------------------------------------- /** * 将对象【及类型】序列化为字节数组 * * @param obj 任意对象 * @param <T> 对象的类型 * @return 序列化后的字节数组 */ public static <T> byte[] writeToByteArray(T obj) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Output output = new Output(byteArrayOutputStream); Kryo kryo = getInstance(); kryo.writeClassAndObject(output, obj); output.flush(); return byteArrayOutputStream.toByteArray(); } /** * 将对象【及类型】序列化为 String * 利用了 Base64 编码 * * @param obj 任意对象 * @param <T> 对象的类型 * @return 序列化后的字符串 */ public static <T> String writeToString(T obj) { try { return new String(Base64.encodeBase64(writeToByteArray(obj)), DEFAULT_ENCODING); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } /** * 将字节数组反序列化为原对象 * * @param byteArray writeToByteArray 方法序列化后的字节数组 * @param <T> 原对象的类型 * @return 原对象 */ @SuppressWarnings("unchecked") public static <T> T readFromByteArray(byte[] byteArray) { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray); Input input = new Input(byteArrayInputStream); Kryo kryo = getInstance(); return (T) kryo.readClassAndObject(input); } /** * 将 String 反序列化为原对象 * 利用了 Base64 编码 * * @param str writeToString 方法序列化后的字符串 * @param <T> 原对象的类型 * @return 原对象 */ public static <T> T readFromString(String str) { try { return readFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING))); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } //----------------------------------------------- // 只序列化/反序列化对象 // 序列化的结果里,不包含类型的信息 //----------------------------------------------- /** * 将对象序列化为字节数组 * * @param obj 任意对象 * @param <T> 对象的类型 * @return 序列化后的字节数组 */ public static <T> byte[] writeObjectToByteArray(T obj) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Output output = new Output(byteArrayOutputStream); Kryo kryo = getInstance(); kryo.writeObject(output, obj); output.flush(); return byteArrayOutputStream.toByteArray(); } /** * 将对象序列化为 String * 利用了 Base64 编码 * * @param obj 任意对象 * @param <T> 对象的类型 * @return 序列化后的字符串 */ public static <T> String writeObjectToString(T obj) { try { return new String(Base64.encodeBase64(writeObjectToByteArray(obj)), DEFAULT_ENCODING); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } /** * 将字节数组反序列化为原对象 * * @param byteArray writeToByteArray 方法序列化后的字节数组 * @param clazz 原对象的 Class * @param <T> 原对象的类型 * @return 原对象 */ @SuppressWarnings("unchecked") public static <T> T readObjectFromByteArray(byte[] byteArray, Class<T> clazz) { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray); Input input = new Input(byteArrayInputStream); Kryo kryo = getInstance(); return kryo.readObject(input, clazz); } /** * 将 String 反序列化为原对象 * 利用了 Base64 编码 * * @param str writeToString 方法序列化后的字符串 * @param clazz 原对象的 Class * @param <T> 原对象的类型 * @return 原对象 */ public static <T> T readObjectFromString(String str, Class<T> clazz) { try { return readObjectFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)), clazz); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } }
He ssian
package cuit.pymjl.utils; import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; /** * @author Pymjl * @version 1.0 * @date 2022/7/2 12:39 **/ public class HessianUtils { /** * 序列化 * * @param obj obj * @return {@code byte[]} */ public static byte[] serialize(Object obj) { Hessian2Output ho = null; ByteArrayOutputStream baos = null; try { baos = new ByteArrayOutputStream(); ho = new Hessian2Output(baos); ho.writeObject(obj); ho.flush(); return baos.toByteArray(); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException("serialize failed"); } finally { if (null != ho) { try { ho.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != baos) { try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 反序列化 * * @param bytes 字节 * @param clazz clazz * @return {@code T} */ public static <T> T deserialize(byte[] bytes, Class<T> clazz) { Hessian2Input hi = null; ByteArrayInputStream bais = null; try { bais = new ByteArrayInputStream(bytes); hi = new Hessian2Input(bais); Object o = hi.readObject(); return clazz.cast(o); } catch (Exception ex) { throw new RuntimeException("deserialize failed"); } finally { if (null != hi) { try { hi.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != bais) { try { bais.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
Protostuff
package cuit.pymjl.utils; import io.protostuff.LinkedBuffer; import io.protostuff.ProtostuffIOUtil; import io.protostuff.Schema; import io.protostuff.runtime.RuntimeSchema; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @author Pymjl * @version 1.0 * @date 2022/6/28 21:00 **/ public class ProtostuffUtils { /** * 避免每次序列化都重新申请Buffer空间 * 这个字段表示,申请一个内存空间用户缓存,LinkedBuffer.DEFAULT_BUFFER_SIZE表示申请了默认大小的空间512个字节, * 我们也可以使用MIN_BUFFER_SIZE,表示256个字节。 */ private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); /** * 缓存Schema * 这个字段表示缓存的Schema。那这个Schema是什么呢?就是一个组织结构,就好比是数据库中的表、视图等等这样的组织机构, * 在这里表示的就是序列化对象的结构。 */ private static final Map<Class<?>, Schema<?>> SCHEMA_CACHE = new ConcurrentHashMap<>(); /** * 序列化方法,把指定对象序列化成字节数组 * * @param obj 对象 * @return byte[] */ @SuppressWarnings("unchecked") public static <T> byte[] serialize(T obj) { Class<T> clazz = (Class<T>) obj.getClass(); Schema<T> schema = getSchema(clazz); byte[] data; try { data = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER); } finally { BUFFER.clear(); } return data; } /** * 反序列化方法,将字节数组反序列化成指定Class类型 * * @param data 字节数组 * @param clazz 字节码 * @return */ public static <T> T deserialize(byte[] data, Class<T> clazz) { Schema<T> schema = getSchema(clazz); T obj = schema.newMessage(); ProtostuffIOUtil.mergeFrom(data, obj, schema); return obj; } @SuppressWarnings("unchecked") private static <T> Schema<T> getSchema(Class<T> clazz) { Schema<T> schema = (Schema<T>) SCHEMA_CACHE.get(clazz); if (schema == null) { schema = RuntimeSchema.getSchema(clazz); if (schema == null) { SCHEMA_CACHE.put(clazz, schema); } } return schema; } }
테스트용 엔터티 클래스 생성:
package cuit.pymjl.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serial; import java.io.Serializable; /** * @author Pymjl * @version 1.0 * @date 2022/7/2 12:32 **/ @Data @AllArgsConstructor @NoArgsConstructor public class Student implements Serializable { @Serial private static final long serialVersionUID = -91809837793898L; private String name; private String password; private int age; private String address; private String phone; }
테스트 클래스 작성:
public class MainTest { @Test void testLength() { Student student = new Student("pymjl", "123456", 18, "北京", "123456789"); int kryoLength = KryoUtils.writeObjectToByteArray(student).length; int hessianLength = HessianUtils.serialize(student).length; int protostuffLength = ProtostuffUtils.serialize(student).length; System.out.println("kryoLength: " + kryoLength); System.out.println("hessianLength: " + hessianLength); System.out.println("protostuffLength: " + protostuffLength); } }
스크린샷 실행:
그림에서 볼 수 있듯이 , Hessian 직렬화 후 바이트가 차지하는 공간은 다른 두 가지 방법보다 상당히 큽니다
Hessian은 고정 길이를 사용하여 int와 long을 저장하는 반면 kryo는 이를 보장하기 위해 가변 길이 int와 long을 사용합니다. 기본 데이터 유형 직렬화 후에는 가능한 한 작아야 합니다. 실제 애플리케이션에서는 매우 큰 데이터가 자주 나타나지 않습니다.
Kryo가 직렬화될 때 전체 클래스 이름을 전달하거나 Register()를 사용하여 클래스를 Kryo에 미리 등록해야 합니다. 해당 클래스는 int 유형 ID와 연결되어 있습니다. , 그래서 시퀀스의 크기는 더 작은 반면, Hessian은 모든 클래스 필드 정보를 직렬화된 바이트 배열에 넣고 역직렬화를 위해 바이트 배열을 직접 사용합니다. 왜냐하면 더 많은 것이 있으면 처리 속도가 느려지기 때문입니다.
Kryo는 직렬화 가능 인터페이스를 구현할 필요가 없으며 Hessian은 이를 구현해야 합니다.
Kryo 데이터 클래스 필드는 증가 및 감소하고 직렬화 및 역직렬화는 호환되지 않지만 Hessian은 호환 가능하며 Protostuff만 추가할 수 있습니다. 마지막에 새 필드를 추가하는 것은
Kryo 및 Hessian과 관련된 데이터 클래스에는 인수가 없는 생성자가 있어야 합니다.
Hessian은 직렬화를 위해 복잡한 개체의 모든 속성을 맵에 저장합니다. 따라서 상위 클래스와 하위 클래스에 동일한 이름을 가진 멤버 변수가 있는 경우 헤시안 직렬화 중에 하위 클래스가 먼저 직렬화되고 그 다음 상위 클래스가 직렬화되므로 역직렬화 결과 동일한 이름의 멤버 변수가 발생하게 됩니다.
Kryo는 스레드로부터 안전하지 않습니다. 스레드로부터 안전하려면 ThreadLocal을 사용하거나 Kryo 스레드 풀을 생성해야 하지만 Protostuff는 스레드로부터 안전합니다.
기본적으로 교차 언어 지원 | 느림 | |
---|---|---|
빠름, protobuf | ne 기반 eds 정적 컴파일 | |
정적 컴파일은 필요하지 않지만 직렬화는 미리 스키마를 전달해야 합니다. | 기본 생성자가 없는 클래스는 역직렬화 시 사용자가 직접 직렬화된 개체를 초기화해야 합니다. 객체 할당을 담당합니다. | |
사용하기 쉽고 모든 클래스를 직렬화할 수 있습니다 | 는 느리고 공간을 차지합니다 |
위 내용은 Java에서 일반적으로 사용되는 직렬화 방법은 무엇입니까? Kryo, Protostuff 및 Hessian을 예로 들어 구현 원리를 설명하십시오.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!