IO中的流对象 |
||
InputStream OutputStream |
字节流 |
|
BufferedInputStream BufferedOutputStream |
字节缓冲区 |
|
ByteArrayInputStream ByteArrayOutputStream |
操作字节数组 |
|
DataInputStream DataOutputStream |
|
|
FileInputStream FileOutputStream |
操作图片、音视频等文件 |
|
FilterInputStream FilterOutputStream |
|
|
ObjectInputStream ObjectOutputStream |
操作对象 |
|
PipedInputStream PipedOutputStream |
|
|
|
|
|
Reader Writer |
字符流 |
|
BufferedReader BufferedWriter |
字符流缓冲区 |
|
CharArrayReader CharArrayWriter |
操作字符数组 |
|
StringReader StringWriter |
操作字符串 |
|
FileReader FileWriter |
操作文本文件的 |
|
InputStreamReader OutputStreamWriter |
转换流 |
|
PipedReader PipedWriter |
|
|
FilterReader FilterWriter |
|
|
|
|
|
LineNumberInputStream |
|
|
LineNumberReader |
|
|
|
|
|
PrintStream |
字节打印流 |
|
PrintWriter |
字符打印流 |
|
|
|
|
PushbackInputStream |
|
|
PushbackReader |
|
|
|
|
|
SequenceInputStream |
|
|
StringBufferInputStream |
操作字符串的字节流 |
|
|
|
|
Console |
|
|
File |
用于封装文件 |
|
FileDescriptor |
|
|
FilePermission |
|
|
ObjectInputStream.GetField |
|
|
ObjectOutputStream.PutField |
|
|
ObjectStreamClass |
|
|
ObjectStreamField |
|
|
RandomAccessFile |
|
|
SerializablePermission |
|
|
StreamTokenizer |
|
|
目的 源 |
内存 |
文件 |
控制台 |
Sokcet |
内存 |
|
|
|
|
文件 |
|
|
|
|
Socket |
|
|
|
|
一、字符流
1、 FileWriter
(1)、FileWriter
FileWriter的继承结构是:
lang.object>io.Writer> io.OutputStreamWriter
构造方法摘要 |
|
FileWriter(File file) |
|
FileWriter(File file, boolean append) |
|
FileWriter(FileDescriptor fd) |
|
FileWriter(String fileName) |
|
FileWriter(String fileName, boolean append) |
|
从类 java.io.OutputStreamWriter 继承的方法 |
close, flush, getEncoding, write, write, write |
从类 java.io.Writer 继承的方法 |
append, append, append, write, write |
使用FileWriter对象,写入数据的步骤:1 导包,2 创建一个FileWriter对象。需要注意的是:该对象一被初始化就必须要有要操作的对象,如果该目录下已有指定的要操作的同名文件,原有文件将被覆盖。3 使用write方法向目标文件中写入数据fw.write("abc"); 4 刷新fw.flush() ; 5 fw.write("def") ;会写在abc后面;6 fw.flush();关闭流资源,关闭前会刷新fw.close();
(2)、文件的续写,
API中FileWriter有构造函数FileWriter(String fileName, boolean append) 。可用于在文件的原有数据上加入数据,而不去覆盖原文件。如果参数append为 true,则将数据写入文件末尾处,而不是写入文件开始处。
(3)、文本文件的读取方式1,读取单个字符就操作
Reader不用刷新,使用Reader的read方法读取的是字符。可以一次读取单个字符然后操作,或是将读取到的字符缓存入字符数组然后操作,不能直接读字符串。Reader使用的是默认的字符编码,使用System.getPorperties()可以得到系统信息。read()返回类型是int 可以根据需要将其强转成(char)。
特点:此方法会自动往下读,读到流的末尾返回-1,并且一直阻塞
使用read()方法读取一次读取单个字符。示例代码
FileReader fr = new FileReader(c:\\demo.txt);
int ch = 0 ;
while((ch = fr.read())!=-1){
//对读取的字符进行操作
}
(4)、文本文件的读取方式2,
读取单个字符,将其存入指定大小的字符数组,待将数组装满,再对结果操作
read(char[] cbuf) 返回的是读到的字符个数,数据类型是int 。使用此方法可以将数据缓冲入cbuf中。
通常为了方便,要将得到的cbuf转换为String类型。方法是new String(cbuf,0,num) ,其中num是read的返回值,这样就将字节数组cbuf中的0~num个字节转成了一个字符串,num是读到的字符个数,即cbuf中的有效字符个数,避免了无效字符的产生。
特点:读到末尾返回-1一直阻塞
示例代码
FileReader fr = new FileReader(c:\\demo.txt);
//定义一个指定大小的字符数组,用以缓存read读到的字符
byte[] cbuf = new byte[1024] ;
int num = 0 ;
while((num = fr.read(cbuf))!=-1){
//对读取的字符进行操作
}
(5)、拷贝文本文件
2 bufferedWriter bufferedReader
(1)、BufferedWriter BufferedReader
字符流缓冲区1、缓冲区的出现提高了对数据的读写效率,在创建缓冲区钱,要操作的流对象必须存在。2、对应类:BufferedWriter和BufferedReader3、缓冲区使用时要结合流4、在流的基础上对流的功能进行了增强5、出现了新的方法。
BufferedWriter BufferedReader是Writer和Reader的子类。继承了它们的读写方法,同时有自己更加功能更实用的方法。
BufferedWriter的void write(int c) ,写入单个字符,write(char[] cbuf, int off, int len) 写入字符数组的某一部分,write(String s, int off, int len) 写入字符串的某一部分,newLine()写入一个行分隔符,这个方法在各种平台通用。有自己的刷新的关闭方法。
BufferedReader的readLine()方法可以一次读取一行,但不包含终止符,如果读到流的末尾返回null。他的结束标记是回车符,返回类型是String。
(2)、通过缓冲区复制文本文件,
readLine方法读取的数据不包含行终止符,所以读取一行后,要加入换行符;
并且写入后一定要刷新,如果不刷新,数据将保留在缓冲区中,刷新后数据才被写进目标;
缓冲区提高了FileWriter的效率,但真正和文件相关联并对文件进行操作的是FileWriter缓冲区的关闭,实际上关闭的是调用底层资源的流对象。
(3)、readLine()方法的原理,
无论是读一行,获者读取多个字符。都必须在硬盘上一个一个字符读取,所以最终使用的还是一次读取一个的read方法,。readLine方法内部封装了一个数组,使用read方法读取一个字符后,并没有将这个字符返回给下一步的操作者,而是将其存储在数组中,直到读到换行符的\r时,再调用一次read方法读取一个字符,如果这个字符是\n,就将数组中的数据一次性返回给下一步的操作者。
4、MyBufferedReader
MyBufferedReader{
private Reader r ;
MyBufferedReader(Reader r){
this.r = r ;
}
public String myReadLine(){
StringBuilder sb = new StringBuilder();
int ch;
while((ch = read() )!=-1) {//使用传入的功能
if(ch == ’\r’)
coutinue;
if(ch == ‘\n’){
return sb.toString();
}
else
sb.append((char)ch); //添加前要转换为char,
}
return null ;
}
}
2 装饰设计模式
(1)、装饰设计模式,
当想要对已有的对象的功能进行增强时,可以定义一个类,将想要增强的类传入,并基于已有的功能,提供新的更强大的功能 。这种方法我们称之为装饰设计模式。增强类称之为装饰类。
装饰类基于被装饰类,并且通常装饰类和被装饰类属于同一个体系。
(2)、装饰和继承的区别
装饰设计模式的由来
是专门用于读取文本数据的类,它有一些子类。在使用MyReader子类读文本时,发现效率低,想到了缓冲技术,由MyReader的子类衍生出了一些用于缓冲的子类。
下面是它的继承体系。
MyReader
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
|--MyDataReader
|--MyBufferDataReader
如果MyReader的每个直接子类都需要增强,这样产生的继承体系,臃肿复杂,这个时候就需要对现有的体系进行优化。思路是可以定义一个类,将需要缓冲的MyReader的直接子类作为参数传入。
MyReader直接子类原来的功能是读,定义的装饰类增强了读取的功能,所以装饰类的功能也是读取,因此也应该是MyReader体系中的一员,所以它应该继承MyReader。
class MyBufferedReaer() extends MyReader
{
private Reader r ;
MyBufferedReaer(MyReader r)
{
this.r = r ;
}
//增强功能
}
这样,原来的体系就变得简单起来,MyReader直接子类和装饰类的关系由继承变成了组合
MyReader
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferBufferReader
装饰类对原有对象的功能进行了增强,它具备的功能和原有对象的功能都可以达到同样的目的。但是装饰类的功能使用起来更加方便。
所以装饰类和被装饰类通常都是属于同一个体系.
(3)、自定义装饰类
在定义装饰类时,定义的增强功能因为原有功能而有可能产生异常时,不能捕捉,而应该抛出,让调用者处理。
4 LineNumberReader:
(1)、LineNumberReader
LineNumberReader是BufferedReader子类,是可以跟踪行号的缓冲字符输入流。
此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。并且有readLine()方法。
(2)、MyLineNumberReader
import java.io.*;
class MyLineNumberReader extends BufferedReader
{
private int lineNumber ;
MyLineNumberReader(Reader r)
{
super(r) ;
}
public String myReadLine() throws IOException
{
//每调用一次myReadLine() lineNumber自增1 ;
lineNumber++ ;
return super.readLine();
}
//特有方法
public void setLineNumber(int lineNumber)
{
this.lineNumber = lineNumber ;
}
public int getLineNumber()
{
return lineNumber;
}
}
二 字节流
1 FileInputStream FileOutputStream:
字节流的read方法读取的是字节,可以读取单个字符,或是将读取的字符存入字节数组。available()方法返回字符的个数( \r 和\n分别是一个字节)。可以使用这个方法定义一个大小等于源的缓冲区。
(1)、字节流File读写操作
字节流write方法不用刷新。
一次读一个字节
FileInputStream fis = new FileInputStream(“c:\\demo.txt”);
FileOutputStream fos = new FileOutputStream(c:\\demo_1.txt) ;
int len = 0 ;
while((len= fis.read())!=-1){
fos.write(len);
}
读入字节数组中,然后写入目标
FileInputStream fis = new FileInputStream(“c:\\demo.txt”);
FileOutputStream fos = new FileOutputStream(c:\\demo_1.txt) ;
byte[] b = new byte[1024];
int num = 0 ;
while((num= fis.read(b))!=-1){
fos.write(b,0,num);
}
available()方法的使用
FileInputStream fis = new FileInputStream(“c:\\demo.txt”);
FileOutputStream fos = new FileOutputStream(c:\\demo_1.txt) ;
byte[] b = new byte[fis.available()];
fis.read(b);
fos.write(b);
(2)、拷贝图片
2 BufferedInputStream BufferedOutputStream:
(1)、字节流缓冲区
(2)、自定义字节流的缓冲区 read和write的特点
读取字节时,如果读到连续八个1,连续的八个1对应的是整数-1,程序会错误地以为是结束标记,会停止读取,导致目标文件不完整,复制失败。
为什么返回的是int而不是byte
11111111 ——>提升为一个int类型,那不还是-1吗?是-1的原因是,在八个1前面补了1,那么在八个1前面补0,就可以避免这种-1,即可以保留原字节数据不变,由可以避免-1的出现
所以用int接收byte,将其类型提升。然后使用&255的方式,在原数据前补0 ,避免了读到连续八个一,等于-1,和结束标记相等 的情况。如下图
byte:-1 ——> int:-1 ;
00000000 00000000 00000000 11111111 255,
11111111 11111111 11111111 11111111 -1
取最后4位:&15() 取最后8位&255
import java.io.*;
class MyBufferedInputStream
{
private InputStream in ;
private byte[] buf = new byte[1024*1024];
private int pos = 0 , count = 0;
MyBufferedInputStream(InputStream in)
{
this.in = in ;
}
//一次读一个字节,从缓冲区(字节数组)获取
public int myRead() throws Exception
{
//通过in对象读取硬盘上数据,并存储buf中
if(count == 0 )
{
count = in.read(buf);
if(count<0)
return -1;
pos = 0 ;
byte b = buf[pos];
count -- ;
pos++ ;
return b&255/*255的十六进制表现形式*/ ;
}
else if(count>0)
{
byte b = buf[pos];
count -- ;
pos++ ;
return b&255;
}
return -1 ;
}
public void myClose()throws Exception
{
in.close();
}
public static void copyMp3()throws Exception
{
MyBufferedInputStream bis =
new MyBufferedInputStream(new FileInputStream("c:\\王麟 - 当爱情离开的时候.mp3"));
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream("c:\\王麟 - 当爱情离开的时候_copy.mp3"));
//byte[] buf = new byte[1024*1024];
//int len = 0 ;
int by = 0 ;
while((by = bis.myRead())!=-1)
{
bos.write(by);
}
bis.myClose();
bos.close();
}
public static void main(String[] args) throws Exception
{
long start = System.currentTimeMillis();
System.out.println(start);
copyMp3();
long end = System.currentTimeMillis();
System.out.println(end);
System.out.println("用时"+(end-start)+"毫秒");
}
}
(3)、读取键盘录取
使用字节流进行键盘录入
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while(true)
{
String s = null ;
int ch = in.read();
if(ch == '\r')
continue ;
if(ch == '\n')
{
s = sb.toString();
if("over".equals(s))
break ;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length());
}
else
{
sb.append((char)ch);
}
}
三 转换流
InputStreamReader OutputStreamWriter
四 流的常规操作
OutputStreamWriter可以指定编码表
怎么选择流对象?
1明确源和目的
源:输入流 InputStream Reader
目的:输出流 OutputStream Writer
2操作的数据是否是存文本
是:用字符流
否:字节流
3当体系明确后,再明确使用哪个具体的对象
源:内存,硬盘,键盘
目的:内存,硬盘,控制台
五 其他操作
1、 改变标准输入输出设备
static void |
setIn(InputStream in) |
static void |
setOut(PrintStream out) |
2、 异常的日志信息
异常的日志信息,以下是异常日志信息的建立方式。但真正开发时,不用这样的方法,在网络上有一个专门建立java日志信息的工具包log4j。
import java.io.*;
import java.util.*;
import java.text.*;
class ExceptionInfo
{
public static void main(String[] args)
{
try
{
int[] arr = new int [2] ;
System.out.println(arr[3]);
}
catch (Exception e)
{
try
{
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(d);
PrintStream ps = new PrintStream("c:\\ExceptionInfo.txt");
ps.println(s);
System.setOut(ps);
}
catch (IOException e2)
{
throw new RuntimeException("创建异常日志文件失败");
}
e.printStackTrace(System.out);
}
}
}
3、 系统信息
System类中的static Properties getProperties() 方法可以得到系统信息。Properties类可以
和流相结合
import java.util.*;
import java.io.*;
class GetProperties
{
public static void main(String[] args) throws IOException
{
Properties prop = System.getProperties();
System.setOut(new PrintStream("systeminfo.txt"));
prop.list(System.out);
}
}
五 流操作文件:File对象
创建,
删除,
判断,
获取,
文件列表-1,
文件列表-2,
列出目录下所有内容--递归,
列出目录下所有内容--带层次,
import java.io.*;
递归的英文是单词recursion
在列出目录的过程中,因为目录中还有目录,只要使用同一个列出目录功能的函数即可完成。在列出过中出现的还是目录的话,还可以再次调用本功能,也就是函数自身调用自身。这种表现形式,或者编程手法称为递归。
递归要限定循环条件,如if等。要不然会死循环;而且要注意递归次数,避免内存溢出。
下面是使用递归的思想列出一个目录下的所有文件和目录
class RecursionDemo
{
public static void main(String[] args)
{
File f = new File("F:\\1115java\\javawork");
showDir(f);
//showDirlist(f);
}
public static void showDir(File f)
{
int x = 0;
so.p(" "+f);
File[] files = f.listFiles();
for(File f2 : files ){
x++ ;
//如果是目录,调用showDir方法.
if(f2.isDirectory())
{
showDir(f2);
}
//如果不是目录,打印。
else
so.p("file"+x+" :"+f2);
}
}
//插入层级目录
public static String getLevel(int level)
{
StringBuilder sb = new StringBuilder();
sb.append("|————");
for(int x = 0; x
{
sb.intsert(0," ");
}
}
public static void showDirlist(File f)
{
int x = 0;
String[] strs = f.list();
for(String str : strs ){
x++ ;
so.p("strs"+ x+" :"+str);
}
}
}
删除带内容的目录,
创建java文件列表
六 Properties
1、Properties的继承结构
java.lang.Object >
java.util.Dictionary
java.util.Hashtable
>java.util.Properties
2、存取
3、存取配置文件
4、练习
七 其他流对象
1、PrintWriter,PrintStream :可以直接操作输入流和文件。
2、序列流(对多个留进行合并)
SequenceInputStream有构造函数SequenceInputStream(Enumeration extends InputStream> e)
Vector集合可以得到Enumeration
1、所以我们可以将文件封装成读取流
FileInputStream(File file) FileInputStream(String name);//InputStream是抽象的
2、再将读取流封装成Vector集合
Vector
v.add(new FileInputStream(String name))
3、再由Vector集合的elements()方法 得到Enumeration
Enumeration
3、切割流
为了达到切割文件的目的,可一讲一个输出流切成几个部分
思路
1将要操作的对象封装成流对象
2建立一个字节数组,这个字节数组的大小将成为切割后单个文件的大小标准。
3建立循环,循环一次,建立一个流,写入规定大小的数据,然后关闭流
import java.io.*;
class slitFile
{
public static void main(String[] args) throws Exception
{
splitFile();
System.out.println("Hello World!");
}
public static void splitFile()throws Exception
{
//1将操作对象封装成流对像
FileInputStream fis = new FileInputStream("c:\\1.jpg");
//2
byte[] b = new byte[1024*10];
//3
int count = 1;
int len = 0;
while((len = fis.read(b))!=-1)
{
FileOutputStream fos =
new FileOutputStream("c:\\"+(count++)+".part");
fos.write(b,0,len);
fos.close();
}
}
}
4、对象的序列化(对象的持久化存储、对象可串行性)
(1)、使用到的流对象ObjectInputStream 与ObjectOutputStream(OutputStream.out)。在ObjectOutputStream类中有写入基本数据类型的write方法,写入对象的writeObject()方法。
(2)、标记接口Serializable 和uid。
类通过实现java.io.Serializable接口以启用其序列化功能。接口Serializable中没有方法,
没有方法的接口通常称为标记接口。
每个类在编译时会声明一个uid,uid是根据类中成员的数字签名生成的。在类中属性改变时,uid会改变。
使用标记接口Serializable,显示声明了所标记类的serialVersionUID :
ANY-ACCESS-MODIFIER /*任何访问修饰符*/ static final long serialVersionUID = 42L; uid可以自己设置数值。抛出异常: java.io.NotSerializableException
(3)、将对象存储为文件后,已经编码。用记事本打开后是乱码。
(4)、存储代码
class demo{
public static void main(){
writeObj() ;
//写入
}
public static void writeObj(){
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream(“c:\\demo.txt”)) ;
oos.writeObject(new Person(“ lisi ” , 39)) ;
}
//读取
public static void readObj() throws ClassNoFoundException{
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream(“obj.txt”)) ;
Person p = (Person)ois.readObject() ;
ois.close() ;
}
}
5、管道流 PipedinputStream和PipedOutputStream
(1)、管道输入流应该链接到管道输出流,使用单个线程可能产生死锁
(2)、管道流链接方式
1、PipedinputStream(PipedOutputStream)
2、connetct()方法
(3)、
读到管道流中,从管道流写入控制台
管道流的示例代码
class Read implements Runnable{
private PipedinputStream in ;
Read(){
this.in = in ;
}
public void run(){
try{
Byte[] buf = new byte[1024] ;
int len = in.read( buf ) ;
String s = new String(buf ,0 ,len) ;
in.close() ;
}
Catch(IOException e){
Throw new RuntimeException(“管道流读取失败”) ;
}
}
}
class Write implements Runnable{
private PipedOutputStream Out ;
Read(){
this.in = in ;
}
public void run(){
try{
out.write(“ …… ”.getBytes() ) ;
out.close() ;
}
Catch(IOException e){
Throw new RuntimeException(“管道流写入失败”) ;
}
}
}
Class PipedStreamDemo{
Public static void main(){
PipedInputStream in = new PipedInputStream() ;
PipedOutputStream out = new PipedOutputStream() ;
in.connect( out ) ;
Read r = new Read( in ) ;
Write w = new Write( out ) ;
new Thread(r).start() ;
new Thread(w).start() ;
}
}
6、RandomAccessFile
(1)、随机访问文件,自身具备读写的方法 。通过skipBytes(int x ), seek(int x ) 来达到随机访问。
(2)、直接继承自Object。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。
(3)、该类只能操作文件,RandomAccessFile(String name ,String mode)
Mode 可以取r(只读)、rw(读取写入)、rws()、rwd()
(4)、方法: 读取基本数据类型,readInt( ) , readLine() ,写入 基本数据类型
(5)、示例
class RandomAccessFileDemo{
public static void main(String[] args) throws IOException{
writeFile() ;
}
public static void writeFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile(“ran.txt ” , “rw”) ;
raf.write(“李四”.getBytes()) ;
raf.write(97) ;
raf.close() ;
}
}
打开ran.txt,里面是“李四a”如果年龄写入的是258,这时会丢失数据。
调整对象中的指针。seek(int x)
(6)、skipBytes(int x )跳过指定字节数 ;
(7)、随机写入,实现数据分段写入
7、操作基本数据类型的流对DataStream,
(1)、DataInputStream 与 DataOutputStream 可用于操作基本数据类型的流对象。
(2)、构造:DataOutputStream(OutputStream out ) ,
(3)、方法:a、写入基本数据类型。
(4)、特殊方法writeUTF(String str) :以一种方式写入字符串,只能用对应的方式读出来。用转换流读不出来
演示代码
基本数据类型的读取与写入:
Class DataStreamDemo{
Public static void main(){
DataInputStream dis =
new DataInputStream(new FileInputStream(“data.txt”));
int num = dis.readInt() ;
boolean b = dis.readBoolean() ;
double d = dis.readDouble() ;
}
Public static void writeData(){
DataOutputStream dos =
new DataOutputStream(new FileOutputStream(“demo.txt”))
dos.writeInt(234) ;
dos.writeBoolean(true) ;
dos.writeDouble(9887.54) ;
dos.close() ;
}
}
WriteUTF ——utf—8修改版
普通写入:
OutputStreamWrite osw =
new OutputStreamWriter(new FileOutputStream(“utf.txt”) , “utf-8”) ;
osw.write(“你好”) ;
osw.close() ;
WriteUTF 的使用,写入要用readUTF
DataOutputtream dos =
new DataOutputStream(new FileOutputStream(“utfdata.txt”)) ;
dos.writeUTF(“你好”) ;
dos.close() ;
readUTF中的EOFException异常。读取所有字节前达到末尾。
8、操作字节数组中数据ByteArrayInputStream 与ByteOutputStream
ByteArrayInputStream将源数据存储进内部缓冲区,一建立对象就要有数据源存在,而且数据源是一个字节数组。ByteArrayOutputStream对象中封装了一个随数据写入自动增长的byte数组。这个数组就是目的。可以使用toByteArray() 和toString()获取数据。
未调用底层资源,关闭流无效。不会产生IO异常。