Java基础:IO包中的其他类及字符编码

来源:互联网 发布:网络最新搞笑歌曲 编辑:程序博客网 时间:2024/05/01 05:00

第一部分:打印流

|---- PrintWriter(字符打印流)

|---- PrintStream(字节打印流)

一、概述

      打印流中提供了打印方法,可以对各种类型数据都原样打印,而write方法只能写最低8位。

二、打印流的特点

1、PrintStream  构造函数可以接收的参数类型

1)File对象

2)字符串路径(String)

3)字节输出流(OutputStream)

2、PrintWriter  构造函数可以接收的参数类型

1)File对象

2)字符串路径(String)

3)字节输出流(OutputStream)

4)字符输出流(Writer)

3、自动刷新

        打印流的构造函数(如:PrintWriter(OutputStream out,boolean autoFlush))如果接收的是一个输出流对象,且autoFlush=true时,该对象调用 void println(各种数据类型) 方法会自动刷新流的缓冲。println方法不仅可以打印到控制台(System.out)上,还可以往文件中打印数据。

web开发就是使用PrintWriter将数据一条一条的打到客户端。

三、示例

package demo;import java.io.BufferedReader;import java.io.FileWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;/* * 需求:打印流演示 */public class PrintDemo {public static void main(String[] args) throws IOException{//读取键盘录入BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//往文件中打印PrintWriter pw2 = new PrintWriter(new FileWriter("print.txt"),true);String line = null;while((line=bufr.readLine())!=null){if("over".equals(line))break;pw2.println(line.toUpperCase());}bufr.close();pw1.close();pw2.close();}}
运行结果:

          


第二部分:序列流(SequenceInputStream)

一、概述

        SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。该流没有对应的输出流。

构造函数:

SequenceInputStream(Enumeration<? extends InputStream> e)

SequenceInputStream(InputStream s1, InputStream s2)

图解:


二、切割文件与合并文件

由于SequenceInputSteam构造函数中需要一个Enumeration参数(来自Vector集合),因此使用时需要依赖Vector集合。

示例代码:

package demo;import java.io.*;import java.util.*;/*需求:切割文件与合并文件切割文件:每读一段数据就写到新建的流对象中合并文件:SequenceInputStream */public class SequenceInputStreamDemo {public static void main(String[] args)  throws IOException{//splitFile();//切割文件sequenceFile();//合并文件}//功能:切割文件public static void splitFile() throws IOException{//创建输入流与文件相关连FileInputStream in = new FileInputStream("E:\\音乐\\原声大碟\\Interstellar\\disc 02\\06 - No Time For Caution.mp3");//输出流只建立引用,不建立对象FileOutputStream out = null;//切割的大小1Mbyte[] buf = new byte[1024*1024];int len = 0;int count = 1;//命名代号while((len=in.read(buf))!=-1){//每读1M数据就写到一个新的输出流中out = new FileOutputStream("D:\\Java\\day\\workspace\\Exam\\SplitFile\\"+(count++)+".part");out.write(buf,0,len);//写入有效数据//数据写完后关流out.close();}//关闭输入流in.close();}//功能:合并文件public static void sequenceFile() throws IOException{//创建Vector集合Vector<FileInputStream> v = new Vector<FileInputStream>();//将需要合并的流对象存入Vector集合中for(int x=1;x<=10;x++){v.add(new FileInputStream("D:\\Java\\day\\workspace\\Exam\\SplitFile\\"+x+".part"));}//获取Vector集合的Enumeration对象Enumeration<FileInputStream> en = v.elements();//将Enumeration对象作为参数传递个SequenceInputStream的构造函数(将多个输入流合并成一个流)SequenceInputStream in = new SequenceInputStream(en);//创建一个输入流并将合并流中的数据写到该输出流中FileOutputStream out = new FileOutputStream("D:\\Java\\day\\workspace\\Exam\\SplitFile\\time.mp3");int len = 0;byte[] buf = new byte[1024*1024];while((len = in.read(buf))!=-1){out.write(buf,0,len);}//关闭资源in.close();out.close();}}/* * 由于合并流需要 Enumeration 作为参数,而Victor集合的效率又低, * 因此可以定义一个类实现Enumeration接口 并覆盖它的两个方法boolean hasMoreElemenets() ,Object nextElement() * 或者采用匿名内部类的方式 *  * 最后将子类对象传递给合并流的构造函数 */class meiju implements Enumeration{public boolean hasMoreElements(){return true;//it.hasNext()}public Object nextElement(){return new Object();//it.next()}}
运行结果:


第三部分:操作对象(对象的持久化)

|---- ObjectInputStream

|---- ObjectOutputStream

一、概述

1、对象存在与堆内存中,当程序运行结后,内存释放,对象就不存在了。而流可以将对象(中的特有数据)写到硬盘文件中,当想要操作该对象中特有数据时,直接创建流与其相关联即可。

2、需要被序列化(持久化)的对象必须要实现 Serializable 接口,标记接口。

该接口中没有方法,只是给序列化的类一个ID标识:UID

static final long serialVersionUID = 42L;

UID是根据成员计算出来的,如果在更改成员的属性或者个数时,UID会发生变化。因此将UID设定为一个固定值是最优的。

3、注意事项

1)只能序列化堆内存中的成员,不能序列化静态成员。

2)若不想序列化非静态成员,则加关键字 transient 修饰,保证该值只在堆内存中存在而不在硬盘文件中存在。

二、常用方法

构造函数:

ObjectInputStream(InputStream in)

ObjectOutputStream(OutputStream out)

特有方法:
输入流:

int readInt()        读取一个 32 位的 int 值(还可以操作其他基本数据类型)

Object readObject()        从 ObjectInputStream 读取对象

输出流:

void writeInt(int val)        写入一个 32 位的 int 值(还可以操作其他基本数据类型)

void writeObkect(Object obj)        将指定的对象写入 ObjectOutputStream

三、示例

示例代码:

package demo;import java.io.*;/* * 需求:对象的序列化演示 */public class ObjectStream {public static void main(String[] args) throws Exception{//writeObj();readObj();}//功能:读取硬盘文件中的对象private static void readObj() throws Exception {//创建一个输入字节输入流对象与制定的文件相关联FileInputStream in = new FileInputStream("Person.object");//创建从指定 字节输入流 读取的 ObjectInputStreamObjectInputStream objin = new ObjectInputStream(in);//兑取对象,并强制转换为Person类型Person p = (Person)objin.readObject();//输出对象System.out.println(p);//关闭资源in.close();objin.close();}//功能:对象持久化private static void writeObj() throws IOException {//创建一个字节输出流对象与文件相关联FileOutputStream out = new FileOutputStream("Person.object");//创建写入指定 字节输出流 的 ObjectOutputStreamObjectOutputStream objout = new ObjectOutputStream(out);//向文件中写入对象(对象的持久化)objout.writeObject(new Person("diudiu",20,"kr"));//关闭资源out.close();objout.close();}}/* * 需要被序列化(持久化)的对象必须实现 Serializable 接口,给对象增加一个UID 标识 * 该接口中没有方法,只是给对象增加一个标识 ,该标识是根据类中的成员计算出来的 *  * 静态成员不能被序列化 *  * 被transient修饰的非静态成员也不能被序列化 */class Person implements Serializable{//设定UID为一个固定值private static final long serialVersionUID = 1L;String name;//被transient修饰的非静态成员不能被序列化transient int age;//被static修饰的成员不能被序列化static String country = "cn";Person(String name,int age,String country){this.name = name;this.age = age;this.country = country;}//覆盖toString方法,方便对Perosn对象的打印输出public String toString(){return this.name+"--"+this.age+this.country;}}

运行结果:


第四部分:管道流

|---- pipedInputStream

|---- PipedOutputStream

一、概述

        管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。

注意:read方法是阻塞式方法,没有读到数据就会一直等待。

构造函数:

PipedInputStream()        创建尚未连接的 PipedInputStream
PipedOutputStream()        创建尚未连接到管道输入流的管道输出流

注意:未连接的管道流需要通过connect方法连接

PipedInputStream(PipedOutputStream src)         创建 PipedInputStream,使其连接到管道输出流 src
PipedOutputStream(PipedInputStream snk)         创建连接到指定管道输入流的管道输出流

二、示例

示例代码:

package demo;import java.io.*;/* * 需求:演示管道流:流中与多线程相结合的流。 *///管道输入流的线程class Read implements Runnable{private PipedInputStream in;Read(PipedInputStream in){this.in = in;}public void run(){try{//从管道输出流中读取数据byte[] buf = new byte[1024];System.out.println("读取前阻塞");int len = in.read(buf);//read方法时阻塞式方法,没有数据就会一直等System.out.println("读取到数据,阻塞结束,数据如下:");System.out.println(new String(buf,0,len));in.close();}catch(IOException e){throw new RuntimeException("读取失败");}}}//管道输出流的线程class Write implements Runnable{private PipedOutputStream out ;public Write(PipedOutputStream out) {this.out = out;}public void run(){try{//将数据写到管道输入流中System.out.println("写数据前先睡5秒");Thread.sleep(5000);out.write("管道流来了".getBytes());System.out.println("数据写完");out.close();}catch(Exception e){throw new RuntimeException("写出失败");}}}public class PipedStream {public static void main(String[] args) throws Exception{//创建未连接的管道输入流与输出流PipedInputStream in = new PipedInputStream();PipedOutputStream out = new PipedOutputStream();//将两个流连接起来in.connect(out);//将管道输入流作为参数传递给Runable接口子类对象Read read = new Read(in);//将管道输出流作为参数传递给Runable接口子类对象Write write = new Write(out);//新建并开启两个线程new Thread(write).start();new Thread(read).start();}}
运行结果:



第五部分:随机访问文件(RandomAccessFile)

一、概述

1、此类的实例支持对随机访问文件的读取和写入

        随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

2、RandomAccessFile不算IO体系中的子类,而是直接继承至Object

        该类内部封装了字节流,让其具备了读写功能。

二、方法

1、构造方法:

RandomAccessFile(File file, String mode) 

          创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定

RandomAccessFile(String name, String mode) 

          创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称

从构造方法可以看出:

1)该类只能操作硬盘上的文件。其他流对象还可以操作内存,标准输入输出等。

2)操作文件有模式,mode值与含义如下:


注意:

1)若mode为“r”,不会在硬盘中创建文件,会去读取一个已存在的文件。若该文件不存在,则会发生异常。

2)若mode为“rw”,文件不存在的会创建,文件存在则不会被覆盖,而其他输出流在创建时会覆盖。

2、常用方法:

write(int val)        写入该数的最低8位(1个字节)

writeInt(int val)        写入该数的32位(4个字节)(还可以操作其他数据类型)

int read()        从文件中读取1个字节

int readInt()        从文件中读取4个字节(还可以操作其他数据类型,读写得对应)


void seek(long pos)        设置指针到指定位置(可以任意移动)

int skipBytes(int n)        指针跳过n个字节(不能忘回跳,只能往前跳)

long getFilePointer()        返回此文件中的当前偏移量 

3、示例

示例代码:

package demo;import java.io.*;public class RandomAccessFileDemo {public static void main(String[] args)  throws Exception{writeFile();write2File();readFile();}//读取文件public static void readFile() throws Exception {//创建只具备读取功能的随机访问文件对象RandomAccessFile in = new RandomAccessFile("RandomFile.txt","r");//通过操作指针来获取任意位置的数据(指针要有规律)in.seek(8*1);//共读取4个字节(两个汉字)存放到数组中byte[] buf = new byte[4];in.read(buf);String name = new String(buf);//readInt读的是4个字节,read方法是 读的一个字节再&255int age = in.readInt();//读取年龄System.out.println("name = "+name);System.out.println("age = "+age);}public static void write2File() throws Exception{//创建只具备读写功能的随机访问文件对象(不会覆盖原文件,而一般输出流会覆盖)RandomAccessFile out = new RandomAccessFile("RandomFile.txt","rw");//通过设置指针来指定要插入数据的位置,若该位置上有数据,则覆盖原来的数据out.seek(8*3);//写2个汉字(4个字节)out.write("赵六".getBytes());//写4个字节,write写的是32位中的最低8位out.writeInt(99);out.close();}public static void writeFile() throws Exception{RandomAccessFile out = new RandomAccessFile("RandomFile.txt","rw");//每次都写8个字节,4个字节代表汉字,4个字节代表年龄out.write("张三".getBytes());out.writeInt(97);out.write("李四".getBytes());out.writeInt(98);out.close();}}
        

三、总结

1、流在操作数据时,是从头到尾的,而RandomAccessFile可以通过指针来(随机)调整读写的位置。在写入数据时,若写入的位置上存在数据,则其被覆盖,但是不会覆盖其他位置的数据。

2、实际应用:数据分段写入(如:多线程下载)

图解:


注意:

1)一般流在采用多线程读写时,数据不连续;而RandomAccessFile可以将数据分段后通过设置指针来分段写入数据,且数据是连续的。

2)多线程控制时,seek方法设置的指针要有规律(按指定倍数增减)。

第六部分:操作基本数据类型的流对象

|---- DataInputStream

|---- DataOutputStream

一、概述

1、简介

DataInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。

DataOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。

2、构造方法

DataInputStream(InputStream in)        使用指定的底层 InputStream 创建一个 DataInputStream

DataOutputStream(OutputStream out)        创建一个新的数据输出流,将数据写入指定基础输出流

二、特有方法

        由于流中的read方法读取的是一个整数的1个字节,而write方法写的也是一个整数的1个字节。当需要写入所有字节时,用以下方法:

1、DataInputStream( 操作基本数据类型)

boolean readBoolean()   

byte readByte() 

char readChar() 

double readDouble() 

float readFloat() 

int readInt()        

long readLong() 

short readShort()

String readUTF()         使用 UTF-8 修改版编码将从流中读取数据

2、DataOutputStream(操作基本数据类型)

void writeBoolean(boolean v) 
void writeByte(int v) 
void writeChar(int v) 
void writeDouble(double v) 
void writeFloat(float v) 
void writeInt(int v) 
void writeLong(long v) 
void writeShort(int v) 
void writeUTF(String str)        使用 UTF-8 修改版编码将一个字符串写入基础输出流

3、示例

package demo;/* * 需求:演示操作基本数据类型的流对象 *  * 注意:读写要对应 */import java.io.*;public class DataStream {public static void main(String[] args) throws IOException{writeData();readData();}public static void readData() throws IOException{//创建一个数据输入流对象与指定的输人流相关联DataInputStream in = new DataInputStream(new FileInputStream("data.txt"));//依次读取int double boolean型数据int a = in.readInt();double b = in.readDouble();boolean c = in.readBoolean();System.out.println(a);System.out.println(b);System.out.println(c);//关闭资源in.close();}public static void writeData() throws IOException{//创建一个数据输出流对象与指定的输出流相关联DataOutputStream out = new DataOutputStream(new FileOutputStream("data.txt"));//依次写入int double booleanout.writeInt(256);out.writeDouble(23.24);out.writeBoolean(true);//关闭资源out.close();}}
运行结果:


注意:读写的操作要对应。


第七部分:操作数组的流对象

|---- ByteArrayInputStream(操作字节数组)

|---- ByteArrayOutputStream

|---- CharArrayReader(操作字符数组)

|---- CharArrayWriter

|---- StringReader(操作字符串)

|---- StringWriter

一、概述

ByteArrayInputStream :包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。 

ByteArrayOutputStream:此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。 

关闭上述流 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。

由与上述流对象均未操作系统资源,故不用进行关闭。

二、构造方法

源:buf

ByteArrayInputStream(byte[] buf)         创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组
ByteArrayInputStream(byte[] buf, int offset, int length)         创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组


目的:该对象内部封装的数组

ByteArrayOutputStream()        创建一个新的 byte 数组输出流。 
ByteArrayOutputStream(int size)        创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)


其他方法:

byte[] toByteArray()        创建一个新分配的 byte 数组
String toString()        使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串

void writeTo(OutputStream out)        将此 byte 数组输出流的全部内容写入到指定的输出流(只有该方法会抛IOException)

三、操作字符数组与字符串

        二者的输入流在构造时,需要接收相同类型的数组或字符串;输出流在构造时,可以不用定义数据目的地,因为输出流对象中已经封装了可变长度的数组,这就是数据的目的地。


第八部分:字符编码

一、概述

字符流的出现是为了操作字符,更重要的是加入了编码转换;

通过子类转换流完成:

InputStreamReader

OutputStreamWriter

在两个对象构造时可以加入字符集。

二、编码表的由来

计算机只能识别二进制数据,早期由来是电信号;

为了方便应用计算机,它可以识别各个国家的文字;

就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。

三、常见的编码表


注意:

1、Java语言char类型使用的是Unicode

2、UTF-8每一个字节的开头都加有标识头,加完后很容器区分。UTF-8中汉字用3个字节表示。

3、中文码表兼容ASCII码表(中文中有拼音)。

四、转换流的编码应用

1、可以将字符以指定编码格式存储。

2、可以对文本数据指定编码格式来解决。

3、指定编码的动作由构造函数完成,构造方法如下:

InputStreamReader(InputStream in, String charsetName) 
          创建使用指定字符集的 InputStreamReader
OutputStreamWriter(OutputStream out, String charsetName) 
          创建使用指定字符集的 OutputStreamWriter

示例代码:

package demo;/* * 需求:转换流演示,操作编码表 */import java.io.*;public class DataStream {public static void main(String[] args) throws IOException{encodeStream();}public static void encodeStream() throws IOException{//定义字符输出流(转换流)与指定的文件相关联,并指定了编码表UTF-8(编码)OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("encode.txt"),"UTF-8");String message = "你好";out.write(message);System.out.println("UTF-8:"+message);out.close();//定义字符输入流(转换流)与指定的文件相关联,并指定编码表GBK(解码)InputStreamReader in = new InputStreamReader(new FileInputStream("encode.txt"),"GBK");int len = 0;char[] by = new char[10];//字符流操作字符数组System.out.print("GBK:");while((len=in.read(by))!=-1){System.out.println(new String(by,0,len));}in.close();}}
运行结果:


注意:FileReader与FileWriter是转换流的子类,其采用的是平台的默认编码。

五、编码与解码

1、定义

编码:字符串转换成字节数组    String→byte[]

解码:字节数组变成字符串        byte[]→String

2、方法(String类中)

编码:

byte[] getBytes() 
          使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中 
byte[] getBytes(Charset charsetName) 
          使用给定的 charset 将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组 

解码:

String(byte[] bytes) 
          通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String
String(byte[] bytes, String charsetName) 
          通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String
String(byte[] bytes, int offset, int length, String charsetName) 
          通过使用指定的字符集解码指定的 byte 子数组,构造一个新的 String 


示例代码1:

package com.itheima;import java.io.UnsupportedEncodingException;/* * 演示编码与解码 */import java.util.*;public class EncodeTest {public static void main(String[] args) throws UnsupportedEncodingException {charsetName1();charsetName2();}//GBK编码public static void charsetName1() throws UnsupportedEncodingException {byte[] value1 = "你好".getBytes("GBK");System.out.println(Arrays.toString(value1));//GBK解码System.out.println(new String(value1,"GBK"));//UTF-8解码System.out.println(new String(value1,"UTF-8"));}//UTF-8编码public static void charsetName2() throws UnsupportedEncodingException {byte[] value1 = "你好".getBytes("UTF-8");System.out.println(Arrays.toString(value1));System.out.println(new String(value1,"GBK"));System.out.println(new String(value1,"UTF-8"));}}

运行结果:


图解:



示例代码2:

package demo;/* * 需求:编码错误,再解码还原数据 *  * GBK编码→ISO8859-1解码ISO8859-1编码→GBK解码 */import java.io.*;import java.util.Arrays;public class EncodeDemo {public static void main(String[] args) throws IOException{encodeDemo();}//GBK编码→ISO8859-1解码ISO8859-1编码→GBK解码public static void encodeDemo()  throws IOException{//编码过程    字符串 ---> 字节数据String src = "你好";byte[] by = src.getBytes("GBK");System.out.println(Arrays.toString(by));//解码过程   字节数组 ---> 字符串   解码依赖于编码后得到的数据String dis = new String(by,"ISO8859-1");System.out.println(dis);//再编码 ,针对不识别中文的 ISO8859-1(可以用其再编码)byte[] newBy = dis.getBytes("ISO8859-1");System.out.println(Arrays.toString(newBy));//再解码,GBK解码System.out.println(new String(newBy,"GBK"));}}
图解:


注意:

1)如果编码编错了,就无法再解码了。

2)之所以可以再解码是因为ISO8859-1不识别中文,解码的时候保持了数据的原样性。如果开始使用UTF-8解码,由于UTF-8支持中文,解码之后数据就发生了变化,不再是原有数据。

3)Tomcat服务器默认编码就是ISO8859-1,当Tomcat服务器出现乱码时,可以再解码还原数据。

4)用记事本打开文件是解码,因为电脑中的数据都是二进制文件。

3、联通现象

在记事本中存入联通,再次打开出现乱码。

示例代码:

package demo;/* * 需求:联通现象解释 */import java.io.*;import java.util.Arrays;public class EncodeDemo {public static void main(String[] args) throws IOException{byte[] value = "联通".getBytes("GBK");for(byte v:value){//获取2进制表示形式System.out.println(Integer.toBinaryString(v&255));}}}
运行结果:

                

现象解释:

UTF-8有自己的标识头,如下:


        联通存入记事本时是按GBK形式存的,而硬盘上的联通的二进制数据刚好符合这个UTF-8标识头的规则,再次打开记事本时其就按UTF-8解码,导致乱码的出现。

0 0
原创粉丝点击