传智博客---Java中的输入输出流

package com.zhou.IO;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import org.junit.Test;
public class TestIOStream {
/**
* IO流:
*    IO流的分类:
*          输入流;
*          输出流;
*      按处理的单位分:
*          字节流(8位的字节);
*          字符流(16位的字节);
*      按流的角色分:
*          节点流:可以从一个特定的IO设备读/写数据的流,节点流就是直接插到文件本身的流就是节点流,;          
*          处理流(也称为包装流或转换流): 对一个已经存在的流进行连接和封装,通过封装后的流类实现数据的读/写操作,然后有的时候我为了提高效率,我可能把这个流的处理效率变得高一点,这个时候我们可以来看这个处理流;      
*/

/**
* 一、测试InputStream(字节输入流):
*
*/
@Test
public void testInputStream() throws IOException{
//InputStream是个抽象类,其典型的实现类是FileInputStream,在new 对象的时候,里面参数可以传一个文件的绝对路径,也就是传一个文件进去,也可以直接传文件名;
//1.创建一个字节输入流;
InputStream in = new FileInputStream("hello.txt"); //这句会出现一个类找不到的异常,这里我们直接抛IOException,抛了IO异常之后就不用管了,它会一个一个处理;
//2.读取文件的内容(现在怎么读呢?一个一个字节的读);
   //2.1一次只读取一个字节这样的来一个一个的读,这样读效率极低,不建议这样读;
  int result = in.read(); //返回的是一个int型;    
//   System.out.println(result);//读的结果是104
//            //104是什么,我把这个result强转为char类型;
//   System.out.println((char)result);//读的结果是字母h,也就是只读取了一个字符h,这样读的效率极低;
//
//        //当result等于-1时,表示读到文件的结尾
//        //那我读完整个文件的内容,这样写:
//   while(result != -1){
//   System.out.print((char)result);//这样读一个;
//      //然后呢,我读完一个只会再读,即我读完一个再读一个,直到读完文件为止:
//   result = in.read();
//   }
/**
* 2.2 一次读取一组:一次读取一组字符,一次把信息读取到字节数组里面;
*/
//  byte[] buffer = new byte[1024 * 10]; //一次读10K这么多;  
//  byte[] buffer = new byte[10];//一次读10个长度;
//               //怎么读呢?我一次读10个长;
//  //--我得看这个方法: public int read(byte[] b) throws IOException;这个方法,读完之后把结果放byte[]里面,这个方法的返回值也有一个-1,返回-1的话表示读到文件的结尾;
//           //于是我们这么写:
//  int len = 0;
//  len = in.read(buffer);
//  System.out.println(len);//返回值是10,第一次读10个,len为10;所以(len = in.read(buffer)) != -1,是直到读完为止时len的返回值是总的读完的长度;
//    //返回文件的字节数,若为-1,表示读取到了文件的结尾;
//  while((len = in.read(buffer)) != -1){//in.read(buffer)是用字节输入流读到buffer这个字节数组里面去,如果这个字节输入流对象的read(byte[] b)方法的返回值没到-1的话,
//
//  //这个时候我把结果打印出来;
//     //先读,因为这是一个数组,用增强的for循环类读:
////      for(byte b:buffer){//这样写读的时候会有问题,因为有可能最后一次读的时候小于读的10个长度,所以最后一次的结果会多上倒数第二次读的一部分;
//////  System.out.println(b);//这是没有转成字符串的结果;
////  System.out.print((char)b);
////      }
//     //我这样读
//  for(int i = 0;i < len;i++){//我一次读 len这么长;
//  System.out.print((char)buffer[i]);
//         }//这个读到len这么长为止了;
//  }
/**
*2.3把内容读取到字节数组的部分连续元素中,也就是现在我有一个足够长的数组类装要读的内容,但是实际上我的内
*      容比数组短,那么现在我可以把这个文件内容读取到我这个足够长的字节数组的指定部分区域,那么这个时
*      候有一个开始位置的问题,还有一个结束位置的问题,那么假如这个开始位置我们定为10,结束位置不是我
*      们能定的吧(应该是10+len);
*/
byte[] result1 = new byte[1024*10]; //定义一个足够长的字节数组;
//于是这样来读:
in.read(result1, 10, in.available()); //从定义的装读取内容的大的字节数组result1的第10个位置开始,长度是in.available()文件内容的实际长度;
//写完之后,我想看一下我这个最终的结果:
 //3.关闭流资源(这个别忘了);
 in.close(); //这样关;
}
/**
*二、测试 Reader(字符输入流):
* @throws IOException
*
*/
@Test
public void testReader() throws IOException{
//1.利用字符输入流读取hello.txt这个文件的内容输出到控制台;
Reader  reader = new FileReader("hello.txt");
//2.定义一个长度为10的char类型的数组来接收;
char[] buffer = new char[10];
int len = 0;
while((len = reader.read(buffer)) != -1){ //reader.read(buffer)字符输入流将文件内容读到字节数组buffer里面,这个方法的返回值不为-1就一直读,直到这个方法的返回值为-1为止;
// 因为是数组,我们这里用for循环来打印;
for(int i = 0;i < len;i++){
System.out.print(buffer[i]);
}
}
//关闭流资源;
reader.close();
}
/**
* 三、测试OuputStream:
*
* @param args
* @throws IOException
*/
@Test
public void testOutputStream() throws IOException{
//1.创建一个字节输出流对象,并传入文件名,这个时候是程序向存储设备(这里是硬盘)硬盘输出创建一个文件;
OutputStream out = new FileOutputStream("zhigang.txt"); //它将直接创建一个名为zhigang.txt的文本文件在工程目录下;
//2.这个时候我要往文件里面写,我先写这样一段话;
String  content = "[email protected]\nHello Java!";
 /**
  * 写法1:
  */
//    //把这段话写进刚创建的zhigang.txt这个文件里去,怎么写呢,跟输入流对应的,这段话很重要;
// byte[] buffer = new byte[10];//我们故意写得很小,我一次只能写10个;
// int len = 10;
// //这个时候我应该对这个content内容循环,内容的长度有个length,我一次只能写10个,但是这10个的话我们一次还写不满;
//   //先分隔content = "[email protected]\n\rHello Java!";,让它一次写10个;
//
// int time = content.length() / 10;//看原内容能读几次;
//   //这里有个问题,当原内容的长度小于10的时候,content.length() / 10的值是0,我们是不是需要time加1啊?
//
//
// //我一次读的长度;
// byte[] contentBytes = content.getBytes();
//
// //这时开始写:
// for(int i = 0;i < content.length() / 10;i++){
//   //怎样得到buffer里的内容,buffer里面存的就是原来连续10个一段的内容,现在是怎样将原来字符串内容整成段;
// //把String拆分成多个buffer;
//
// out.write(contentBytes, i * 10, len);//这个时候问题是buffer是谁啊?buffer是content = "[email protected]\n\rHello Java!"这里面的连续10个为一段中的一段;
// }
// //以上读得不全,还有最后一部分没有读,下面来读最后那部分;
// if(content.length() % 10 != 0){//当内容的长度模10不等于0时,说明,有一次写的时候会有小于10的情况;
// out.write(contentBytes, 10 * (content.length() / 10), content.length() - (10 * (content.length() / 10)));
// }
/**
* 写法2:最简单的写法;
*/
out.write(content.getBytes()); //这个字符串里面有个getBytes()方法直接获得字符串的全部字节数组内容,这样一步就搞定;
//关闭输出流资源;
out.close();
}
/**
* 开发的时候我们是将文件写到一个位置,即文件的复制;
*     文件的复制需要结合输入、输出流一起来用:
*  
* 四、利用字节输入、输出流来完成文件的复制:  
*    把该文件复制成zhigang02.txt;  
* @throws IOException
*/
@Test
   public void testCopyFile() throws IOException{
 /**
       * 典型的文件复制的流程:
       *    这样其实任何一个文件都能这样来复制;
       *    这种方式不但可以复制文本文件,而且还可以复制二进制文件;
       */
//1.创建定位到zhigang.txt文件的输入流;
InputStream inputstream = new FileInputStream("zhigang.txt");
//2.创建定位到zhigang02.txt文件的输出流;
OutputStream  outputstream = new FileOutputStream("zhigang02.txt");
//3.创建一个byte数组,用于读写文件,读一点写一点,即边读边写,一般情况下这样写;
byte[] buffer = new byte[1024 * 10]; //定义这么长的缓冲字节数组,作为读与写的缓存;
//要写个长度:
int len = 0;
//4.读写文件,用的是读的in.read(buffer),和写的out.write(buffer,0,len)这个方法;
       while((len = inputstream.read(buffer)) != -1){ //我只要没读到结尾就可以,我把这个结果读到这个缓冲字节数组buffer里面;
//边读的同时在边写;
//         outputstream.write(buffer);//最后一次可能没有所定义的字节数组那么长,所以除了最后一次我可以这样写,但是最后一次的长度个之前的长度都是len这么长,所以用下面的写法:
outputstream.write(buffer, 0, len); //我一次性将buffer缓冲字节数组buffer里面的内容,从0开始到len这么长的数据写到zhigang02.txt文件中;
       }
//5.关闭流资源;
   outputstream.close();
   inputstream.close();
}
/**
* 五、利用字符输入流Reader和字符输出流Writer来完成文本文件的复制;
*    方法与利用字节输入、输出流类似;
*
*/
@Test
public void testTxtFileCopy() throws IOException{
//1.创建定位到zhigang.txt文本文件的字符输入流;
Reader reader = new FileReader("zhigang.txt");
//2.创建定位到zhigang03.txt文本文件的字符输出流;
Writer  writer = new FileWriter("zhigang03.txt");
//3.创建一个字符数组,用于读写文件,读一点写一点,即边读边写,一般情况下这样写;
char[] bufferchar = new char[1000];
//定义一个长度;
int len = 0;
     //4.边读边写;
while((len = reader.read(bufferchar)) != -1){ //我只要没读到结尾就可以,我把这个结果读到这个缓冲字符数组bufferchar里面;
//边读边写数据输出流定位到的文件中;
writer.write(bufferchar, 0, len); //将字符缓冲数组bufferchar里面的内容,从0开始写len这么长的数据到字符输出流定位到的文件里;
}
//5.关闭流资源,倒着关;
writer.close();
reader.close();
}
/**
* 六、缓冲流:2.6 BufferedInputStream    3.6 BufferedOutputStream    4.6 BufferedReader     5.6 BufferedWriter
*       缓冲流比基本的字节、字符输入/输出流有更高的效率;
*      
*    复制文件zhigang.txt为zhigang04.txt;
*    
*    使用  缓冲流确实比基本的字符、字节流复制文件要提高效率,我们可以一行一行的读/写;
*    我们在创建 BufferedReader和BufferedWriter的时候,其实是对Reader和Writer的一个包装,叫包装流,而且关闭的时候也比较方便,我们直接关闭包装流就可以;
* @throws IOException
*/
@Test
public void testBufferedReaderAndBufferedWriter() throws IOException{
//1.创建BufferedReader和BufferedWriter;
/**
* 查看帮助文档BufferedReader实际上是Reader的一个包装,所以只需传一个Reader对象进去就可以了所以说这个时候也叫包装流;BufferedWriter的道理也一样,传个Writer的对象进行就可以
* 在包装流的内部它会去关闭节点流;
*/
   //1.1先创建一个Reader输入流对象,这个Reader称为节点流;
Reader reader = new FileReader("zhigang.txt");
 //通过输入流创建缓冲流;
BufferedReader bufferedReader = new BufferedReader(reader);
   //1.2创建一个Writer输出流对象,这个Writer称为节点流;
   //通过输出流创建缓冲流;
Writer writer = new FileWriter("zhigang04.txt");
BufferedWriter  bufferedWriter = new BufferedWriter(writer);
//2.进行读写操作;
   //定义一个String类型的变量;
String str = null;
//定义一个int类型的变量来控制换行;
int i = 0;
while((str = bufferedReader.readLine()) != null){ //包装流可以一次读一行,一直读到没有内容为null的时候
//边读一行边写一行;
if(i != 0){ //如果不是第一行的话,我就在前面那一行都加上一个换行;
bufferedWriter.write("\n"); //读完一行之后,加上一个换行;
 }
bufferedWriter.write(str); //这个是一行连着的,没有加上一个换行;
i++; //然后我每写完一行之后,让换行标志加1;
/**
  * 这样会多出一个最后的换行,怎样把最后的那个换行给去掉呢?
  * 实际上我们在每次写完第一行之后都有一个换行,即除了第一行之后的下一行的前面都有一个换行;
  */
}
//3.关闭IO流资源;
/**
   * 这里直接关闭外面的这个包装流就可以了,关闭包装流的同时在其内部会关闭节点流;
   */
bufferedWriter.close(); // 先关里面再关外面
bufferedReader.close();
}
/**
* 七、利用BufferedInputStream和BufferedOutputStream完成
*    zhigang.txt文件到zhigang05.txt文件的复制;
* @param args
* @throws IOException
*/
@Test
public void testBufferedInputStreamAndBufferedOutputStream() throws IOException{
//1.创建BufferedInputStream和BufferedOutputStream;
  //1.1 创建InputStream字节输入流对象;
InputStream inputStream = new FileInputStream("zhigang.txt");
BufferedInputStream  bufferedInputStream = new BufferedInputStream(inputStream);
//1.2创建OutputStream字节输出流对象;
OutputStream  outputStream = new FileOutputStream("zhigang05.txt");
BufferedOutputStream  bufferedOutputStream = new BufferedOutputStream(outputStream);
//2.进行读写操作;
/**
* 第一种写法:
*/
//    int len = 0;
// while((len = bufferedInputStream.read()) != -1){//可以读一行的数据;
// bufferedOutputStream.write(len);//边读边写;
// }
/**
* 第二种写法:
*/
byte[] buffer = new byte[1024 * 10];
int len = 0;
while((len = bufferedInputStream.read(buffer)) != -1){ / /将其读到字节数组中去;
//边读边写,读一次,写一次;
bufferedOutputStream.write(buffer, 0, len); //将字节缓冲数组的内容写到文件中去;
}
//3.关闭流资源;
bufferedOutputStream.close();
bufferedInputStream.close();
}
/**
* 八、转换流:只有4.7 InputStreamReader 和 5.7 OutputStreamWriter两个,它是把字节流转换为字符流的,而没有把字符流转换为字节流的;
*        字节流什么时候都能用,但是字符流只是用于读取文本文件,因为我要是音频或视频、或图片这样以二进制方式存在的文件,里面是字节而不是字符,你要是用字符流的话
*        字符流本身其实也是字节流,所以如果是一个文档的话也可以用字节流来处理,但是若是文档的话用字符流处理更加方便,仅此而已;
* 如果我拿到的一个流是字节流,但它实际上它定位的文件是一个文本文件,这个时候很明显,我们给它转换成字符流会更加的方便,这个时候我读的这个数组与写的这个效率会更高,也
* 更加符合我们的习惯,所以只有从字节流转换为字符流的,而没有从字符流转换为字节流的;
* @param args
* @throws IOException
*/
@Test
public void teatInputStreamReader() throws IOException{
//1.先创建一个InputStream字节输入流,并定位到zhigang.txt文件;
InputStream  inputStream = new FileInputStream("zhigang.txt"); //这是一个指向文档的字节流;
//2.把上面的流转换为字符流;
Reader  reader = new InputStreamReader(inputStream);
//3.把字符流转换为带缓冲的字符流;
BufferedReader  bufferedReader = new BufferedReader(reader);
 /**
  * 有时候可以这样写:
  */
// BufferedReader  bufferedReader2 = new BufferedReader(new InputStreamReader(new FileInputStream("zhigang.txt")));
//4.读操作;
String str = null;
while((str = bufferedReader.readLine()) != null){ //字符缓冲流一行一行的读,直到读到空为止;
//边读边打印
System.out.println(str); //打印结果
}
//5.关闭流资源;
  /**
   * 读的时候从里面关;
   */
inputStream.close();
reader.close();
bufferedReader.close();
// bufferedReader2.close();
}
/**
* 九、字节输出流转换为字符输出流 OutputStreamWriter;
*     练习题:先创建两个字节输入、输出流,分别指向zhigang.txt文件和zhigang06.txt文件;
*          然后再转换为字符输入、输出流,最后再转为带缓冲的字符输入输出流,完成文件的复制;
*    
* @param args
* @throws IOException
*/
@Test
public void OutputStreamWriter() throws IOException{
//1.创建字节输入、输出流,分别指向zhigang.txt文件和zhigang06.txt文件;
InputStream  inputStream = new FileInputStream("zhigang.txt");
OutputStream  outputStream = new FileOutputStream("zhigang06.txt");
//2.转换为字符输入、输出流;
Reader reader = new InputStreamReader(inputStream);
Writer writer = new OutputStreamWriter(outputStream);
//3.转换成带缓冲的字符输入输出流;
BufferedReader  bufferedReader = new BufferedReader(reader);
BufferedWriter  bufferedWriter = new BufferedWriter(writer);
//4.完成文件的复制,边读边写;
String str = null;
int i = 0; //定义换行标志;
while((str = bufferedReader.readLine()) != null){ //一行一行的读,直到读的数据为空为止;
//读一行的同时写一行数据到输出缓冲流指向的文件;
if(i != 0){ //写入文件时除了第一行以外的下一行的上面一行都加上一个换行;
bufferedWriter.write("\n");
}
bufferedWriter.write(str); //这样是连着写的,要加上换行,这个换行得这样加:除了第一行之外的下一行的上一行都加上一个换行;
i++; //写完第一行之后我让i加1  
}
//5.关闭流资源;
/**
* 从里往外关;
*/
bufferedWriter.close();
writer.close();
outputStream.close();
bufferedReader.close();
reader.close();
inputStream.close();
}
/**
* 十、对象输入流和对象输出流: 2.8 ObjectInputStream  3.8 ObjectOutputStream  
*      对象输入流:我什么时候需要把这个对象写到硬盘上,什么时候又需要把这个对象读出来呢,有这个必要吗?很有这个必要。
*      来看一下对象流的意义,其内容叫对象的序列化:
*          对象序列化的目标是将对象保存到硬盘上,或允许在网络中直接传输对象--就叫对象的序列化;那么将对象从磁盘上
*             或者从网络上读出来就叫对象的反序列化;
*          序列化是RMI(Remote Method Invoke-远程方法调用)过程的参数和返回值都必须实现的机制,而
*          RMI是JavaEE的基础。因此序列化机制是JavaEE平台的基础;比如说我这服务器去调用另外一台服务器的方
*          法,那么去调用方法的时候,比如那个方法需要接收参数,这个参数是是不是需要通过网络的方式传过去啊?再那个
*          方法的结果可能是一个对象,是不是需要通过网络的方式再给我传回来啊,所以:我传过去的时候就需要把那个对象在
*          网络中传输,读出来的话就把那台服务器通过网络传过来的对象读出来,这个时候实际上就是对象的序列化。
*       注意:如果需要让某个对象支持序列化机制,则必须让类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两
*          个接口之一:
*              Serializable;---异常Exception的时候就实现了这个接口,这个接口没有定义任何方法,
*                              这种接口叫标记接口。就是说你只要继承这个接口的话,你就可以被序列化,就
*                              意味着这个对象可以被序列化以及反序列化;
*              
*              Externalizable;
*      
*
*/
/**
* 使用对象流序列化对象:
*      若某个类实现了Serializable接口,该类的对象就是可序列化的;
*            -创建一个ObjectOutputStream;
*            -调用ObjectOutputStream对象的writeObject()方法输出可序列化的对象,即把一个对象写到硬盘上;
* 反序列化:
*      -创建一个ObjectInputStream;
*      -调用readObject()方法读取流的对象,把这个对象从硬盘等存储设备读到程序中;
* 如果某个类的字段不是基本数据类型 或String类型,  而是另一个引用类型,那么这个引用类型必须是可序列化的,
* 否则拥有该类型的Field的类也不能序列化;          
* @throws IOException
*
*/
@Test
public void testSerializable() throws IOException{
//1.我先创建一个Person类的对象;
Person person = new Person("AA",12);
 //我想对我在Person里新加的另外一个引用类型字段进行序列化;
person.setAddress(new Address("四川")); //直接创建Adress类而没有实现任何接口这样运行是不行的,因为这个Person类对象能写到硬盘上的话,我要求里面的所有属性都能够写到硬盘上,因为这时候Address还没有实现Serializable接口,想写的话,这个Address也必须实现Serializable接口;
/**
    * 也就是Address这个类必须是序列化的,拥有Adress这个类的属性的Person类才能被序列化,也就是Adress这个类必须实现Serializable接口,实现了Serializable接口的Person才能被序列化;
    */
//2.写完之后我尝试着用ObjectOutputStream把这个对象写到硬盘上;
OutputStream outputStream = new FileOutputStream("obj.txt"); //这里写到本程序工程目录下的obj.txt文件里;
ObjectOutputStream  objectOutputStream = new ObjectOutputStream(outputStream); //这里需要一个输入流,也就是你写到哪个文件里;
//3.进行写的操作;
objectOutputStream.writeObject(person);
//4.写完之后我们还要关闭流资源;
/**
  * 输出先关的是里面的;
  */
outputStream.close();
objectOutputStream.close();
}
/**
* 写完之后我还可以把这个硬盘上的对象给读出来----叫对象的反序列化;
*    ---我们在具体作开发的时候不用类写这些,但是我们需要知道对象
*       序列化与反序列化其底层原理是什么样的,因为开发的时候服务
*       器内置了两种功能的框架在里面。
*
* @throws IOException
* @throws Exception
*
*/
@Test
public void testObjectInputStream() throws IOException, Exception{
//1.首先我用输入流定位到刚写好的那个obj.txt文件;
InputStream inputStream = new FileInputStream("obj.txt");
//2.创建对象的输入流的对象;
ObjectInputStream  objectInputStream = new ObjectInputStream(inputStream);
//3.进行读操作;
Object obj = objectInputStream.readObject() ;//这个读返回的是Object类型,所以用一个Object类型的去接收;
System.out.println(obj);//打印读到程序里的结果;
//4.关闭流资源;
objectInputStream.close();
inputStream.close();
}
public static void main(String[] args){
}
}



package com.zhou.IO;
import java.io.Serializable;
public class Person implements Serializable{
//加上一个类 的版本号:用于对象的序列化,具体用于读取对象时比对硬盘上对象的版本和程序中对象的版本是否一致,若不一致则读取失败,并抛出异常;
private static final long serialVersionUID = 1L;
/**
* 在这加上一个ID,序列化版本的ID,这个号的作用是:我有的时候可能会对Person进行改造,这个版本号我也改了,那么
* 我在读的时候就读不出来了,因为在读的时候版本不对了,这个时候我就不应该再把这个对象读出来了;
* 只是读的时候用这个ID号,写的时候不用,因为写是将程序中的版本写到硬盘,程序版本是由程序控制的,所以写的时候就不需要用;
*/
private String name;
private int age;
private Address address;
public Person() {
// TODO Auto-generated constructor stub
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address
+ "]";
}
}



package com.zhou.IO;
import java.io.Serializable;
public class Address implements Serializable {
  private static final long serialVersionUID = 1L; //加上一版本号;
  private String city;
  public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
   public Address(String city) {
super();
this.city = city;
}
  public Address() {
// TODO Auto-generated constructor stub
   }
 @Override
public String toString() {
return "Address [city=" + city + "]";
}
}


本文出自 “IT技术JAVA” 博客,转载请与作者联系!

你可能感兴趣的:(Java中的输入输出流)