------- android培训、java培训、期待与您交流!----------
IO流包括输入流与输出流,又由于以字节为单位的流处理处理字符很不方便,所以又增加了字节流。
对于流的学习有一个通则:如何从能够发送的字节序列的任何数据源取得输入,以及如何将输出发送到能够接收字节序列的任何目的地。这些序列的源和目的地可以是文件,也可以是网络连接甚至是内存块。
流操作数据的时候,必须将操作的字节的输入或或输出完成,否则Java就会挂起这个线程,让其他线程先工作,知道这些操作完成,这是流的阻塞特性,如read方法,必须读取到数据,否则就会挂起。
流在操作数据的时候会不停的访问设备,为了减少对设备的访问以提高效率,可以将一个已经存在流传递给另一个流,
比如缓冲流与打印流都可以接受一个已经存在的流。
读取或者写入,否则JAVA就会挂起这个线程,让其他线程工作,直到完成
两个方都能阻塞(block),一个线程直到字节被真正的
读取或者写入,否则JAVA就会挂起这个线程,让其他线程工作,直到完成两个方都能阻塞(block),一个线程直到字节被真正的
读取或者写入,否则JAVA就会挂起这个线程,让其他线程工作,直到完成两个方都能阻塞(block),一个线程直到字节被真正的
读取或者写入,否则JAVA就会挂起这个线程,让其他线程工作,直到完成
1.IO体系
1.1字符流
1.1.1 Reader:读取流,子类必须要实现的方法只有read(char[] cbuf, int off, int len)或者read(char[] cbuf) 和close
|---BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
|---LineNumberReader:跟踪行号的缓冲字符输入流。
|---InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
|---FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
|---CharArrayReader: 可以用作字符输入流的字符缓冲区
|---StringReader: 如何理解这些读取流呢?
比如,键盘输入System.in返回的是字节流,但为了操作文本类文件方便(使用readLine()方法),使用转换流(字符与字节之间的桥梁)方式,将获得的字节转换为字符,格式为:
InputStreamReader isr = new InputStreamReader(System.in);
如果你想提高读取的效率就是用缓冲读取流,他需要一个读取流对象,格式为:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
以上步骤就将从键盘读取的字节转换为了好操作的字符。
当然,如果你读取的是文件(比如一个txt),那么直接利用转换流的子类FileReader,他操作的对象就是设备上的字节。格式为:
FileReader fr = new FileReader("C:// 1.txt");
特别要注意的是,Reader的read()方法,返回的是一个字符数组,而InputStream的read()方法返回的的是一个字节数组。所以他们读取的数据在内存中的数据类型是不同的,要事先创建好一个字符数组或字节数组。如果使用的是缓冲流,那么就不必创建数组了,因为BufferedReader会自动创建一个默认大小的缓冲区,又由于read方法会返回读取的字符数,所以要想建立一个int类型的变量来接收这个数字
<span style="font-size:18px;"> <span style="font-size:18px;"></span>bi = new BufferedInputStream(new FileInputStream("C:\\image.jpg"));
bo = new BufferedOutputStream(new FileOutputStream("D:\\image.jpg"));
int len = 0;
while((len = bi.read()) != -1)
{
bo.write(len);//字节流不必刷
}</span>
<span style="font-size:18px;"><span style="color:#ff00;">Writer<span style="font-family:宋体;">:</span></span>写入字符流的抽象类。子类必须实现的方法仅有<span style="font-family:Times New Roman;"> write(char[], int, int)</span><span style="font-family:宋体;">、</span><span style="font-family:Times New Roman;">flush() </span><span style="font-family:宋体;">和</span><span style="font-family:Times New Roman;"> </span>close()<span style="font-family:宋体;">。</span><span style="font-family:Times New Roman;"> </span></span>
特别要注意的是在关闭写入流的之前必须要先刷新一下,否则所读取的数据不会被存储到目的地,
而字节输出流OutputStream在close的时候可以自动刷新。
|---BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
|---OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
|---FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。
|---PrintWriter:
在内存中已经存在了由FileReader读取的数据(字符数组的形式存在),若想把这些数据以字符的形式写入设备就采用FileWriter;如果内存中存在一个字节流(以字节数组的形式存在),若想把他以字符的形式保存到设备上,就要像想把这个字节流转换为字符流,格式如下:
OutputStreamWriter osw = new OutputStreamWriter(new OutputStream("C://1.txt"));
若想提高效率就采用缓冲流。
那么什么时候使用打印流PrintWriter呢?
PrintWrite为其他输出流添加了一些功能,比如可以自动换行,自动刷新。
他可以接受FIle对象,字符串对象,字节输出流OutputStream,字符输出流Writer。
自动刷新的构造方法:
PrintWriter(OutputStream out(或者Writer), boolean autoFlush)
通过现有的 OutputStream 创建新的 PrintWriter
自动刷新的限制:
1 只有操作流对象才能自动刷新
2.如果启用了自动刷新,则只有在调用 println、printf 或format 的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。
<span style="font-size:18px;">//把键盘上输入的数据写入本地文件中或者打印到控制台上
public class PrintWriterDemo {
public static void main(String[] args) throws IOException
{
//接受键盘输入的数据,并将他转换为字符流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//创建一个打印流,将数据打印到控制台上,他可以接受字节输出流
PrintWriter pw = new PrintWriter(System.out);
//打印到txt文件,高效加自动刷新
PrintWriter pwtxt = new PrintWriter(new BufferedWriter(new FileWriter("C://111.txt")),true);
//BufferedReader提供了ReadLine的方法,可以读取一行,返回读取内容的String形式,如果已到达流末尾,则返回 null
//read()返回的是读取的字符/字节,到达流末尾返回-1
//read(byte[](char[]))返回的是读取的字符/字节数,到达流末尾返回-1
String line;//readLine
// while((line = br.readLine())!= null)
// {
// //写入数据,可以用write,但是不能自动换行
// pw.println(line);
// pw.flush();
// }
// pw.close();
String linetxt;
while((linetxt = br.readLine()) != null)
{
pwtxt.println(linetxt);
}
pwtxt.close();
br.close();
}
}
</span>
1.2 字节流:
InputStream:是表示字节输入流的所有类的超类。
|--- FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|--- FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
|--- BufferedInputStream:该类实现缓冲的输入流。
-----------------------------------------------
OutputStream:此抽象类是表示输出字节流的所有类的超类。
|--- FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
|--- FilterOutputStream:此类是过滤输出流的所有类的超类。
|--- BufferedOutputStream:该类实现缓冲的输出流。
|--- PrintStream:
|--- DataOutputStream:
将C盘下的图片复制到D盘
<span style="font-size:18px;">public class CopyImage {
public static void main(String[] args) throws Exception
{
BufferedInputStream bi = new BufferedInputStream(new FileInputStream("C://111.jpg"));
PrintStream bo = new PrintStream(new BufferedOutputStream(new FileOutputStream("D://3333.jpg")));
// BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D://11222.jpg"));
int len;
while((len = bi.read()) != -1)
{
bo.write(len);
bo.flush();
}
bo.close();
bi.close();
}
}</span>
1.3转换流的说明
转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
转换流的最强功能就是基于 字节流 + 编码表 。没有转换,没有字符流。
发现转换流有一个子类就是操作文件的字符流对象:
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"); //因为简化。 如果需要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。
凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。
1.4IO流的操作步骤:
IO流所操作的设备可以分为:
数据源对应的设备:硬盘(File),内存(数组),键盘(System.in)
数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。 1.明确源和目的。
源:硬盘上的字节文件如图片、视频等,采用FileInputStream
硬盘上的纯文本文件如txt文件,采用FileReader;
键盘输入:System.in,System.in本身就是一个字节输入流,如果你想把它转换成字符流就采用转换流
内存中的字节、字符数组,都已经读到内存中了,还用什么读取流啊,它和键盘输入差不多。
目的:
硬盘上的字节文件:FileOutputStream、PrintStream
硬盘上的字符文件:FileWriter、PrintWriter
控制台:直接sop
1,打印流:
PrintStream:字节打印流。
特点:
1,构造函数接收File对象,字符串路径,字节输出流。意味着打印目的可以有很多。
2,该对象具备特有的方法 打印方法 print println,可以打印任何类型的数据。
3,特有的print方法可以保持任意类型数据表现形式的原样性,将数据输出到目的地。
对于OutputStream父类中的write,是将数据的最低字节写出去。
PrintWriter:字符打印流。
特点:
1,当操作的数据是字符时,可以选择PrintWriter,比PrintStream要方便。
2,它的构造函数可以接收 File对象,字符串路径,字节输出流,字符输出流。
3,构造函数中,如果参数是输出流,那么可以通过指定另一个参数true完成自动刷新,该true对println方法有效。
什么时候用?
当需要保证数据表现的原样性时,就可以使用打印流的打印方法来完成,这样更为方便。
保证原样性的原理:其实就是将数据变成字符串,在进行写入操作。
2.需不需要提高效率(一般都需要)
直接将建好的输入输出流人给Buffered***就可以了。
//从内存中的数组/集合类中读取数据写入硬盘
//将E://毕向东//黑马程序员_毕向东最新经典Java基础视频下的所有avi文件的绝对存储路径写入dir.txt中
public class FileListDemo {
public static void main(String[] args) throws IOException
{
File file = new File("E://毕向东//黑马程序员_毕向东最新经典Java基础视频");
ArrayList<File> list = new ArrayList<File>();
fileToList(file, list);
BufferedWriter bw = new BufferedWriter(new FileWriter("E://毕向东//黑马程序员_毕向东最新经典Java基础视频//dir.txt"));
for(File f : list )
{
bw.write(f.getAbsolutePath());
bw.newLine();
bw.flush();
}
bw.close();
}
public static void fileToList(File file,ArrayList list)
{
//首先要获得所有的目录
File[] files = file.listFiles();
for(File f : files)
{
if(f.isDirectory())
{
fileToList(f,list);
}
else if(f.isFile() && f.getName().endsWith("avi"))
{
list.add(f);
}
}
}
}
1.5 Properties
Properties是Hashtable的子类,也就是说它具备Map集合的特点,而且它里面存储的键值都是字符串。
换句话说:Properties是集合中和IO技术相结合的容器。
使用的地方:可以用于键值对形式的配置文件。
它的键和值都是字符串。
常用方法:
String | getProperty(String key) 就是用键获取值。用指定的键在此属性列表中搜索属性。 |
Object | setProperty(String key,String value) 就是根据键修改相应的值,修改的只是内存中的值,文件中的值不变,改完可以store调用 Hashtable 的方法 put |
void | load(InputStream inStream) 将流中的数据加载进集合。从输入流中读取属性列表(键和元素对)。 |
void | load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 |
void | store(OutputStream out,String comments) 改完之后使用以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此Properties 表中的属性列表(键和元素对)写入输出流。 |
void | store(Writer writer,String comments) 以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。 |
Set<String> |
|
即使我们不学properties也可以将流中的数据以键值对的形式加载进文件中,但是Properties将这一过程简化为load方法了。
Load方法的原理:
public class propertiesDemo {
public static void main(String[] args) throws IOException
{
BufferedReader br = new BufferedReader(new FileReader("info.txt"));
String line;
Properties p = new Properties();//建立一个properties集合
while((line = br.readLine()) != null )
{
String [] strs = line.split("=");
p.put(strs[0], strs[1]);
}
br.close();
}
}
下面写一个记录软件运行次数的小程序
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.util.Properties;
public class SoftwareRunTimes
{
public static void main (String[] arr)throws Exception
{
//先判断配置文件是否存在,如果不存在就建立一个
File file = new File("D://count.ini");
if(!file.exists())
{
file.createNewFile();
}
//将配置文件中的内容按键值对的形式加载进Properties集合
Properties p = new Properties();
FileInputStream fi = new FileInputStream(file);
p.load(fi);
//获得配置文件中软件的使用次数value,它是字符串形式的
//设置一个计数器记录软件的使用次数
int count = 0;
String value = p.getProperty("time");//如果是是新创建的文件返回的肯定是null,因为文件内没有值
if(value != null)
{
count = Integer.parseInt(value); // 这就是value的int形式
if(count > 5)
{
System.out.println("软件有效期过");
return;
}
}
count++;
p.setProperty("time", count+"");//加空格字符串是为了让他转化为子串
//修改完内存中的数据了,要想将修改后的数据写入文件就要使用输出流,并利用store方法
FileOutputStream fo = new FileOutputStream(file);
p.store(fo, "软件的使用次数,只能试用5次");
}
}