IO流的分类:
根据处理数据类型的不同分为:字符流和字节流(早期是没有字符流的而字符流的出出现是为了融合编码表)。
根据数据流向不同分为:输入流和输出流。
1 按数据流向分
读取流:InputStream Reader
写出流:OutPutStream Writer
2 按流的类型分类
字节流:InputStream OutputStream
其相应的继承图如下:
字符流:Reader Writer
其相应的继承图如下:
提示:这四个类派生出来的子类名称都是以其父类名作为子类的后缀。
如:InputStream的子类FileInputStream如:Reader的子类FileReader
两种流的特点与区别:读取流是从输入设备或数据对象中读取数据到程序,用程序进行处理读入的数据,写出流是把程序处理的数据输出到输出设备上比如硬盘和控制台。字节流读取和写入的数据单位是字节,可以读取和写入任何类型的数据。字符流读取跟写入的数据单位是字符,只能读取和写入文本类型的数据。当需要读取或写入文本型的数据时要用字符流,因为它会比字节流读写字符更方便和高效,相反当数据不是文本型时只能用字节流来读取跟写入。
一 字节流:
字节流基类:InputStream OutputStream
字节输入流的用法及其特点:
InputStream//此抽象类是表示字节输入流的所有类的超类。
PipedInputStream//管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。
ByteArrayInputStream//包含一个内部缓冲区,该缓冲区包含从流中读取的字节。
SequenceInputStream//对多个流进行合并 表示其他输入流的逻辑串联。
ObjectInputStream//对以前使用ObjectOutputStream写入的数据和对象进行反序列化
FilterInputStream类及其子类:
FilterInputStream//包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
DataInputStream//数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
BufferedInputStream//为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。
PushbackInputStream//为另一个输入流添加性能,即“推回 (push back)”或“取消读取 (unread)”一个字节的能力。
字节输出流的用法及其特点:
FileOutputStream//文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
PipedOutputStream//可以将管道输出流连接到管道输入流来创建通信管道。
ByteArrayOutputStream//此类实现了一个输出流,其中的数据被写入一个 byte 数组。
ObjectOutputStream//将 Java 对象的基本数据类型和图形写入 OutputStream。
FilterOutputStream及其子类:
FilterOutputStream//此类是过滤输出流的所有类的超类。
DataOutputStream//数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。
BufferedOutputStream//该类实现缓冲的输出流。
PrintStream//可以直接操作输入流和文件,为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。内部封装了BufferedWiter和OutputStreamWriter对象,它的print方法本质上是将int long char等数据利用String.valueof方法转为字符串,再往外写。
1 FileOutputStream类和FileInputStream类:
FileOutputStream fos = new FileOutputStream("D://xxx.xxx");
fos.write("dsfdsf".getBytes());//写入字节数组
fos.close();//用完后需要关闭流,释放资源。字节流不需要Flush
FileInputStream fis = new FileInputStream("D://xxx.xxx");
fis.read() ;//读取一个字节
is.close();
2 ByteArrayOutputStream类:利用该类工具方法把流里面的内容转化成String字符串
public class StreamTools {
public static String readFromStream1(InputStream is) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
is.close();
String result = baos.toString();
baos.close();
return result;
}
//无乱码的写法
public static String readFromStream2(InputStream is) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
is.close();
String result = baos.toString();
baos.close();
if(result.contains("content=\"text/html;charset=gbk\"")){
byte[] temp=baos.toByteArray();
result=new String(temp,"gbk");
}
return result;
}
}
3 SequenceInputStream类:
当1.txt 2.txt 3.txt三个文件读入4.txt文件,若一个一个读入则4.txt中的内容可能被覆盖掉。代码如下:public class SequenceDemo {//实现多文件数据合并成一个文件
public static void main(String args[]) throws IOException{
Vector<FileInputStream> v=new Vector<FileInputStream>();
v.add(new FileInputStream("c:/1.txt"));
v.add(new FileInputStream("c:/2.txt"));
v.add(new FileInputStream("c:/3.txt"));
Enumeration<FileInputStream> en=v.elements();
SequenceInputStream sis=new SequenceInputStream(en);
FileOutputStream fos=new FileOutputStream("c:/4.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1){
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}
切割文件后再合并,代码如下:
public class SplitFile {
public static void main(String args[]) throws IOException{
splitFile();
merge();
}
public static void merge() throws IOException {
ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
for (int x = 1; x <= 3; x++) {
al.add(new FileInputStream("c:/splitfiles/" + x + ".part"));
}
final Iterator<FileInputStream> it = al.iterator();
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
public boolean hasMoreElements() {
return it.hasNext();
}
public FileInputStream nextElement() {
return it.next();
}
};
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("c:/splitfiles/0.bmp");
byte[] buf = new byte[1024];
int len = 0;
while ((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
public static void splitFile() throws IOException {
FileInputStream fis = new FileInputStream("c:/1.bmp");
FileOutputStream fos = null;
byte[] buf = new byte[1024 * 1024];
int len = 0;
int count = 1;
while ((len = fis.read(buf)) != -1) {
fos = new FileOutputStream("c:/splitfiles/" + (count++) + ".part");
fos.write(buf, 0, len);
fos.close();
}
fis.close();
}
}
4 ObjectInputStream类与ObjectOutputStream类:被操作的对象需要实现Serializable()标记接口。
提示:把对象存储到硬盘上被称为对象的持久化存储或对象的序列化或对象的课串行性。
需求:将Person对象写到Object.txt中。代码如下:
public class ObjectStreamDemo {
public static void main(String args[]) throws Exception{
writeObj();
readObject();
}
public static void writeObj() throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
"obj.txt"));
oos.writeObject(new Man("kid", 100));
oos.close();
}
public static void readObject() throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("obj.txt"));
Man m=(Man) ois.readObject();
System.out.println(m);
ois.close();
}
}
class Man implements Serializable{
String name;
int age;
Man(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + ":" + age;
}
}
实现Serializable接口的类编译时会生成一个serialVersionUID=42L;
如:public static final long serialVersionUID=42L;该序列号用以判断该对象是否由对应的类所产生的。类自定义UID,代码如下:
public class Person1 implements Serializable{
public static final long serialVersionUID=42L;
String name;
int age;
Person1(){
this.name=name;
this.age=age;
}
public String toString(){
return name+":"+age;
}
}
内存中堆中的数据可以被序列化,内存其他地方的数据不能被序列化。例如:方法区中的静态数据。另外成员前加上关键字transient后即使是在堆中也不可被序列化。
5 关于管道流:
PipedOutputStream 的write将数据写入到管道流
PipedInputStream 的read从管道流读入数据
实例代码如下:
public 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();
}
}
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) {
throw new RuntimeException("管道读取失败");
}
}
}
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("piped lai la".getBytes());
out.close();
} catch (Exception e) {
throw new RuntimeException("管道输出流失败");
}
}
}
6 DataInputStream类和DataOutputStream类:
可以用于操作基本数据类型的数据的流对象,例子代码如下:
public class DataStreamDemo {
public static void main(String args[]) throws Exception{
writeUTFDemo();
readUTFDemo();
writeData();
readData();
}
public static void readUTFDemo() throws Exception {
DataInputStream dis = new DataInputStream(
new FileInputStream("c:/utf.txt"));
String s = dis.readUTF();
System.out.println(s);
dis.close();
}
public static void writeUTFDemo() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"c:/utf.txt"));
dos.writeUTF("你好");//它写的只能用readUTF读
dos.close();
}
public static void readData() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream(
"c:/data.txt"));
int num = dis.readInt();
boolean b = dis.readBoolean();
Double d = dis.readDouble();
System.out.println("num=" + num);
System.out.println("booleam=" + b);
System.out.println("double=" + d);
dis.close();
}
public static void writeData() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"c:/data.txt"));
dos.writeInt(123);
dos.writeBoolean(true);
dos.writeDouble(87.534);
dos.writeUTF("你你你你你你你你");
}
}
打印结果为:你好
num=123
booleam=true
double=87.534
7 ByteArrayInputStream类和ByteArrayOutputStream类:
ByteArrayOutputStream类:在构造的时候,不要定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。
因为这两个流对象都操作的数组,并没有使用系统资源。所以不用进行close关闭。
在流操作规律讲解时, 源设备:键盘 System.in 硬盘 FileStream 内存ArrayStream
目的设备:控制台 System.out 硬盘 FileStream 内存ArrayStream
用流的思想操作数组,例子代码如下:
public class ByteArrayStream {
public static void main(String args[]) throws IOException{
//数据源
ByteArrayInputStream bis=new ByteArrayInputStream("abcdefg".getBytes());
//数据目的地
ByteArrayOutputStream bos=new ByteArrayOutputStream();
int by=0;
while((by=bis.read())!=-1){//数组流的好处之一就是不用再次去判断数组的长度
bos.write(by);
}
System.out.println(bos.size());
System.out.println(bos.toString());
bos.writeTo(new FileOutputStream("x.txt"));
}
}
想要操作图片数据,这时就要用到字节流。用字符流读取图片会失败,当读入内存时字符会先查表,若某些数据查不到就会用类似的数据代替。造成生成新图片数据不同于旧图片。
InputStream中的read()方法描述:从输入流中读取数据的下一个字节。返回0~255范围内的int字节值。案例:通过字节缓冲区完成MP3文件复制,代码如下:
public class BufferStreamDemo {
public static void main(String args[]) throws IOException{
BufferedInputStream bufis=new BufferedInputStream(new FileInputStream("c://0.mp3"));
BufferedOutputStream bufos=new BufferedOutputStream(new FileOutputStream("c://1.mp3"));
int by=0;
while((by=bufis.read())!=-1){
bufos.write(by);
}
bufos.close();
bufis.close();
}
}
写入到”流“中的”流“其实是位于内存的。既然IO流是用于操作数据的,那么数据常见的基本形式是:文件。
需求:在硬盘上创建一个文件并写入一些数据。
步骤:1 创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件。且该文件会被创建到指定目录下,如果该目录下已有同名文件,将被覆盖。其实就是要明确数据要存放的位置。
2 调用write方法,将字符串写入到流中。
3 刷新流对象中的缓冲中的数据。将数据刷新到目的地中。
4 关闭流资源,但是关闭之前会刷新一次内部缓冲的数据。将数据刷到目的地中。和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
例子程序如下:
import java.io.FileWriter;
import java.io.IOException;
public class FileWreiterDemo {
public static void main(String args[]) {
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt"); // 这里可能会出现FileNotFoundException
fw.write("abc");
} catch (IOException e) {
System.out.println("catch:" + e.toString());
} finally {
try {
if (fw != null) {// 若不作此判断可能会由FileNotFoundException引起此处的NullPointerException
fw.close();
}
} catch (IOException e) {
System.out.println(e.toString());
}
}
}
}
流中读写方法的示例。(当用到IO流时就有可能出现IO异常,所以需要处理可能的异常)读取键盘录入的两种方法:1 System.out:对应的是标准设备,控制台
2 System.in: 对应的标准输入设备,键盘
需求:通过键盘录入数据,当录入一行数据后,就将该行数据进行打印。如果录入的是over,那么停止录入。并转为大写。
实例代码如下:
public class ReadIn {
public static void main(String args[]) throws IOException {
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while (true) {
int ch = in.read();
if (ch == '\r')
continue;
if (ch == '\n') {
String s = sb.toString();
if ("over".equals(s))
break;
System.out.println(s.toUpperCase());
} else {
sb.append((char) ch);
}
}
}
}
二 字符流:字符输入流的用法:
Reader//用于读取字符流的抽象类。
CharArrayReader//此类实现一个可用作字符输入流的字符缓冲区。用来读取字符文件的便捷类。
PipedReader//传送的字符输入流。
StringReader//其源为一个字符串的字符流。
InputStreamReader及其子类:
InputStreamReader//是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。
FileReader//用来读取字符文件的便捷类。
BufferedReader及其子类:
BufferedReader//从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
LineNumberReader//跟踪行号的缓冲字符输入流。
FilterReader及其子类:
FilterReader//用于读取已过滤的字符流的抽象类。
PushbackReader//允许将字符推回到流的字符流 reader。
字符输出流的用法:
Writer//写入字符流的抽象类。
BufferedWriter//将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
CharArrayWriter//此类实现一个可用作 Writer 的字符缓冲区。
FilterWriter//用于写入已过滤的字符流的抽象类。
PipeWriter//传送的字符输出流。该类能实现缓冲其实都是由BufferedWriter所修饰
StringWriter//一个字符流,可以用其回收在字符串缓冲区中的输出来构造字符串。
PrintWriter//跟PrintStream几乎是一样的
OutputStreamWriter及其子类:
OutputStreamWriter//OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。
FileWriter//用来写入字符文件的便捷类。
FileWriter fw = new FileWriter("D:\\xxx.txt");
fw.write("sdfsdfsdf");//可以直接写入字符串
fw.flush();//写完后需要Flush,才能真正写道输出设备
fw.close();//close()时也会Flush.
FileReader fr = new FileReader("D:\\xxx.txt");
fr.read(char[] ch);//可以读取一个字符数组的内容
fr.close();
关于FileWriter类与OutoutStreamWriter类的关系:
FileWriter的所有方法都继承自OutputStreamWriter类其中构造函数是调用OutputStreamWriter的构造函数并把file对象或路径字符串传入FileOutputStream再把该FileOutputStream对象传入OutputStreamWriter中。说白了FileWriter类其实就是为了简化"new OutputStream(new FileOutputStream("file"));"而存在的1 利用字符流读取文本文件的两种方式:
第一种方式:逐个字符读取
步骤:1)创建一个文件读取流对象,和指定名称的文件相关联,要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
2)调用读取流对象的read方法。read()一次读取一个字符,而且会自动往下读。
程序的主要代码如下:
FileReader fr=new FileReader("demo.txt");
int ch=0;
while((ch=fr.read())!=-1){
System.out.println((char)ch);
}
第二种方式:
通过字符数组进行读取过程:定义一个字符数组,用于存储读到的字符。其中用到的read(char[])返回的是读到字符个数。
程序的主要代码如下:
FileReader fr = new FileReader("demo.txt");
char[] buf = new char[1024];
int num = 0;
while ((num = fr.read(buf)) != -1) {
System.out.println(new String(buf, 0, num));
}
fr.close();
2 利用字符流将一个文本文件复制到另一个文本文件的两种方式:
第一种方式:
public static void copy1() throws IOException{
FileWriter fw=new FileWriter("DemoCopy1.txt");
FileReader fr=new FileReader("DemoCopy2.txt");
int ch=0;
while((ch=fr.read())!=-1){//read方法读取返回的值是int类型(在byte类型前加24个零),避免与结束返回的-1想冲突
fw.write(ch);//此write方法将int类型转换成byte类型。
}
fw.close();
fr.close();
}
第二种方式:
public static void copy2() {
FileWriter fw = null;
FileReader fr = null;
try {
fw = new FileWriter("Demo-copy2.txt");
fr = new FileReader("Demo.java");
int len = 0;
char[] buf = new char[1024];
while ((len = fr.read(buf)) != -1) {
fw.write(buf, 0, len);
}
} catch (IOException e) {
throw new RuntimeException("读取失败");
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
三 装饰设计模式:
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类的装饰类。装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
例子代码如下:
public class Person {
public void eat() {
System.out.println("吃饭");
}
}
class SuperPerson {
private Person p;
SuperPerson(Person p) {
this.p = p;
}
// 基于eat方法的加强方法
public void superEat() {
System.out.println("喝一口开胃酒");
p.eat();
System.out.println("吃电饭后甜点");
System.out.println("再抽根饭后烟");
}
}
class PersonDemo {
public static void main(String args[]) {
Person p=new Person();
p.eat();
System.out.println("++++++++++进入超级吃饭模式:");
SuperPerson sp=new SuperPerson(p);
sp.superEat();
}
}
打印结果:
吃饭
++++++++++进入超级吃饭模式:
喝一口开胃酒
吃饭
吃电饭后甜点
再抽根饭后烟
关于装饰模式的一个例子程序,代码如下:
public class LineNumberReaderDemo {
public static void main(String args[]) throws IOException{
FileReader fr=new FileReader("Person.java");
LineNumberReader lnr=new LineNumberReader(fr);
String line=null;
lnr.setLineNumber(100);
while((line=lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
提示:字符流需要缓存的原因:一个字符由多个字节组成,因此读取多个字节才能查编码表转换。StreamEncoder类的其源码如下:
public class StreamEncoder extends Writer {
private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
private volatile boolean isOpen = true;
private void ensureOpen() throws IOException {
if (!isOpen)
throw new IOException("Stream closed");
}
// Factories for java.io.OutputStreamWriter
public static StreamEncoder forOutputStreamWriter(OutputStream out,
Object lock, String charsetName)
throws UnsupportedEncodingException {
String csn = charsetName;
if (csn == null)
csn = Charset.defaultCharset().name();
try {
if (Charset.isSupported(csn))
return new StreamEncoder(out, lock, Charset.forName(csn));
} catch (IllegalCharsetNameException x) {
}
throw new UnsupportedEncodingException(csn);
}
public static StreamEncoder forOutputStreamWriter(OutputStream out,
Object lock, Charset cs) {
return new StreamEncoder(out, lock, cs);
}
public static StreamEncoder forOutputStreamWriter(OutputStream out,
Object lock, CharsetEncoder enc) {
return new StreamEncoder(out, lock, enc);
}
// Factory for java.nio.channels.Channels.newWriter
public static StreamEncoder forEncoder(WritableByteChannel ch,
CharsetEncoder enc, int minBufferCap) {
return new StreamEncoder(ch, enc, minBufferCap);
}
// -- Public methods corresponding to those in OutputStreamWriter --
// All synchronization and state/argument checking is done in these public
// methods; the concrete stream-encoder subclasses defined below need not
// do any such checking.
public String getEncoding() {
if (isOpen())
return encodingName();
return null;
}
public void flushBuffer() throws IOException {
synchronized (lock) {
if (isOpen())
implFlushBuffer();
else
throw new IOException("Stream closed");
}
}
public void write(int c) throws IOException {
char cbuf[] = new char[1];
cbuf[0] = (char) c;
write(cbuf, 0, 1);
}
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0)
|| ((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
implWrite(cbuf, off, len);
}
}
public void write(String str, int off, int len) throws IOException {
/* Check the len before creating a char buffer */
if (len < 0)
throw new IndexOutOfBoundsException();
char cbuf[] = new char[len];
str.getChars(off, off + len, cbuf, 0);
write(cbuf, 0, len);
}
public void flush() throws IOException {
synchronized (lock) {
ensureOpen();
implFlush();
}
}
public void close() throws IOException {
synchronized (lock) {
if (!isOpen)
return;
implClose();
isOpen = false;
}
}
private boolean isOpen() {
return isOpen;
}
// -- Charset-based stream encoder impl --
private Charset cs;
private CharsetEncoder encoder;
private ByteBuffer bb;
// Exactly one of these is non-null
private final OutputStream out;
private WritableByteChannel ch;
// Leftover first char in a surrogate pair
private boolean haveLeftoverChar = false;
private char leftoverChar;
private CharBuffer lcb = null;
private StreamEncoder(OutputStream out, Object lock, Charset cs) {
this(out, lock, cs.newEncoder()
// 为此 charset 构造新的编码器。
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
}
private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {
super(lock);
this.out = out;
this.ch = null;
this.cs = enc.charset();
this.encoder = enc;
// This path disabled until direct buffers are faster
if (false && out instanceof FileOutputStream) {
ch = ((FileOutputStream) out).getChannel();
if (ch != null)
bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
}
if (ch == null) {
bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
}
}
private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {
this.out = null;
this.ch = ch;
this.cs = enc.charset();
this.encoder = enc;
this.bb = ByteBuffer.allocate(mbc < 0 ? DEFAULT_BYTE_BUFFER_SIZE : mbc);
}
private void writeBytes() throws IOException {
bb.flip();// 反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记//
// 这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取.
int lim = bb.limit();// int 返回此缓冲区的限制。
int pos = bb.position();// 返回此缓冲区的位置。
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (rem > 0) {
if (ch != null) {
if (ch.write(bb) != rem)// WritableByteChannel ch
// 可写入字节的通道。将字节序列从给定的缓冲区中写入此通道。返回字节数
assert false : rem;// 其中Expression1应该总是一个布尔值,Expression2是断言失败时输出的失败消息的字符串。
} else {
out.write(bb.array(), bb.arrayOffset() + pos, rem);// OutputStream的write()方法
// arrayOffsect返回此缓冲区中的第一个元素在缓冲区的底层实现数组中的偏移量
}
}// bb.array:返回实现此缓冲区的 byte 数组
// bb.arrayOffsect返回此缓冲区中的第一个元素在缓冲区的底层实现数组中的偏移量
bb.clear();// 清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。
}
private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)
throws IOException {
if (!haveLeftoverChar && !endOfInput)
return;
if (lcb == null)
lcb = CharBuffer.allocate(2);
else
lcb.clear();
if (haveLeftoverChar)
lcb.put(leftoverChar);
if ((cb != null) && cb.hasRemaining())
lcb.put(cb.get());
lcb.flip();
while (lcb.hasRemaining() || endOfInput) {
CoderResult cr = encoder.encode(lcb, bb, endOfInput);
if (cr.isUnderflow()) {
if (lcb.hasRemaining()) {
leftoverChar = lcb.get();
if (cb != null && cb.hasRemaining())
flushLeftoverChar(cb, endOfInput);
return;
}
break;
}
if (cr.isOverflow()) {// 通知此对象是否描述溢出情况。
assert bb.position() > 0;
writeBytes();
continue;
}
cr.throwException();
}
haveLeftoverChar = false;
}
void implWrite(char cbuf[], int off, int len) throws IOException {
CharBuffer cb = CharBuffer.wrap(cbuf, off, len);// 将字符数组包装到缓冲区中。给定的字符数组将支持新缓冲区;即缓冲区修改将导致数组修改,反之亦然。新缓冲区的容量和界限将为
// array.length,其位置将为零,其标记是未定义的。其底层实现数组将为给定数组,并且其数组偏移量将为零。
if (haveLeftoverChar)
flushLeftoverChar(cb, false);
while (cb.hasRemaining()) { // 告知在当前位置和限制之间是否有元素。
CoderResult cr = encoder.encode(cb, bb, false);// 从给定输入缓冲区中编码尽可能多的字符,把结果写入给定的输出缓冲区
// 在创建对象是已指定用某编码的编码器encoder
if (cr.isUnderflow()) {// 通知此对象是否描述下溢情况。指示已编码尽可能多的输入缓冲区。如果没有进一步的输入,则调用者可以进行到编码操作的下一个步骤。否则,应该使用进一步的输入再次调用此方法。
assert (cb.remaining() <= 1) : cb.remaining();// 返回当前位置与限制之间的元素数。
if (cb.remaining() == 1) {// 该值理论上应该为0???????
haveLeftoverChar = true;
leftoverChar = cb.get();// 相对 get
// 方法。读取此缓冲区当前位置的字符,然后该位置递增//将cb中剩下的char传给leftoverChar
}
break;
}
if (cr.isOverflow()) {// 通知此对象是否描述溢出情况。
// 指示该输出缓冲区中没有足够空间来编码任何更多字符。应该使用具有更多剩余字节的输出缓冲区再次调用此方法。这通常是通过排空输出缓冲区的所有编码字节来完成的
assert bb.position() > 0;
writeBytes();
continue;
}
cr.throwException();// 抛出相应于此对象描述的结果的异常。
}
}
void implFlushBuffer() throws IOException {
if (bb.position() > 0)
writeBytes();
}
void implFlush() throws IOException {
implFlushBuffer();
if (out != null)
out.flush();// 构造函数传入的OutputStream
}
void implClose() throws IOException {
flushLeftoverChar(null, true);
try {
for (;;) {
CoderResult cr = encoder.flush(bb);
if (cr.isUnderflow())
break;
if (cr.isOverflow()) {
assert bb.position() > 0;
writeBytes();
continue;
}
cr.throwException();
}
if (bb.position() > 0)
writeBytes();
if (ch != null)
ch.close();
else
out.close();
} catch (IOException x) {
encoder.reset();
throw x;
}
}
String encodingName() {
return ((cs instanceof HistoricallyNamedCharset) ? ((HistoricallyNamedCharset) cs)
.historicalName() : cs.name());
}
}
注:中文注释是自己写的有一些不理解的地方四 转换流
当需要流之间的转换时会用到转换流。
1 把字节读取流转换成字符读取流
InputStreamReader isr = new InputStreamReader(newFileInputStream("xxx.xxx"));
2 把字符输出流转化成字节输出流
OutputStreamWriter osw = new OutputStreamWriter(newFileOutputStream("xx.xxx"));
五 缓冲流
需要提高流的读写效率时会用到缓冲流
1 字节缓冲流
BufferedInputStream bis = new BufferedInputStream(newFileInputStream("xx"));
BufferedOutputStream bos = new BufferedOutputStream(newFileOutputStream("xx"));
定义自己的字节缓冲流
OutputStream类的flush方法
public class MyBufferedInputStream {
private byte[] buf = new byte[1024];
private int pos = 0, count = 0;
private InputStream in;
public MyBufferedInputStream(InputStream in) {
this.in = in;
}
// 一次读一个字节,从缓冲区(字节数组)获取
public int myRead() throws IOException {
// 通过in对象读取硬盘上的数据,并存储buf中
if (count == 0) {
count = in.read(buf);// 读入缓冲区的总字节数
if (count < 0)
return -1;
pos = 0;
byte b = buf[pos];
count--;
pos++;
return b;
} else if (count > 0) {
byte b = buf[pos];
count--;
pos++;
return b;
}
return -1;
}
public void myclose()throws Exception{
in.close();
}
}
注意到普通字节输出流和缓冲字节输出流BufferOutputStream类都有flush()方法,但它们之间有什么区别呢?OutputStream类继承了Flushable接口,所以重写了flush()方法,如下:
public void flush() throws IOException {
}
可以看出该方法是空方法(不是抽象方法)。OutputStream的直接子类有:
ByteArrayOutputStream
FileOutputStream
FilterOutputStream
ObjectOutputStream
OutputStream//注意:这里的子类OutputStream是包 org.omg.CORBA.portable 的。
PipedOutputStream
对于FileOutputStream、ByteArrayOutputStream、org.omg.CORBA.portable.OutputStream类它们的flush()方法均是从父类继承的flush方法。
FilterOutputStream类重写了flush()方法,但是实质还是调用父类的flush方法。ObjectOutputStream、PipedOutputStream类重写了flush()方法。
先贴出两段代码,然后通过运行结果观察其中的差异:
public class Test {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos = new DataOutputStream(fos);
dos.writeBytes("java io");
}
}
public class Test2 {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024*8];
bos.write(b);
bos.flush();
}
}
这两段代执行后,分别会在当前目录下产生7字节的文件(内容为java io)和1KB字节的文件。现在修改第二个代码,主要是注释掉调用flush()方法,如下:
public class Test2 {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024*8];
bos.write(b);
//bos.flush();
}
}
再次运行代码,文件大小是0字节。为什么呢?仔细的你会发现,第一个代码并没有调用flush()方法,但文件并不为空。看源码发现DataOutputStream继承FilterOutputStream,实现了DataOutput接口。我们知道FilterOutputStream类重写了flush()方法,但是实质还是调用父类的flush方法。DataOutputStream类的flush()方法效仿其父类FilterOutputStream的做法,代码如下:
public void flush() throws IOException {
out.flush();
}
那么,即使你在代码后面加上dos.flush();与不加是一样的效果,因为它们的父类flush()方法均为空,这就是为什么第一个代码的神奇所在。再看看第二个代码的病因在哪里?先看看BufferedOutputStream类的结构:
public class BufferedOutputStream extends FilterOutputStream
再看看,它的flush()方法:public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
/** Flush the internal buffer */
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
不错,该类重写了flush()方法,不像前面几个那样不是继承就是继承父类的flush()方法。BufferedOutputStream 类是一个使用了缓冲技术的类。这种类一把都会自己实现flush()方法。那么,有人会问使用这种类的时候,难道必须使用flush()方法吗,当然不是。不过有个前提,你的字节数据必须不能小于8KB。实例代码,注意没有flush()方法。
package mark.zhang;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
File file = new File("text.txt");
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] b = new byte[1024*8];
bos.write(b);
//bos.flush();
}
}
执行代码,会产生8KB的文本文件。当然,怎么可能你每时每刻都知道你的数据一定会不小于8KB呢,所以还是调用flush()方法比较安全。不过,话又说回来,一般用完IO流之后(如果你有一个好的习惯)我们都会去调用close()方法,看源码可以知道该方法也是调用相对应的flush()方法。所以,大多数情况下你不必要担心。这里提醒一下,如果你的文件读写没有达到预期目的,十之八九是因为你没有调用flush()或者close()方法。另外,字符流类大多数都实现了flush()或者close()方法,只不过,它们调用的是StreamEncoder类的该方法。其源码在我们jdk中是没有的。其源代码在上面已经贴出。2、字符缓冲流
字符流的缓冲区:
缓冲区的出现提高了对数据的读写效率。
对应类: BufferedWriter类
BufferedReader类
特点:这两个的特别之处之一可定义缓冲区的大小,且BufferedReader具有readLine()方法。缓冲区要结合流才能使用。在流的基础上对流的功能进行了增强。
BufferedReader br = new BufferedReader(new FileReader("xx.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("xx.txt"));
缓冲流对读写功能进行了增强,而且使用缓冲技术提高了读写效率,所以当需要提高程序的,读写效率时要使用缓冲流。
个人认为:通过观察BufferedWriter源码和上文贴出的StreamEncoder源代码,我认为BufferedWriter修饰某个Writer类,当该类调用write方法时只是将字符写入内存中的某个字符数组中,当字符数组写满时就会调用其修饰的Witer类的write方法而该方法是先将字符编码成字节数组中。
BufferedWriter类中的flush方法的分析:
1 通过观察BufferedWriter类中的源代码可知其flush方法调用了其flushBuffer方法后再调用其修饰的Writer类的flush方法,代码如下:
public void flush() throws IOException {
synchronized (lock) {
flushBuffer();
out.flush();
}
}
1)flushBuffer方法内部调用了Writer类的write(char[] c,int off,int len)方法,代码如下:
void flushBuffer() throws IOException {
synchronized (lock) {
ensureOpen();
if (nextChar == 0)
return;
out.write(cb, 0, nextChar);
nextChar = 0;
}
}
若修饰的是OutputStreamWriter类,观察OutputStreamWriter类的源码可知其write(char[] c,int off,int len),调用的是StreamEncoder类的write(char[] c,int off,int len)方法。 代码如下:
public void write(char cbuf[], int off, int len) throws IOException {
se.write(cbuf, off, len);
}
2)若修饰的是OutputStreamWriter类,观察OutputStreamWriter类的源码其对应的flush方法内部调用StreamEncoder的flush方法:
public void flush() throws IOException {
se.flush();
}
通过观察上文贴出的StreamEnoder源码可看出StreamEncoder的flush方法运行到最后其实是调用该OutputStreamWriter类所修饰的OutputStream类的write(byte[] b,int off,int len)方法。因此可以得出此结论BufferedWriter的方法某程度上是调用了BufferedWriter修饰的OutputStreamWiter类所修饰的OutputStream类的write(byte[] b,int off,int len)方法,当然观察StreamEncoder类可知这其中经历里字符串解码成字节的一系列过程。
字符写出流的缓冲区的实例代码如下:
public class BufferedWriterDemo {
public static void main(String args[]) throws IOException {
FileWriter fw = new FileWriter("buf.txt");
BufferedWriter bufw = new BufferedWriter(fw);
for (int x = 1; x < 5; x++) {
bufw.write("abcde");
bufw.newLine();// 写入的内容换行
bufw.flush();// 其实关闭缓冲区,就是在关闭缓冲区中的流对象
}
bufw.close();
}
}
字符读取流缓冲区的实例代码:
public class BufferedReaderDemo {
public static void main(String args[]) throws IOException{
FileReader fr=new FileReader("buf.txt");
BufferedReader bufr=new BufferedReader(fr);
String line=null;
while((line=bufr.readLine())!=null){
System.out.println(line);
}
bufr.close();
}
}
readLine()方法的原理:无论是读一行,获取读取多个字符,其最终都是在硬盘上一个一个读取。所以最终使用的还是read方法。在上文中的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理。也就是readLine方法。
问:能不能直接使用readLine()方法来完成键盘录一行数据的读取呢?readLine方法是字符流BufferedReader类中的方法。而键盘录入的方法是字节流InputStream方法。
实例代码如下:
public class TransferDemo {
public static void main(String args[]) throws IOException {
//获取键盘录入对象
InputStream in = System.in;
//将字节流对象转换成字符流对象,使用转换流,InputStreamReader
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术较高的操作,使用BufferedReader
BufferedReader bufr = new BufferedReader(isr);
String line = null;
while ((line = bufr.readLine()) != null) {
if ("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
}
关于 Writer类的flush方法()
Writer类是一个抽象类,声明如下:
public abstract class Writer implements Appendable, Closeable, Flushable
Writer类的flush()方法是一个抽象方法,其子类一般都实现了该方法。所以,一般使用字符流之后,调用一下flush()或者close()方法。
abstract public void flush() throws IOException;
字符流类大多数都实现了flush()或者close()方法,只不过,它们调用的是StreamEncoder类的该方法。该类位于sun.nio.cs包下面,其源码在我们jdk中是没有的。StreamEncoder类的源码在上文已经贴出。
缓冲区的综合使用案例,通过缓冲区复制一个.java文件,实例代码如下:
public class CopyTextByBuf {
public static void main(String args[]) throws IOException {
BufferedReader bufr=null;
BufferedWriter bufw=null;
bufr=new BufferedReader(new FileReader("A.java"));
bufw=new BufferedWriter(new FileWriter("B.txt"));
String line=null;
while((line=bufr.readLine())!=null){
bufw.write(line);
bufw.newLine();//换行
bufw.flush();
}
bufr.close();
bufw.close();
}
}
明白了BufferedReader类特有方法readLine方法的原理后。可以自定义一个类包含一个功能和readLine一致的方法,模拟一下BufferedReader,自定义的类的代码如下:
import java.io.IOException;
import java.io.Reader;
public class MyBufferedReader {
private Reader r;
public MyBufferedReader(Reader r) {
this.r = r;
}
public String myReadline() throws IOException {
//定义一个临时容器。原BufferedReader封装的字符数组。为了方便。定义一个StringBuilder容器,因为最终还是要将数据变为字符数组
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch = r.read()) != -1) {
if (ch == '\r')
continue;
if (ch == '\n')
return sb.toString();
else
sb.append((char) ch);
}
if (sb.length() != 0)
return sb.toString();
return null;
}
// 覆盖Reader类中的抽象方法
public int read(char[] cbuf, int off, int len) throws IOException {
return r.read(cbuf, off, len);
}
public void close() throws IOException {
r.close();
}
}
六 流操作的基本规律:
最痛苦的就是流对象有很多,不知道用哪一个。
通过三个明确来完成:
1 明确源和目的:
源:输入流 InputStream Reader
目的:输出流 OutputStream Writer
2 操作的数据是否为纯文本。体系
是:字符流
否:字节流
3 当体系明确后,再明确要使用哪个具体的对象。对象通过设备来进行区分:
源设备:内存,硬盘,键盘
目的设备:内存,硬盘,控制台
利用此判断过程的例子:
需求:将一个文本数据存储到另一个文件,复制文件。
明确源:因为是源,所以使用读取流。InputStream或Reader体系。
问:是否操作文本文件
答:是,这时就可以选择Reader。这样体系就明确了。
明确要使用体系中的哪个对象。
Reader体系中可以操作文本的对象是FileReader
问:是否要提高效率
答:是,加入Reader体系中缓冲区BufferedReader
FileReader fr=new FileReader("a.txt");
BufferedReader bufr=new BufferedReader(fr);
明确设备:硬盘上的一个文本。
----------------------------------------------------------------------------------------------
明确目的:OutputStream Writer
明确要使用体系中的哪个对象:
问:是否为纯文本
答:是,Writer
问:是否要提高效率
答:是,
FileWriter fw=new FileWriter("c.txt");
BufferedWriter bufw=new BufferedWriter(fw) ;
明确设备:硬盘的一个文件
七 File类的使用
File类:
用来将文件或文件夹封装成对象,方便对文件与文件夹的属性信息进行操作,File对象可以作为参数传递给流的构造函数
1 创建
boolean createNewFile()//在指定位置创建文件,如果该文件已经存在,则不创建,返回false.和输出流不一样,输出流对象已建立创建文件。而且文件已经存在,会覆盖。
boolean mkdir()//创建文件夹
boolean mkdirs()//创建多级文件夹
注意:在判断文件对象是否是文件或者目的时,必须要先判断该文件对象封装的内容是否存在。通过exists判断。
关于创建文件和文件夹的代码:public class FileTest {
public static void main(String args[]) throws IOException{
File f=new File("file.txt");//file.txt并不存在,并不会创建file.txt文件
//FileOutputStream fos=new FileOutputStream(f);//会创建一个叫file的txt文件s
//f.mkdir();穿件一个叫file.txt文件夹
System.out.println("path:"+f.getPath());
System.out.println("abspath:"+f.getAbsolutePath());
}
}
File f=new File("file.txt");
f.mkdir();//其实这个只是创建了名为file.txt的文件夹
2 删除
boolean delete()//删除失败时返回false.如果文件正在被使用,则删除不了返回false.
void deleteOnExit()//在程序退出时删除指定文件。
3 判断
boolean exists()//文件是否存在。
isFile()//是不是文件
isDirectory()//是不是文件夹
isHidden()//是不是隐藏文件
isAbsolute()//是不是绝对路径
4 获取信息
getName()//文件名
getPath()//文件路径
getParent()//上一层路径,该方法返回的是绝对路径中的父目录。如果获取的是相对路径,则返回null,如果相对路径中有上一层目录那么该目录返回结果。
getAbsolutePath()//绝对路径
length()//返回由此抽象路径名表示的文件的长度。如果此路径名表示一个目录,则返回值是不确定的。
lastModified()//返回此抽象路径名表示的文件最后一次被修改的时间。
renameTo(File dest)//重新命名此抽象路径名表示的文件。
String[] list()//返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
关于renameTo方法 此方法类似于剪切,由f1到f2,代码如下:
void cutFile(){
File f1=new File("c:\\test.java");
File f2=new File("d:\\haha.java");
System.out.println("rename"+f1.renameTo(f2));
}
关于list方法 列出某目录下文件夹及文件名称及文件的大少:
public class FileDemo2 {
public static void main(String args[]) {
File dir = new File("D:\\java");
String[] arr = dir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
});
System.out.println("len:" + arr.length);
for (String name : arr) {
System.out.println(name);
}
}
}
需求:列出指定目录下文件或者文件夹,包含子目录中的内容,也就是列出指定目录下的所有内容。因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身,这种表现形式,或编程方法,称为递归。递归注意的条件:1.限定条件2.要注意递归的次数,尽量避免内存溢出。代码如下:
public class ListFiles {
public static void main(String args[]){
File dir=new File("D:/Media");
showDir(dir, 0);
}
public static String getLevel(int level){
StringBuilder sb=new StringBuilder();
for(int x=0;x<level;x++){
//sb.append("|--");
sb.insert(0, "|--");
}
return sb.toString();
}
public static void showDir(File dir,int level){
System.out.println(getLevel(level)+dir.getName());
level++;
File[] files=dir.listFiles();
for(int x=0;x<files.length;x++){
if(files[x].isDirectory())
showDir(files[x],level);
else
System.out.println(getLevel(level)+files[x]);
}
}
}
删除原理:在windows中,删除目录从里面往外面删除的。既然是从里面删除。就需要用到递归。代码如下:
public class RemoveDir {
public static void main(String args[]) {
File dir = new File("d:/textdir");
removeDir(dir);
}
private static void removeDir(File dir) {
// TODO Auto-generated method stub
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].toString() + ":-file-:"
+ files[x].delete());
}
System.out.println(dir + "::dir::" + dir.delete());
}
}
练习:将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。建立一个java文件列表文件。思路:1.对指定的目录进行递归 2.获取递归过程所以的java文件的路径 3.将这些路径存储到集合中 4.将一个集合写到一个文件中
代码如下:
public class JavaFileList {
public static void main(String args[]) throws IOException {
File dir = new File("d:/java");
List<File> list = new ArrayList<File>();
fileToList(dir, list);
File file = new File(dir, "javalist.txt");// dir为其父目录
writeToFile(list, file.toString());
}
public static void fileToList(File dir, List<File> list) {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
fileToList(file, list);
} else {
if (file.getName().endsWith(".java")) {
list.add(file);
}
}
}
}
public static void writeToFile(List<File> list, String javaListFile)
throws IOException {
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(javaListFile));
for (File file : list) {
String path = file.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
} catch (IOException e) {
throw e;
} finally {
try {
if (bufw != null) {
bufw.close();
}
} catch (IOException e) {
throw e;
}
}
}
}
八 字符编码
1 字符流的出现是为了方便操作字符
2 更重要的是加入编码转换
3 通过子类转换流来完成
InputStreamReader
OutputStreamWriter
4 在两个对象进行构造的时候可以加入字符集
Unicode是用两个字节表示一个字符但一个字节可以表示的字符若用两个字节表示则有点浪费空间,于是UTF-8出现了:最多可以用三个字节表示一个字符。
编码:字符串变成字节数组 String-->byte[]; str.getBytes(charsetName);
解码:字节数组变成字符串 byte[]-->String; new String(byte[],charsetName);
例子代码如下:
public class EncoderDemo {
public static void main(String args[]) throws Exception{
String s="哈哈";
byte[] b1=s.getBytes("GBK");
System.out.println(Arrays.toString(b1));
String s1=new String(b1,"utf-8");
System.out.println("s1="+s1);
//对s1进行iso8859-1编码
byte[] b2=s1.getBytes("utf-8");
System.out.println(Arrays.toString(b2));
String s2=new String(b2,"utf-8");
System.out.println("s2="+s2);
}
}
打印结果如下:[-71, -2, -71, -2]
s1=????
[-17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67]
s2=????
九 IO包中的其他类
RandomAccessFile类该类不算IO体系中子类而是直接继承自Object类。但它是IO包中的成员。因为它具备读和写功能,内部封装了一个数组,而且通过指针对数组的元素进行操作可以通过getFilePointer获取指针的位置,同时通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流。通过构造函数可以看出,该类只能操作文件。而且操作文件还有模式:"r" "rw" "rws" "rwd"
而且该对象的构造函数要操作的文件不存在,会自动创建。如果存在不会覆盖。如果模式为只读r,不会创建文件,会去读取一个已存在文件,如果该文件不存在,则会出现异常。如果模式为只读rw。操作的文件不存在,会自动创建。如果存在则不会覆盖。
随机访问文件,具备读写的方法
通过skipBytes(int x),seek(int x)来达到随机访问。例子代码如下:
public class RandomAccessFileDemo {
public static void main(String args[]) throws IOException{
/* RandomAccessFile raf=new RandomAccessFile("c:/ran.txt","rw");
raf.write("haha".getBytes());*/
writeFile2();
readFile();
}
public static void readFile() throws IOException{
RandomAccessFile raf=new RandomAccessFile("c:/ran.txt", "r");
//调整对象中的指针,raf.seek(8*1);跳过指定的字节数
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 writeFile2() throws IOException{
RandomAccessFile raf=new RandomAccessFile("c:/ran.txt", "rw");
raf.write("李套".getBytes());
raf.writeInt(10);
raf.write("王五".getBytes());
raf.writeInt(20);
raf.close();
}
public static void writeFile1() throws IOException{
RandomAccessFile raf=new RandomAccessFile("c:/ran.txt", "rw");
raf.seek(8*0);
raf.write("周期".getBytes());
raf.writeInt(103);
raf.close();
}
}
打印结果如下:name=王五
age=20
十 结合IO和集合的一个练习
有五个学生,每个人 有3门功课的成绩。
需求:从键盘输入数据(姓名 三门课成绩)输入格式:如 han,20,40,50计算出总成绩,并把学生的信息和计算的总分数高低顺序放在磁盘文件“stud.txt”中。
思路:1 通过获取键盘录入一行数据,并将该行中的信息取出封装成学生对象。
2 因为学生有很多,那么就需要存储,使用到集合。因为要对学生的总分排序。所以可以使用TreeSet。
3 将集合的信息写入到一个文件中。
代码如下:
public class StudentInfoTest {
public static void main(String args[]) throws IOException {
Set<Student> studs=StudentInfoTool.getStudents();
StudentInfoTool.write2File(studs);
}
}
class Student implements Comparable<Student> {
private String name;
private int ma, cn, en;
private int sum;
Student(String name, int ma, int en, int cn) {
this.name = name;
this.ma = ma;
this.en = en;
this.cn=cn;
sum = ma + cn + en;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
return name.hashCode() + sum * 78;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Student))
throw new ClassCastException("类型匹配");
Student s = (Student) obj;
return this.name.equals(s.name) && this.sum == s.sum;
}
@Override
public String toString() {
return "Student[" + name + "," + ma + "," + cn + "," + en + "]";
}
public int getSum() {
return sum;
}
@Override
public int compareTo(Student s) {
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
return num;
}
}
class StudentInfoTool{
public static Set<Student> getStudents() throws IOException{
Set<Student> studs=new TreeSet<Student>();
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
String[] info=line.split(",");
Student stu=new Student(info[0],Integer.parseInt(info[1]),Integer.parseInt(info[2]),Integer.parseInt(info[3]));
studs.add(stu);
}
bufr.close();
return studs;
}
public static void write2File(Set<Student> studs) throws IOException{
BufferedWriter bufw=new BufferedWriter(new FileWriter("c:/studinfo.txt"));
for(Student stu:studs){
bufw.write(stu.toString()+"\t");
bufw.write(stu.getSum()+""+"\r\n");
bufw.flush();
}
bufw.close();
}
}
在控制台输入:a,32,43,54
b,2,32,43
c,32,43,45
d,3,42,43
over
对应文件中的结果为: