以下介绍IO包中扩展功能的流对象:基本都是装饰设计模式。
Java.io.outputstream.PrintStream:打印流
1:提供了更多的功能,比如打印方法。可以直接打印任意类型的数据。
2:它有一个自动刷新机制,创建该对象,指定参数,对于指定方法可以自动刷新。
3:它使用的本机默认的字符编码.
4:该流的print方法不抛出IOException。
构造函数。
PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn) :创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out) :创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush) :创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, Stringencoding) :创建新的打印流。
PrintStream(String fileName) :创建具有指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn)
只有输出目的为 流的时候 才能够设置 是否自动刷新 操作文件 则没有设置自动刷新需要手动刷新
其println()的有功能是 在字符串后面加上换行符号 (能够与Buffered的readLine()方法相呼应,TCP传输的细节)
PrintStream可以操作目的:1:File对象。2:字符串路径。3:字节输出流。
前两个都JDK1.5版本才出现。而且在操作文本文件时,可指定字符编码了。
当目的是一个字节输出流时,如果使用的println方法,可以在printStream对象上加入一个true参数。这样对于println方法可以进行自动的刷新,而不是等待缓冲区满了再刷新。最终print方法都将具体的数据转成字符串,而且都对IO异常进行了内部处理。
--------------------------------------------------------
PrintWriter:是字符流的子类,可以直接操作字符数据,同时也可以指定具体的编码。具备了PrintStream的特点同时,还有自身特点:
该对象的目的地有四个:1:File对象。2:字符串路径。3:字节输出流。4:字符输出流。
只有输出目的为 流的时候 才能够设置 是否自动刷新 操作文件 则没有设置自动刷新需要手动刷新
建议:开发时尽量使用PrintWriter。
构造方法中(目的)直接操作文件的第二参数是编码表。
(目的)直接操作输出流的,第二参数是自动刷新。
//读取键盘录入将数据转成大写显示在控制台.
BufferedReader bufr = new BufferedReader(newInputStreamReader(System.in));//源:键盘输入
//目的:把数据写到文件中,还想自动刷新。
PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//设置true后自动刷新
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());//转大写输出
}
//注意:System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。随时可以使用。当jvm结束了,这两个流就结束了。但是,当使用了显示的close方法关闭时,这两个流在提前结束了。
out.close();
bufr.close();
**********************************************************************************************************************
SequenceInputStream:序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止,并 补上文件结束的标致。
这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
构造方法
SequenceInputStream(InputStream s1,InputStream s2) 和并两个输入流为一 最简单
SequenceInputStream(Enumeration<? extendsInputStream> e) 哟要合并两个以上流 涉及到集合枚举
该对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。
但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。
ArrayList<FileInputStream> a = new ArrayList<FileInputStream>();
for (int i = 0; i < parts; i++) {
fis = new FileInputStream(new File(path,(i+1)+".part"));
System.out.println(i);
a.add(fis);
}
final Iterator it = a.iterator();//变量it在局部位置的内部类被访问 所以it必须为final
//Enumeration e = Collections.enumeration(a); 测试过不可以呀
Enumeration e = new Enumeration(){
@Override
public boolean hasMoreElements() {
// TODO Auto-generated method stub
return it.hasNext();
}
@Override
public Object nextElement() {
// TODO Auto-generated method stub
return it.next();
}};
SequenceInputStream ss = new SequenceInputStream(e);
FileOutputStream fos = new FileOutputStream(un);
byte[] buf = new byte[1024*1024];
int len = 0;
while ((len=ss.read(buf))!=-1) {
fos.write(buf, 0, len);
fos.flush();
}
合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。
切割大文件FileInputStream fis = new FileInputStream("E:/1998.rmvb");
FileOutputStream fos = new FileOutputStream("E:/1.part");
byte[] by = new byte[1024*1024];//创建一个存储大小为1M 的字节数组
int len = 0;
int count=1;
int i=0;
while ((len = fis.read(by))!=-1)
{
i++;
if(i==101)
{
count++;
fos = new FileOutputStream("E:/"+count+".part");
i=0;
}
fos.write(by,0,len);//字节数组内有多少 写多少 字节流不需要刷新即可把数据写入目标文件
}
fos.close();//养成关流的好习惯
fis.close();
类 ObjectOutputStream 实现了DataInput接口 能够写如完整的基本数据
注意被写入的对象需要实现Serializable
构造方法
ObjectOutputStream(OutputStreamout) 不可直接操作文件 FileInputStream是专门操作文件的字节流
创建写入指定OutputStream 的 ObjectOutputStream。
常用方法
void write(int val) 写入一个字节。
void writeInt(int val) 写入一个 32 位的 int 值。
void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。
类 ObjectInputStream
构造方法
ObjectInputStream(InputStreamin)
创建从指定InputStream 读取的 ObjectInputStream。
常用方法
int readInt() 读取一个 32 位的 int 值。
int read() 读取数据字节。
Object readObject() 从ObjectInputStream 读取对象。
ObjectOutputStream存的文件 只能用ObjectInputStream来读取
对象的序列化:目的:将一个具体的对象进行持久化,写入到硬盘上。
Serializable:给类定义标记,用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的,成员变动则UID变化。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号,static final longserialVersionUID = 42L; 依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。
注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中,无法被序列化存到文件。
如何将非静态的数据不进行序列化?用transient关键字修饰此变量即可。
****************************************************************************************************RandomAccessFile: 直接继承Object 实现DataInput,DataOutput能够读和写 能操作基本数据类型
特点:
1:该对象即可读取,又可写入。
2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组。
3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。
4:该对象操作的源和目的必须是文件。
5:其实该对象内部封装了字节读取流和字节写入流。
注意:实现随机访问,最好是数据有规律。该类不算是IO体系中的子类,而是直接继承自Object但是它是IO包中的成员,因为它具备读和写功能,内部封装了一个数组,而且通过指针对数组的元素进行操作,其实完成读写的原理就是内部封装了字节输入流和输出流。
构造方法
RandomAccessFile(File file, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
RandomAccessFile(String name, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
通过构造函数可看出 该类只能操作文件, 而且操作文件还有模式:只读r 读写 rw
如果模式为只读 r ,不会创建文件。会去读取一个已存在的文件,如果该文件不存在,则会出现异常
如果模式为 rw的话,当操作的文件不存在,会自动创建,如果存在不会覆盖
常用方法
long length() 返回此文件的长度。
int read() 从此文件中读取一个数据字节。
int read(byte[] b,int off, int len) 将最多 len 个数据字节从此文件读入 byte 数组。
int read(byte[] b,int off, int len) 将最多 len 个数据字节从此文件读入 byte 数组。
读取基本数据类型 readByte() readChar() readFloat() readDouble() readBoolean()
String readLine() 从此文件读取文本的下一行。
void seek(longpos) 此文件开头开始测量的指针偏移量,在该位置发生下一个读取或写入操作。
int skipBytes(intn) 尝试跳过输入的 n 个字节以丢弃跳过的字节。
void write(byte[]b) 将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。
void write(int b) 向此文件写入低8位的int。
void writeInt(intv) 按四个字节将 int 写入该文件,先写高字节。
void writeLong(longv) 按八个字节将 long 写入该文件,先写高字节。
********************************************************************************************
管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。
注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,而read方法是阻塞式的,没有数据的read方法会让线程等待。
类 PipedOutputStream
构造方法
PipedOutputStream(PipedInputStreamsnk) 创建连接到指定管道输入流的管道输出流。
常用方法
PipedOutputStream(PipedInputStreamsnk) 创建连接到指定管道输入流的管道输出流。
voidwrite(int b) 将指定 byte 写入传送的输出流。
类 PipedInputStream
PipedInputStream() 创建尚未连接的 PipedInputStream。
PipedInputStream(PipedOutputStreamsrc) 创建 PipedInputStream,使其连接到管道输出流 src。
PipedInputStream(PipedOutputStreamsrc, int pipeSize)
创建一个PipedInputStream,使其连接到管道输出流 src,并对管道缓冲区使用指定的管道大小。
常用方法
voidconnect(PipedOutputStream src) 使此管道输入流连接到管道输出流 src。
int read() 读取此管道输入流中的下一个数据字节。
int read(byte[] b,int off, int len) 将最多 len 个数据字节从此管道输入流读入 byte 数组。
protected void receive(int b) 接收数据字节。
**************************************************************************
ByteArrayInputStream:源:内存 在构造的时候,需要接收数据源,而且数据源是内存中的一个字节数组
ByteArrayOutputStream:目的:内存在构造的时候不要定义数据目的,因为该对象中已经内部封装了可变长度的字节数组,这就是数据目的地。
这两个流对象不涉及底层资源调用,操作的都是内存中数组,并没有使用系统资源,所以不需要close关闭。
ByteArrayOutputStream 主要方法
voidwriteTo(OutputStream out)
将此 byte 数组输出流的全部内容写入到指定的输出流参数中。
String toString()
使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串。
int size() 返回缓冲区的当前大小。
直接操作字节数组就可以了,为什么还要把数组封装到流对象中呢?因为数组本身没有方法,只有一个length属性。为了便于数组的操作,将数组进行封装,对外提供方法操作数组中的元素。
对于数组元素操作无非两种操作:设置(写)和获取(读),而这两操作正好对应流的读写操作。这两个对象就是使用了流的读写思想来操作数组。
//创建源:
ByteArrayInputStreambis = new ByteArrayInputStream("abcdef".getBytes());
//创建目的:
ByteArrayOutputStreambos = new ByteArrayOutputStream();
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++编码表
常见的编码表
ASCII:美国标准信息交换码。
用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表
用一个字节的8位表示。
GB2312:中国的中文编码表6000多字。两字节表示而且两字节的高位都是1兼容ASCII
GBK:中国的中文编码表升级,融合了更多的中文文字符号,一中文两字节。两万多
Unicode:国际标准码,融合了多种文字。
所有文字都用两个字节来表示,Java语言使用的就是unicode
UTF-8:最多用三个字节来表示一个字符。
在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。
程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。
你好 utf-8 >>> gbk 浣犲ソ
gbk >>> utf-8 ???
可以用来判断编码是utf-8还是gbk
String s = "钱学森";
byte[]b = s.getBytes();
for(int i = 0; i < b.length; i++) {
System.out.print(b[i]+",");
输出:-57,-82,-47,-89,-55,-83,
中文用两字节表示而且两字节的高位都是1即都是负数
字符流能不能用于对图片的操作,是不可以的。
原因 用字符流赋值图片的话 有可能无法打开 因为 字符流按默认编码表读取数据字符流读两字节数据(单字符)就会去查编码表返回中文字符 如果能查到编码表
对应的中文字符 那么读取的两字节数据不会变(编码不变) 如果查不到 那么就会去未知数据区域查编码会改变所读取的编码 那么数据就可能会产生变化
字节流 + 字符编码 = 字符流
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列
//为什么你好 gbk -> 编码 -> iso8859-1 ->解码 =乱码 -> iso8859-1 -> 编码 -> gbk ->解码 =你好
// 你好 utf-8 -> 编码 -> iso8859-1 ->解码 =乱码 -> iso8859-1 -> 编码 -> utf-8 ->解码 =你好
// 你好 utf-8 -> 编码 -> gbk ->解码 =乱码 -> gbk -> 编码 -> utf-8 ->解码 =你好
//不可以?? 你好 gbk -> 编码 -> utf-8 ->解码 =乱码 -> utf-8 -> 编码 -> gbk ->解码 = 乱码
原数据 编码存入文件(文件中的是编码后数据) 读取通过对照同一编码表 读取 还原数据
原数据 编码存入文件(文件中的是编码后数据) 读取通过对照另一个编码表 读取 当遇到的编码字节数据 找不到对应的符号 那么就会 拿着编码 去 未知字符区查找 字符 得出二进制数据 原数据改变
原二进制数据 通过编码表转码 的到编码数据 gbk两字节二进制数据 转码为 两字节编码数据 utf-8 三字节原二进制数据 通过utf-8编码表 转码为三字节编码数据
在 Java 中,“字符串”与“字节串”之间可以方便地按照指定编码规则进行转化:
byte[] b = "中文123".getBytes("GB2312");// 从字符串按照 GB2312 得到字节串
System.out.println(b.length); // 得到长度为7个 (D6,D0,CE,C4,31,32,33)
Stringstr = newString(b,"GB2312");// 从字节串按照 GB2312 得到字符串
System.out.println("中文123".length()); // 得到长度为5,因为是5个字符
在 java.io.* 包里面有很多类,其中,以“Stream”结尾的类都是用来操作“字节串”的类,以“Reader”,“Writer”结尾的类都是用来操作“字符串”的类。任何一个文件保存到硬盘上的时候,都是以字节为单位保存的,当要把“字符串”保存到硬盘上的文本文件,必然要选择一种编码。
有两种方法来指定编码:
Stringstr = "中文123";
// 第一种办法:先用指定编码转化成字节串,然后用 Stream 类写入
OutputStream os =newFileOutputStream("1.txt");
byte[] b = str.getBytes("utf-8");
os.write(b);
os.close();
// 第二种办法:构造指定编码的 Writer 来写入字符串
Writer ow=newOutputStreamWriter(newFileOutputStream("2.txt"),"utf-8");
ow.write(str);
ow.close();
// 最后得到的 1.txt 和 2.txt 都是 9 个字节。(汉字在 utf-8编码规则中占3字节