一、File类
1、File类概述
用来将文件或者文件夹封装成对象;方便对文件与文件夹的属性信息进行操作。File对象可以作为参数传递给流的构造函数。File类是文件和目录路径名的抽象表示形式。
示例:File类的基本操作
1. import java.io.*; 2. class FileDemo 3. { 4. public static void main(String[] args) 5. { 6. fileMethod(); 7. } 8. 9. public static void fileMethod() 10. { 11. //将a.txt封装成File对象;可以将已有的或者未出现的文件或者文件夹封装成对象; 12. File f1 = new File("a.txt"); 13. File f2 = new File("D:"+File.separator+"test","b.txt"); 14. File f = new File("D:"+File.separator+"test"); 15. File f3 = new File(f,"c.txt"); 16. 17. System.out.println("f1:"+f1); //打印的是它们的路径; 18. System.out.println("f2:"+f2); 19. System.out.println("f3:"+f3); 20. } 21.} 22. /* 23.打印结果: 24. f1:a.txt 25.f2:D:\test\b.txt 26. f3:D:\test\c.txt 27.*/
2、File类中的常见方法
1)、创建:
booleancreateNewFile():在指定位置创建文件,如果该文件已经存在则不再创建并返回false。(和输出流不同,输出流对象一建立就创建文件,如果该文件已存在,便将其覆盖)
booleanmkdir():创建文件夹;
booleanmkdirs():创建多级文件夹;
FilecreatTempFile(String prefix, String suffix) :在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。
FilecreatTempFile(String prefix, String suffix, File directory) :在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
2)、删除
booleandelete(): 删除文件或者目录。删除失败则返回false;
voiddeleteOnExit():在程序退出时,删除指定文件。
3)、判断
booleancanExecute():该文件是否可执行;
booleancanRead():该文件是否可读;
booleancanWriter():该文件是否可写;
booleanexists():文件是否存在;
booleanisFile():是否是文件;
booleanisDirectory():是否是目录;
booleanisHidden():是否隐藏;
booleanisAbsolute():测试此抽象路径名是否为绝对路径名。
4)、获取信息
StringgetName():获取名称;
StringgetPath():获取路径;
StringgetParent():获取符目录;该方法返回的是绝对路劲中的父目录;如果获取的是相对路径则返回null;如果相对路径中有上层目录那么该目录就是返回结果。
StringgetAbsolutePath():获取绝对路径;
longlastModified():获取最后一次修改时间;
long length():获取文件长度;
5)、其他常用方法
File[]listRoots():获取文件系统跟目录;
String[]list():获取指定目录下的文件及文件名;调用list方法的file对象必须是一个封装目录,该目录还必须存在;
一些常用方法示例:
import java.io.*; class FileDemo { public static void main(String[] args) throws IOException { listMethod_2(); } public static void listMethod_2() { File dir = new File("D:\\"); File[] files = dir.listFiles(); for(File f:files) { System.out.println(f.getName()+":"+f.length()); } } public static void listMethod_1() { File dir = new File("F:\\Java test\\day18"); String[] arr = dir.list(new FilenameFilter() //通过匿名内部类按照自己的需求覆盖其accept方法; { public boolean accept(File dir,String name) { return name.endsWith(".java"); } }); for(String name:arr) { System.out.println("length:"+arr.length+"*** name:"+name);//打印出指定目录下的所有.java文件; } } public static void method()throws IOException { File f = new File("D:\\1.txt"); f.createNewFile();//创建一个新文件; f.mkdir(); System.out.println(f.exists()); System.out.println(f.canExecute()); System.out.println(f.canRead()); System.out.println(f.canWrite()); System.out.println(f.lastModified()); } }
3、递归
示例:列出某一文件夹下的所有文件包括其子目录中的文件及内容;
分析:因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可,在列出的过程中,出现的还是目录的话,还可以再次调用本功能,也就是函数自身调用自身,这种体现形式或者编程手法称为递归。
递归使用注意事项:
1)、要限定条件,避免出现死循环;
2)、要注意递归的次数,尽量避免内存溢出。
import java.io.*; class FileDemo2 { public static void main(String[] args) { File dir = new File("f:\\Java test\\day20"); showDir(dir); //toBin(6); //System.out.println(getSum(10000)); //注意内存溢出; } public static void showDir(File dir) { System.out.println(dir); //打印该目录; File[] files = dir.listFiles(); //列出该目录中的目录和文件; for(int x=0; x<files.length; x++) { if(files[x].isDirectory()) //判断该文件是否为目录; showDir(files[x]); //如果是目录,则继续调用本功能寻找该目录中的文件; else System.out.println(files[x]); //如果不是,则直接打印; } } public static int getSum(int num) { if(num == 1) return 1; else return num + getSum(num-1); } public static void toBin(int num) { if(num > 0) { toBin(num/2); //使用递归调用十进制数转换成二进制; System.out.print(num%2); } } }
4、删除目录
删除原理:在Windows中,删除目录是从里往外删除的,既然从里往外删除,则需要使用递归;
import java.io.File; class RemoveDirDemo { public static void main(String[] args) { File f = new File("D:\\Java test"); removeDir(f); } public static void removeDir(File dir)//定义一个删除目录方法; { File[] files = dir.listFiles(); for(int x=0; x<files.length; x++) { if(!files[x].isHidden() && files[x].isDirectory()) removeDir(files[x]);//如果该文件是目录,则继续调用本功能,进入该目录; else //如果是文件,则直接将其删除; System.out.println(files[x]+"files:"+files[x].delete());//判断文件是否删除成功; } System.out.println(dir + "Dir:"+ dir.delete());//判断目录是否删除成功; } }
5、创建java文件列表
要求:将一个指定目录下的java文件的绝对路径存储到一个文本文件中,建立一个java文件列表。
思路:1)、对指定的目录进行递归;
2)、获取递归过程中所有java文件的路径;
3)、将这些路径存储到集合中;
4)、将集合中的数据写到文件中。
import java.util.*; import java.io.*; class JavaFileListDemo { public static void main(String[] args) { File file = new File("F:\\Java test");//指定要寻找java文件的目录; List<File> list = new ArrayList<File>();//创建一个集合用于存放文件路径; fileToList(file,list); //System.out.println(list.size()); File f = new File("D:\\JavaListFile.txt");//指定文本文件存放的路径; writeToFile(list,f.toString()); } public static void fileToList(File dir, List<File> list)//定义一个将文件路径存放到集合的方法; { File[] files = dir.listFiles(); for(File file: files) { if(file.isDirectory())//使用递归寻找.java文件; fileToList(file,list); else { if(file.getName().endsWith(".java")) list.add(file); } } } public static void writeToFile(List<File> list, String JavaListFile)//定义一个将文件路径写入文本文件的方法; { BufferedWriter bufw = null; try { bufw = new BufferedWriter(new FileWriter(JavaListFile)); for(File f:list) { String path = f.getAbsolutePath(); bufw.write(path); bufw.newLine(); bufw.flush(); } } catch (IOException e) { throw new RuntimeException("写入失败!!!"); } finally { try { if(bufw != null) bufw.close(); } catch (IOException e) { throw new RuntimeException("关闭失败!!!"); } } } }
6、Properties概述
Properties是Hashtable的子类,因此它具有Map集合的特点,而且它里面存储的键值对都是字符串;它也是集合和IO技术结合的集合容器。
该对象特点:可以用于键值对形式的配置文件。在加载数据时需要有固定格式:键=值。
通过示例来了解一下Properties的用法,
需求:将D盘中的info.txt中的键值数据存入集合进行操作。
思路:1)、用一个流和info.txt文件相关联;
2)、读取一行数据,将该行数据用”==”进行切割;
3)、将等号左边作为键,右边作为值存入到Properties集合中。
见:示例中的method方法。 Method方法为loadMethod方法中load的原理。
import java.util.*; import java.io.*; class PropertiesDemo { public static void main(String[] args) throws IOException { //setAndGetMethod(); //method(); loadMethod(); } public static void loadMethod()throws IOException//使用Properties类中的load方法也可以实现method方法中的 { Properties prop = new Properties(); FileInputStream fis = new FileInputStream("D:\\info.txt"); prop.load(fis); //load方法中接受一个FileInputStream类型的文件; prop.setProperty("java01","40");//将java01中的内容修改为40,但是这个不会改变源文件中的内容; FileOutputStream fos = new FileOutputStream("D:\\info.txt"); prop.store(fos,"将java01中的值改为40");//store方法可以修改源文件中的内容; System.out.println(prop); fis.close(); fos.close(); } public static void method() throws IOException { BufferedReader bufr = new BufferedReader(new FileReader("D:\\info.txt")); Properties prop = new Properties(); String line; while((line = bufr.readLine()) != null) { String[] arr = line.split("=");//使用“==”将字符串切割; prop.setProperty(arr[0],arr[1]);//再将左边的作为Properties集合的键; } //右边作为集合的值; System.out.println(prop);//打印结果:{java03=30, java02=20, java01=10, java04=40}; bufr.close(); } //设置和获取元素 public static void setAndGetMethod() { Properties prop = new Properties(); prop.setProperty("java01","10"); prop.setProperty("java02","20"); prop.setProperty("java03","30"); String value = prop.getProperty("java01");//通过键获取值; //System.out.println(value); prop.setProperty("java03","40"); //设置键的值; Set<String> names = prop.stringPropertyNames(); for(String s:names) { System.out.println(s + ":" + prop.getProperty(s)); } } }
练习:设计一个方法用于记录应用程序运行次数,如果次数一到则提示注册信息。
思考:该方法要定义一个计数器,程序没运行一次,计数器就累加一次,但是程序退出后,在下一次运行时,计数器要在原来的基础上累加。所以要建立一个将配置文件,用于记录该软件的运行次数。该配置文件使用键值对的形式,这样便于阅读数据并操作数据。键值对数据是map集合。数据是以文件形式存储,使用io技术。 即map+ioàPorperties。配置文件可以实现应用程序数据的共享。
代码如下:
import java.util.*; import java.io.*; class RunCount { public static void main(String[] args) throws IOException { Properties prop = new Properties(); File file = new File("count.ini"); if(!file.exists()) file.createNewFile(); FileInputStream fis = new FileInputStream(file); prop.load(fis); int count = 0; String value = prop.getProperty("times"); if(value != null) { count = Integer.parseInt(value); if(count >= 5) { System.out.println("您好,您使用的次数已到!请您注册使用。"); return ; } } count++; prop.setProperty("times",count+""); FileOutputStream fos = new FileOutputStream(file); prop.store(fos,""); fis.close(); fos.close(); } }
二、IO包中的其他类
1、打印流
打印流:该流提供了打印方法,可以将各种数据类型的数据都原样打印。
字节打印流:PrintStream
其构造函数可以接受的参数类型:
1)、File对象:File
2)、字符串路径:String
3)、字节输出流:OutputStream
字符打印流:PrintWrite
其构造函数可以接受的参数类型:
1)、File对象:File
2)、字符串路径:String
3)、字节输出流:OutputStream
4)、字符输出流:Writer
import java.io.*; class PrintStreamDemo { public static void main(String[] args) throws IOException { BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(System.out,true);//当这儿传入参数true时,使用println打印时就不 //需要刷新流了; //PrintWriter pw = new PrintWriter(new FileWriter("D:\\1.txt"),true); String line = null; while((line = bufr.readLine()) != null) { if(line.equals("over")) break; pw.println(line.toUpperCase()); //pw.flush(); } pw.close(); bufr.close(); } }
2、序列流(合并流)
SequenceInputStream:该流用于对多个流进行合并。它表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
通过演示示例来了解SequenceInputStream。
import java.io.*; import java.util.*; class SequenceInputStreamDemo { public static void main(String[] args) throws IOException { Vector<InputStream> v = new Vector<InputStream>();//因为要使用到枚举,所以此处集合使用Vector; v.add(new FileInputStream("D:\\1.txt")); v.add(new FileInputStream("D:\\2.txt")); v.add(new FileInputStream("D:\\3.txt")); Enumeration<FileInputStream> en = v.elements(); //使用枚举将元素添加到序列流中; SequenceInputStream sis = new SequenceInputStream(en); FileOutputStream fos = new FileOutputStream("D:\\4.txt");//将前面三个文件的内容写到该文件中; byte[] bt = new byte[1024]; int len = 0; while((len = sis.read(bt)) != -1) { fos.write(bt,0,len); } fos.close(); sis.close(); } }
3、文件切割
思路:1)、创建一个字节读取流与要被切割的文件相关联;
2)、创建一个合适大小的数组,将读取的流存放在数组中;
3)、创建一个字节写入流,将数组中的内容写入到该流中,就这样通过不断地读写,将文件切割。
代码如下:
import java.io.*; import java.util.*; class SplitFileDemo { public static void main(String[] args) throws IOException { //splitFile(); merge(); } //切割文件; public static void splitFile()throws IOException { FileInputStream fis = new FileInputStream("D:\\1.jpg");//创建一个文件输入流与被切割的文件相关联; FileOutputStream fos = null; byte[] bt = new byte[1024*512]; int len = 0; int count = 1; while((len=fis.read(bt)) != -1) { fos = new FileOutputStream("D:\\splitFiles\\"+(count++)+".part"); fos.write(bt,0,len); fos.close(); } fis.close(); } //合并文件; public static void merge()throws IOException { ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();//创建一个集合,将碎片文件存放在集合中; for(int x=1; x<=3; x++) { al.add(new FileInputStream("D:\\splitFiles\\"+x+".part")); } final Iterator<FileInputStream> it = al.iterator(); Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() { //创建一个Enumeration接口;将其方法覆盖; public boolean hasMoreElements() { return it.hasNext(); } public FileInputStream nextElement() { return it.next(); } }; SequenceInputStream sis = new SequenceInputStream(en);//用枚举型参数来初始化序列流; FileOutputStream fos = new FileOutputStream("D:\\splitFiles\\2.jpg");//将碎片文件合并在2.jpg文件内; byte[] bt = new byte[1024]; int len = 0; while((len=sis.read(bt)) != -1)//不断地读写将文件合并; { fos.write(bt,0,len); } sis.close(); fos.close(); } }
4、对象操作流
对象操作流主要有ObjectInputStream和ObjectOutputStream。被操作的对象需要实现Serializable(标记接口)。
ObjectOutputStream将Java 对象的基本数据类型和图形写入OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
ObjectOutputStream 和 ObjectInputStream 分别与 FileOutputStream 和 FileInputStream 一起使用时,可以为应用程序提供对对象图形的持久存储。ObjectInputStream 用于恢复那些以前序列化的对象。其他用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。
注意:静态是不能被序列化的。
如果非静态的成员变量想要不被序列化可以使用关键字transient修饰;
创建一个Person类:
import java.io.*; class Person implements Serializable { public static final long serialVersionUID = 42L;//将UID固定后,如果对象有所改动则依旧可以继续运行; private String name; static String country = "cn";//当该变量被静态修饰,该变量就不能被序列化; int age; //如果非静态变量不想被序列化可以使用关键字transient修饰;如:transient int age; Person(String name, int age,String country) { this.name = name; this.age = age; this.country = country; } public String toString() { return this.name+":"+this.age+" "+this.country; } }
示例:
import java.io.*; class ObjectStreamDemo { public static void main(String[] args) throws Exception { //writeObject(); readObject(); } //将存储的对象取出; public static void readObject() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Object.txt")); Person p = (Person)ois.readObject(); System.out.println(p); ois.close(); } //将存储的对象存入到硬盘的文件中; public static void writeObject() throws IOException { //创建流对象与要操作的文件相关联; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Object.txt")); oos.writeObject(new Person("java01",01,"USA"));//将对象写入到流中;如果后面的country被静态修饰,则不被序列化; oos.close(); } }
5、管道流
管道输入流:PipedInputStream。管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏
管道输出流:PipedOutputStream。可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。通常,数据由某个线程写入 PipedOutputStream 对象,并由其他线程从连接的 PipedInputStream 读取。不建议对这两个对象尝试使用单个线程,因为这样可能会造成该线程死锁。如果某个线程正从连接的管道输入流中读取数据字节,但该线程不再处于活动状态,则该管道被视为处于毁坏状态。
演示示例:
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) { e.printStackTrace(); } } } 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("管道流实验".getBytes()); out.close(); } catch (Exception e) { e.printStackTrace(); } } } 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(); } }
6、随机访问文件
RandAccessFile:此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。
其完成读写的原理就是内部封装了字节输入流和字节输出流;通过气其构造方法可以看出,该类只能操作文件。其操作文件还有模式。其模式为:
值 |
含意 |
"r" |
以只读方式打开。调用结果对象的任何 write 方法都将导致抛出IOException。 |
"rw" |
打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
"rws" |
打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
"rwd" |
打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 |
如果此模式参数与 "r"、"rw"、"rws" 或"rwd" 的其中一个不相等,则会抛出IllegalArgumentException。
在创建新对象的时候,如果模式为只读r,则不会创建新文件,而是去读取一个已有的文件,如果该文件不存在,怎会抛出异常;如果模式为可读可写rw,操作的文件存在不会对其覆盖(更改其内容),如果该文件不存在则会自动创建。
演示示例:
import java.io.*; class RandomAccessFileDemo { public static void main(String[] args) throws IOException { writeFile_1(); //readFile(); } public static void readFile() throws IOException { RandomAccessFile raf = new RandomAccessFile("D:\\test.txt","rw");//如果该模式为只读r则将不再能写数据; //raf.seek(8); //调整对象中的指针; raf.skipBytes(8); //跳过指定的字节数; byte[] buf = new byte[4]; raf.read(buf); String name = new String(buf); int age = raf.readInt(); System.out.println("name:"+name); System.out.println("age:"+age); raf.close(); } public static void writeFile_1() throws IOException { RandomAccessFile raf = new RandomAccessFile("D:\\test.txt","rw"); raf.skipBytes(8*2); raf.write("周期".getBytes()); raf.writeInt(108); //使用writeInt可以避免当数据较大时出现丢失; raf.close(); } public static void writeFile() throws IOException { RandomAccessFile raf = new RandomAccessFile("D:\\test.txt","rw"); raf.write("java".getBytes()); raf.writeInt(97); //使用writeInt可以避免当数据较大时出现丢失; raf.write("java".getBytes()); raf.writeInt(100); raf.close(); } }
7、操作基本数据类型
DateInputStream和DateOutputStream:可以用于操作基本数据类型的数据的流对象。
DateInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。 DataInputStream 对于多线程访问不一定是安全的。 线程安全是可选的,它由此类方法的使用者负责。
DateOutputStream:数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
import java.io.*; class DataStreamDemo { public static void main(String[] args) throws IOException { //writeData(); //readData(); //writeUTF(); //readUTF(); writeutf(); } public static void writeData()throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\data.txt")); dos.writeInt(546); dos.writeBoolean(true); dos.writeDouble(123.456); dos.close(); } public static void readData() throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("D:\\data.txt")); int num = dis.readInt(); boolean b = dis.readBoolean(); Double d = dis.readDouble(); System.out.println("num="+num); System.out.println("b="+b); System.out.println("d="+d); dis.close(); } public static void writeUTF() throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\UTFdata.txt")); //该方式使用 UTF-8 修改版编码将一个字符串写入基础输出流,所以也只能使用对应的readUTF读出其字符串; //该方式每个字符使用4个字节存储; dos.writeUTF("你好"); dos.close(); } public static void readUTF() throws IOException { DataInputStream dis = new DataInputStream(new FileInputStream("D:\\UTFdata.txt")); System.out.println(dis.readUTF());//该方式只能读取修改版的UTF-8字符集,如果读取writeutf()中的内容将会出现异常; dis.close(); } public static void writeutf() throws IOException { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\utf.txt"),"UTF-8"); osw.write("你好"); //该方式是使用指定UTF-8字符集来存储字符串的;每个字符使用3个字节存储; osw.close(); } }
注:修改版的UTF-8与标准版的不同之处:
1)、null 字节 '\u0000' 是用 2-byte 格式而不是 1-byte 格式编码的,因此已编码的字符串中决不会有嵌入的 null。
2)、仅使用 1-byte、2-byte 和 3-byte 格式。
3)、增补字符是以代理项对的形式表示的。
8、操作字节数组
ByteArrayInputStream和ByteArrayOutputStream:用于操作字节数组的流对象。ByteArrayInputStream:在构造的时候需要接受数据源,而且数据源是一个字
节数组。
该类包含一个内部缓冲区,该缓冲区包含从流中读取的节。内部计数器跟踪read 方法要提供的下一个字节。关闭ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据的目的地。
此类实现了一个输出流,其中的数据被写入一个byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()和toString() 获取数据。 关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
由于这两个流对象都操作的是数组,并没有使用系统资源,所以不用使用close关闭流资源。
import java.io.*; class ByteArrayStreamDemo { public static void main(String[] args) { //数据源; ByteArrayInputStream bis = new ByteArrayInputStream("ABCEDFG".getBytes()); //数据目的; ByteArrayOutputStream bos = new ByteArrayOutputStream(); //由于对象中已经封装了数组,此时也就不用再定义一个新数组了; int len = 0; while((len=bis.read()) != -1) { bos.write(len); } System.out.println(bos.toString()); //直接将对象中的内容打印出; } }
学会了操作字节数组,其他的字符数组(CharArrayReader、CharArrayWriter)与字符串(StringReader、StringWriter)操作方法与字节数组的操作方法类似。
三、字符编码
1、字符编码概述
字符流的出现为了方便操作字符,更重要是的加入了编码转换。它主要是通过子类转换流来完成:InputStreamReader和OutputStreamWriter。在两个对象进行构造的时候可以加入字符集。
编码表的由来:计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表。
常见编码表:
1)、ASCII:美国标准信息交换码。用一个字节的7位可以表示;
2)、ISO8859-1:拉丁码表(欧洲码表)。用一个字节的8位表示;
3)、GB2312:中国的中文编码表;
4)、GBK:中国的中文码表升级,融合了更多的中文字符符号;
5)、Unicode:国际标准码。融合了更多的文字。所有的文字都用两个字节来表示,java语言使用的就是Unicode码表。
6)、UTF-8:最多用3个字符来表示一个字符。
演示示例:
import java.io.*; class EncodeStream { public static void main(String[] args) throws IOException { //writeText(); readText(); } public static void writeText() throws IOException { //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\GBK.txt"),"GBK"); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\UTF.txt"),"UTF-8"); osw.write("你好"); osw.close(); } public static void readText() throws IOException { //GBK编码的文件使用GBK解码,打印正常字符"你好"; //InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\GBK.txt"),"GBK"); //GBK编码的文件使用UTF-8解码,出现乱码情况;打印"??"; InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\GBK.txt"),"UTF-8"); //UTF-8编码的文件使用UTF-8解码,打印正常字符"你好"; //InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\UTF.txt"),"UTF-8"); //UTF-8编码的文件使用GBK解码,出现乱码情况;打印"浣犲ソ"; //InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\UTF.txt"),"GBK"); char[] buf = new char[10]; int len = isr.read(buf); String str = new String(buf,0,len); System.out.println(str); isr.close(); } }
2、字符编码
编码:字符串转换成字节数组;
String à byte[]; str.getBytes(charsetName).
解码:字节数组转换成字符串;
Byte[] à String; newString(byte[], charsetName).
演示示例:
import java.util.*; class EncodeDemo { public static void main(String[] args) throws Exception { String s = "你好"; //对s进行编码; byte[] b1 = s.getBytes("UTF-8"); System.out.println(Arrays.toString(b1)); //解码; String s1 = new String (b1,"ISO8859-1"); /*如果此处使用的是GBK对内容进行错误的解析,如同下方,再次使用GBK对其编码, 然后使用UTF-8进行解码,则依然会出现乱码的情况,原因是这两个码表都支持中文;*/ System.out.println("s1="+s1); //由于错误使用ISO8859-1对UTF-8进行编码的内容进行解码,所以需要 //重新使用ISO8859-1对其进行编码,在使用UTF-8对其解码,才能做出正确的解析; byte[] b2 = s1.getBytes("ISO8859-1"); System.out.println(Arrays.toString(b2)); String s2 = new String(b1,"UTF-8"); System.out.println(s2); } }
“联通”字符小问题探讨:
在Windows系统中新建一个记事本文件,输入两个中文字符“联通”,保存。再次打开就会出现乱码的情况。这是为什么呢?
如下面所示的示例,将字符“联通”进行GBK编码后,通过编码打印处于其在内存中的码值,打印结果如示例所示。我们在查看UTF-8的编码特征(详情请参看API文档),这两个字符的字节码都已110 10打头,则符合了UTF-8码表的特种,这是每个字符也是使用两个字节存储,记事本便使用UTF-8码表来解析字符,而我们却使用的是GBK编码,所以就会出现乱码的情况了。
class EncodeDemo2 { public static void main(String[] args) throws Exception { String s = "联通"; byte[] bt = s.getBytes("GBK"); for(byte b: bt) { System.out.println((Integer.toBinaryString(b&0xff))); /*打印结果:11000001 10101010 11001101 10101000*/ } } }
3、练习:有5个学生,每个学生有3门课的成绩,从键盘输入以上数据(包括姓名和3门课程的成绩),输入格式如:zhangsan,30, 40, 60;计算出总成绩,并 学生的信息和计算出的总成绩按高低顺序存放在硬盘文件”Stu.txt”中。
步骤:1)、描述学生类;
2)、定义一个可以操作学生对象的工具类。
思路:1)、通过键盘获取一行数据,并将该行中的信息取出封装成学生对象;
2)、因为学生有很多,那么就需要使用集合存储;因为要对学生的总分排序,所以可以使用TreeSet集合;
3)、将集合中的信息写入到文件中。
import java.io.*; import java.util.*; class StudentInfoTest { public static void main(String[] args) throws IOException { Comparator<Student> cmp = Collections.reverseOrder();//强行逆转排序; Set<Student> stus = StudentInfoTool.getStudents(cmp); StudentInfoTool.writeToFile(stus); } } class Student implements Comparable<Student> { private String name; private int math,chinese,english; private int sum; Student(String name, int math, int chinese, int english) { this.name = name; this.math = math; this.chinese = chinese; this.english = english; this.sum = math+chinese+english; } public String getName() { return name; } public int getSum() { return sum; } public int hashCode() { return name.hashCode() + sum*39; } public String toString() { return "Student["+name+", "+math+", "+chinese+", "+english+"]"; } public boolean equals(Object obj) { if(!(obj instanceof Student)) throw new ClassCastException("类型不匹配"); Student stu = (Student)obj; return this.name.equals(stu.name) && this.sum==stu.sum; } public int compareTo(Student s) { int num = new Integer(this.sum).compareTo(new Integer(s.sum)); if(num == 0) return this.name.compareTo(s.name); return num; } } class StudentInfoTool { public static Set<Student> getStudents() throws IOException //不使用自定义比较器; { return getStudents(null); } public static Set<Student> getStudents(Comparator cmp) throws IOException//使用自定义比较器; { BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); String line = null; Set<Student> stus = null; if(cmp == null) stus = new TreeSet<Student>();//使用集合将学生对象进行存储; else stus = new TreeSet<Student>(cmp); while((line=bufr.readLine()) != null) { if(line.equals("over")) break; String[] info = line.split(","); Student stu = new Student(info[0], Integer.parseInt(info[1]), Integer.parseInt(info[2]), Integer.parseInt(info[3])); stus.add(stu); } bufr.close(); return stus; } public static void writeToFile(Set<Student> stus) throws IOException//定义方法将学生信息存储到文件中; { BufferedWriter bufw = new BufferedWriter(new FileWriter("D:\\stu.txt")); for(Student stu: stus) { bufw.write(stu.toString()+"\t"); bufw.write(stu.getSum()+""); bufw.newLine(); bufw.flush(); } bufw.close(); } }