io流基础
IO流的分类
根据处理数据类型的不同分为:字符流和字节流
根据数据流向不同分为:输入流和输出流
输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流
输入流:InputStream(字节流),Reader(字符流)
输出流:OutPutStream(字节流),Writer(字符流)
这四个类都是抽象类,可以把这四个类想象成四根不同的管道。一端接着你的程序,你可以通过输出管道从数据源里面往外读数据,也可以通过输入管道往数据源里面输入数据,总之,通过这四根管道可以让数据流进来和流进去。
JavaIO流类图
IO流分类
InputStream(输入流)
InputStream的基本方法
read()方法是一个字节一个字节地往外读,每读取一个字节,就处理一个字节,read(byte[] buffer)方法读取数据时,先把读取到的数据填满这个byte[]类型的数组buffer(buffer是内存里面的一块缓冲区),然后再处理数组里面的数据。这就跟我们取水一样,先用一个桶去接,等桶接满水后再处理桶里面的水。
案例
【使用FileInputStream流来读取FileInputStream.java文件的内容】
public class InputStreamTest {
public static void main(String[] args) throws IOException {
int b=0;//使用变量b来装调用read()方法时返回的整数
FileInputStream in = null;
try{
in = new FileInputStream("D:\\material\\img\\temp\\temp.jpg");
}catch (FileNotFoundException e){
System.out.println("系统找不到指定文件");
System.exit(-1);//系统非正常退出
}
long num = 0;//使用变量num来记录读取到的字符数
try {
while ((b=in.read())!=-1){
System.out.println((char)b);//把数字表示的汉字和英文字母转换成字符输出
num++;
}
in.close();//关闭输入流
System.out.println();
System.out.println("总共读取了"+num+"个字节的文件");
}catch (IOException e){
System.out.println("文件读取错误!");
}
}
}
OutputStream(输出流)
OutputStream的基本方法
案例
【使用FileOutputStream流往一个文件里面写入文件】
public class OutputStreamTest {
public static void main(String[] args){
int b = 0;
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("D:\\material\\img\\temp\\temp.jpg");
out = new FileOutputStream("D:\\material\\img\\temp\\temp2.jpg");//指明要写入数据的文件,如果指定的路径不存在这样的文件,则系统会自动创建一个
while ((b=in.read())!=-1){
out.write(b);//调用write(int c)方法把读取的字符全部写入到指定文件中去
}
out.close();
in.close();
}catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream和FileOutputStream这两个流都是字节流,都是以一个字节为单位进行输入和输出的。所以对于占用2个字节存储空间的字符来说读取出来时就会显示成乱码。
Reader流
Reader:和inputStream一模一样,唯一的区别就在于读的数据单位不同
继承自Reader的流都是用于向程序中输入数据,且数据的单位为字符(16bit)
16位:一个字符也就是两个字节,使用Reader流读取数据时都是两个字节两个字节往外读的,为什么还在要有这两种两个字节的读取方式呢?因为有些字符是占2个字节的,如我们的中文字符在java里面就是栈两个字节的,如果采用一个字节一个字节往外读的方式,那么读出来的就是半个汉字,这样子java就没有办法正确的显示中文字符的,所以有必要存在这种流,一个字符一个字符地往外读。
Reader的基本方法
Writer流
writer的基本方法
演示
【演示:使用FileWriter(字符流)向指定文件中写入文件】
package com.axuan.demo01;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
//使用FileWriter(字符流)向指定文件中写入数据时以1个字符为单位进行写入
public class WriterTest {
public static void main(String[] args) throws IOException {
//使用FileWriter输出流从程序把数据写入到uicode.dat文件中
//使用FileWriter流向文件写入数据时是一个字符一个字符写入的
FileWriter fw = null;
try{
fw = new FileWriter("D:\\material\\file\\temp1.txt");
//这里使用for循环把0-60000里面的所有整数都输出
//这里相当于是把全世界各个国家的文字都0-60000内的整数的形式来表示
for (int c=0;c<=60000;c++){
fw.write(c);//应该默认的将c进行了强制转换成char乐行
}
int b=0;
long num =0;
FileReader fr = null;
fr = new FileReader("D:\\material\\file\\temp1.txt");
while((b=fr.read())!=-1){
System.out.print((char)b+"\t");
num++;
}
System.out.println();
System.out.println("总共读取了"+num+"个字符");
}catch (Exception e){
}
}
}
FileReader和FileWriter这两个流都是字符流,都是以一个字符为单位进行输入和输出的。所以读取和写入占用2个字节的字符时都可以正常地显示出来,以上都是节点流。所谓的节点流指定就是直接把输入流或输出插入到数据源上,直接往数据源里面写入数据或读取数据。
处理流讲解
第一种处理流–缓冲流(Buffering)
缓冲流要‘套接’在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。J2SDK提供了四种缓冲流,常用构造方法如下:
BufferedReader(Reader in)
BufferedReader(Reader in,int sz) //sz 为自定义缓冲区的大小
BufferedWriter(Writer out)
BufferedWriter(Writer out,int sz)
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in,int size)
BufferedOutputStream(InputStream in)
BufferedOutputStream(InputStream in,int size)
- 缓冲输入流支持其父类的mark和reset方法
- BufferedReader提供了readLine方法用于读取一行字符串
- BufferedWriter提供了newLine用于写入一个行分隔符
- 对于输出的缓冲流,写出的数据会在现在内存中缓存,使用flush方法将会使内存中的数据立即写出
带有缓冲区的,缓冲区(Buffer)就是内存里面的一小块区域,读写数据时,都是先把数据放到这块缓冲区域里面,减少io对硬盘的访问次数,保护我们的硬盘。可以先把缓冲区想象成一个小桶,把要读写的数据想象成水,每次读取数据或者写入数据之前,都是先把数据装到这个桶里面,装满了以后再做处理。这就是所谓的缓冲。先把数据放到缓冲区上,等到缓冲区满了以后,再一次把缓冲区里面的数据写入到硬盘上或者读取出来,这样可以有效地减少对硬盘的访问次数,有利于保护我们的硬盘。
【缓冲流测试代码:BufferedInputstream】
package com.axuan.demo02;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class BufferStreamTest {
public static void main(String[] args) {
FileInputStream fis = null;
try{
fis = new FileInputStream("D:\\material\\img\\temp\\temp.jpg");
//在FileInputStream节点流的外面套接一层处理流BufferedInputStream
BufferedInputStream bis = new BufferedInputStream(fis);
int c = 0;
System.out.println((char)bis.read());
System.out.println((char)bis.read());
bis.mark(100);//在第100个字符处做一个标记
for (int i = 0; i <= 10 && ((c=bis.read())!=-1); i++) {
System.out.print((char)c);
}
System.out.println();
bis.reset();//重新回到原来标记的地方
for (int i = 0; i <= 10 && (c=bis.read())!=-1; i++) {
System.out.print((char)c);
}
bis.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
【演示:BufferedReader】
package com.axuan.demo02;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class BufferedReaderTest {
public static void main(String[] args) {
try {
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\material\\file\\temp2.txt"));
String s = null;
for (int i = 0; i < 100; i++) {
s = String.valueOf(Math.random());
bw.write(s);//把随机数数字符串写入到指定文件中
bw.newLine();//调用newLine()方法使得写入一个随机数就换行显示
}
bw.flush();//调用flush()方法清空缓冲区
BufferedReader br = new BufferedReader(new FileReader("D:\\material\\file\\temp2.txt"));
while((s=br.readLine())!=null){
//使用BufferedReader处理流里面String readLine()方法读取文件中的数据时是一行一行读取的
//循环结束的条件就是使用readLine()方法读取数据返回的字符串为空值后则表示已经读取到文件的末尾了
System.out.println(s);
}
bw.close();
br.close();
}catch (Exception e){
}
}
}
第二种处理流-转换流
- InputStreamReader和OutputStreamWriter用于字节数据到字符数据之间的转换
- InputStreamReader需要和InputStream“套接”
- OutputStreamWriter需要和OutputStream“套接”
- 转换流在构造时可以指定其编码集合
InputStream isr = new InputStreamReader(System.in,"ISO8859-1")
转换流非常的有用,它可以把一个字节流转换成一个字符流,转换流有两种,一种叫InputStreamReader,另一种叫OutputStreamWriter。InputStream是字符流,Reaer是字符流,InputStreamReader就是把InputStream转换成Reader。OutputStream是字节流,Writer是字符流,OutputStreamWriter就是把OutputStream转换成Writer。把OutputStream转换成Writer之后就可以一个字符一个字符地通过管道写入数据了,而且还可以写入字符串。我们如果用一个FileOutputStream流往文件里面写东西,得要一个字节一个字节地写进去,但是如果我们在FileInputStream流上套上一个字符转换流,那我们就可以一个字符串一个字符串地写进去。
【转换流测试代码】
public class TestTransform1 {
public static void main(String[] args) {
try{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\material\\file\\temp1.txt"));
osw.write("MircosoftsunIBMOracleApplet");//把字符串写入到指定文件中去
System.out.println(osw.getEncoding());//使用getEncoding()方法取得当前系统的默认字符编码
osw.close();
//如果在调用FileOutputStream的构造方法时,如果没有加入true,那么新加入的字符串就会替换掉原来写入的字符串,在调用构造方法时指定了字符的编码
osw = new OutputStreamWriter(new FileOutputStream("D:\\material\\file\\temp1.txt",true),"ISO8859_1");
osw.write("MircosoftsunIBMOracleApplet");
System.out.println(osw.getEncoding());
osw.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
public class TestTransform2 {
public static void main(String[] args) {
try {
InputStreamReader isr = new InputStreamReader(System.in);
//System.in这里的in是一个标准的输入流,用来接收从键盘输入的数据
BufferedReader br = new BufferedReader(isr);
String s = null;
s = br.readLine();//readLine()方法把读取到的一行字符串保存到字符串
while (s != null) {
System.out.println(s.toUpperCase());//把保存在内存s中的字符串打印出来
s = br.readLine();
if (s.equalsIgnoreCase("exit")) {//只要输入exit循环就结束,就会退出
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
第三种处理流-数据流
- DataInputStream和DataOutputStream分别继承自InputStream和OutputStream,它属于处理流,需要分别“套接”在InputStream和OutputStream类型的节点流上
- DataInputStream和DataOutputStream提供了可以存取与机器无关的java原始类型数据(int,double等)的方法
- DataInputStream和DataOutputStream的构造方法
DataInputStream (InputStream in)
DataOutputStream (OutputStream out)
【数据流测试代码】
public class TestDataStream {
public static void main(String[] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//在调用构造方法时,首先会在内存里面创建一个ByteArray字节数组
DataOutputStream dos = new DataOutputStream(baos);
//在数据流的外面套一层数据流,用来处理int,double类型的数
try{
dos.writeDouble(Math.random());//把产生的随机数直接写入到字节数组ByteArray中
dos.writeBoolean(true);//布尔类型的数据在内存中就只占一个字节
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
System.out.println(bais.available());
DataInputStream dis = new DataInputStream(bais);
System.out.println(dis.readDouble());//先写进去的就先读出来,调用readDouble()方法读取写入的随机数
System.out.println(dis.readBoolean());//后写进去的就后读出来,这里面的读取顺序不能更改位置,否则会打印出不正确的结果
dos.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
通过bais这个流往外读取数据的时候,是一个字节一个字节地往外读取的,因此读出来的数据无法判断是字符串还是bool类型的值,因此要在它的外面套一个流,通过dataInputStream把读出来的数据转换就可以判断了。注意了:读取数据的时候先写进去的就先读出来,因此读ByteArray字节数组数据的顺序应该是先把占8个字节的double类型的数读出来,然后再读那个只占一个字节的boolean类型的数,因为double类型的数是先写进数组里面的,读的时候也要先读它。这就是所以为的先写的要先读。若果先读出来Boolean类型的那个数,那么读出来的情况可能把double类型数的8个字节里面的一个字节读了出来。
打印流-Print
- PrintWriter和PrintStream都属于输出流,分别针对于字符和字节
- PrintWriter和PrintStream提供了重载的print
- Println方法用于多种数据类型的输出
- PrintWriter和PrintStream的输出操作不会抛出异常,用户通过检查错误状态获取错误信息
- PrintWriter和PrintStream有自动Flush功能
PrintWriter(Writer out)
PrintWriter(Writer out,boolean autoFlush)
PrintWriter(OutputStream out)
PrintWriter(OutputStream out, boolean autoFlush)
PrintStream(OutputStream out)
PrintStream(OutputStream out,boolean autoFlush)
【测试代码】
public class TestPrintStream {
public static void main(String[] args) {
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream("D:\\material\\file\\temp1.txt");
ps = new PrintStream(fos);//在输出流的外面套接一层打印流,用来控制打印输出
if (ps != null) {
System.setOut(ps);//这里调用setout()方法改变了输出窗口,以前写System.out.println()默认的输出窗口就是命令行窗口
//但现在使用System.setOut(ps)将打印输出窗口改成了由ps指定的文件里面,通过这样设计以后,打印输出时都会在指定的文件内打印输出
//在这里将打印输出窗口设置到了temp1.txt这个文件里面,所以打印出来的内容会在temp1.txt这个文件里面看到
}
for (char c = 0; c <= 1000; c++) {
System.out.print(c + "\t");//把全国各地的文字打印到temp1.txt这个文件中去
}
}catch (Exception e){
e.printStackTrace();
}
}
}
对象流-Object
直接将Object写入或读出
- transient关键字
- transient:透明的,用它来修饰的成员变量在序列化的时候,不予考虑, 也就是当成不存在。
- serializable接口
- externaliazble接口
public class TestObject {
public static void main(String[] args) {
T t = new T();
t.k = 8;//把k的值修改成8
try{
FileOutputStream fos = new FileOutputStream("D:\\material\\file\\temp1.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//ObjectOutputStream流专门用来处理Object的,在fos流的外面套接ObjectOutputStream流就可以直接把一个Object写进去
oos.writeObject(t);//直接把一个t对象写入到指定的文件里面
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("D:\\material\\file\\temp1.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
//ObjectInputStream专门用来读一个Object的
T tRead = (T)ois.readObject();
//直接把文件里面的内容全部读取出来然后分解成一个Object对象,并使用前置转换成指定类型T
System.out.println(tRead.i+"\t"+tRead.j+"\t"+tRead.d+"\t"+tRead.k);
ois.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
//凡是要将一个类的对象序列化成一个字节流就必须实现Serializable接口
//Serializable接口中没有定义方法,Serializable接口是一个标记性接口,用来给类作标记,只是起一个标记作用
//这个标记是给编译器看的,编译器看到这个标记之后就可以知道这个类可以被序列化 如果想把某个类的对象序列化,就必须实现Serializable接口
class T implements Serializable{
//Serializalbe的意思是可以被序列化
int i =10;
int j = 9;
double d = 2.3;
int k =15;
//transient int k = 15;
//在声明变量时如果加上transient关键字,那么这个变量就会被当做是透明的,即不存在
}
直接实现Serializable接口的类是JDK自动把这个类的对象序列化,而如果实现public interface Externalizable extends Serializable的类则可以自己控制对象的序列化,建议能让JDK自己控制序列化的就不要自己去控制