>  기사  >  Java  >  Java에서 일반적으로 사용되는 직렬화 방법은 무엇입니까? Kryo, Protostuff 및 Hessian을 예로 들어 구현 원리를 설명하십시오.

Java에서 일반적으로 사용되는 직렬화 방법은 무엇입니까? Kryo, Protostuff 및 Hessian을 예로 들어 구현 원리를 설명하십시오.

王林
王林앞으로
2023-05-07 21:01:07847검색

    머리말

    얼마 전 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);
        }
    }

    스크린샷 실행:

    Java에서 일반적으로 사용되는 직렬화 방법은 무엇입니까? Kryo, Protostuff 및 Hessian을 예로 들어 구현 원리를 설명하십시오.

    그림에서 볼 수 있듯이 , 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는 스레드로부터 안전합니다.

    • Protostuff와 Kryo 직렬화의 형식은 비슷합니다. 둘 다 Mark를 사용하여 필드 유형을 기록하므로 직렬화된 볼륨이 상대적으로 작습니다.
    • 크리요

      빠른 속도, 작은 직렬 볼륨
    교차 언어 지원 더 복잡함

    HessianProtostuffProtostuff-RuntimeJava
    기본적으로 교차 언어 지원 느림
    빠름, protobuf ne 기반 eds 정적 컴파일
    정적 컴파일은 필요하지 않지만 직렬화는 미리 스키마를 전달해야 합니다. 기본 생성자가 없는 클래스는 역직렬화 시 사용자가 직접 직렬화된 개체를 초기화해야 합니다. 객체 할당을 담당합니다.
    사용하기 쉽고 모든 클래스를 직렬화할 수 있습니다 는 느리고 공간을 차지합니다

    위 내용은 Java에서 일반적으로 사용되는 직렬화 방법은 무엇입니까? Kryo, Protostuff 및 Hessian을 예로 들어 구현 원리를 설명하십시오.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제