java 基础 浅析IO 字节流,字符流

IO,前面简单了说了一下File类,当然对File类的简单说明也是为了对IO操作进行铺垫。IO其实就是两个单词的缩写(Input/Output)。而其输入输出操作者是程序,所以输入输出是以程序来判断是输入还是输出的。

既然学习编程,那就错不开这个,而java自然也有自己的IO操作的类,而其也根据不同的分类标准,将其分成了不同的种类。

分类:

  • 按照处理数据的单位不同分为:字节流和字符流。
  • 根据处理数据的方向分为:输入流和输出流。
  • 根据流的角色不同分为:节点流和处理流。

java中的流操作共有40多个,但是其都是4个基本抽象类衍生的。而且仔细观看的话,其有固定规律,下面就列出一些,方便大家观看。

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件**FileInputStream **FileOutputStreamFileReaderFileWriter
访问数组ByteArrayIutputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
访问管道PipedInputStreamPipedOnputStreamPipedReaderPipedWriter
访问字符串StringReaderStringWriter
缓冲流BufferedInputStreamBufferedOnputStreamBufferedReaderBufferedWriter
转换流InputStreamReaderOuputStreamWriter
对象流ObjectInputStreamObjectOutputStream
FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流PrintStreamPrintWriter
推回输入流PushbackInputStreamPushbackReader
特殊流DataInputStreamDataOutputStream

看见上图可能有点懵,不过下面会依次简单的使用,这样更加容易对其理解。途中加黑的类是重要使用的类,其他的IO如果感兴趣可以再聊,本章不会过多说。

对于IO主要就是了解期使用,以及各个IO操作的特点以及方法。所以本章主要就是使用。

先开始用字符流进行操作文件,开始进入IO操作

注意:IO流的操作不会自动关闭连接,需要手动关闭其数据连接。

字符流

字符流读取

读取的文件

public static void read() {
		File file=new File("src\\test\\IO.txt");
		FileReader fileReaer=null;
		try {
			 fileReaer=new FileReader(file);
			 int len=-1;
			 String data="";
			 char[] chr=new char[2];// 这个长度写2设置可以写1024 看自己实际情况
			while((len=fileReaer.read(chr))!=-1) {//如果有数据回返回读取的数组长度,这个长大于0,小于自己设置长度(比如我设置数组2) 
//				data=data+new String(chr, 0, 2);//如果这样 最后data值为:Abcded 比如文件中多出一个字符
				data=data+new String(chr, 0, len);//最后data为  Abcde    这个不可以用len 不用2,因为比如最后一次读取的1个,而数组下标所在的数据只会被覆盖,如果没有覆盖返回上一此读取的值
				
			 }
			 System.out.println(data);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(fileReaer!=null) {
				try {
					fileReaer.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		
	}
	
	public static void read() {
		File file=new File("src\\test\\IO.txt");
		FileReader fileReaer=null;
		try {
			 fileReaer=new FileReader(file);
			 System.out.println(fileReaer.read()); //65
             System.out.println((char)fileReaer.read()); //b
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(fileReaer!=null) {
				try {
					fileReaer.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		
	}
// 65
为什么会如此因为fileReaer.read() 返回的是一个单字符,而且是以字符编码格式
// b
 这个地方可以看出其read是,依次向下运行的。

再运行中,不可能为了得到每个字符,然后写多个read方法,第一,不会知道具体执行多少次。第二:代码会让人感觉冗余,代码很长。所以写法如下:

public static void read() {
		File file=new File("src\\test\\IO.txt");
		FileReader fileReaer=null;
		try {
			 fileReaer=new FileReader(file);
			 int j=-1;
			 String data="";
			while((j=fileReaer.read())!=-1) {// 因为如果没有字符read返回-1
				data=data+(char)j;
			 }
			 System.out.println(data);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(fileReaer!=null) {
				try {
					fileReaer.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		
	}

上面这种读取方式,一般不会单独使用read()这个方法,而是使用read(char[])这种方式。

  • read() 读取流,返回一个字符。如果没有返回-1
  • read(char[] cbuf) 读取流将数据放入字符数组中,如果没有返回-1
  • read(char[] cbuf,int offset,int length): 这种方法就是将数据读取放入数组以偏移量开始,然后长度为参数值,如果没有数据返回-1。这种方法一般时候用的偏少.

可能看方法解释有点乱,所以简单先看代码:

public static void read() {
		File file=new File("src\\test\\IO.txt");
		FileReader fileReaer=null;
		try {
			 fileReaer=new FileReader(file);
			 int len=-1;
			 String data="";
			 char[] chr=new char[2];// 这个长度写2设置可以写1024 看自己实际情况
			while((len=fileReaer.read(chr))!=-1) {//如果有数据回返回读取的数组长度,这个长大于0,小于自己设置长度(比如我设置数组2) 
//				data=data+new String(chr, 0, 2);//如果这样 最后data值为:Abcded 比如文件中多出一个字符,为什么下面解释
				data=data+new String(chr, 0, len);//最后data为  Abcde    这个不可以用len 不用2,因为比如最后一次读取的1个,而数组下标所在的数据只会被覆盖,如果没有覆盖返回上一此读取的值
				
			 }
			 System.out.println(data);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(fileReaer!=null) {
				try {
					fileReaer.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		
	}
	
字符输出流
public class IoTest {

	public static void main(String[] args) {
	
		
	File file=new File("src\\test\\IO1.txt");//创建文件,如果没有就创建(前提是路径是正确的也就是路径是存在的,只能创建文件无法创建路径)。
	//System.out.println(file.getAbsolutePath());
	FileWriter write=null;
	try {
		write=new FileWriter(file);
		String zifu="dfdfddfdf";
		write.write(zifu);//将字符串写出数据之中,这个方法不会从尾部开始写而是直接覆盖重写。
		
		
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally {
		if (write!=null) {
			try {
				write.close();//流必须关闭的,这种连接是物理连接,java是无法自己关闭的,所以需要手动关闭
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	}

}

当然 输出和输入也是又几个不同的方法

  • write(int args):写入数字,利用 ASCII 码表 或者 Unicode 表转化

  • write(String str):写入字符串

  • write(String str,int offset,int count):写入字符串的一部分

  • write(char[] array):写入字符数组(很多地方称之为水桶论)

  • write(char[] array,int offset,int count):写入字符数组的一部分

    不再一一实现,毕竟其方法实现大致相同。

    其实这个时候,应该有疑问了,为什么有了字符流,还要有以字节流,现在我们在说一个实现,用字符流的读入和写出一起使用,也可以理解为用字符流实现的复制。

    首先我们实现一个txt文件的复制后,文件没有出错。但是我们复制了一个图片,发现自己写出的图片竟然无法打开。

    具体模板如下,将文件名变化即可

    public static void CopyFile() {
    
    		
    		File readfile=new File("src\\test\\要读取的文件全名");
    		File writefile=new File("src\\test\\要写出的文件全名");
    
    		FileWriter write=null;
    		FileReader read=null;
    		try {
    			read=new FileReader(readfile);
    			write=new FileWriter(writefile);
    			char[] tem=new char[5];
    			int len=-1;
    			while ((len=read.read(tem))!=-1) {
    				write.write(tem,0,len);
    			}
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}finally {
    			if (write!=null) {
    				try {
    					write.close();
    				} catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}finally {
    					if (read!=null) {
    						try {
    							write.close();
    						} catch (IOException e) {
    							// TODO Auto-generated catch block
    							e.printStackTrace();
    						}
    				}
    			}
    			}
    			}
    
    		
    	}
    

    既然有这个问题,也就意味着需要字节流进行弥补,毕竟常用的数据不都是文本格式。

字节流

字节输入流

其实有点不想写的,因为和字符流很像,除了其创建流的时候不同。其read

  • read() 读取流,返回一个字符。如果没有返回-1
  • read(byte[] bbuf) 读取流将数据放入字符数组中,如果没有返回-1
  • read(byte[] bbuf,int offset,int length): 这种方法就是将数据读取放入数组以偏移量开始,然后长度为参数值,如果没有数据返回-1。这种方法一般时候用的偏少.

先上代码

public static  void readInputStream() {
  	File file=new File("src\\test\\IO.txt");
  	InputStream ins=null;
  	try {
  		ins=new FileInputStream(file);
  		 int len=-1;
  		 String data="";
  		 byte[] b=new byte[10];
  		while((len=ins.read(b))!=-1) {
  			data=data+new String(chr, 0, len);
  			
  		 }
  		 System.out.println(data);
  	} catch (Exception e) {
  		// TODO Auto-generated catch block
  		e.printStackTrace();
  	}finally {
  		if(ins!=null) {
  			try {
  				ins.close();
  			} catch (IOException e) {
  				// TODO Auto-generated catch block
  				e.printStackTrace();
  			}
  		}
  	}
  	
  	
  }

为什么不多写,看代码就值得这个和字符流的写法没有什么区别,区别就是其创建的io类 。所以对其输出输入的代码不在写了,因为重复太多了。

既然字符在读写非文本文档的时候,出现了问题,那字节流就可以读取任何文件吗?

如果是中文的话,如果使用read(),然后打印的话就会乱码。如果使用read(byte[] b)的话如果这个byte[] b长度大于带有中午的文本还好,如果小于的好分割的时候也会出现乱码,不在用代码演示,上面代码可以直接使用将文件路径变了即可。

  • 补充: 如果不在控制台打印,然后直接用FileInputStream读取字节或者字节数组,然后直接放入FileOutputStream中write,那其输出的文件不会出现乱码。

代码如下

public static void byteInput() {
  File file=new File("D:\\te.txt");
  FileInputStream fileInput=null;
  File file1=new File("D:\\te2.txt");
  FileOutputStream fileOutput=null;
  try {
  	 fileInput=new FileInputStream(file);
  	 fileOutput=new FileOutputStream(file1);
  	 int k=-1;
  	 while( (k=fileInput.read())!=-1) {
  		 fileOutput.write(k);
  		 System.out.println((char)k);
  	 }

  } catch (Exception e) {
  	// TODO Auto-generated catch block
  	e.printStackTrace();
  }finally {
  	if(fileInput!=null) {
  		try {
  			fileInput.close();
  		} catch (IOException e) {
  			// TODO Auto-generated catch block
  			e.printStackTrace();
  		}finally {
  			if(fileOutput!=null) {
  				try {
  					fileOutput.close();
  				} catch (IOException e) {
  					// TODO Auto-generated catch block
  					e.printStackTrace();
  				}	
  			
  			
  		}	
  	}
  }

}

}

总结:

  • 对于文本文件,比如txt,doc等文件尽量尽量用字符流。
  • 对于非文本文件,比如图片,视频,音频等文件尽量使用字节流。

缓冲流

缓冲流也可以说是一种处理流,这种流把数据从原始流成块读入或把数据积累到一个大数据块后再成批写出,通过减少[系统资源]的读写次数来加快程序的执行。

先看代码,然后在进行简单的讲解。先以BufferedInputStream 为例简单的看一下

 
    private static int DEFAULT_BUFFER_SIZE = 8192;

    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    /**
     * Creates a <code>BufferedInputStream</code>
     * with the specified buffer size,
     * and saves its  argument, the input stream
     * <code>in</code>, for later use.  An internal
     * buffer array of length  <code>size</code>
     * is created and stored in <code>buf</code>.
     *
     * @param   in     the underlying input stream.
     * @param   size   the buffer size.
     * @exception IllegalArgumentException if {@code size <= 0}.
     */
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

看完其声明方法,就明白了吧,其实本质和前面所说的水桶论差不多,当然人家的read实现方法,肯定写的很漂亮,大家有兴趣的话可以看一下。

​ 总的来说缓冲流读取文件会很快,但同时也会有一个弊端就是占内存,因为其本质就是将数据先缓存在内存,然后统一输出。毕竟内存的读写速度要快于硬盘的读写。

既然是一个处理流,是对节点流的包装,本章不深究其代码原理,所以还是看起方法。

现在对比一下BufferedInputStream和BufferedReader中常用方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EMnqaNRq-1622381969938)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20210526104342662.png)]

​ 上图为bufferedInputStream中所有方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyzOMOlS-1622382115027)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20210526103816023.png)]

​ 上图为BufferedReader 中的方法

可以看出其没有read(byte[] b)/ read(char[] cbuf)这个方法,因为前面说过其本质就是,所以官方文档其类不在有这个方法。

其他的方法不在多说,现在说一个其和前面不一样,也就是BufferedReader常用的一个方法readLine()也就是读取一行的文本,数据,如果没有数据返回NULL。

具体实现:

读取下面文档

我是一个中国人。
我也是一个中国人
我爱中国
我也爱中国

代码具体实现:

public static void bufferdRead() {
	File file=new File("D:\\te.txt");
	FileReader fileReader=null;
	BufferedReader bufferedReader=null;
	try {
		fileReader=new FileReader(file);
		bufferedReader=new BufferedReader(fileReader);
	    String buf=null;
		 while( (buf=bufferedReader.readLine() )!=null) {
			
			 System.out.println(buf);
		 }
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally {
		if(bufferedReader!=null) {
			try {
				bufferedReader.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
//			finally {//  关闭处理流也会关闭其使用的节点流,这个可以省区
//				if(FileReader!=null) {
//					try {
//						FileReader.close();
//					} catch (IOException e) {
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}	
//				
//				
//			}	
//		}
	}
输出
    我是一个中国人。
	我也是一个中国人
	我爱中国
	我也爱中国

注意: 关闭处理流也会关闭其使用的节点流,这个可以省区

转换流

前面所讲的IO流前面讲了字节流和字符流,但两者是否可以进行可以进行转换,以及转换有什么意义?

自然是可以转换的,也就是要说的转换流。转换流简单说就是:转换流也是一种处理流,它提供了字节流和字符流之间的转换。

当然其转换也是需要遵守其应有的规则:

字符编码:就是一套自然语言的字符与二进制之间的对应规则

​ 编码:字符(能看懂的)—>字节(看不懂的)
​ 解码:字节(看不懂的)---->字符(能看懂的)

img

img

​ (ps,为了省事,所以上面二图是从别处复制而来)

​ 毕竟乱码问题,在编程中很难忽视的问题,因为IO流本身就很长,所以不在对编码过多讲解,如果需要可以单独开一篇聊聊。

​ 转换流,只有两个类。

​ InputStreamReader : 将程序读取的自己留转换成字符流。

​ OuputStreamWriter: 将需要写出的字符流转载成字节流输出。

具体看InputStreamReader用法,

public static void changeStream() {
	
	File file=new File("D:\\te.txt");	
	FileInputStream fileInput=null;
	InputStreamReader fileRead=null;
	try {
		fileInput=new FileInputStream(file);
		fileRead=new InputStreamReader(fileInput);
//		fileRead=new InputStreamReader(fileInput, "GBK"); 第二参数可以字符串或者charset类型  这个就是前面suo'y  
//		fileRead.read()
		int i=-1;
		while((i=fileRead.read())!=-1) {// 其read方法和字符流一样也是常用的三种read()  ,read(char[] cha),read(char[] cha,int offset,int length)
			System.out.println((char)i);
		}
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally {
		if(fileRead!=null) {
			try {
				fileRead.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
	
}

看OuputStreamWriter的用法

public static void changeStream() {
	
	File file=new File("D:\\te.txt");	
	FileOutputStream fileOutput=null;
	OutputStreamWriter fileWrite=null;
	try {
		fileOutput=new FileOutputStream(file);
		fileWrite=new OutputStreamWriter(fileOutput);
		fileWrite.write("我是中国人");
		
		
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally {
		if(fileWrite!=null) {
			try {
				fileWrite.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}	
}

既然有转换流,自然有其使用的场景,所以简单的说一下使用场景之一:在urlconnection中常用的。后面讲解的时候网络IO的时候会举例,现在知道其使用的方式即可

数据流和对象流

数据流:其实就是将内存的中的数据实例化到硬盘上,然后再需要的时候取出,这样可以节约内存空间。

还是老规矩上代码

private static void dataStreamWrite() {
	File file=new File("D:\\te.txt");
	DataOutputStream dataout=null;
	try {
		 dataout=new DataOutputStream(new FileOutputStream(file));
		 dataout.writeDouble(1.234d);
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally {
		if (dataout!=null) {
			try {
				dataout.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

因为将double数据写入文档,第一反应就是下输出的就是1.234罢了,到那时打开txt却如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WrIpbdTZ-1622381969953)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20210527115526210.png)]

所以读取去内容的时候也需要用DataOutputStream读取。代码如下:

private static void dataStreamRead() {
	File file=new File("D:\\te.txt");
	DataInputStream datain=null;
	try {
		datain=new DataInputStream(new FileInputStream(file));
		System.out.println(datain.readDouble());//需要根据放入的不同类型的数据,读取的时候依次对应
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}finally {
		if (datain!=null) {
			try {
				datain.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

这个可以将基本数据类型和String在内存中实例化,但是其他的需要了对象流。

首先了解一个对象序列化

  • 对象序列化允许把内存中的java对象转换成平台无关的二进制流,从而允许把这种二级制流持久的保存到磁盘上,或者通过网络将这种二进制流输入到另一个网络节点。当其它程序获取这种二进制流,就可以恢复成原来的java对象。

  • 如果想让某个对象支持序列化,则必须让对象所属的类以及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,就会抛出异常。

    • Serializable (常用的接口)
    • Externalizale
  • 序列化的好处在于可以将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

  • 序列化化是RMI(Remote Method Invoke -远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础。

上面几条是摘抄他人的,简单的说一下就是

  • 序列化就是可以让对象可以传输或者存储在硬盘上,同时又可以还原数据。
  • 而自建的类序列化需要实现两个接口:Serializable和Externalizale,一般常用的是Serializable这个接口。

看代码,简单理解一下。

首先声明一个不实现Serializable这个接口的对象类

package test;

public class Person {
int age;
 String name;
 public Person(int age, String name) {
	super();
	this.age = age;
	this.name = name;
}
public int getAge() {
	return age;
}
public void setAge(int age) {
	this.age = age;
}
public String getName() {
	return name;
}
public void setName(String name) {
	this.name = name;
}

    @Override
public String toString() {
	return "Person [age=" + age + ", name=" + name + "]";
}
}

试着通过上面的对象类创建一个对象,然后将其存储到硬盘上,看其是否可以执行。

//对对象进行实例化
public  static void writeObject() {
	File file=new File("D:\\te.txt");
	FileOutputStream fileout=null;
	ObjectOutputStream objectOutput=null;
	try {
		fileout=new FileOutputStream(file);
		objectOutput=new ObjectOutputStream(fileout);
		Person person=new Person(10, "小明");
		objectOutput.writeObject(person);
		
		
		
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		if(objectOutput!=null) {
			try {
				objectOutput.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
}
//程序执行失败
java.io.NotSerializableException: test.Person
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at test.IoTest.writeObject(IoTest.java:35)
	at test.IoTest.main(IoTest.java:21)

可见其没实现接口,会有一个序列化异常。如果将对象类实现Serializable接口。如下就不会报错

public class Person implements Serializable {
 int age;
 String name;
 public Person(int age, String name) {
	super();
	this.age = age;
	this.name = name;
}
public int getAge() {
	return age;
}
public void setAge(int age) {
	this.age = age;
}
public String getName() {
	return name;
}
public void setName(String name) {
	this.name = name;
}

    @Override
public String toString() {
	return "Person [age=" + age + ", name=" + name + "]";
}
}

其输出的是文档,应该通过文本打开,而是同对象流进行打开,当然如果有文本打开会如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WVpR4znQ-1622381969954)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20210530113546827.png)]

为了减少文档的长度,所以这次将读出和写入同时在一个方法中表现出来。

//对对象进行实例化
public  static void writeObject() {
	File file=new File("D:\\te.txt");
	FileOutputStream fileout=null;
	FileInputStream fileinput=null;
	ObjectOutputStream objectOutput=null;
	ObjectInputStream obejectInput=null;
	try {
		fileout=new FileOutputStream(file);
		objectOutput=new ObjectOutputStream(fileout);
		Person person=new Person(10, "小明");
		objectOutput.writeObject(person);
		
		
		fileinput =new FileInputStream(file);
		obejectInput =new ObjectInputStream(fileinput);
		Person person1=(Person) obejectInput.readObject();
		System.out.println(person1);

		
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		if(obejectInput!=null) {
			try {
				obejectInput.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(objectOutput!=null) {
			try {
				objectOutput.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	
	
}
//输出
Person [age=10, name=小明]



  • 如果用的是基本类型和String默认是实现Serializable接口的,所以如果对象类有自己写的属性类,那么属性类也需要实现Serializable方法接口

下面代码在Person中多了一个自建的Count类的属性,如果这个类型不实现Serializable,就算Person实现了Serializable接口也会报序列化错误。

public class Person implements Serializable {
 int age;
 String name;
 Count count;//银行账户
public Person(int age, String name, Count count) {
	super();
	this.age = age;
	this.name = name;
	this.count = count;
}
public int getAge() {
	return age;
}
public void setAge(int age) {
	this.age = age;
}
public String getName() {
	return name;
}
public void setName(String name) {
	this.name = name;
}
public Count getCount() {
	return count;
}
public void setCount(Count count) {
	this.count = count;
}

}
 //自己建的类,如果作为其他类的属性类,也必须实现  Serializable接口,不然也会报错
 class Count implements Serializable{
	String CountID;

	public Count(String countID) {
		super();
		CountID = countID;
	}

	public String getCountID() {
		return CountID;
	}

	public void setCountID(String countID) {
		CountID = countID;
	}
	
}

不在测试实现不实现Serializable接口,运行了,上面有代码自己可以运行测试一下。

Serializable

既Serializable然是常用的一个接口,看下面源码,可以知道Serializable是一个标准接口,没有方法和属性。

public interface Serializable {
}

其源码中看着很大,其实很对都是些说明,不过其中的一句:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

serialVersionUID,这个值是很重要的一个参数,其如果不写的化,在运行的时候也会自动生成。如果这样认为其没有意义,那就是大错特错,因为它就代表一个类的身份id,而反序列化的时候也会对比是否有这个类,如果不写,那么类经过改变后就会重新生成一个serialVersionUID。

这个可能需要通过代码进行简单的演示,才能看出其作用。

现在声明两个对象类,然后

//写入硬盘时对象如下


public class Person implements Serializable {
 int age;
 String name;
 public Person(int age, String name) {
	super();
	this.age = age;
	this.name = name;
}


    @Override
public String toString() {
	return "Person [age=" + age + ", name=" + name + "]";
}
}


// 输出完毕后将对象Person类稍微修改一下,只是简单将Get和Set方法删除如下:


public class Person implements Serializable {
 int age;
 String name;
 public Person(int age, String name) {
	super();
	this.age = age;
	this.name = name;
}
public int getAge() {
	return age;
}
public void setAge(int age) {
	this.age = age;
}
public String getName() {
	return name;
}
public void setName(String name) {
	this.name = name;
}

    @Override
public String toString() {
	return "Person [age=" + age + ", name=" + name + "]";
}
}

具体做实验

public  static void writeObject() {
	File file=new File("D:\\te.txt");
	FileOutputStream fileout=null;
	FileInputStream fileinput=null;
	ObjectOutputStream objectOutput=null;
	ObjectInputStream obejectInput=null;
	try {
   //将对象保存在上面两个对象中的前一个然后写入硬盘,然后注释掉读入的代码,运行输出代码
        
//		fileout=new FileOutputStream(file);
//		objectOutput=new ObjectOutputStream(fileout);
//		Person person=new Person(10, "小明");
//		objectOutput.writeObject(person);
		
	//写入硬盘后,将对象Person类变成上面两个对象中的后,一个然后,然后注释掉输出的代码,然后运行读入的代码	
	//	fileinput =new FileInputStream(file);
	//	obejectInput =new ObjectInputStream(fileinput);
	//	Person person1=(Person) obejectInput.readObject();
		//System.out.println(person1);
		
	
		
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		if(obejectInput!=null) {
			try {
				obejectInput.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(objectOutput!=null) {
			try {
				objectOutput.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	
	
}

运行输出的时候,不会报错,但是现在读取的时候会报错如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t9s4zvXT-1622381969955)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20210530120805658.png)]

现在可以看出serialVersionUID如果默认生成的话,是会根据对象代码变化而变化的的。所以要求:serialVersionUID一定要自己写一个值,而非使用自动生成。

同时发现一个事情,比如存储的时候没有Set和Get方法,但是在后面读取的时候,Person还是会根据当前的Person类的代码接口可以调用Set和Get方法。可以看出serialVersionUID中作为序列化对象的的身份id,而不能表示对象类是否前后代码一样,但是不能改变对象类的名。

随机存储文件流

RandomAccessFile类,这个就是常说的随机流。为什么会有这个流的,因为前面的流有一个问题,就是无法在文件后面继续添加数据,如果重新输出的话,会将原有的文档进行覆盖。

随机存储流这个名字其实是直译,会让人以为是文件流在使用的时候是随机,其实应该说是:可任意操作文件流。这样说可能不会让人有歧义。

注意RandomAccessFile的特点

  • RandomAccessFile直接继承与Java.lang.object类,实现了DataInput和DataOutput接口。

  • RandomAccessFile即可可以作为输入流,也可以作为输出流。这一点有点像是python中open,其中如何表明其输出还是输入,需要有一个参数。参数如下

    • r :以只读的方式打开。
    • rw:可以读入也可以写入
    • rwd:打开以便于读入和写入,同步文件内容的更新
    • rws:打开以便于读入和写入,听不文件内容和元数据的更新

    补充:如果参数为 r ,则不会创建文件,而是会读取一个已存在的文件,如果文件不存在就会报异常。参数为rw读写时候,如果文件不存在则会去创建文件,如果存在则不会创建文件。

    构造器如下:其中的mode 就是上面所述的参数。

    RandomAccessFile (File file, String mode) :创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。 
    RandomAccessFile (String name, String mode) : 创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。 
    
    

其方法很多,不会一一测试, 所以直接截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6JyZn8wT-1622381969956)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20210530184728371.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zIHel8jC-1622381969957)(C:\Users\jhfan\AppData\Roaming\Typora\typora-user-images\image-20210530184812772.png)]

因为方法太多,所以用常用的几个方法,演示按要求玩法输出流的操作。

// 在 te.txt 文件中存储  ABCDEFG
//要求,在已存在的文件内的数据后面 添加数据
public static void RandomStream() {
	File file=new File("D:\\te.txt");
	RandomAccessFile raf=null;
	
	try {
		raf=new RandomAccessFile(file, "rw");
		long seeknum=  file.length();//得到文件的长度
		raf.seek(seeknum);// 将输入指针放在已有的数据最后面
		//也可以直接输出中文
		raf.write("中国人".getBytes());

		
	} catch (Exception e) {
		
		e.printStackTrace();
	}finally {
		if (raf!=null) {
			try {
				raf.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
}
//最后文件中变成了
 ABCDEFG中国人

不过这个又有一个新的疑问了,那就是如果偏移量低于数据的长度,后面的数据会被覆盖,还是类似插入?

答案是:偏移量后的数据会被覆盖。

// 在 te.txt 文件中存储  ABCDEFG
//
public static void RandomStream() {
	File file=new File("D:\\te.txt");
	RandomAccessFile raf=null;
	
	try {
		raf=new RandomAccessFile(file, "rw");
		long seeknum=  file.length()-3;//得到文件的长度,然后故意让其小于长度
		raf.seek(seeknum);// 将输入指针放在已有的数据最后面
	
		raf.write("H".getBytes());

		
	} catch (Exception e) {
		
		e.printStackTrace();
	}finally {
		if (raf!=null) {
			try {
				raf.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
}
// 打开文件看到如下:
ABCDHFG

可见内容是被覆盖了,如果需要实现插入的功能,那样的话就需要将偏移量后面的数据读取缓存,然后再最后写入具体如下:

// 在 te.txt 文件中存储  ABCDEFG
//要求,在已存在的文件内的数据后面 添加数据
public static void RandomStream() {
	File file=new File("D:\\te.txt");
	RandomAccessFile raf=null;
	
	try {
		raf=new RandomAccessFile(file, "rw"); 
		long length=  file.length();//得到文件的长度
		long seeknum=  length-3;//得到文件的长度
		raf.seek(seeknum);// 将输入指针放在已有的数据最后面
		byte[] buf=new byte[(int)(length-seeknum)];
		raf.read(buf);
		raf.seek(seeknum);// 再返回要插入的位置
		raf.write("H".getBytes());
		raf.write(buf);
		
	} catch (Exception e) {
		
		e.printStackTrace();
	}finally {
		if (raf!=null) {
			try {
				raf.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
}

//最后的文档
ABCDHEFG

补充

只是一个简单的补充,了解即可,有兴趣的朋友可以看下官方文档

打印流

打印流的作用:实现将基本数据类型的数据格式转化为字符串输出。

打印流包括:PrintStream和PrintWriter

  • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出。
  • PrintStream和PrintWriter的输出不会抛出IOException.有自动的fluash功能
  • PrintWriter 打印数据是时候会自动使用平台默认的字符,所以建议在打印的是字符而非字节的时候,可以优先考虑PrintStream类
  • system.out返回是PrintStream的实例 具
标准流

在键盘输入的时候一般用Scanner去实现,但是也可以通过标准流实现

system.in: 标准输入流

system.out: 标准出流

数组流

ByteArrayInputStream:包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException

ByteArrayOutputStream:对byte类型数据进行写入的类 相当于一个中间缓冲层,将类写入到文件等其他outputStream。它是对字节进行操作,属于内存操作流

简单的说:java.io.ByteArrayInputStream将一个字节数组当作流输入的来源,而java.io.ByteArrayOutputStream则可以将一个字节数组当作流输出目的地。

其他

除了这些还有一些第三方的IO流的包。就是将前面的IO 进行包装,然后打包的工具类,还有就是java 中的NI0.2为了弥补java中的Flie不足引入了Path,Paths,Files等类,后面如果需要再进行讲解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值