IO流(三)之IO包其他功能流及字符编码详解

来源:互联网 发布:超星网络选修课答案 编辑:程序博客网 时间:2024/04/30 20:38


打印流(PrintStreamPrintWriter

该流提供了打印方法,可以将各种数据类型的数据都原样打印

字节打印流:PrintStream

构造函数可以接受的参数类型

a) File对象 File

b) 字符串路径。String

c) 字节输出流。OutputStream

 

字符打印流:PrintWriter

构造函数可以接受的参数类型

a) File对象 File

b) 字符串路径。String

c) 字节输出流。OutputStream

d) 字符输出流.Writer

 

我们开发常用的是printWriter,那么printWriter有什么常用的方法呢?

a) 特殊构造方法:PrintWriter(OutputStream out, booleanautoFlush):如果为true,可以自动刷新println,printf,format

b) voidprintln(String s);打印

 

我们用这些方法来演示下PrintWriter的基本方法:

import java.io.*;

class PrintWriterDemo

{

publicstatic void main(String[] args) throws IOException

{

           method();

}

 

publicstatic void method() throws IOException

{

          BufferedReaderbr=

          newBufferedReader(new InputStreamReader(System.in));

          PrintWriterpw=new PrintWriter(new FileWriter("a.txt"),true);

          Stringline=null;

          while((line=br.readLine())!=null)

          {

                   if("over".equals(line))

                   {

                            break;

                   }

                   pw.println(line.toUpperCase());//

                   //pw.flush();

          }

  br.close();

  pw.close();

}

}

 

合并流(SequenceInputStream

我们首先将一个mp3文件切割成一个个碎片,这里就涉及到IO流的应用,我们把切割代码演示如下:

importjava.io.*;

classSpilteDemo

{

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

        {

                  spite();

        }

        public static void spite()throwsIOException

        {

                  FileInputStream fis=newFileInputStream("1.mp3");

                  FileOutputStream fos=null;

 

                  byte[] buf=newbyte[1024*1024];

                  int len=0;

                  int count=1;

                  while((len=fis.read(buf))!=-1){

                           //每次循环我们都创建一个写入流,保存到递增的文件中

                           fos=newFileOutputStream("c://splitest//"+(count++)+".part");

                           fos.write(buf,0,len);

                           fos.close();

                  }

                  fis.close();

        }

}

然而我们有时需要将文件合并到一起,那么我们怎么办呢?我们这时就需要一个IO流的合并流

合并流(SequenceInputStream即将多个流合并成一个流,即多个文件合并成一个文件

构造方法:SequenceInputStream(Enumeration<? extendsInputStream> e):接收一个Enumeration类型参数,需要用到集合

我们通过代码简单演示下如何将之前MP3文件合并在一起:

importjava.io.*;

importjava.util.*;

classSequenceInputStreamDemo

{

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

        {

                  //只有Vector才有Enumeration

                  Vector<FileInputStream>v=new Vector<FileInputStream>();

                  v.add(new FileInputStream("c://splitest//1.part"));

                  v.add(newFileInputStream("c://splitest//2.part"));

                  v.add(newFileInputStream("c://splitest//3.part"));

                  v.add(newFileInputStream("c://splitest//4.part"));

                  v.add(newFileInputStream("c://splitest//5.part"));

                  v.add(newFileInputStream("c://splitest//6.part"));

                  v.add(newFileInputStream("c://splitest//7.part"));

                  v.add(newFileInputStream("c://splitest//8.part"));

                  

                  Enumeration<FileInputStream>e=v.elements();

                  //创建一个合并流,且构造方法传入的必须是Enumeration类型

                  SequenceInputStream sis=newSequenceInputStream(e);

                  //将多个文件的数据传入到4.txt

                  BufferedOutputStream bw=

                  new BufferedOutputStream(newFileOutputStream("c://mp3//1.mp3"));

                  int len=0;

                  while((len=sis.read())!=-1){

                           bw.write(len);

                  }

                  sis.close();

                  bw.close();

 

        }

}

 

对象流(对象序列化 ObjectStream

对象流是可以将对象以序列化的方式存入到一个文件里,对象流有非常重要的两个类,该两个类是成对出现的。

ObjectOutputStream:将对象存储到一个文件中

存储方法:void writeObject(Object obj)

ObjectInputStream:操作输出对象流一保存的数据,通常和ObjectOutputStream成对使用。

读取方法:void readObject();

 

我们基本演示下将Person对象存入到文件中:

importjava.io.*;

classObjectDemo

{

        public static void main(String[] args)throws Exception

        {

                  writeObj()//要先写再读

                  readObj();

 

        }

 

        public static void readObj()throwsException

        {

        ObjectInputStream ois=newObjectInputStream(new FileInputStream("object.txt"));

        Person p=(Person)ois.readObject();

        System.out.println(p);

        ois.close();

        }

        public static void writeObj()throwsIOException

        {

                  ObjectOutputStream oos=newObjectOutputStream(new FileOutputStream("object.txt"));

 

                   oos.writeObject(newPerson("zhangsan",13));

                   oos.close();

        }

}

classPerson implements Serializable//必须要实现这个接口,是对象序列化

{

        String name;

        int age;

        Person(String name,int age)

        {

                  this.age=age;

                  this.name=name;

        }

        public String toString()

        {

                  return name+"::"+age;

        }

}

 

注意:

a) 当我们该掉Person任何一个成员属性时,再读取就会出错,是因为java内部有个叫UID的长整型的编号将Person中的属性编号,无论改变成员变量值还访问权限,都会报错,读取失败,如果想改变而又不读取失败,可以加上这段代码:ANY-ACCESS-MODIFIERstatic final long serialVersionUID = 42L;红色的是任何访问修饰符。、

b) 我们想存储的对象的类必须要实现一个接口Serializable,是其对象序列化

c) 要存储的对象类中成员属性不能是static修饰的,这样无法序列化,当然我们想让成员变量无法序列化也可在其加上transient

 

管道流(PipedStream

管道流是IO中一个非常特殊的流,他是和多线程联系在一起的。管道流输入输出是连接在一起的。

输入流:

PipedInputStream

输出流:

PipedOutputStream

管道流的输入输出连接是通过两种方法实现:

a) 构造一个无参数的构造方法,使用connect()方法去连接输入和输出

b) 构造一个有参数的构造方法,传入的是自己的输入或者输出流

 

我们基本演示下管道流的应用:

import java.io.*;

class Read implements Runnable

{

PipedInputStreampis;

Read(PipedInputStreampis){

 this.pis=pis;  

}

publicvoid run()

{

          try

          {

                   byte[]buf=new byte[1024];

                   System.out.println("没有数据,线程没有阻塞");

                   intlen=pis.read(buf);

                            System.out.println("读取数据中。。阻塞结束");

                   Stringstr=new String(buf,0,len);

                   System.out.println(str);//打印读取到的数据

                   pis.close();

          }

          catch(Exception e )

          {

                   thrownew RuntimeException("管道流输入失败");

          }

          

}

}

class Write implements Runnable

{

PipedOutputStreampos;

Write(PipedOutputStreampos){

          this.pos=pos;

}

publicvoid run()

{

          try{

                   System.out.println("开始写入数据,等待6秒后");

          Thread.sleep(6000);

          pos.write("haha,pioedis come".getBytes());

          pos.close();

          }catch(Exceptione){

                   thrownew RuntimeException("管道流输出失败");

          }

}

}

class PipedIODemo

{

publicstatic void main(String[] args) throws Exception

{

          PipedInputStreampis=new PipedInputStream();

          PipedOutputStreampos=new PipedOutputStream();

          pis.connect(pos);//连接输入和输出

          newThread(new Read(pis)).start();//开启读取线程

          newThread(new Write(pos)).start();//开启写入线程

}

}

 

随机访问流(RandomAccessFile

该类是随机访问文件的一个类,该类不能算是IO体系中子类,而是直接继承自Object,但是它是IO包中的成员,因为他具备读和写功能。其内部封装了一个数组,而且通过指针对数组的元素进行操作。我们可以通过getFIlePointer获取指针位置。同时可以通过seek改变指针的位置。

其实完成读写的原理就是内部封装了字节输入流和输出流。但它有一个局限性就是通过构造方法可以看出,该类只能操作文件,而且操作文件还有模式

模式:

a) r:以只读方式打开

b) rw:打开以便读取和写入

c) rws:打开以便读取和写入

d) rwd:打开以便读取和写入

 

如果模式为只读r,不会创建文件,会去读取一个已存在文件,如果该文件不存在,则会出现文件找不到(FileNotFoundException)异常。

如果模式为rw,操作的文件不存在,会自动创建,如果存在,不会覆盖。

 

而且该对象的构造函数要操作的文件不存在,会自动创建。如果存在,不会覆盖。

我们基本演示下该类的用法:

import java.io.*;

class RandomAccessFileDemo

{

publicstatic void main(String[] args) throws IOException

{

          writeFile_2();

          //readFile();

}

 

publicstatic void readFile()throws IOException

{

          RandomAccessFileraf=new RandomAccessFile("ran.txt","r");

          byte[]buf=new byte[4];

          //调整对象中的指针

          //raf.seek(8);

          

          //跳过指定的字节数

          raf.skipBytes(8);

 

          raf.read(buf);

          Stringname=new String(buf);

          System.out.println("name::"+name);

          

          intage=raf.readInt();

          System.out.println("age::"+age);

}

publicstatic void writeFile()throws IOException

{

          RandomAccessFileraf=new RandomAccessFile("ran.txt","rw");

          raf.write("张三".getBytes());

          //写入Int类型,以免超出无法保存,一般开发够用了

        raf.writeInt(97);

          raf.write("王五".getBytes());

          raf.writeInt(99);

          raf.close();

}

/*

写入文件数据的随机性

*/

publicstatic void writeFile_2()throws IOException

{

          RandomAccessFileraf=new RandomAccessFile("ran.txt","rw");

          //可以随机控制要写入的字节位置

          raf.seek(8*0);

          raf.write("周期".getBytes());

          raf.writeInt(101);

          raf.close();

}

}

总结:该类三种特殊的地方:首先它有有一个模式控制文件的读写操作,然后它有一个seek方法控制写入字节的位置,最后他的写入读取有多种基本类型,我们常用的是writeInt方法。该类经常用在文件的下载,我们要重点掌握。

 

基本数据类型流(DataStream

其用于操作基本数据类型的流

        输入流:

        DataInputStream

        输出流

        DataOutputStream

 

我们通过代码演示,基本的了解下DataStream的用法:

importjava.io.*;

classDataStreamDemo

{

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

        {

                  //writeData();

                  readData();

        }

                  public static voidwriteData()throws IOException

        {

                  DataOutputStream dos=newDataOutputStream(new FileOutputStream("data.txt"));

                  //我们按照什么顺序写入什么基本类型数据

                  dos.writeInt(289);

                  dos.writeBoolean(true);

                  dos.writeDouble(988.26);

                  dos.close();

        }

        public static void readData()throwsIOException

        {

                  DataInputStream dis=newDataInputStream(new FileInputStream("data.txt"));

                  //我们就按照什么顺序读取什么基本类型。必须按顺序,否则取出来乱码

                  int num=dis.readInt();

                  Boolean b=dis.readBoolean();

                  Double d=dis.readDouble();

                  System.out.println(num);

                  System.out.println(b);

                  System.out.println(d);

        }

}

 

注意这两个类个分别有特殊的方法是专门用来输入和读取UTF的方法,我们只能对应的去取文件的数据。这两个方法分别是readUTF(),writeUTF()。我们演示下这两个方法的用法:

importjava.io.*;

classDataStreamDemo

{

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

        {

                  //writeUTFData();

                  readUTFData();

        }

 

        public static void writeUTFData()throwsIOException

        {

                  DataOutputStream dos=newDataOutputStream(new FileOutputStream("utfdata.txt"));

                  dos.writeUTF("你好");//writeUTF只能解析中文

                  dos.close();

        }

        public static void readUTFData()throwsIOException

        {

                  DataInputStream dis=newDataInputStream(new FileInputStream("utfdata.txt"));

                  String s=dis.readUTF();

                  System.out.println(s);

        }

 

基本数据类型数组流(ByteArrayStream

ByteArrayStream是用来操作字节数组的流对象

 

ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据源是一个字节数组。

构造方法:ByteArrayInputStream(byte[]buf)

 

ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组,这就是数据目的地、

构造方法:ByteArrayOutputStream()

常用的方法:

a) int size:返回缓冲区的当前数组大小

b) String toString:将缓冲区内容转换为字符串

c) void writeToOutputStream out):将byte数组全部内容存到指定输入流文件中,这段代码需要抛IOException

 

因为是两个流对象都操作的数组,并没有使用系统资源,所以不用进行close关闭,且没有涉及到文件或者键盘等操作,所以不用抛IOException

这个类就是用流的读写思想操作数组

          我们来通过代码基本演示下这个流:

import java.io.*;

class ArrayStreamDemo

{

publicstatic void main(String[] args)

{

          //数据源

          ByteArrayInputStreambais=new ByteArrayInputStream("ABCDEFG".getBytes());

 

          //数据目的

          ByteArrayOutputStreambaos=new ByteArrayOutputStream();

 

          inta=0;

          while((a=bais.read())!=-1)

          {

                   baos.write(a);

          }

          System.out.println(baos.size());//缓冲区的大小

          System.out.println(baos.toString());//唯一取出数组的方法

}

}

 

总结:我们不仅有操作字节数组的,还有其他的基本类型数组操作,他们基本用法是一致的:

操作字符数组

CharArrayReaderCharArrayWrite

操作字符串

StringReaderStringWriter

 

字符编码

我们常用的编码表是UTF8GBKUTF-8是国际码表,一个汉字占三个字节,GBk是中国的码表,一个汉字占两个字节。

字节转换字符只能用IO流两个类

a) InputStreamReaderoutputStreamWriter

b) PrintStreamPrintWriter这是打印流,不涉及读取。

 

我们通过以下代码,测试下UTF-8GBK之间的区别

import java.io.*;

class UnicodeDemo

{

        publicstatic void main(String[] args) throws IOException

        {

                  //method_write();

                  method_read();

        }

        publicstatic void method_read()throws IOException

        {

                  InputStreamReaderisr=new InputStreamReader(newFileInputStream("gbk.txt"),"utf-8");

                  char[]buf=new char[10];

                  intlen=isr.read(buf);

                  Strings=new String(buf,0,len);

                  System.out.println(s);

        }

        publicstatic void method_write()throws IOException

        {

                  OutputStreamWriterosw=

                  newOutputStreamWriter(newFileOutputStream("utf.txt"),"utf-8");//(gbk.txt),"gbk"

                  osw.write("你好");

                  osw.close();

        }

}

我们通过代码打印“你好“分析

a) 创建的两个文件,看到UTF-86个字节,GBK占三个字节

b) 不同编码方式读取文件给的结果不一样,当拿GBK的字符用UTF8解析,打印为??

当拿UTF-8的字节用GBK解析,打印为浣犲ソ

 

字符的编码解码

编码:字符串变成字节数组

字符串编程字节数组,即:String--àbyte[];使用str.getBytes(String charsetName);方法

解码:字节数组变成字符串

字节数组变成字符串,即byte[]--àString;使用new Stringbyte[] b,String charsetName

 

我们通过代码来看看代码的编码解码:

importjava.util.*;

classEncodeDemo

{

public static void main(String[] args) throwsException

{

          String s="你好";

          //使用utf-8编码

          byte[] b=s.getBytes("utf-8");

          //使用此方法可以将数组换成字符串打印

          System.out.println(Arrays.toString(b));

          String str=new String(b,"utf-8");

          System.out.println("str="+str);

}

}

 

当我们使用Iso8859-1解码的时候,打印会出现乱码,我们可以再编码再解码,就可以打印正确的中文。这其实就是Tomcat的一种乱码的解决方式,它默认的就是iso8859-1。注意我们不能用UTF-8这样去做。

以下是乱码解决演示:

importjava.util.*;

classEncodeDemo

{

public static void main(String[] args) throwsException

{

          String s="你好";

          //使用gbk编码

          byte[] b=s.getBytes("gbk");

          //使用iso8859-1解码,出现乱码

          String str=newString(b,"iso8859-1");

          System.out.println("str="+str);

 

          //一旦遇到乱码,我们可以再用ISO8859-1编码,再用gbk解码

   byte[] b2=str.getBytes("iso8859-1");

          String s2=newString(b2,"gbk");

          System.out.println("s2="+s2);

          

}

}

 

特殊字:联通当我们用windows记事本写上联通,保存后再打开就会出现乱码。这是什么原因呢?这其实跟UTF-8解析内部有关,当遇到0,或者11010或者111010这三种,记事本会默认使用utf-8去打开,而联通这个字刚好是11010开头,所以系统默认用utf-8解析,出来就成了乱码。我们使用代码来看看联通这个字的二进制。

importjava.util.*;

classUnicodeDemo2

{

public static void main(String[] args) throwsException

{

          String s="联通";

          byte[]by=s.getBytes("gbk");

          for (byte b:by)

          {

                   System.out.println(Integer.toBinaryString(b&255));

          }

}

}

我们看到打印结果是:110000011010101011001101 10101000

根据结果,我们就知道了,原来系统把联通使用了utf-8去使用了。

原创粉丝点击