Java IO流 序列化和反序列化

简介

IO即Input和Output,数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。我们常用的file类其实也涉及读写操作,这里只说InputStream 和 OutputStream。网上关于IO怎么用都有教程,这边给自己的经历做个记录防止之后忘了。之后有更多想法或者要用到的时候再来更新。点击左侧目录,跳转到序列化章节。

InputStream和OutputStream

InputStream

java.io.InputStream 类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。InputStream并不是一个接口,而是一个抽象类,它是所有输入流的超类。下面两个表格来自Java8.

Modifier and TypeMethod and Description
int

available() 返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。

void

close() 关闭此输入流并释放与流相关联的任何系统资源
void

mark(int readlimit) 标记此输入流中的当前位置

boolean

marksupported() 测试这个输入流是否支持mark和reset方法。

abstract int

read() 从输入流读取数据的下一个字节

int

read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区b

int

read(byte[] b,int off,int len)  从输入流读取最多len字节的数据到一个字节数组。

voidreset()  将此流重新定位到上次在此输入流上调用mark方法时的位置
long

skip(long n) 跳过并丢弃来自此输入流的n字节数据

public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。若返回值=-1说明没有读取到任何字节读取工作结束。 流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时。

inputstream.available():如果用于网络操作,网络通讯往往是间断性的,一串字节分批发送,可能出现文件不全,导致系统出现长时间暂停状态

outputStream

flush 清空输出流

  1. flush()下达一条命令给缓冲区,让它将所储存的数据全部清空,即发送给下一级。
  2. flush()刷空输出流,并输出所有被缓存的字节。由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。
  3. OutputStream.flush()方法将所有写入到OutputStream的数据冲刷到相应的目标媒介中。比如,如果输出流是FileOutputStream,那么写入到其中的数据可能并没有真正写入到磁盘中。即使所有数据都写入到了FileOutputStream,这些数据还是有可能保留在内存的缓冲区中。通过调用flush()方法,可以把缓冲区内的数据刷新到磁盘(或者网络,以及其他任何形式的目标媒介)中。

两个抽象类都实现了Closeable,所以都有close方法用来关闭流和释放资源,Java IO 相关的操作可能会引发各种异常,如 IOException、FileNotFoundException 等,因此,我们需要用try ... finally来保证IOStream在无论是否发生IO错误的时候都能够正确地关闭。

常用IO流

SequenceInputStream

可以将两个或多个其他input stream 合并为一个,按照顺序读取。

//两个流合流用构造方法
sequenceInputSream(inputstream s1,inputStream s2)

//多个流合流,将实例放入vector

Vector<inputstream> stream = new vector<>();

Stream.add(input1); //input1是input stream流 类似三个就能添加进去

SequenceInputstream sequenceinputstream = new sequenceinputstream(stream.elements());

AudioInputStream

用来简单的处理音频文件。下面举例获取时长和播放的方法,我做服务端的,一般不常用这个

获取MP3文件的时长

// 载入音频流

AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File("example.mp3"));

// 获取音频流的格式

AudioFileFormat fileFormat = AudioSystem.getAudioFileFormat(new File("example.mp3"));

// 获取音频流的长度

double durationInSeconds = (audioInputStream.getFrameLength() + 0.0) / audioInputStream.getFormat().getFrameRate();

// 打印结果

System.out.println("时长:" + durationInSeconds);


播放MP3音频文件

在Java编程中播放MP3音频文件,可以使用Java库中的Clip类完成。Clip是一个专门用于播放音频剪辑的类,主要包括load(),start(),stop()和close()等方法

// 载入音频流

AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File("example.mp3"));

// 创建Clip对象

Clip clip = AudioSystem.getClip();

// 开始播放

clip.open(audioInputStream);

clip.start();

Thread.sleep(10000); // 播放10秒

clip.stop(); // 停止播放

FileInputStream/FileOutputStream 

处理文件。等我哪天再补这块,太多了写起来费劲,先随便放几个占坑

public FileOutputStream(String name,boolean append) throws FileNotFoundException

创建一个向具有指定 name 的文件中写入数据的输出文件流。如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。

BufferedInputStream/BufferedOutputStream

缓冲流。读取大文件经常用到。

如果文件太大,一次性读取这个大文件的所有数据,内存可能装不下,但是如果每次只读一个字节数据,会耗时太慢。

public static void main(String[] args) throws IOException
{
    BufferedInputStream bufferInput = null;
    BufferedOutputStream bufferOutput = null;
    try
    {
        // 输入缓冲流
        InputStream input = new FileInputStream(new File("c:\\100MB.txt"));
        bufferInput = new BufferedInputStream(input);
        // 输出缓冲流
        OutputStream output = new FileOutputStream(new File("c:\\100MB_test.txt"));
        bufferOutput = new BufferedOutputStream(output);
        // 定义个8kb字节数组,作为缓冲区流
        byte[] bytes = new byte[1024 * 8];
        int len = 0;
        while((len = bufferInput.read(bytes)) != -1)
        {
            bufferOutput.write(bytes, 0, len);
        }
    }
    catch(IOException e)
    {
    }
    finally
    {
        try
        {
            if(bufferInput != null)
            {
                bufferInput.close();
            }
            if(bufferOutput != null)
            {
                bufferOutput.close();
            }
        }
        catch(IOException e)
        {
        }
    }
} 

ObjectInputStream/ObjectOutputStream

对象流,操作对象,对象序列化,可实现持久存储

ByteArrayInputStream/ByteArrayOutputStream

ByteArrayOutputStream是字节数组输出流,写入ByteArrayOutputStream的数据被写入到一个byte数组,缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()和toString()获取数据

public static void main(String[] args) throws IOException {
        byte[] data = { 72, 101, 108, 108, 111, 33 };
        try (InputStream input = new ByteArrayInputStream(data)) {
            int n;
            while ((n = input.read()) != -1) {
                System.out.println((char)n);
            }
        }
    }

PipedInputStream/PipedOutputStream

管道流,暂时没用过

题外话分割线

为什么我说话老是没有重点,写了五千字还没有说一点跟标题相关的,怪不得同事都不喜欢和我说话,嘤嘤嘤 一定要改啊!!!!!铁咩

序列化

简介

序列化的核心:把java对象转为字节序列。

把该字节序列保存起来(例如:保存在一个文件里),以后可以随时将该字节序列恢复为原来的对象。甚至可以将该字节序列放到其他计算机上或者通过网络传输到其他计算机上恢复,只要该计 算机平台存在相应的类就可以正常恢复为原来的对象。

对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态非静态字段的值。

序列化的基本方法

Serializable

序列化和反序列都是以Object对象进行操作,所以在需要序列化的对象必须实现序列化接口(Serializable),才能进行序列化,否则将出现异常。这个接口,没有任何方法,只是一个【标准】没有实现Serializable接口的话,在序列化时就会抛出NotSerializableException异常

serialVersionUID

在对象中必须显示声明序列化id,如果我们自己不定义serialVersionUID,编译器会自动声明一个在序列化过程中,serialVersionUID是且是唯一的标识符,如果serialVersionUID不匹配则无法进行反序列化。静态static修饰的值不会写入,序列化文件里面不包含序列化id。

如果没有序列化id,再同一个jvm中,编译器也可能反序列化成功(如果这个对象你没有修改任何属性或者字段是可以反序列化的,但是当你修改了任意一个地方,隐式声明的序列化id在编译的时候会变,版本不兼容一样会报错导致不能反序列化。)不同的jvm绝对不认识这个文件,这也是为什么会出现在本地联调发现代码没问题,一上到服务器就会报java.io.InvalidClassException

用idea也简单,serialVersionUID按下ALT+ENTER 他是会自动生成一串数字的

Transient

不希望一个类所有的元素都被编译器序列化,可以用transient来修饰我们不希望被jvm自动序列化的元素。

如果你用gson还可以用@Expose去灵活定义字段是否需要序列化、反序列化。用@SerializedName注解来解决序列化过程中字段名字对应的问题

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TDecisionSetting implements Serializable {

    private static final long serialVersionUID = -4848267162000579818L;

    private Long docid;

    @SerializedName(value = "doc_name")
    //@Expose()参与序列化和反序列化
    //@Expose(serialize = false)不参与序列化,只参与反序列化
    // @Expose(deserialize = false)只参与序列化,不参与反序列化
    @Expose(serialize = false,deserialize = false)//序列化和反序列化都不参与
    private String name;

    @Transient
    private String operator;

    private Float score;

    private Long planid;
}

序列化文件代码

 /**
     * 一键导出,生成序列化文件,后缀名.ser
     * .ser Java中对象状态序列化文件
     *
     * @param response
     * @throws IOException
     */ 
public <T> void download(Collection<T> collection, String fileName, HttpServletResponse response) throws IOException {
        fileName = URLEncoder.encode(fileName + ".ser", StandardCharsets.UTF_8.name());
        response.setCharacterEncoding(Charsets.UTF_8.name());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName + ";" + "filename*=utf-8''" + fileName);
        try {
            OutputStream outputStream = response.getOutputStream();
            //将ArrayList转化为byte[]
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(collection);//写入objectOutput流
            objectOutputStream.flush();//刷新缓冲区到指定文件
            byte[] data = byteArrayOutputStream.toByteArray();
            //输出
            objectOutputStream.close();
            byteArrayOutputStream.close();
            outputStream.write(data);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
        //打印堆栈
        }finally {
            if (null != outputStream) {
                outputStream.close();
            }
        }
    }

因为我是服务端,所以是直接把文件通过http给到前端下载,这里如果做客户端可以改成用FileOutputStream生成本地文件就好。 

 /**
     * 导入单个序列化文件
     * 要求实体类可序列化
     *
     * @param file 格式正确的MultipartFile文件
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public <T> List<T> upload(MultipartFile file) throws IOException, ClassNotFoundException {
        InputStream inputStream = file.getInputStream();
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            List<T> infoList = (List<T>) objectInputStream.readObject();
            objectInputStream.close();
            return infoList;
        } catch (ClassNotFoundException e) {
           
        } catch (IOException e) {
        }finally {
            if (null != inputStream ) {
                inputStream .close();
            }
        }
        return null;
    }

记得最后一定要finally释放所占用的资源,别把内存挤爆了

精简版大概是这样:(从网上抄的)

 //序列化 Object->byte[] 对象转到字节
    public static byte [] serialize(Object obj){
        ObjectOutputStream obi=null;
        ByteArrayOutputStream bai=null;
        try {
            bai=new ByteArrayOutputStream();
            obi=new ObjectOutputStream(bai);
            obi.writeObject(obj);
            byte[] byt=bai.toByteArray();
            return byt;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    //反序列化 byte[] ->object 字节数组转对象
    public static Object unserizlize(byte[] byt){
        ObjectInputStream oii=null;
        ByteArrayInputStream bis=null;
        bis=new ByteArrayInputStream(byt);
        try {
            oii=new ObjectInputStream(bis);
            Object obj=oii.readObject();
            return obj;
        } catch (Exception e) {  
            e.printStackTrace();
        } 
        return null;
    }

异常报错

java.io.StreamCorruptedException: invalid stream header异常

原因是数据发送端发送对象到接收端

接收端对于同一个输入流创建了不同的对象输入流,而后用不同的对象输入流进行接收

一般是所有数据都在一个输入输出流,取数据也是取这一路流。

使用 toByteArray()代替toString() ,使用 ByteArrayInputStream(byte [])构造函数。比如:

错误方式:

public void testDeserializeTest() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        BigInteger bi = new BigInteger("0");
        oos.writeObject(bi);
        String str = baos.toString();
        System.out.println(str);
        ObjectInputStream ois = new ObjectInputStream(
                new BufferedInputStream(new ByteArrayInputStream(str.getBytes())));
        Object obj = ois.readObject();
        assertEquals(obj.getClass().getName(), "java.math.BigInteger");
        assertEquals(((BigInteger) obj).intValue(), 0);
    }
正确方式:

 public void testDeserialize() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        BigInteger bi = new BigInteger("0");
        oos.writeObject(bi);
        byte[] str = baos.toByteArray();
        ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new ByteArrayInputStream(str)));
        Object obj = ois.readObject();
        assertNotNull(obj);
        assertEquals(obj.getClass().getName(), "java.math.BigInteger");
        assertEquals(((BigInteger) obj).intValue(), 0);
    }

做序列化的时候其实有遇到很多问题,现在回顾都不记得了。关于流,还有使用zip流压缩,还有处理图片等,之后慢慢再把这些东西也写进来

  • 26
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值