字节流的抽象基类:
InputStream , OutputStream 。
字符流的抽象基类:
Reader , Writer 。
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀
一、字符流:
1.Writer :
*FileWriter :
//创建该对象的同时创建一个文件,如果该文件已经存在,则覆盖。
File Writer fw = new FileWriter("demo.txt");
//调用write方法,将数据写入到输出流中。
fw.write("abcde");//此方法有很多重载的方法。
//刷新流对象中的缓冲数据。将数据刷新到目的地中。
fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲区中的数据。将数据刷新到目的地中。
fw.close();
*对IO异常的处理方式。
利用try,catch,finally语句处理,在finally语句块中进行关闭流对象。
//若文件存在,则将写入的文件加到文件的末尾(true||false);
FileWriter(String fileName, boolean append);
2.Reader :
*FileReader :
//创建一个文件读取流对象,和指定名称的文件相关联,要保证该文件是已经存在的,否则异常。
FileReader fr = new FileReader("demo.txt");
*两种读取方式:
1.//每次读取一个字符,如果已达到末尾,则返回-1;
fr.read();
int ch = 0;//循环读取,直到文件末尾,效率低。
while((ch = fr.read())!=-1){
System.out.println((char)ch);
}
2.//将文件中的字符读取到字符数组中。返回读取的字符个数,到文件末尾处返回-1;
public int read(char[] cbuf);
char[] buf = new char[1024];
int num = 0;
while((num = fr.read(buf))!= -1){
//不能使用println,因为如果字符已经达到1024个,那么就要换行,与字符在文件中的格式不同。
System.out.print(new String(buf,0,num));
}
*拷贝文本文件原理:
创建一个FileReader(String fileName)数据输入流对象 fr,
再创建一个FileWriter(String fileNmae)数据输出流对象 fw,
利用 fr 对象的read方法读取数据流中的数据,并存入数组(缓冲区)中。
在用 fw 对象的write方法将数组中的数据存入输出流中,
刷新输出流的时候就会将文本刷新到目的文件中。
3.BufferedWriter
这个类的作用,将文本写入字符输入流,缓冲各个字符,
从而提供单个字符、数组和字符串的高效写入。
*使用方法:
FileWriter fw = new FileWriter("buf.txt");
//为了提高字符写入流效率,加入了缓冲技术。
//只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
BufferedWriter bufw = new BufferedWriter(fw);
bufw.newLine();//换行符,跨平台的。
4.BufferedReader
此类的作用与BufferedWriter基本相同,都是为了提高效率的。
//为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲对象的构造函数。
BufferedReader bufr = new BufferedReader(new FileReader("buf.txt"));
readLine();//读取一个文本行。包含该行内容的字符串,不包含任何行终止符如果已到达流末尾,则返回 null;
*用法:
String line = null;
while((line = bufr.readLine())!= null){
System.out.println(line);
}
5.装饰设计模式:
*定义:当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,
并提供加强功能,那么自定义的该类成为装饰类。BufferedWriter与BufferedReader就是
装饰设计模式,对FileWriter与FileReader的功能加强。
*装饰和继承:
继承虽然可以扩展父类的功能,对父类的方法进行覆写,但同时也使得整个体系变得臃肿了。
装饰模式比继承要灵活,避免了集成体系臃肿,而且降低了类与类之间的关系,
只不过提供了更强功能。装饰类一般都可以接收被装饰类的父类和父类的所有子类,
所以装饰类和被装饰类通常是都属于一个体系中的。
二、字节流:
1.OutputStream
*FileOutputStream :
FileOutputStream fos = new FileOutputStream("fos.txt");
//操作字节的write方法不需要刷新输出流。
fos.write("abcde".getBytes());
2.InputStream
*FileInputStream :
FileInputStream fis = new FileInputStream("fos.txt");
byte[] buf = new byte[1024];
int len = 0;
//读取的方法与字符流读取的方法基本相同
while((len = fis.read(buf))!= -1){
System.out.println(new String(buf,0,len);
}
3.BufferedInputStream
4.BufferedOutputStream :
//利用缓冲区对象拷贝MP3实例
public static void copy()throws IOException{
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3");
BufferedOutputStream bufis = new BufferedOutputStream(new FileOutputStream("d:\\0.mp3");
int by = 0;
while((bu = bufis.read())!=-1){
bufos.write(by);
}
}
*模拟缓冲区字节流读取方法。
class MyBufferedInputStream{
private InputStream in = null;
private int count = 0;
private int pose = 0;
byte[] buf = new byte[1024];
MybufferedInputStream(InputStream in){
this.in = in;
}
public int myRead(){
if(count == 0){
count = in.read(buf);
if(count<0)//调用InputStream对象方法,如果count为-1,则表示数据到尾了。
return -1;//返回-1作为结束标记。
count--;//用来记录缓冲区字节的剩余个数
pose++;//用来记录缓冲区数组buf当前读取下角标。
//在返回数据的过程中,有可能会读到字节为11111111的数,像这样的数
//值为-1 ,如果返回-1则表示读取结束了,但实际上并没有,所以要对其进行处理。
//因为byte为2个字节,存为int类型的时候,会转成8个字节,这里就采用如下方式解决。
// 11111111 11111111 11111111 11111111
//&00000000 00000000 00000000 11111111 8个1的值为255,或者为0xff(16进制)
// ---------------------------------------
//=00000000 00000000 00000000 11111111
return b&255;//将每一次从数组中读取的字节返回出去。
}else if(count > 0 ){
pose++;//每读取一次,角标加一,作为指针用。
count--;//记录数组中字节剩余个数,每读取一次则减一。
return b&0xff;
}
}
}
public static void copy()throws IOException{
MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\0.mp3");
BufferedOutputStream bufis = new BufferedOutputStream(new FileOutputStream("d:\\0.mp3");
int by = 0;
while((bu = bufis.MyRead())!=-1){
//疑虑:既然MyRead()方法将byte形式转成int类型,由2个字节转成8个字节,那岂不是多了4倍
//在此,write方法,会强制将int类型的by数据转成byte类型并写入文件中去。
bufos.write(by);
}
}
-----------------------------------------------
读取键盘录入:
System.out:对应的是标准输出设备:控制台。
System.in:对应的标准输入设备:键盘。
InputStream in = System.in;//System.in获取键盘输入转换成输入字节流对象InputStream
char a = in.read();//调用in对象的读取字节的方法。此方法为阻塞式方法,若没有键盘录入,则等待。
System.out.println('\r'+0);//获取字符'\r'的ASCII值;
*录入一行数据,并将改行数据打印,如果录入数据时over,那么停止录入。
public static void main(String[] args)throws IOException{
InputStream in = System.in;
StringBuilder sb = new StringBuilder();//定义一个缓冲区。
while(true){
int ch = in.read();
if(ch=='\r')
continue;
if(ch=='\n'){
if("over".equals(sb.toString()))
break;
System.out.println(sb.toString().toUpperCase());
sb.delete(0,sb.length());//清空缓冲区。
}
else
sb.append((char)ch);
}
}
转换流:
1.InputStreamReader :将字节流转换成字符流。
public static void main(String[] args)throws IOException{
InputStream in = System.in;//获取键盘录入对象。
//将字节流对象转换成字符流对象,使用转换流。
InputStreamReader inReader = new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效录入。
BufferedReader bufr = new BufferedReader(inReader);
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
2.OutputStreamWriter :字符流通向字节流的桥梁。
public static void main(String[] args)throws IOException{
//为了提高效率,将字符串进行缓冲区技术高效录入。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//System.out为输出流对象,输出到控制台上,同样也可以将此对象替换为指向文件的。
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.flush();//因为字符流将数据存入缓冲区内,所以需要刷新缓冲区。
}
bufr.close();
bufw.close();
}
3.流操作的基本规律:
流对象有很多,到底该用哪一个?
通过两个明确来完成:
1.明确源和目的。
源:输入流,inputStream Reader .
目的:输出流,OutputStream Writer.
2.操作的数据是否是纯文本,
是:字符流。
不是:字节流。
3.当体系明确后,再明确要用哪个具体类的对象。
通过设备来进行区分:
源设备:内存,硬盘,键盘。
目的设备:内存,硬盘,控制台。
如果在存储的时候需要对编码格式进行改变,则需要用如下方式。
OutputStreamWriter osw = new OutputStreamWriter(FileOutputStream("demo.txt"),"utf-8");
setIn(InputStream in) 重新分配“标准”输入流。将原有的System.in对象改为重新分配的输入流对象。
setOut(PrintStream out) 重新分配“标准”输出流。将原有的System.out对象该为重新分配的输出流。
------------------------------------------------
File 类:用来将文件或者文件夹封装成对象的类。
*构造函数:
//将a.txt封装成file对象,可以将已有的和未出现的文件或者文件夹封装成对象。
File f1 = new File("a.txt");
//将目录与文件名分开传递。
File f2 = new File("c:\\abc",str);
File f2 = new File("c:"+File.separator+"abc",str);//File.separator为目录分割符,跨平台使用。
*File类常见方法:
1.创建。 boolean createNewFile();//若文件不存在则创建并返回true,反之亦然。
boolean mkdir();创建单级目录。
boolean mkdirs();创建多级目录。
2.删除。 boolean delete();//若文件存在则删除返回true,反之亦然。
void deleteOnExit();//虚拟机终止时,将指定文件删除。
3.判断。 boolean exists() :文件是否存在。
boolean isDirectory();//判断所指对象是不是目录,在判断之前一定要先判断文件或目录是否存在。
boolean isFile();//判断所指对象时不时文件,在判断前,要先判断存在与否。
boolean isAbsolute();//判断是否是绝对路径。即使文件不存在也能判断。
4.获取。 String getPath();//获取文件对象的相对路径。
String getAbsolutePath();//获取绝对路径。
String getParent();创建文件的时候用的是绝对路径,则此方法返回其父目录,如果创建文件对象时用的是
文件名,则返回空。意思就是返回文件名的所有上级,但与文件创建路径有关。
getParent()与getName();放在一起就是绝对路径。
long lastModified();//最后一次修改时间。
File[] listFiles();//获取指定目录下的所有文件及文件夹。
String[] list();//获取指定目录下的所有文件及文件夹。
递归调用需要注意:
一定要有限定条件,否则将会务休止的递归下去。同时也要注意递归的次数,防止内存溢出。
5.删除一个带内容的目录。
删除原理:在window中,删除目录是从里面往外面删除的。既然是从里往外删除,就要用到递归。
public static void removeDir(File dir){
File[] files = dir.listFiles();
for(int x = 0;x<files.length;x++){
if(files[x].isDirectory())
removeDir(files[x]);
else
System.out.println(files[x].toString()+"...file...."+files[x].delete());
}
System.out.println(dir+"...dir..."+dir.delete());
}
-------------------------------------------------------
Properties 是 Hashtable的子类。
也就是说,它具备map集合的特点,而且它里面存储的键值对都是字符串。
是集合与IO技术相结合的容器,该对象特点:可以用于键值对形式的配置文件。
Set<T> stringPropertyNames();返回键的set集合。
*本地配置文件加载到Properties对象中。
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("info.txt");
prop.load(fis);//将输入流加载到prop对象中。
prop.setProperty(key,value);//修改集合中相应键值。
FileOutputStream fos = new FileOutputStream("info.txt");
prop.store(fos,"comments");//将修改后的内容保存到文件中,并加comments注释。
------------------------------------------------------
IO包中的其他类:
1.打印流对象:
打印流:该流提供了打印方法,可以讲各种数据类型的数据都原样打印。
*字节打印流:
PrintStream :构造函数可以接收的类型:
1.file对象。 File
2.字符串路径。 String
3.字节输出流, OutputStream
*字符打印流:
PrintWriter :构造函数可以接收的参数类型:
1.File对象, File
2.字符串路径, String
3.字节输出流, OutputStream
4.字符输出流, Writer
*对应一个小实例:
public static void main(String[] args)throws IOException{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//创建一个打印流对象, 且自动刷新。
PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true);
String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
out.println(line.toUpperCase());
}
out.close();
bufr.close();
}
2.合并流(序列流): SequenceInputStream
构造函数: SequenceInputStream(Enumeration<? extends InputStream> e)
SequenceInputStream(InputStream s1, InputStream s2)
有如下小实例:
public static void main(String[] args) throws IOException{
//为什么会用Vector,因为此集合是通过Enumeration遍历的。可以返回一个枚举对象。
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("c:\\1.txt"));//添加文件输入流
v.add(new FileInputStream("c:\\2.txt"));
v.add(new FileInputStream("c:\\3.txt"));
Enumeration<FileInputStream> en = v.elements();
//通过构造函数,将三股输入流合并为一股输入流。
SequenceInputStream sis = new SequenceInputStream(en);
//定义目的;
FileOutputStream fos = new FileOutputStream("c:\\4.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = sis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
切割文件:
切割小实例:
public static void splitFile()throws IOException{
FileInputStream fis = new FileInputStream("c:\\1.bmp");
FileOutputStream fos = null;
byte[] buf = new byte[1024];
int len = 0;
int count = 1;
while((len=fis.read(buf))!=-1){
fos = new FileOutputStream("c:\\splitfile\\"+count+".part");
fos.write(buf,0,len);
fos.close();
}
fis.close();
}
3.序列化对象:ObjectInputStream、ObjectOutputStream
对象序列化:
*对于序列化的一些规定:静态成员不能被序列化,如果对与非静态的成员也不想进行序列化的话,
被 transient 修饰的成员将不被序列化,只在堆内存中存在,不会被序列化到文件中。
class ObjectStreamDemo{
public static void main(String[] args)throws Exception{
readObj();
}
//读取序列化对象文件,并解析
public static void readObj()throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
//将对象存入文件中,进行持久化操作。
public static void writeObj()throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi",39));
oos.close();
}
}
//序列化对象Person
class Person implements Serializable{
//自定义的序列号,防止对象内部有改动时产生的序列号与之前不一致。
public static final long serialVersionUID = 42L;
private String name;
private int age;
static String country = "cn";
Person(String name,int age,String country){
this.name = name;
this.age = age;
this.country = con;
}
}
4.管道流:PipedInputStream、PipedOutputStream.
特点:管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,
数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。
class Read implements Runnable{
private PipedInputStream in;
Read(PipedInputStream in){
this.in = in;
}
//读取流线程
public void run(){
try{
byte[] buf = new byte[1024];
int len = in.read(buf);
String s = new String(buf,0,len);
System.out.println(s);
in.close();
}catch(IOException e){
throw new RuntimeException("管道读取流失败");
}
}
}
class Write implements Runnable{
private PipedOutputStream out;
Write(PipedOutputStream out){
this.out= out;
}
//输入流线程
public void run(){
try{
out.write("piped lai la");
out.close();//本应放在finally中,在此简写。
}catch(IOException e){
throw new RuntimeException("管道输出流失败");
}
}
}
class PipedStreamDemo{
public static void main(String[] args)throws IOException{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);//将输入与输出关联。
new Thread(new Read(in)).start();
new Thread(new Write(out)).start();
}
}
5.RandomAccessFile :操作文件
介绍:该类不是IO体系中子类,而是直接继承自Object,但是他是IO包中的成员,因为它具备读和写功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,
同时可以通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流。
通过构造函数可以看书,该类只能操作文件。而且操作文件有模式:只读,读写等
可以实现分段写入,例如多线程下载软件,多个线程同时向同一个硬盘下载文件,就需要用到此类。
seek(offset)调整对象中指针位置。
6.DataOutputStream:操作java基本数据类型
DataInputStream:
介绍:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,
应用程序可以使用数据输入流将数据读入。
7.ByteArrayInputStream:操作字节数组
ByteArrayOutputStream:
介绍:ByteArrayInputStream在构造的时候,需要接受数据源,而且数据源是一个字节数组。
ByteArrayOutputStream:在构造的时候,不用定义数据目的地,因为该对象中已经内部封装了可变长度的字节数组。
这就是数数据目的地。
因为这两个流对象都操作数组,并没有使用系统资源,所以不用进行close关闭。