14Java的IO流
2021-02-08 06:16
标签:计算机 输入输出流 cli 处理 ascii码 过滤 file类 rename work 例子 通常把某个文件夹里面的文件和目录全部遍历出来,不管层级有多深,但是不知道有多少层目录,就会导致遍历文件的代码会重复,因为每进入一个目录,就要遍历里面的文件和目录,下面举一个例子说明遍历文件夹所有文件/目录时,陷于重复代码。 从上面的例子,可以发现for下面的代码就开始重复了,但是不知道要重复多少次,因为目录不知道有多少层,现在用递归来遍历。 注意:递归需要有结束条件,如果没有结束条件会一直执行递归,直到内存溢出报异常。上面的例子的结束条件是目录数,目录是有限的,所以遍历完目录/文件就会结束递归。 iO流用来处理设备之间的数据传输。 Java程序中,对于数据的输入/输出操作以”流(stream)” 的方式进行。 java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
文件需要读取内容时,可以使用FileInputStream类,下面举一个例子说明FileInputStream类如何读取文件。 文件需要写进数据时,可以使用FileOutputStream类,下面举一个例子说明FileOutputStream类如何对文件写入内容。 练习 把一个文件复制到另外一个目录去,利用FileInputStream和FileOutputStream来操作。 注意:文件字节流非常通用,可以用来操作字符的文档,还可以操作任何的其他类型文件(图片,压缩包等等),引用字节流直接使用二进制。字节流是通过二进制方式传输数据的。 字符输入流和字节输入流用法差不多,区别在于,一个是用于字节传输的方式,一个是字符传输的方式。唯一改变的就是临时数组字节流就采用byte[]数组,字符流就采用char[]数组。 最重要的区别:字节流可以传输类型比较多的文件,但是字符流只能传输文本文档,word文档虽然是文本文档,但是文档存在格式,虽然可以传输成功,但是文件会打不开。也就是只能支持txt类型文本。 字符输出流和字节输出流用法差不多,区别在于,一个是用于字节传输的方式,一个是字符传输的方式。 练习 把一个文件复制到另外一个目录去,利用FileReader和FileWriter来操作。 注意: 1、定义文件路径时,注意:可以用“/”或者“\\”。 2、在写入一个文件时,如果目录下有同名文件将被覆盖。 3、在读取文件时,必须保证该文件已存在,否则出异常。 没有缓冲流的时候,IO流的操作是“ 读一点文件,然后写入文件,再读一点文件,然后再写入文件 “。 举个例子说明缓冲流的作用:比如送快递,寄快递不可能是有人寄出了,就马上送,而是在一个时间点内,把当天的全部快递统一装车运输,这里面运输快递的车就是相当于缓冲流。 IO流都是计算机与硬盘之间发送的io操作,基于硬盘的读写相对式比较慢,这个操作的速度受到硬盘的读写速度的约束,为了能够提高读写速度,一定程度上绕过硬盘的限制,Java提供一种缓冲流来实现。 缓冲流就是先把数据缓冲到内存里,在内存中去做io操作,基于内存的io操作大概能比基于硬盘的io操作快75000多倍. 缓冲流不同的类对应不同的IO流类,不可混合使用缓冲流类。 BufferedInputStream——FileInputStream BufferedInputStream——FileOutputStream BufferedReader——FileReader BufferedWriter——FileWriter BufferedInputStream是字节缓冲输入流。 例子 BufferedOutputStream是字节缓冲输出流。 例子 练习 把一个文件复制到另外一个目录去,利用BufferedInputSream和BufferedOutputStream来操作。 BufferedReader是字符缓冲输入流。 例子 BufferedWriter是字符缓冲输出流。 例子 练习 把一个文件复制到另外一个目录去,利用BufferedReader和BufferedWriter来操作。 注意:字符流只能操作文本文档txt,其他类型文件虽然可以传输成功,但是会打不开使用字符流传输后的文件。 转换流提供了在字节流和字符流之间的转换 用于将字节流中读取到的字节按指定字符集解码成字符。需要和 InputStream“套接”。 例子 用于将要写入到字节流中的字符按指定字符集编码成字节。 需要和OutputStream“套接”。File类
File类常用方法
import java.io.File;
import java.io.IOException;
/**
*
* @author leak File类只能操作文件本身,但是不能操作文件内容 例如:你可以把一个txt文档增删改查,但是不能对txt里面的内容增删改查
*/
public class Test {
public static void main(String[] args) {
// 1访问文件名
// 这个时候对象f就是abc.txt文件
// 文件路径的反斜杆\和斜杆/,如果是\那么需要\\两个,一个做转义符作用D:\\abc
// 如果是斜杆D:/ABC/ABC.TXT,一个就够了
// File f = new File("文件路径");把文件转为对象操作
File f = new File("D:/abc/abc.txt");
// 创建文件对象有两种方式,一种上面的直接加路径
// 一种就是File f1 = new File("父路径","子路径");
// File f1 = new File("D:/abc","abc.txt");
// 使用相对路径来创建file对象
File f5 = new File("/Test1/src/org/chen/day12/Test.java");
// 获取当前的文件名,如果是路径,那么就是获取最后一级目录名
System.out.println(f.getName());
// 如果new File对象的路径是相对路径,那么getPath就是获取相对路径地址
// 如果是绝对路径,那么getPath就是获取绝对的地址
System.out.println(f.getPath());
// 获取当前文件/夹的路径,是完整的路径(绝对路径)
System.out.println(f.getAbsolutePath());
System.out.println(f5);// 打印文件的相对路径
// 返回一个用当前的文件的绝对路径构建的file对象
System.out.println(f5.getAbsoluteFile());
// 返回当前文件/夹的父级路径,也就是文件的前面的完整路径,比如:D:\abc\abc.txt就是获取D:\abc父级路径
System.out.println(f.getParent());
// 文件重命名
f.renameTo(new File("D:/abc/rename.txt"));
System.out.println("====================");
// 2文件检测
File f6 = new File("D:/abc/favicon.ico");
// exists()判断文件/夹是否存在,返回布尔值
System.out.println(f6.exists());
// canWrite()判断文件/夹是否可写,canRead()判断文件/夹是否可读
System.out.println(f6.canWrite());
System.out.println(f6.canRead());
// 判断当前的file对象是不是文件
System.out.println(f6.isFile());
// 判断当前的filed对象是不是文件夹/目录
System.out.println(f6.isDirectory());
// 获取文件最后修改的时间,返回的是一个毫秒数
System.out.println(f6.lastModified());
// 返回文件的长度,单位是字节数
System.out.println(f6.length());
System.out.println("====================");
// 3文件/夹操作
File f3 = new File("D:/abc/abc.txt");
System.out.println(f3.exists());
// 如果文件不存在,则创建
if (!f3.exists()) {
try {
f3.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
// 删除文件
f3.delete();
File f2 = new File("D:/abc/cc");
System.out.println(f2.exists());
if (!f2.exists()) {
// 创建单层目录,如果使用mkdir方法创建多层目录,只能一层层的执行mkdir方法
f2.mkdir();
}
File f4 = new File("D:/abc/dd/aa/ee");
if (!f4.exists()) {
// 创建多层目录,注意是mkdirs(),上面的mkdir少了s
f2.mkdirs();
}
File f7 = new File("D:/abc");
// list()返回当前文件夹的子集的名称,包括目录和文件名
String[] flist = f7.list();
for (String name : flist) {
System.out.println(name);
}
// listFiles()返回当前文件夹的子集的file对象,包括目录和文件名
//就是返回子集里面所有的文件名/目录的绝对路径,例如: D:\abc\cc, D:\abc\favicon.ico
File[] files = f7.listFiles();
for(File file : files) {
System.out.println(file);
}
}
}
递归遍历文件夹/文件
import java.io.File;
public class Test2 {
public static void main(String[] args) {
}
//递归的引出
public void findAllFiles(File file) {
if(file.isFile()) {
System.out.println(file.getAbsoluteFile()+"是一个文件");
}else {
//是文件夹则继续遍历里面的文件和目录
System.out.println(file.getAbsolutePath()+"是文件夹");
//获取文件夹中的所有目录/文件,使用file数组接收
File[] files = file.listFiles();
//遍历数组里面的文件/目录
for(File f : files) {
//如果是文件则打印文件, 下面开始第一层重复
if(file.isFile()) {
System.out.println(file.getAbsoluteFile()+"是一个文件");
}else {
//如果是文件夹则继续遍历里面的文件和目录
System.out.println(file.getAbsolutePath()+"是文件夹");
//获取文件夹中的子集,包含文件/目录
File[] files2 = file.listFiles();
//遍历数组里面的文件/目录
for(File f2 : files) {
//如果是文件则打印文件, 这里的下面是第二层重复
if(file.isFile()) {
System.out.println(file.getAbsoluteFile()+"是一个文件");
}else {
//如果是文件夹则继续遍历里面的文件和目录
System.out.println(file.getAbsolutePath()+"是文件夹");
//获取文件夹中的子集,包含文件/目录
File[] files3 = file.listFiles();
//然后继续遍历files3数组里面的文件和目录,如果是目录那么继续遍历
//这里就不重复写代码了,for循环后面都是一样的代码,这样一直遍历,不知道要写多少层,是死循环代码
//下面是第三层重复代码,就不写了,如果是重复的代码,而且不知道写到几层,就应该使用递归方法。递归就是方法调用本身,方法调用自己
}
}
}
}
}
}
}
import java.io.File;
public class Test3 {
public static void main(String[] args) {
//创建一个目录对象
File file = new File("D:/abc");
//然后把目录对象,传给findAllFiles()方法遍历该目录底下的所有层级的目录/文件
new Test3().findAllFiles(file);
}
//递归遍历所有的目录/文件,每一层
public void findAllFiles(File file) {
if(file.isFile()) {
System.out.println(file.getAbsoluteFile()+" 是一个file");
}else {
//是文件夹则继续遍历里面的文件和目录
System.out.println(file.getAbsolutePath()+" 是一个文件夹");
//获取文件夹中的所有目录/文件,使用file数组接收
File[] files = file.listFiles();
//遍历数组里面的文件/目录
for(File f : files) {
//重点在这里,递归:自己调用自己,直到遍历完所有文件/目录,否则重复调用findAllFiles(f);
findAllFiles(f);
}
}
}
}
IO原理
IO流的分类
IO流体系
FileInputStream 文件字节输入流
import java.io.FileInputStream;
public class Test4 {
public static void main(String[] args) {
Test4.readFile("D:/abc/rename.txt");
}
//读取文件的方法
public static void readFile(String path) {
try {
//把文件转为输入流
FileInputStream in = new FileInputStream(path);
//设置byte数组接收读取的文件的内容
byte[] b = new byte[10];
//设置临时变量存储每次读取的长度,也就是in.read(10)返回的长度
//肯定有人说为什么不直接使用in.read(10)的返回值,而是要新创建变量len去接收返回值再使用
//因为每次执行in.read(b)方法时,都会读取一次文件,每次返回的值都不一样
//比如下面的len = in.read(b)第一次返回10,但是你在循环体中输出in.read(b)的长度就不是10了,因为又执行了一遍读操作
int len = 0;
//因为byte数组固定了长度为10,所以需要循环读取文件,每次读取10个字符
//补充:read()方法是读取一个字节,返回字节如果是字母就返回ASCII码,比如文件内容第一个字节是a就返回97
//read(byte[] b)方法是返回每次读取字节数的个数,个数根据byte数组长度决定,所以下面使用read(byte[] b)方法,而不使用上面的read()方法
while((len = in.read(b))!=-1) {
System.out.println(len);
// System.out.println(in.read(b));//这里注释掉了,如果想了解len变量为什么要存在,可以取消注释,测试一下
//new String(缓冲的数组,数组里面的字符串转换起始坐标,每次转换的长度),这里是把每次读取的数据转为字符串输出
System.out.println(new String(b,0,len));
}
//每次读取数据都要关闭输入流,因为流操作的时候凡是跨出虚拟机边界的资源都要求程序员自己关闭,不要指望垃圾回收。
in.close();//你读一个文件,忘记关闭了流,你在操作系统里对这个文件的写,删除等操作就会报错,告诉你这个文件被某个进程占用。
} catch (Exception e) {
e.printStackTrace();
}
}
}
FileOutputStream 文件字节输出流
import java.io.FileOutputStream;
public class Test5 {
public static void main(String[] args) {
//传一个文件过去,写进数据
Test5.writeFile("D:/abc/abc.txt");
}
public static void writeFile(String absoluteFile) {
//创建输出流
try {
FileOutputStream out = new FileOutputStream(absoluteFile);
//创建要写入的数据
String str = "abcdefghijklmnopkrstuvwxyz";
//把数据写入到输出流,因为OutputStream是二级制流,所以str需要转换为Byte类型才能写入到输出流
out.write(str.getBytes());//把数据写入到内存
out.flush();//把数据从内存刷进硬盘
//关闭输出流
out.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test6 {
public static void main(String[] args) {
//sAbsoluteFile是要获取的文件路径,前面变量名的s是start
String sAbsoluteFile = "D:/abc/a.jpg";
//eAbsoluteFile是要输出的文件路径,前面变量首字母e是end
String eAbsoluteFile = "D:/abc/cc/sui.jpg";
//把sAbsoluteFile文件复制到eAbsoluteFile中去
Test6.copyFile(sAbsoluteFile, eAbsoluteFile);
}
/**
* 复制文件到指定位置
* @param sAbsoluteFile 源文件路径
* @param eAbsoluteFile 复制到文件夹位置
*/
public static void copyFile(String sAbsoluteFile,String eAbsoluteFile) {
try {
//创建输入流获取要复制的文件内容,也就是要读取的源文件
FileInputStream in = new FileInputStream(sAbsoluteFile);
//创建输出流,输出到指定位置,也就是要复制到哪里
FileOutputStream out = new FileOutputStream(eAbsoluteFile);
//创建接收的数组
byte[] b = new byte[100];
//创建临时变量接收返回每次读取文件的长度
int len = 0;
//循环读取数据,注意read()方法和read(byte[] b)的区别,一个是返回字节,一个是返回每次读取字节的个数
while((len = in.read(b)) != -1) {
//把读取到的数据通过输出流写入
out.write(b, 0, len);//把数据写入到内存
}
//把内存中的数据刷入硬盘
out.flush();
//关闭输出/输入流
out.close();
in.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
FileReader文件字符输入流
import java.io.FileReader;
public class Test7 {
public static void main(String[] args) {
String inPath = "D:/abc/abc.txt";
Test7.testFileReader(inPath);
}
/**
* 文件字符输入流FileReader
* @param inPath 源文件路径
*/
public static void testFileReader(String inPath) {
try {
//创建文件字符输入流的对象
FileReader fr = new FileReader(inPath);
//创建临时存数据的字符数组
char[] ch = new char[10];
//定义临时变量存储每次读取数据的长度
int len = 0;
//循环遍历字符输入流
while((len = fr.read(ch)) != -1) {
System.out.println(new String(ch,0,len));
}
//关闭输入流
fr.close();
}catch(Exception e) {
e.printStackTrace();
}
}
FileWriter文件字符输出流
import java.io.FileWriter;
public class Test8 {
public static void main(String[] args) {
//传一个文件过去,写进数据
Test8.TestFileWriter("D:/abc/abc.txt");
}
public static void TestFileWriter(String absoluteFile) {
//创建输出流
try {
FileWriter out = new FileWriter(absoluteFile);
//创建要写入的数据
String str = "abcdefghijklmnopkrstuvwxyz";
//把数据写入到输出流
out.write(str);//把数据写入到内存
out.flush();//把数据从内存刷进硬盘
//关闭输出流
out.close();
}catch(Exception e) {
e.printStackTrace();
}
}
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Test9 {
public static void main(String[] args) {
String inPath = "D:/abc/abc.txt";
String outPath = "D:/abc/cc/ss.txt";
Test9.copyFile(inPath, outPath);
}
/**
*
* @param inPath 源文件路径
* @param outPath 传输到指定目录
*/
public static void copyFile(String inPath, String outPath) {
FileReader fr = null;
FileWriter fw = null;
try {
// 创建字符输入流
fr = new FileReader(inPath);
// 创建字符输出流
fw = new FileWriter(outPath);
//创建临时数组接收数据
char[] ch = new char[10];//因为是字符流所以使用char数组
int len = 0;
//循环遍历读取文件,然后写入到内存中
while((len = fr.read(ch)) != -1) {
fw.write(ch,0,len);
}
//把内存的数据刷入硬盘
fw.flush();
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭流
try {
fw.close();
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲流
BufferedInputStream
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class Test {
public static void main(String[] args) {
Test.testBufferedInputStream();
}
/**
* 缓冲字节输入流
* BufferedInputStream
*/
public static void testBufferedInputStream() {
try {
//文件字节输入流
FileInputStream in = new FileInputStream("E:\\workspace-sts\\Test1\\src\\org\\chen\\day13\\tt.txt");
//把文件字节输入流放到缓冲字节输入流,BufferedInputStream(InputStream类/子类)
BufferedInputStream br = new BufferedInputStream(in);
//创建临时数组接收数据,下面的注释就不写了,操作都差不多
byte[] b = new byte[10];
int len = 0;
while((len = br.read(b)) != -1) {
System.out.println(new String(b,0,len));
}
//关闭流的时候,本着一个最晚开的最早关,依次关闭
br.close();//关闭缓冲流
in.close();//关闭输入流
} catch (Exception e) {
e.printStackTrace();
}
}
}
BufferedOutputStream
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
public class Test2 {
public static void main(String[] args) {
Test2.testBufferedOutputStream();
}
/**
* 缓冲字节输出流
* BufferedOutputStream
*/
public static void testBufferedOutputStream() {
try {
//创建输出流
FileOutputStream out = new FileOutputStream("E:\\workspace-sts\\Test1\\src\\org\\chen\\day13\\tt.txt");
//缓冲字节输出流
BufferedOutputStream bos = new BufferedOutputStream(out);
//把内容写入到文件中,注意写入的文件如果名字一样,内容会被覆盖
String str = "需要被写入的内容。。。。。谢谢谢谢谢谢";
//写入到内存中,write方法只能写入字节,所以str转换byte写入
bos.write(str.getBytes());
bos.flush();//刷到硬盘上
//先开后关原则。
bos.close();
out.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test4 {
public static void main(String[] args) {
String inPath = "D:/abc/a.jpg";
String outPath = "D:/abc/cc/tou.jpg";
Test4.copyFile(inPath, outPath);
}
/**
* 缓冲流实现文件的复制
* @param inPath 源文件
* @param outPath 复制到指定路径
*/
public static void copyFile(String inPath,String outPath) {
try {
//创建输入流
FileInputStream fs = new FileInputStream(inPath);
//缓冲输入流
BufferedInputStream bis = new BufferedInputStream(fs);
//创建输出流
FileOutputStream fo = new FileOutputStream(outPath);
//缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(fo);
//临时数组
byte[] b = new byte[10];
int len = 0 ;
//循环读取缓冲输入流
while((len = bis.read(b)) != -1) {
//读取的数据写入到缓冲输出流
bos.write(b,0,len);
}
//刷到硬盘中
bos.flush();
//先开后关原则
bos.close();
fo.close();
bis.close();
fs.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
BufferedReader
import java.io.BufferedReader;
import java.io.FileReader;
/**
* 缓冲字符流
* BufferedReader
* @author leak
*
*/
public class Test5 {
public static void main(String[] args) {
String file = "D:/abc/abc.txt";
Test5.testBufferedReader(file);
}
/**
*
* @param file 源文件
*
*/
public static void testBufferedReader(String file) {
try {
//创建字符输入流
FileReader fr = new FileReader(file);
//创建字符缓冲流
BufferedReader br = new BufferedReader(fr);
//创建临时数组
char[] ch = new char[10];
int len = 0;
while((len = br.read(ch)) != -1) {
System.out.println(new String(ch,0,len));
}
//关闭流
br.close();
fr.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
BufferedWriter
import java.io.BufferedWriter;
import java.io.FileWriter;
/**
* 字符缓冲输出流
* BufferedWriter
* @author leak
*
*/
public class Test6 {
public static void main(String[] args) {
String file = "D:/abc/abc.txt";
Test6.testBufferedWriter(file);
}
public static void testBufferedWriter(String file) {
try {
//创建字符输出流
FileWriter fw = new FileWriter(file);
//字符缓冲流
BufferedWriter bw = new BufferedWriter(fw);
String str = "需要录入的 字符。。。";
bw.write(str);//把内容写进内存
bw.flush();//把数据刷入硬盘
//先开后关原则,先开的字符输出流,最后才关字符输出流
bw.close();
fw.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class Test7 {
public static void main(String[] args) {
String inPath = "D:/abc/abc.txt";
String outPath = "D:/abc/cc/add.txt";
Test7.copyFile(inPath, outPath);
}
/**
* 文件复制
*
* @param inPath 源文件
* @param outPath 复制到指定的路径
*/
public static void copyFile(String inPath, String outPath) {
try {
// 创建字符输入流
FileReader fr = new FileReader(inPath);
// 字符缓冲流
BufferedReader br = new BufferedReader(fr);
// 创建字符输出流
FileWriter fw = new FileWriter(outPath);
//字符缓冲流
BufferedWriter bw = new BufferedWriter(fw);
//临时数组
char[] ch = new char[100];
int len = 0;
//循环读取数据
while((len = br.read(ch)) != -1) {
bw.write(ch,0,len);//把数据写入内存
}
bw.flush();//刷入硬盘
//关闭流
bw.close();
fw.close();
br.close();
fr.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
转换流
Java API提供了两个转换流:
InputStreamReader和OutputStreamWriter
字节流中的数据都是字符时,转成字符流操作更高效。
InputStreamReader
构造方法
InputStreamReader(InputStream in)
public InputSreamReader(InputStream in,String charsetName)
如: Reader isr = new InputStreamReader(System.in,”ISO5334_1”);import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class Test8 {
public static void main(String[] args) {
/**
* 所有文件都有编码格式
* 对我们来说,txt和Java文件一般都有三种编码
* ISO8859-1,西欧编码,是纯英文编码,不适应汉字
* GBK和UTF-8,这两编码适用于中文和英文
* 一般使用UTF-8编码
*/
String inPath = "D:/abc/abc.txt";
String charsetName = "UTF-8";
Test8.testInputStreamReader(inPath, charsetName);
}
/**
* 字节输入流转换字符输入流
* @param inPath 源文件
* @param charsetName 编码格式
* 注意,在转换字符流的时候,设置的字符集编码要与读取文件的数据的编码一致
* 不然会出现乱码,比如,你要读取的文件编码是GBK,但是你转为字符流设置了UTF-8,那么读取的内容就会乱码 显示
*/
public static void testInputStreamReader(String inPath, String charsetName) {
try {
// 字节输入流
FileInputStream fs = new FileInputStream(inPath);
// 字节流转换字符流
InputStreamReader isr = new InputStreamReader(fs, charsetName);//参数1是字节流,参数2编码
// 缓冲流
BufferedReader br = new BufferedReader(isr);
// 临时数组
char[] ch = new char[100];
int len = 0;
while ((len = br.read(ch)) != -1) {
System.out.println(new String(ch, 0, len));
}
//关闭流
br.close();
isr.close();
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
OutputStreamWriter
构造方法
public OutputStreamWriter(OutputStream out)
public OutputSreamWriter(OutputStrea