17 IO流(内容有字节流、字符流、转换流、缓冲流、打印流、序列化)

1、什么是IO流

IO即InputStream和OutputStream,意思是输入流和输出流。

2、怎么区分输入流和输出流

刚开始学IO流的时候这个问题把我弄懵了一下

程序操作的数据都应该是在内存里面,内存是你操作的主对象,把数据从其他资源里面传送到内存里面,就是输入,反之,把数据从内存传送到其他资源,就是输出。

读文件
不管你从磁盘读,从网络读,或者从键盘读,读到内存,就是InputStream。数据读入到内存叫输入流。

写文件
不管你写倒磁盘,写到网络,或者写到屏幕,都是OuputStream。从内存写到别的地方去叫输出流。

3、输入流的具体用法

从文件读取内容到内存

例子:读文件的一个字节

read()方法是读取一个字节,返回值就是读到的数据值,如果读取的数据值等于-1,则读取到文件末尾。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainTest {
	public static void main(String[] args) {
		
//		创建File对象
//		File对象描述一个文件
		File file=new File("D:\\filetest\\file1.txt"); 
		 //!!首先得建立一个通道
		
		try
		{
//			新建一个FileInputStream对象
			FileInputStream fileInputStream=new FileInputStream(file);
//    !!建立通道后你得让流知道进哪个通道,因为你有可能new了几个File();

 //           读一个字节
			int c1 = fileInputStream.read();
			System.out.println(c1);//读出的是数字,是对应的ASCII码
			int c2=fileInputStream.read();
			System.out.println((char)c2);//读下一个字节,读出的是乱码
			int c3=fileInputStream.read();
			System.out.println((char)c3);
			int c4=fileInputStream.read();
			System.out.println((char)c4);
			
		}catch(FileNotFoundException e)
		{
			//输出堆栈信息
			e.printStackTrace();
		}catch(IOException e)
		{
			e.printStackTrace();
		}finally {
			
			try {
				//关闭输出流,释放资源
				fileInputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}			
		}
	}
}

例子:输出文件里的所有内容

创建一个数组buf
然后用read(buf):实现从文件读取内容到内存,不会乱码,是什么就是什么

public class HuanChongLiu {
	public static void main(String[] args) {

		// 创建File对象
		// File对象描述一个文件
		File file = new File("F:\\Day0730test.txt");
/*		try {
			file.createNewFile();  这一段代码是为了防止没有描述的文件,
			防止程序找不到路径,有了文件的话可不要这段代码
		} catch (IOException e1) {
			e1.printStackTrace();
		}
*/
		FileInputStream fileInputStream=null;
		try
		{
			//新建一个FileInputStream对象
			fileInputStream=new FileInputStream(file);
			
			//新建一个字节数组,长度为32
			byte []buf=new byte[32];
			
//read(buf):此方法的返回值就是当前读取的字节个数,将数据读取到buf数组
//将readLen变量也就是read方法的返回值,当此变量等于-1,则说明读到文件末尾
			int readLen=-1;
			while((readLen=fileInputStream.read(buf))!=-1)//
			{
				//将字节数组转换成字符串
				//String(buf,0,readLen)表示字节数组buf从第1位到
				//第readLen位都转换成字符
				String content=new String(buf,0,readLen);
				System.out.print(content);//将转换承德字符输出
			}
		}catch(FileNotFoundException e)
		{
			e.printStackTrace();
		}catch(IOException e)
		{
			e.printStackTrace();
		}finally {
			try {
				//文件输入流关闭(释放资源)
				fileInputStream.close();
			} catch (IOException e) {
				// TODO Auto-generated catchblock
				e.printStackTrace();
			}	
		}
	}
}

4、输出流的具体用法

输出流
将内存的数据写到文件

例子:将多个字符写到文件中

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainTest2 {

	public static void main(String[] args) {

		// 通过File对象描述文件
		File file = new File("D:\\filetest\\file3.txt");
		// 创建FileOutputStream对象
		FileOutputStream fileOutputStream = null;
		try {
			fileOutputStream = new FileOutputStream(file);
			
			String content="weclome to gec,I love java";
			//将字符串转换成字节数组
			byte []buf=content.getBytes();
			fileOutputStream.write(buf);
			//清除缓冲
			fileOutputStream.flush();
/*flush()一般主要用在IO中,即清空缓冲区数据,就是说你用读写流的时候,
其实数据是先被读到了内存中,然后用这里的数据写到文件中,当你文件的数据
读完的时候(即读到内存)不代表你的数据已经写完了(即写到另一个文件),
因为还有一部分有可能会留在内存这个缓冲区中。这时候如果你调用了
close()方法关闭了读写流,那么这部分数据就会丢失,所以应该在关闭读写流之
前先flush(),先刷新缓存清空数据。*/    
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {

			try {
				// 关闭输出流,释放资源
				fileOutputStream.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

对于将数据写道文件,在new一个File()的时候,如果没有原文件或者路径不对,输出流会自动创建一个文件。

5、练习题

编写一个应用程序,将用户从键盘输入的10个整数存入文件,从文件读取再顺序读出。

import java.io.*;
import java.util.Scanner;

public class Test {

    //System.in:默认是键盘设备
    public static void main(String[] args) {

        File file = new File("F:\\Day0730test.txt");
		int i = 0;
		System.out.print("请输入十个整数:");
		@SuppressWarnings("resource")
		Scanner scan = new Scanner(System.in);
		FileOutputStream fileOutputStream = null;
		//往文件写入10个整数
		try {
			fileOutputStream = new FileOutputStream(file);
			 while (i < 10) {
		            int str = scan.nextInt();//读取键盘输入
		            fileOutputStream.write(str);//将该行写到文件中
		            i++;
		        }
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			fileOutputStream.close();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		//读取存入的10个整数并输出
		FileInputStream fileInputStream = null;
		try {
			fileInputStream = new FileInputStream(file);
			for(int q = 0;q<10;q++){	
			//只读一个数据的情况:int c1 = fileInputStream.read()
			System.out.println(fileInputStream.read());
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			fileInputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
}

6、IO流又可以分为字节流和字符流

上面讲的FileInputStream和FileOutStream都是字节流,调用read方法读的都是一个字节,会产生乱码。
而字符流就不会产生乱码,因为字符流调用read方法读的是一个字符。
下面来看看字符流的使用吧

字符流的读:FileReader

主要方法
在这里插入图片描述
-------------------------------------例子---------------------------------

public class MainTest {

	public static void main(String[] args) {
		
		//以File对象描述file5.txt
		File file=new File("D:\\filetest\\file5.txt");
		//构造FileReader对象
		FileReader reader=null;
		try
		{
			reader=new FileReader(file);
			//定义字符数组
			char []cbuf=new char[16];
			//定义读取字符的长度
			int readLen=-1;
		//不断将数据读到字符数组,read方法的返回值就是读到的字符个数,
			//直到readLen等于-1,则意味读取文件的末尾,然后跳出循环
			while((readLen=reader.read(cbuf))!=-1)
			{
				System.out.print(new String(cbuf,0,readLen));
			}
			
		}catch(FileNotFoundException e)
		{
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			
			if(reader!=null)
			{
				try {
					reader.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

字符流的写:FileWriter

主要方法
在这里插入图片描述
----------------------例子----------------------------

public class MainTest {

	public static void main(String[] args) throws IOException {
		
		//创建文件
		File file=new File("file2.txt");
		FileWriter fw=new FileWriter(file);
		
		String message="我来自广州";
		fw.write(message);
		
		/*String message="java,hello world";
		fw.write(message);*/
		
		/*fw.write(0+48);
		fw.write(1+48);
		char cbuf[]= {'a','b'};
		fw.write(cbuf);*/
		fw.flush();
		fw.close();
	}
}

7、转换流

InputStreamReader将字节输入流转换成字符输入流,
OutputStreamWriter将字节输出流转换成字符输出流

InputStreamReader

在这里插入图片描述

public class MainTest {

	public static void main(String[] args) throws IOException {

		File file = new File("file3.txt");
		FileOutputStream fileOutputStream = new FileOutputStream(file);
		
		//将输出字节流---->输出字符流
		//将数据以字符方式写到文件里面
		OutputStreamWriter writer=new OutputStreamWriter(fileOutputStream);
		writer.write("欢迎来到粤嵌,学习java");
		writer.flush();
		writer.close();
		fileOutputStream.close();
	}
}

OutputStreamWriter

在这里插入图片描述

public class MainTest {

	public static void main(String[] args) throws IOException {

		File file = new File("file3.txt");
		FileOutputStream fileOutputStream = new FileOutputStream(file);
		
		//将输出字节流---->输出字符流
		//将数据以字符方式写到文件里面
		OutputStreamWriter writer=new OutputStreamWriter(fileOutputStream);
		writer.write("欢迎来到粤嵌,学习java");
		writer.flush();
		writer.close();
		fileOutputStream.close();
	}
}

8、缓冲流

缓冲流最大的特点是有一个缓冲区,默认分配一个8k大小的缓冲区,来储存数据,针对大量的读取及写入数据,能提高操作效率。
在这里插入图片描述

BufferReader类用法

public class MainTest {

	public static void main(String[] args) throws IOException {
		
		FileReader reader=new FileReader(new File("file4.txt"));
		BufferedReader br=new BufferedReader(reader);
		
		System.out.println((char)br.read());//a
		System.out.println((char)br.read());//b
		//设置一个mark:标记一个位置
		System.out.println("mark 操作");
		br.mark(2);//reset的时候会从第3个元素开始读取,只有缓冲流能这么做,因为他有缓冲区
		System.out.println((char)br.read());//c
		System.out.println((char)br.read());//d
		System.out.println("reset 操作");
		br.reset();
		System.out.println((char)br.read());//c
		System.out.println((char)br.read());//d	
		
		//按行读取
/*		String line1=br.readLine();
		String line2=br.readLine();
		String line3=br.readLine();
		String line4=br.readLine();
		
		System.out.println(line1);
		System.out.println(line2);
		System.out.println(line3);
		System.out.println(line4);*/
	}
}

9、一些例子(他人代码)

使用IO流拷贝文件

import java.io.*;

public class Copy {
	public static void main(String[] args) {
		//调用方法执行文件复制
		boolean isSuccess = copyFile("要复制的文件(全路径或相对路径+文件名)","新的文件名");
		if(isSuccess) {
			System.out.println("文件复制成功!");
		}else {
			System.out.println("文件复制失败!");
		}
	}
	
	private static boolean copyFile(String oldFile, String newFile) {
		//定义输入输出流
		InputStream is = null;
		OutputStream os = null;
		StringBuilder builder = new StringBuilder();
		try {
			//读取
			is = new FileInputStream(oldFile);
			//输出
			os = new FileOutputStream(newFile);
			//定义字节缓冲数组
			byte[] flush = new byte[1024];
            //定义读取长度
            int len;
            while ((len = is.read(flush)) != -1) {
                os.write(flush, 0, len);
            }
            os.flush();
		}  catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //先开启的流 后关闭
            try {
                if (null != os) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        if (new File(newFile).exists()) {
            return true;
        }
        return false;
	}
}

目录的拷贝

import java.io.*;
import java.time.Duration;
import java.time.Instant;

public class CopyDir {
    public static void main(String[] args) {
        Instant start = Instant.now();
        boolean isOk = dirOrFile("要复制的目录路径", "目标路径");
        if(isOk){
            System.out.println("目录复制完成!");
        }else {
            System.out.println("目录复制失败!");
        }
        Instant end = Instant.now();
        System.out.println("耗时:"+Duration.between(start,end).toMillis()+"毫秒!");
    }

	private static boolean copyDir(String oldDir, String newDir) {
        //定义流
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new FileInputStream(oldDir);
            os = new FileOutputStream(newDir);
            //定义缓冲字节数组
            byte[] flush = new byte[1024];
            //定义读取长度
            int len;
            while ((len = is.read(flush)) != -1) {
                os.write(flush, 0, len);
            }
            //刷新流
            os.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭流
            try {
                if (null != os) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (null != is) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

	private static boolean dirOrFile(String oldDir, String newDir) {
        //定义写入写出文件
        File old = new File(oldDir);
        File copy = new File(newDir);
        //判断如果是目录的话 创建目录
        if (old.isDirectory()) {
            copy.mkdir();
            File[] files = old.listFiles();
            for (int i = 0; i < files.length; i++) {
                dirOrFile(files[i].getAbsolutePath(), newDir + "/" + files[i].getName());
            }
        }
        if (old.isFile()) {
            copyDir(oldDir, newDir);
        }
        if (old.length()==copy.length()){
            return true;
        }
        return false;
    }
}

获取文本上字符出现的字数并将数据输出到文件

/**
 * 获取文本上字符出现的次数,把数据写入文件
 *
 * 思路:
 * 1.遍历文本每一个字符
 * 2.字符出现的次数存在Map中
 *
 * Map<Character,Integer> map = new HashMap<Character,Integer>();
 * map.put('a',18);
 * map.put('你',2);
 *
 * 3.把map中的数据写入文件
 *
 */
public class WordCount {
  
    @Test
    public void testWordCount() {
        FileReader fr = null;
        BufferedWriter bw = null;
        try {
            //1.创建Map集合
            Map<Character, Integer> map = new HashMap<Character, Integer>();
 
            //2.遍历每一个字符,每一个字符出现的次数放到map中
            fr = new FileReader("dbcp.txt");
            int c = 0;
            while ((c = fr.read()) != -1) {
                //int 还原 char
                char ch = (char) c;
                // 判断char是否在map中第一次出现
                if (map.get(ch) == null) {
                    map.put(ch, 1);
                } else {
                    map.put(ch, map.get(ch) + 1);
                }
            }
 
            //3.把map中数据存在文件count.txt
            //3.1 创建Writer
            bw = new BufferedWriter(new FileWriter("wordcount.txt"));
 
            //3.2 遍历map,再写入数据
            Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();
            for (Map.Entry<Character, Integer> entry : entrySet) {
                switch (entry.getKey()) {
                    case ' ':
                        bw.write("空格=" + entry.getValue());
                        break;
                    case '\t'://\t表示tab 键字符
                        bw.write("tab键=" + entry.getValue());
                        break;
                    case '\r'://
                        bw.write("回车=" + entry.getValue());
                        break;
                    case '\n'://
                        bw.write("换行=" + entry.getValue());
                        break;
                    default:
                        bw.write(entry.getKey() + "=" + entry.getValue());
                        break;
                }
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关流
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
 
            }
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
 
            }
        }
 
    }
}

10、缓冲流源码分析

  • 分析构造器
//java的源码是不能修改的,你可以把里边的代码复制出来新建一个类。
public MyBufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;
        //自定义缓存大小
        //分配8个字符的缓存大小
        cb = new char[sz];
        //nextChar:记录下一次读取字符的位置
        //nChars:记录当前缓存区可读的字符个数
        nextChar = nChars = 0;
    }
  • 分析read方法
public int read(char cbuf[], int off, int len) throws IOException {
    	//编写同步锁代码块
        synchronized (lock) {
        	//确认输入流不为空
            ensureOpen();
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }
            //从缓存区的数据复制到cbuf数组里面
            int n = read1(cbuf, off, len);
            if (n <= 0) return n;
            //将之前处理不完的数据复制到cbuf数组,再次调用read1方法
            while ((n < len) && in.ready()) {
                int n1 = read1(cbuf, off + n, len - n);
                if (n1 <= 0) break;
                n += n1;
            }
            return n;
        }
    }
  • read1方法
private int read1(char[] cbuf, int off, int len) throws IOException {
    	//nextChar:记录下一次读取字符的位置
    	//nChars:记录缓存区可读的字符个数
    	//如果nextChar>nChars,则重新刷新缓存区
        if (nextChar >= nChars) {
            /* If the requested length is at least as large as the buffer, and
               if there is no mark/reset activity, and if line feeds are not
               being skipped, do not bother to copy the characters into the
               local buffer.  In this way buffered streams will cascade
               harmlessly. */
            if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
                return in.read(cbuf, off, len);
            }
            //针对缓存区的数据,重新刷新,将新的数据重新更新到缓存区
            fill();
        }
        if (nextChar >= nChars) return -1;
        if (skipLF) {
            skipLF = false;
            if (cb[nextChar] == '\n') {
                nextChar++;
                if (nextChar >= nChars)
                    fill();
                if (nextChar >= nChars)
                    return -1;
            }
        }
        int n = Math.min(len, nChars - nextChar);
        //将缓冲区的数据复制到cbuf
        System.arraycopy(cb, nextChar, cbuf, off, n);
    	//并且更新nextChar的位置值
        nextChar += n;
        return n;
    }
  • fill方法
private void fill() throws IOException {
        int dst;
        //是否有调有mark方法,如果没有设置mark值,则markedChar <= UNMARKED为true
        if (markedChar <= UNMARKED) {
            /* No mark */
            dst = 0;
        } else {
            /* Marked */
            int delta = nextChar - markedChar;
            if (delta >= readAheadLimit) {
                /* Gone past read-ahead limit: Invalidate mark */
                markedChar = INVALIDATED;
                readAheadLimit = 0;
                dst = 0;
            } else {
                if (readAheadLimit <= cb.length) {
                    /* Shuffle in the current buffer */
                    System.arraycopy(cb, markedChar, cb, 0, delta);
                    markedChar = 0;
                    dst = delta;
                } else {
                    /* Reallocate buffer to accommodate read-ahead limit */
                    char ncb[] = new char[readAheadLimit];
                    System.arraycopy(cb, markedChar, ncb, 0, delta);
                    cb = ncb;
                    markedChar = 0;
                    dst = delta;
                }
                nextChar = nChars = delta;
            }
        }

        int n;
        do {
        	//将输入流的数据读取到缓存区里面,n变量决定读取多个字符
            n = in.read(cb, dst, cb.length - dst);
        } while (n == 0);
        //如果n>0,代表已经读到数据
        if (n > 0) {
        	//nChars:缓存区可读的数据
            nChars = dst + n;
            //下一次可读数据的位置
            nextChar = dst;
        }
    }

  • 图如下
    缓冲区其实就是一个数组来的
    在这里插入图片描述

11、打印流PrintStream

PrintStream

一般我们用System.out.println(“hellow world”);是输出到控制台的,如果我们想让他输出到其他的文件,就可以用重定向。例子如下

这是我们之前学的:

FileOutStream afileOutStream = new FileOutpStream(new File("a.txt"));
//你不是要输出到a.txt吗,那就创建一个输出流,将信息输出到文件
PrintStream ps = new PrintStream(afileOutStream);
ps.println("你好啊");
ps.flush();
ps.close();
afileOutStream.close();

此时将在文件a.txt中显示 你好啊

  • 现在修改重定向设备:
    标准输出到a.txt.
FileOutputStream afileOutStream = new FileOutputStream(new File("a.txt"));
//你不是要输出到a.txt吗,那就创建一个输出流,将信息输出到文件
PrintStream ps = new PrintStream(afileOutStream);
//这里就是修改重标准输出设备了,从原本的控制台,定向到a.txt文件
System.setOut(ps);//如果是标准错误输出,用setErr()
System.out.println("11111");
System.out.println("222222");

PrintWrite

public class Printstream {//类名不能与关键字同名。。。。。

	public static void main(String[] args) throws FileNotFoundException {
		
		//获取键盘数据,用PrintStream打印输出到文件
		FileOutputStream fileOutputStream = new FileOutputStream(new File("a.txt"));
		PrintStream ps = new PrintStream(fileOutputStream);
		
		System.setOut(ps);
		Scanner sc = new Scanner(System.in);
		String str = sc.nextLine();
		System.out.println(str);
		
		/*
		 * 循环捕获键盘输入,当按q时结束循环
		 * Scanner sc = new Scanner(System.in);
		 * String content = null;
		 * while((content=sc.nextLine())!=null){
		   		System.out.println(content);
		   		if(content.equals("q")){  //如果键盘输入q则结束循环
		   			break;
		  		 }
		   }
		   sc.close();//关闭资源
		 */
		
		
		
		
		//获取键盘数据,用PrintWrite打印输出到文件
		FileOutputStream a = new FileOutputStream(new File("b.txt"));
		PrintWriter pw = new PrintWriter(a);
		
		Scanner sr = new Scanner(System.in);
		String st = sr.nextLine();
		pw.write(st);
		pw.flush();
		pw.close();
		sr.close();
	}
}

12、一个强大的读写文件的类RandomAccessFile(不属于IO流体系但可以任意读取文件)

  • RandomAccessFile类的最大特点就是可以自由访问文件的任意位置(用seek()方法),支持字节、字符操作
  • 例子
package RandomAccessFile实例;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class MainTest {

	public static void main(String[] args) throws IOException {
		
		//创建一个File对象
		File file=new File("f.txt");
		
		RandomAccessFile randomAccessFile=new RandomAccessFile(file, "rw");
		System.out.println((char)randomAccessFile.read());
		//将读取的起始位置定位到第4个字符开始读取
		System.out.println("将读取的起始位置定位到第4个字符开始读取,调用seek(3)");
		randomAccessFile.seek(3);
		System.out.println((char)randomAccessFile.read());
		System.out.println((char)randomAccessFile.read());
		System.out.println((char)randomAccessFile.read());
		System.out.println((char)randomAccessFile.read());
		System.out.println("将读取的起始位置定位到第4个字符开始读取,调用seek(3)");
		randomAccessFile.seek(3);
		System.out.println((char)randomAccessFile.read());
		System.out.println((char)randomAccessFile.read());
		System.out.println((char)randomAccessFile.read());
		System.out.println((char)randomAccessFile.read());
		
		//读取行数
		System.out.println(randomAccessFile.readLine());
		System.out.println(randomAccessFile.readLine());
		System.out.println(randomAccessFile.readLine());
		System.out.println(randomAccessFile.readLine());

	}

}

13、对象流:对象序列化处理

(1)什么是序列化、反序列化

  • 把对象转换为字节序列的过程称为对象的序列化。序列化后存到文件以保存它的信息。存到文件所以是输出流,用writeObject。

  • 把字节序列恢复为对象的过程称为对象的反序列化。

  • 对象的序列化主要有两种用途

··· 1) 把对象的字节序列永久地保存到硬盘上(这样就可以把对象的信息保存起来了),通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些session先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。当两个进程在进行远程通信时,彼此可以发送各种类型的数据。
  无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
  举个真实的案例:当我们需要使用的对象很复杂或者需要很长时间去构造,这时就会引入使用代理模式(Proxy)。例如:如果构建一个对象很耗费时间和计算机资源,代理模式(Proxy)允许我们控制这种情况,直到我们需要使用实际的对象。一个代理(Proxy)通常包含和将要使用的对象同样的方法,一旦开始使用这个对象,这些方法将通过代理(Proxy)传递给实际的对象。

  • 可通过对象输出流(ObjectOutputStream)将对象序列化,然后通过对象输入流(ObjectInputStream)恢复对象

  • java.io. Serializable接口用来作为实现对象序列化的工具 ,接口中没有任何方法。当一个类声明实现Serializable接口后,表明该类可被序列化。

  • 注意:
    1、序列化只能保存对象的非静态成员变量,而且只保存变量的值,不能保存任何的成员方法和静态的成员变量,对于变量的任何修饰符都不能保存。
    2、数组和集合对象是可以直接被序列化存储的。
    3、为了解决类的版本问题,可配合使用serialVersionUID成员。

(2)对象序列化实例

package 对象序列化处理;

import java.io.Serializable;

/*
 * 此类对象为要序列化处理的对象
 */
public class Person implements Serializable { //要序列化的对象对应的类要实现Serializable接口
	
	private String name;//私有化属性,下面提供gette,setter方法,有参和无参构造方法。
	private Integer age;
	
	public Person() {
		
	}
	
	public Person(String name, Integer age) {
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}
package 对象序列化处理;
public class MainTest {

	public static void main(String[] args) throws IOException {
		
		//序列化:将一个类的对象序列化成字节序列,保存到磁盘文件
		//创建一个输出流的文件
		FileOutputStream fileOutputStream=new FileOutputStream(new File("person.txt"));
		//ObjectOutputStream针对此fileOutputStream进行处理
		ObjectOutputStream os=new ObjectOutputStream(fileOutputStream);
		
		//创建Person对象
		Person p=new Person();
		p.setName("xiaoming");
		p.setAge(30);
		
		os.writeObject(p);
		
		os.close();
		fileOutputStream.close();
	}
}

反序列化实例

package 对象反序列化处理;
import 对象序列化处理.Person;

public class MainTest {

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		
		//创建读取文件的输入流对象
		FileInputStream fileInputStream=new FileInputStream(new File("person.txt"));
		//创建一个读取对象的输入流对象,读取person.txt文件
		ObjectInputStream os=new ObjectInputStream(fileInputStream);
		//反序列成一个对象
		Person p=(Person) os.readObject();
		
		System.out.println("用户名="+p.getName());
		System.out.println("年龄="+p.getAge());
		
		os.close();
		fileInputStream.close();
	}
}

transient关键字

  • 用法:如果使用transient关键字修饰成员变量,则此变量忽略序列化处理。反序列化时会给这个transient修饰的变量对应类型的默认值。

14、自定义序列化处理

可以自定义实现序列化逻辑处理。

(1)具体实现

在序列化对象类重写:readObject和writeObject方法

  • writeObject:编写自定义序列化处理的逻辑

  • readObject:编写自定义反序列化处理的逻辑

有时候,可能是由于独特的序列化需求、性能方面的考虑又或是希望在对象反序列化后能够执行其他操作,我们需要重写这个默认的序列化实现。这时候,我们可以通过如下两个方法来实现。

  • private void writeObject(ObjectOutputStream out) throws IOException
  • private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException
package 自定义序列化处理;

import java.io.IOException;
import java.io.Serializable;

public class Person implements Serializable {
	
	private String name;
	private Integer age;
	
	
	public Person() {
		super();
	}
	public Person(String name, Integer age) {
		this.name = name;
		this.age = age;
	}
	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}
	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}
	/**
	 * @return the age
	 */
	public Integer getAge() {
		return age;
	}
	/**
	 * @param age the age to set
	 */
	public void setAge(Integer age) {
		this.age = age;
	}
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
	/*
	 * 通过重写此方法,自定义反序列化逻辑 (从字节序列文件读取还原对象状态)
	 * */
	private void readObject(java.io.ObjectInputStream in) throws Exception
	{
		//以大写字母输出用户名
		setName(in.readUTF().toUpperCase());
		setAge(in.readInt());
	}
	
	/*
	 * 通过重写此方法,自定义序列化逻辑(序列化:将对象序列化成字节序列,存储到磁)
	 * */
	private void writeObject(java.io.ObjectOutputStream out) throws IOException
	{
		//将用户名序列化
		out.writeUTF(getName());
		//将年龄序列化
		out.writeInt(getAge());
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值