第十八天
IO:
IO流用来处理设备之间的数据传输
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中
流按操作数据分为两种:字节流与字符流。
流按流向分为:输入流,输出流。
IO流常用基类
字节流的抽象基类:
InputStream ,OutputStream 。
字符流的抽象基类:
Reader , Writer 。
读取的两种方式
方式一:
//调用读取流对象的read方法。
//read():一次读一个字符。而且会自动往下读。
FileReader fr = new FileReader("demo.txt");
int ch = 0;
while((ch=fr.read())!=-1)
{
System.out.println(
}
方式二:
FileReader fr = new FileReader("demo.txt");
//定义一个字符数组。用于存储读到字符。
//该read(char[])返回的是读到字符个数。
char[] buf = new char[1024];
int num = 0;
while((num=fr.read(buf))!=-1)
{
System.out.println(new String(buf,0,num));
}
读写实例:
拷贝文件(两种方式):
方式一:读一个写一个
public static void copy_1()throws IOException
{
//创建目的地。
FileWriter fw = new FileWriter("RuntimeDemo_copy.txt");
//与已有文件关联。
FileReader fr = new FileReader("RuntimeDemo.java");
int ch = 0;
while((ch=fr.read())!=-1)
{
fw.write(ch);
}
fw.close();
fr.close();
}
方式二:(顺带解释异常)
public static void copy_2()
{
FileWriter fw = null;//定义引用给finally里引用
FileReader fr = null;
try
{
fw = new FileWriter("SystemDemo_copy.txt");
//这部分要try,而且finally里面要使用对象的方法。所以引用定义在外面。
fr = new FileReader("SystemDemo.java");
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1)
{
fw.write(buf,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
if(fr!=null)//如果对象建立成功,但关闭失败,再处理异常
try
{
fr.close();
}
catch (IOException e)
{
}
if(fw!=null)
try
{
fw.close();
}
catch (IOException e)
{
}
}
}
字符流的缓冲区:
BufferedReader:读取缓冲区
readLine():
该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取。
当返回null时,表示读到文件末尾。
readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。
BufferedWriter:写入缓冲区
跨平台换行方法:newLine()
提高流的效率
代码列子:
通过缓冲区复制一个.java文件。
import java.io.*;
class CopyTextByBuf
{
public static void main(String[] args)
{
BufferedReader bufr = null;//里面有关流方法
BufferedWriter bufw = null;//里面有关流方法
try
{
bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
bufw = new BufferedWriter(new FileWriter("bufWriter_Copy.txt"));
String line = null;
while((line=bufr.readLine())!=null)//读取一行,/r/n不存入自带数组里面,当读取带/n的时候,
//就将数组里的数据变成字符串返回
{
bufw.write(line);
bufw.newLine();//跨平台换行方法
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}
装饰设计模式:如FileReader和BufferedReader
自己理解:平时我们看见的API里面一些,传入对象的构造函数的类
当想要对已有的对象进行功能增强时,
可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。
那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象。
并基于被装饰的对象的功能,提供更强的功能。
代码解释:
class Person
{
public void chifan()
{
System.out.println("吃饭");
}
}
class SuperPerson
{
private Person p ;
SuperPerson(Person p)
{
this.p = p;
}
public void superChifan()
{
System.out.println("开胃酒");
p.chifan();
System.out.println("甜点");
System.out.println("来一根");
}
}
class PersonDemo
{
public static void main(String[] args)
{
Person p = new Person();
SuperPerson sp = new SuperPerson(p);
sp.superChifan();
}
}
装饰与继承的区别:
继承体系:
MyReader//专门用于读取数据的类。
|--MyTextReader
|--MyBufferTextReader//增强的读取文本
|--MyMediaReader
|--MyBufferMediaReader//增强的读取媒体
|--MyDataReader
|--MyBufferDataReader//增强的读取数据
//再增强有得加,所以扩展性极差
上面这个类扩展性很差。
找到其参数的共同类型。通过多态的形式。可以提高扩展性。
优化后的体系:
MyReader//专门用于读取数据的类。
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferReader//增强的装饰类
class MyBufferReader extends MyReader//增强的装饰类
{
MyBufferReader(MyTextReader text)
{}
MyBufferReader(MyMediaReader media)
{}
}
//上面代码即转换成:
class MyBufferReader extends MyReader
{
private MyReader r;
MyBufferReader(MyReader r)
{}
}
以前是通过继承将每一个子类都具备缓冲功能。
那么继承体系会复杂,并不利于扩展。
现在优化思想。单独描述一下缓冲内容。
将需要被缓冲的对象。传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。
这样继承体系就变得很简单。优化了体系结构。
装饰模式比继承要灵活。避免了继承体系臃肿。
而且降低了类于类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所以装饰类和被装饰类通常是都属于一个体系中的。
字节流:
字节流的抽象基类:
InputStream:读取流(输入流)
特有方法: int available()
返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
byte[] buf = new byte[fis.available()];
//定义一个刚刚好的缓冲区。不用在循环了。小文件可用,大文件千万别用。所以不要用为好。
OutputStream:写入流(输出流)
write方法不需要刷新。
读取方式和字符流相同
代码解释:
复制文件(可以复制任何文件,因为底层是字节传输):
import java.io.*;
class CopyMp3
{
public static void main(String[] args) throws IOException
{
long start = System.currentTimeMillis();//开始时间
copy();
long end = System.currentTimeMillis();//复制结束时间
System.out.println((end-start)+"毫秒");//复制用时
}
//通过字节流的缓冲区完成复制。
public static void copy()throws IOException
{
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));
int by = 0;
while((by=bufis.read())!=-1)
{
bufos.write(by);
}
bufos.close();
bufis.close();
}
}
转换流
键盘录入
//键盘的最常见写法。
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));//输入转换流传入参数是InputStream in
BufferedWriter bufw =
new BufferedWriter(new OutputStreamWriter(System.out));//输出转换流OutputStream out
注意:OutputStreamWriter(OutputStream out,String charsetName)//自定义编码表
InputStreamReader(InputStream in, String charsetName)
其实转换是为了使用 BufferedReader 及 BufferedWriter 里的特有方法,如
readLine()及newLine()
IO流规则(重点):
通过三个明确来完成。
1,明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer
2,操作的数据是否是纯文本。
是:字符流。
不是:字节流。
3,当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘。键盘
目的设备:内存,硬盘,控制台。
第二十天:
File:
用来将文件或者文件夹封装成对象
方便对文件与文件夹的属性信息进行操作。
File对象可以作为参数传递给流的构造函数。
了解File类中的常用方法。
File类常见方法:
1,创建。
boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。
和输出流不一样,输出流对象一建立创建文件。而且文件已经存在,会覆盖。
boolean mkdir():创建文件夹。
boolean mkdirs():创建多级文件夹。
2,删除。
boolean delete():删除失败返回false。如果文件正在被使用,则删除不了返回falsel。
void deleteOnExit();在程序退出时删除指定文件。
3,判断。
boolean exists() :文件是否存在.
isFile():
isDirectory();
isHidden();
isAbsolute();
4,获取信息。
getName():
getPath():
getParent():
getAbsolutePath()
long lastModified()
long length()
方法是用注意:
static String separator 在WIN下等效于\\ //跨平台分隔符
如:File f1 = new File("c:"+File.separator+"Test.java");
void deleteOnExit() //退出时删除指定文件
在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。
//记住在判断文件对象是否是文件或者目的时,必须要先判断该文件对象封装的内容是否存在。
//通过exists判断。
File f = new File("d:\\java1223\\day20\\file2.txt");
f.exists();
sop("dir:"+f.isDirectory());
获取盘符:static File[] listRoots()
列出可用的文件系统根。
String[] list()
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
调用list方法的file对象必须是封装了一个目录。该目录还必须存在
递归:方法类调用自己方法,有里向外运行(有里向外删除,可以递归)
代码解释:
void toBin(int mum)
{
if(mum>0)
{
toBin(num/2);
num%2;
}
}
Properties:
是hashtable的子类。
也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串。
是集合中和IO技术相结合的集合容器。
该对象的特点:可以用于键值对形式的配置文件。
那么在加载数据时,需要数据有固定格式:键=值。
import java.io.*;
import java.util.*;
class PropertiesDemo
{
public static void main(String[] args) throws IOException
{
loadDemo();
}
public static void loadDemo()throws IOException
{
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("info.txt");//将已存在的文件中数据加载进流中
//将流中的数据加载进集合。
prop.load(fis);//将流中的数据加载到集合中
prop.setProperty("wangwu","39");//改完之后,只是在内存集合中修改了,想存入原文件。及看下面
FileOutputStream fos = new FileOutputStream("info.txt");//定义一个输入流关联文件。
prop.store(fos,"haha");//利用输入流,将修改后的信息存入文件中,#haha是注释信息,不会被加载
// System.out.println(prop);
prop.list(System.out);
fos.close();
fis.close();
}
定义配置文件,可以限制免费客户运行次数。
打印流:
该流提供了打印方法,可以将各种数据类型的数据都原样打印。
字符打印流:
PrintWriter
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
4,字符输出流,Writer。
字节打印流:
PrintStream
构造函数可以接收的参数类型:
1,file对象。File
2,字符串路径。String
3,字节输出流。OutputStream
合并流:
SequenceInputStream (合并输入流,读取流)
表示其他输入流的逻辑串联。它从输入流的有序集合开始,
并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,
依次类推,直到到达包含的最后一个输入流的文件末尾为止。
构造方法:
SequenceInputStream(Enumeration<? extends InputStream> e)
Enumeration 是 Vector 集合里的
合并文件:
import java.io.*;
import java.util.*;
class SequenceDemo
{
public static void main(String[] args) throws IOException
{
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();//通过Vector里的方法获取Enumeration
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*1024];
int len = 0;
int count = 1;
while((len=fis.read(buf))!=-1)
{
fos = new FileOutputStream("c:\\splitfiles\\"+(count++)+".part");
fos.write(buf,0,len);
fos.close();
}
fis.close();
}
文件合并:
public static void merge()throws IOException
{
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();//Vector效率低
for(int x=1; x<=3; x++)
{
al.add(new FileInputStream("c:\\splitfiles\\"+x+".part"));
}
final Iterator<FileInputStream> it = al.iterator();
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()
{
public boolean hasMoreElements()
{
return it.hasNext();
}
public FileInputStream nextElement()
{
return it.next();
}
};//匿名内部类对象
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("c:\\splitfiles\\0.bmp");
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
对象的持久化存储:静态是不能被序列化的,transient int age是不能被序列化的;
即被transient修饰的非静态是不可以序列化;
ObjectOutputStream
将 Java 对象的基本数据类型和图形写入 OutputStream
ObjectInputStream
用于恢复那些以前序列化的对象
类 implements Serializable 给对象序列化的接口
管道流:(多线程)
PipedInputStream(管道读取流)
PipedOutputStream(管道写入流)
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);
System.out.println("读到数据。。阻塞结束");
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
{
System.out.println("开始写入数据,等待6秒后。");
Thread.sleep(6000);
out.write("piped lai la".getBytes());
out.close();
}
catch (Exception 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);//连接两流
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
特殊流,读写都可以的流:
RandomAccessFile
该类不是算是IO体系中子类。
而是直接继承自Object。
但是它是IO包中成员。因为它具备读和写功能。
内部封装了一个大型 byte数组,而且通过指针对数组的元素进行操作。
指针:
可以通过getFilePointer获取指针位置,
同时可以通过seek改变指针的位置。
//调整对象中指针。
seek(8*1);
//跳过指定的字节数
skipBytes(8);
其实完成读写的原理就是内部封装了字节输入流和输出流。
通过构造函数可以看出,该类只能操作文件。
而且操作文件还有模式:只读r,,读写rw等。
如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
如果模式rw。操作的文件不存在,会自动创建。如果存则不会覆盖。
构造方法:mode模式,即只读r,读写rw等
RandomAccessFile(File file, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
RandomAccessFile(String name, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
操作Java数据类型的流(使用里面的方法以各种数据类型存储读取)
DataInputStream
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。
对于多线程访问不一定是安全的。 线程安全是可选的,它由此类方法的使用者负责。
构造方法:
DataInputStream(InputStream in)
使用指定的底层 InputStream 创建一个 DataInputStream
DataOutputStream
数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
构造方法:
DataOutputStream(OutputStream out)
创建一个新的数据输出流,将数据写入指定基础输出流。
(此流没有调用底层资源,即流没有关闭流方法(源和目的都在内存中))
(而FileInputStream,调用了底层方法。)
ByteArrayInputStream :在构造的时候,需要接收数据源,。而且数据源是一个字节数组。
构造方法:
ByteArrayInputStream(byte[] buf)
创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
ByteArrayInputStream(byte[] buf, int offset, int length)
创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组
ByteArrayOutputStream :在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。
这就是数据目的地。
构造方法:
ByteArrayOutputStream()
创建一个新的 byte 数组输出流。
ByteArrayOutputStream(int size)
创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。
因为这两个流对象都操作的数组,并没有使用系统资源。
所以,不用进行close关闭。也不用抛异常。
字符编码:
编码:字符串变成字节数组。
String-->byte[]; str.getBytes(charsetName);
解码:字节数组变成字符串。
byte[] -->String: new String(byte[],charsetName);
代码解释:
import java.util.*;
class EncodeDemo
{
public static void main(String[] args)throws Exception
{
//将字符串”哈哈“编码
String s = "哈哈";
byte[] b1 = s.getBytes("GBK");
System.out.println(Arrays.toString(b1));//返回指定数组内容的字符串表示形式。
//解码
String s1 = new String(b1,"utf-8");
System.out.println("s1="+s1);
//乱码还原方法
//对s1进行utf-8编码。
byte[] b2 = s1.getBytes("utf-8");
System.out.println(Arrays.toString(b2));
//对s2进行gbk编码。
String s2 = new String(b2,"gbk");
System.out.println("s2="+s2);
}
}
特殊的"联通"
在记事本里面:
GBK和utf-8重复的地方