学习笔记2-IO流

来源:互联网 发布:qr是什么软件 编辑:程序博客网 时间:2024/05/18 00:30


IO

引子输入/输出系统不仅需要考虑三种不同种类(文件、控制台、网络连接)的IO,而且需要通过大量不同的方式与它们通信(顺序、随机访问、二进制、字符、按行、按字等等)。

IO流的定义与分类

定义:可以理解为数据的流动。就是一个数据流。Java程序通过流来完成输入/输出,IO流最终要以对象来体现。

用处:流的概念使得文件、网络、内存等设备可以被统一处理。用来处理设备之间数据传输.JAVA对数据的操作是通过流的方式.

流的操作只有两种:读和写。

分类: 

数据类型     相对内存方向      功能

字节流       输入      节点流 

字符流       输出      链接流

字符编码

Ø 计算机里只有数字,字符与数字对应的编码固定下来后,这套编码规则被称为ASCII码 

Ø 许多国家都把本地的字符集引入了计算机,扩展了计算机中字符的范围,中国大陆为每一个中文字符都指定了一个对应的数字,这套编码规则称为GBK

Ø 为了解决各个国家和地区使用自不同的本地化字符编码带来的不便,人们将全世界所有的符号进行了统一编码,称之为Unicode编码    

为什么要有字符流呢?

字节流:处理字节数据的流对象。设备上的数据无论是图片或dvd,文字等,都是以二进制存储的。二进制最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据。因为每个国家的字符都不一样。所以涉及到了字符编码问题。那么GBK编码的中文用unicode编码解析是有问题的。所以需要获取中文字节数据的同时+指定的编码表才可以解析正确数据。为了方便于文字的解析。所以将字节流和编码表封装成对象。这个对象就是字符流。这就是字符流的由来。只要操作的是字符数据,优先考虑使用字符流体系。

流的体系

因为功能不同,但有共性内容,所以不断抽取形成基本体系。该体系有四个基类。而且都是抽象类。

 

共性特点:子类名后缀都是父类名。前缀名都是这个子类的功能名称。

共性方法

read方法用来读入数据,不带参数的read方法一次读入一个字节,返回值表示读入的数据;byte数组的方法一次可读入多个字节,返回值表示本次读入了多少个字节,但这样得到的是一个字节数组,有时在程序中处理起来并不方便,所以有了缓冲流。当读到一个流的结尾时,这两个方法的返回值为-1,因此一般通过一个循环读入一个输入流中的所有数据:详细范例代码参见FileInputStream

    close方法用来关闭输入流,大多数输入流在使用完毕后都需要调用close方法关闭输入流。

        当在程序中创建一个IO流对象时,计算机内存中实际上产生了一个java程序中类的实例对象,一个系统本身产生的某种资源。 Java垃圾回收器只能管理程序中的类的实例对象,没法去管理系统产生的资源,所以程序需要调用close方法,去通知系统释放自身产生的资源。

 

iO流的读写示例

close()flush()的区别:

flush():将缓冲区的数据刷到目的地中后,流可以使用。

close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定要做。

--------------------------------------------------------------------------------------------------------------------

读取数据:自定义缓冲区。较为高效。

import java.io.*;

class FileReaderDemo2{

public static void main(String[] args) throws IOException{

//创建读取流对象时会同时调用系统底层资源,在指定的位置创建一个存储数据的文件

FileReader fr = new FileReader("demo.txt");

//使用read(char[])方法,要将读取到的字符存入数组。所以要创建一个一般长度是1024的整数倍的字符数组。。

char[] buf = new char[1024];

int len = 0;

while((len=fr.read(buf))!=-1){

System.out.println(new String(buf,0,len));

}

fr.close();//关闭流,其实就是关闭java调用的系统底层资源。在关闭前,会自动先刷新该流。

}

}

--------------------------------------------------------------------------------------------------------------------

将指定目录中的文本文件复制到另一个目录中。

import java.io.*;

class  CopyText2{

public static void main(String[] args) {

FileReader fr = null;

FileWriter fw = null;

try{

fr = new FileReader("FileWriterDemo2.java");

fw = new FileWriter("write2_copy.txt");

char[] buf = new char[1024];

int len = 0;

while((len=fr.read(buf))!=-1)

{

fw.write(buf,0,len);

fw.flush();

}

}

catch (IOException e)

{

//System.out.println("write :"+e.toString());

throw new RuntimeException("复制失败");

}

finally

{

if(fw!=null)

try

{

fw.close();

}

catch (IOException e)

{

throw new RuntimeException("写入关闭失败");

}

if(fr!=null)

try

{

fr.close();

}

catch (IOException e)

{

throw new RuntimeException("读取关闭失败");

}

}

}

}

 

流的操作规律

流对象:就是读取和写入。但因为功能的不同,流体系中提供了很多对象。那到底该用哪个对象更为合适呢?这就需要明确流的操作规律。

想要知道开发时用到哪些对象。只需要通过四个明确即可。

1, 明确源和目的().

源:InputStream Reader

目的:OutputStream Writer

2, 明确数据是否是纯文本数据。

源:如果是纯文本:Reader

不是纯文本:InputStream

目的:是纯文本Writer

不是纯文本:OutputStream

到这里,就可以明确需求中具体要使用哪个体系。

3, 明确具体的设备。

源设备:

硬盘:File

键盘:System.in

内存:数组,(其实就是缓冲区)

网络:Socket流。

目的设备:

硬盘:File

控制台:System.out

内存:数组

网络:Socket流。

4, 是否需要其他额外功能。

a, 是否需要高效(缓冲区)

是,就加上buffer

b,是否需要转换流

需求1:复制一个文本文件。

1, 明确源和目的。

源:InputStream Reader

目的:OutputStream Writer

2, 是否是纯文本:是

源:Reader

目的:Writer

3, 明确具体设备。

源:硬盘:FileFileReader fr = new FileReader(a.txt);

目的:硬盘:FileFileWriter fw = new FileWrtiter(b.txt);

4, 需要额外功能吗?

需要,需要高效。

---------------------------------------------------------------------------------------------------------------------------------

需求2:读取键盘录入信息,并写入到一个文件中。

1,明确源和目的。

源:InputStream Reader

目的:OutputStream Writer

2,是否是纯文本呢?

是。

源:Reader

目的:Writer

3,明确设备:

源:键盘。System.inInputStream in = System.in

目的:硬盘。FileFileWriter fw = new FileWriter(b.txt);

这样做可以完成,但麻烦。将读取的字节数据转成字符串,再由字符流操作。

4,需要额外的功能吗?

需要:转换。将字节流转换成字符流。因为明确的源是Reader,这样操作文本数据做便捷。所以要将已有的字节流转换成字符流。使用字节转换字符流:InputStreamReader.

InputStreamReader isr = new InputStreamReader(System.in);

FileWriter fw = new FileWriter(b.txt);

需要高效吗?需要。

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

BufferedWriter bufw = new BufferedWriter(new FileWriter(b.txt));

======================================================================

需求3:将以个文本文件数据显示在控制台上。

1, 明确源和目的。

源:InputStream Reader

目的:OutputStream Writer

2, 是否是纯文本呢?

是。

源:Reader

目的:Wrter

3, 明确具体设备:

源:硬盘:File

目的:控制台:System.out

FileReader fr = new FileReader(a.txt);

OutputStream out = System.out; //PrintStream

4, 需要额外的功能吗?

需要:转换。

FileReader fr = new FileReader(a.txt);

OutputStreamWriter osw = new OutputStreamWriter(System.out);

需要高效吗?需要

BufferedReader bufr = new BufferedReader(new FileReder(a.txt);

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

======================================================================

需求4读取键盘录入数据,显示在控制台上。

1, 明确源和目的:

源:InputStream Reader

目的:OutputStream Writer

2, 是否是纯文本呢?

源:Reader

目的:Writer

3, 明确设备:

源:键盘:System.in

目的:控制台:System.out

InputStream in = System.in;

OutputStream out = System.out;

4, 明确额外功能?

需要转换,因为都是字节流,但是操作的却是文本数据,所以使用字符流操作起来更为便捷。

InputStreamReader isr = new InputStreamReader(System.in);

OutputStreamWriter isw = new OutputStreamWriter(System.out);

需要将其高效

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in);

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out);

======================================================================

需求5:将一个中文字符串数据按照指定的编码表写入到一个文本文件中。

1, 目的:OutputStream Writer

2, 是纯文本,Writer

3, 设备:硬盘File

FileWriter fw = new FileWriter(a.txt);

fw.write(你好);

注意:既然需求中已经明确了指定编码表的动作。

那就不可以使用FileWriter,因为FileWriter内部使用的是默认的本地码表。

只能使用其父类。OutputStreamWriter.

OutputStreamWriter接收一个字节输出流对象,既然是操作文件,对象应该是FileOutputStream

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(a.txt).charsetName);

需要高效吗?

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(a.txt),charsetName));

 

转换流

转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。

转换流的最强功能就是基于字节流+编码表。凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。

发现转换流有一个子类就是操作文件的字符流对象。

InputStreamReader

|--FileReader

OutputStreamWriter

|--FileWrier

想要操作文本文件,必须要进行编码转换。而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。

但是子类有一个局限性。就是子类中使用的编码是固定的是本机默认的编码表。对于简体中文版的系统默认码表是GBK.

FileReader fr = new FileReader("a.txt");

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");

这两行代码的功能一摸一样。如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); 

如果需要制定码表,必须用转换流。

标准输入输出流

Ø 语言包java.lang中的System类管理标准输入/输出流和错误流

Ø 标准输入输出是操作系统提供的输入输出流,默认情况下: 标准输入System.in对应键盘的输入,是InputStream类型的,程序使用System.in可以读取从键盘上输入的数据;标准输出System.out对应控制台,是PrintStream类型的;标准错误输出System.err 默认情况下也会输出到控制台,在Eclipse开发环境中,标准错误输出打印出的内容用红色字体显示

Ø System类提供了一些用于重定向流的静态方法。

q setIn(InputStream in):对标准输入流重定向

q setout(PrintStream out):对标准输出流重定向

q setErr(PrintStream out):对标准错误输出流重定向

打印流PrintStream

1:提供了更多的功能,比如打印方法。可以直接打印任意类型的数据。

2:自动刷新。不用手动刷新

3:使用本机默认字符编码. 

4:该流的print方法不抛出IOException。

该对象的构造函数。

Ø PrintStream(File file) 创建具有指定文件且不带自动行刷新的新打印流。 

Ø PrintStream(String fileName) 创建具有指定文件名称且不带自动行刷新的新打印流。 

Ø PrintStream(OutputStream out) 创建新打印流。

Ø 前两个都JDK1.5版本才出现。而且在操作文本文件时,可指定字符编码了。

PrintStream可以操作目的:

1:File对象。

2:文件的字符串路径。

3:字节输出流。

当目的是一个字节输出流时,如果使用的println方法。可以在printStream对象上加入一个true参数。这样对于println方法可以进行自动的刷新。而不是等待缓冲区满了再刷新,最终print方法都将具体的数据转成字符串。而且都对IO异常进行了内部处理。

PrintWriter对象与PrintStream区别?

1, 可以手动指定编码,可以转化成字符来操作

2, 目标地比PrintStream多了一个输出字符流。

3, 两个相对尽量多使用PrintWriter,因为操作字符的多

PrintWriter具备PrintStream的特点,同时还有自身特点。开发时尽量使用PrintWriter。

/*

读取键盘录入将数据转成大写显示在控制台.

*/

//源:

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

 

//目的:又想把数据写到文件中。还想自动刷新。

PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);

String line = null;

while((line=bufr.readLine())!=null)

{

if("over".equals(line))

break;

out.println(line.toUpperCase());

//out.flush();

}

/*

注意:

System.in,System.out这两个标准的输入输出流,在jvm启动时已经存在了。

随时可以使用。当jvm结束了。这两个流就结束了。

但是,当使用了显示的close方法关闭时,这两个流在提前结束了。

*/

out.close();

bufr.close();

}

}

其他流

管道流:

 管道流用来把一个程序、线程或代码块的输出连接到另一个程序、线程或代码块的输入。主要作用是可以连接两个线程间的通信。管道流也分为字节流(PipedInputStreamPipedOutputStream)与字符流(PipedReaderPipedWriter

 管道读取流和管道写入流可以像管道一样对接上。管道读取流就可以读取管道写入流写入的数据。

通过构造方法连接

PipedInputStream(PipedOutputStream pos);PipedOutputStream(PipedInputStream pis);

通过各自的connect()方法连接

在类PipedInputStream中,connect(PipedOutputStream pos)

在类PipedOutputStream中,connect(PipedInputStream pis)

注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法,是阻塞式的。没有数据的read方法会让线程等待。

public static void main(String[] args) throws IOException

{

PipedInputStream pipin = new PipedInputStream();

PipedOutputStream pipout = new PipedOutputStream();

 

pipin.connect(pipout);

 

new Thread(new Input(pipin)).start();

new Thread(new Output(pipout)).start();

}

ByteArrayOutputStream&ByteArrayInputStream 

1, 操作设备目的是File和数组

2, 两个源和目的都是内存。把内存中的一个缓冲区(ByteArray)作为输入流和输出流使用

3, 里面就是封装了数组,本身只有一个length属性,可以读取和设置

数据流类DateInputStreamDateInputStream:是FilterInputStream的子类,专门用于处理java的基本数据类型,必须成对出现

序列流SequenceInputStream

Ø 作用就是将多个读取流合并成一个读取流。表示其他输入流的逻辑串联。这样做,可以更方便的操作多个读取流。

Ø 它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。

Ø 序列流的构造函数参数是枚举,想要获取枚举,需要有Vector集合。而枚举和迭代器是功能一样的。所以,可以用迭代替代枚举。

Enumeration<FileInputStream> en = new Enumeration<FileInputStream>()

{

public boolean hasMoreElements()

{

return it.hasNext();

}

public FileInputStream nextElement()

{

return it.next();

}

};

这样定义很麻烦。应该想到集合框架中工具类是否有提供相应的转换方法。

Collections.enumeration(Collection c);该方法的原理往上看。

RandomAccessFile:

特点:

1:该对象能操作的源和目的必须是文件。该对象既可读取,又可写入,允许对文件内容同时完成读和写操作

2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组 。

3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。

4:其实该对象内部封装了字节读取流和字节写入流。

注意:实现随机访问,最好是数据有规律。

File

Ø File类是IO包中唯一代表磁盘文件本身的对象,File类定义了一些与平台无关的方法来操纵文件

Ø File类将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。

Ø Java约定使用/来作路径分隔符,或者\\

Ø RandomAccessFile类是Java语言中功能最为丰富的文件访问类

RandomAccessFile能以只读或读写方式打开文件

   new RandomAccessFile(f,"rw");// 读写方式

   new RandomAccessFile(f,"r");// 只读方式

File类常见方法:

构造函数

q File(String pathFile f1 = new File("/");

q File(String path, String filenameFile f2 = new File("/","autoexec.bat");

q File(File file, String filenameFile f3 = new File(f1,"autoexec.bat");

1:创建。

boolean createNewFile()在指定目录下创建文件,如果该文件已存在,则不创建。

而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。

boolean mkdir()创建此抽象路径名指定的目录。

boolean mkdirs():创建多级目录。 

2:删除。

boolean delete()删除此抽象路径名表示的文件或目录。

void deleteOnExit():在虚拟机退出时删除。

注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用delete删除。

注意:window的删除动作,是从里往外删。java删除文件不走回收站,要慎用。

3:获取.

long length():获取文件大小。

String getName()返回由此抽象路径名表示的文件或目录的名称。

String getPath()将此抽象路径名转换为一个路径名字符串。

String getAbsolutePath()返回此抽象路径名的绝对路径名字符串。

String getParent()返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回null

long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。

File.pathSeparator返回当前系统默认的路径分隔符windows默认为“;”。

File.Separator返回当前系统默认的目录分隔符windows默认为“\

4:判断:

boolean exists():判断文件或者文件夹是否存在。

boolean isDirectory()判断此抽象路径名是否是目录。

boolean isFile()测试此抽象路径名表示的文件是否是一个标准文件。

boolean isHidden()测试此抽象路径名指定的文件是否是一个隐藏文件。

boolean isAbsolute()测试此抽象路径名是否为绝对路径名。

5:重命名。

 boolean renameTo(File dest):可以实现移动的效果。剪切+重命名

String[] list():列出指定目录下的当前的文件和文件夹的名称。包含隐藏文件。

如果调用list方法的File 对象中封装的是一个文件,那么list方法返回数组为null。

如果封装的对象不存在也会返回null。只有封装的对象存在并且是文件夹时,这个方法才有效。

FilenameFilterFileFilter的区别?

当需要文件夹对象的时候可以用过滤器,可以过滤一些自定义的条件

1, 当文件夹的对象以字符串的形式返回子目录的一些文件,需要定义FilenameFilter接口,实现里面的方法accipt(File dir,String name)

2, 当文件夹的对象是以File的形式返回的一些子目录的文件,需要FileFilter这个接口,实现里面的方法accept(File pathname)

递归

Ø 就是一个方法每次用不同的参数值反复调用自己。其实递归就是在栈内存中不断的加载同一个方法。

何时用递归呢?

当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。

简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。

递归的注意事项:

1:一定要定义递归的条件。

2:递归的次数不要过多。容易出现StackOverflowError 栈内存溢出错误

Test:删除一个目录下的所有文件.

public static void del(File f) {

File[] files = f.listFiles();

for(File file:files){

if(file.isDirectory()){

del(file);

}else{

file.delete();

}

}

f.delete();

}



0 0
原创粉丝点击