关于java文件读写、字节流、字符流的一点心得

转载]转:关于java文件读写、字节流、字符流的一点心得

一.转载网址:http://my.oschina.net/u/232879/blog/155440
关于编码方式我们不讲,有兴趣自己去看,这里大概提一下。

UTF-16采用等幅编码,即每个字符占2个字节。优点:简单;缺点:西文会膨胀到200%,冗余!而且字与字之间的疆界不好找,容易划分错误,没有考虑好前缀问题。这一点huffman编码做的很好。
UTF-8为不等幅编码,有1到3个字节的不等长度。优点:由于采用了很好的前缀,不会出现字之间疆界不好找和划分错误的情况。缺点:中日韩等文字膨胀到150%,冗余。

现在主要说一下字节流、字符流的区别,顾名思义字节流是以字节为单位读取数据的,而字符流是以字符为单位读取的。我们都知道java采用了两种I/O读写 方式主要有两个超类系:一个是inputstream和outputstream字节流类系;另一个是Reader和Writer字符流类系。使用他们来 读写文本文件时有些要注意的问题:

0. java中BufferedInputStream类相比InputStream类,提高了输入效率,增加了输入缓冲区的功能。

InputStream流是指将字节序列从外设或外存传递到应用程序的流
BufferedInputStream流是指读取数据时,数据首先保存进入缓冲区,其后的操作直接在缓冲区中完成。
继承关系是这样的:
Java.lang.Object
     Java.io.InputStrean
          Java.io.FilterInputStream
                Java.io.BufferedInputStream


1、使用inputstream类的子类读取的单位为字节,对于英语字母(只占一个字节)不受任何影响,而中文文字在unicode编码为两个字节(或者以上?),在读取中一个中文文字会被划分为两个(或多个)字节,因而受到影响。
如果将读取到的字节保存在byte[]数组中,为了正确地处理字节到字符的转化应注意如下问题:

对byte[]数组采用toString的方法转化为字符,会导致错误的分割方式生成字符,不能正确地显示字符;而采用String的构造函数String(byte[] b)可以正确的分割构造字符。(或者String(byte[] b, Charset a)带编码方式的构造函数,在知道要读入的文本的编码方式的情况下。)

2、使用Reader类的子类读取的单位为字符,即中文、英文都为两个字节大小,故而都不受影响。
如果将读取到的每一个字符保存到一个字符数组char[] a中问题又来了:
(特别注意!!!)如对字符数组char[]使用toString()函数时同样会遇到错误的分隔,建议使用String类的构造函数String(char[] c)来构造正确的字符划分。
当然有更简便的方法:如果你采用的读取函数是readline()当然不存在如上字符转换的问题(请注意在InputStream类系的子类中是不存在类似readline()这样的函数的,只有read()这样的函数)。

StringBuffer就像一个动态数组一样,我可以保存任意长的char字符,需要时只需将他们读取出来就行了。
在这里我们将读到的每一个byte转化为一个char保存在StringBuffer中,需要时将他们读出再转化为byte,此过程数据不会溢出,精度不会受到损失。

另外好像对转义字符 ’\’,String类的split(String a)函数出现了问题,这是怎么回事?java本身的bug吗?

当一个带完整文件夹路径的文件名,传给File类的mkdir(String FilePathAndName)函数时一定要小心,如果该文件名中包含的文件路径不存在(或者说还未被创建的)的话,直接用此函数创建文件是会出错的, 你需要先一层层的创建好这些路径方可创建该路径下的文件。(创建路径和文件其实是一个函数,本质上也没有什么区别!^_^)

下面是我写的测试程序,代码如下。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.CharBuffer;


public class FileReaderWriter {
public FileReaderWriter() {
}
public String reader(String fullpath) throws FileNotFoundException
//用readline的方式读取文件内容
{
try
{

String content ="";

一. File -> FileReader->BufferedReader

File file = new File(fullpath);

if(file.exists()) {
   System.out.println("FILE: "+fullpath+" EXIST, Now I will read it!");
   FileReader fr = new FileReader(file);
   BufferedReader br = new BufferedReader(fr);

try {
   System.out.println("-----------Now is content of file(Reading):----------");
   int n = 0;
   for(;;)  {
      n++;
      String temp = br.readLine();
      System.out.println("Line"+n+":");
      System.out.println(temp);
      content+=temp;
      if(temp==null)  {
        System.out.println("----------OVER---------");
       br.close();
        break;
    }
}
} catch (IOException ex) {
   ex.printStackTrace();
}
}
else  {
    System.out.println("FILE:"+fullpath+" is Not EXIST"+"now I will do nothing but exit");
}
return content;
}
catch (FileNotFoundException ex)  {
   ex.printStackTrace();
}
return null;
}


二. File -> FileWriter->BufferedWriter


public void writer(String fullpath,String content)
{
File f = new File(fullpath);
if(!f.exists())
{
System.out.println("File is not exist! Now I will create it!");
createfile(fullpath);
//f.mkdir();//带有目录时这样创建是不行的
}
FileWriter fw;
try
{
fw = new FileWriter(f);
BufferedWriter bw = new BufferedWriter(fw);

System.out.println("I am Writing Now!");

bw.close();
System.out.println("-----------Writing Content:---------");
System.out.println(content);
System.out.println("------------Writing Complete!-------------");
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static void main(String[] args)
{
FileReaderWriter frw = new FileReaderWriter();
//做一下各种文件路径的测试^_^
//String path ="info.txt";
//String path2 = "stream.txt";
//String path ="d:\try1/1\2\info.txt";
//String path2 = "d:\try2\1/2/3\stream.txt";
//String path2 = "\4/2/3\stream.txt";

String path ="1\2\info.txt";
String path2 = "1/2/stream.txt";
String test ="";
String text1 ="Hello, My name is GuiZhiPengn";
String text2 ="我的名字是桂志鹏n";
String text3 ="I come From China! Now I'm Studying in WuHan University of HuBei Province.n";
String text4 ="我来自中国,现在湖北省的武汉大学读书";
test= text1+text2+text3+text4;
frw.writer(path,test);
try {
frw.reader(path);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}

frw.outputstream(path2,test);
frw.inputstream(path2);
//frw.inputstream("LoginEndpoint.wsdl");

}

public String readerbycharacter(String fullpath) throws IOException
//试图通过一个字节一个字节(或者一个字符一个字符)的读取方式读出流后恢复字符编码,
{
String content ="";
File f = new File(fullpath);
FileReader fr;
int in;
try {
fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);
StringBuffer sb = new StringBuffer();
//byte[] save = new byte[1000];
//char[] save = new char[1000];
int num =0;
do
{
in = br.read();
if(in!=-1)
{
byte b = (byte)in;//强制转化会出现问题(有中文时,将char转化为byte损失位数,将使中文显示有问题)
char c = (char)in;
sb.append(c);
System.out.println(c);
System.out.println(b);
System.out.println((char)b);
//save[num++] = b;
num++;
}
}while(in!=-1);
br.close();
System.out.println("NUM: "+num);
System.out.println("CHAR Num: " + sb.length());

//content = new String(save);
content = sb.toString();
//content = new String(t);
//content = t.toString();

System.out.println(content);
writer("test.txt",content);
}
catch (FileNotFoundException ex)
{
ex.printStackTrace();
}
return content;
}

public String inputstream(String fullpath)
{

三. File -> FileInputStream->BufferedInputStream

File f = new File(fullpath);
int inbyte = 0;
String content ="";
StringBuffer sb = new StringBuffer();
int num=0;
try {
   FileInputStream fin = new FileInputStream(f);
   BufferedInputStream bin = new BufferedInputStream(fin);

do {
   try {
      inbyte = bin.read();
   } catch (IOException ex) {
   ex.printStackTrace();
}
if(inbyte!=-1) {
   sb.append((char)inbyte);
   //System.out.println((char)inbyte);
   num++;
}
}while(inbyte!=-1);
try {
   bin.close();
} catch (IOException ex) {
   ex.printStackTrace();
}
System.out.println("Num: "+num);
byte[] save = new byte[num];
for(int i = 0; i
{
save[i] = (byte)sb.charAt(i);
}
content = new String(save);

System.out.println(content);
System.out.println("Reading stream success!");

} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
return content;
}

四. File -> FileOutputStream->BufferedOutputStream

public void outputstream(String fullpath, String content)
{
File f = new File(fullpath);
if(!f.exists())
{
System.out.println("File is not exist! Now I will create it!");
createfile(fullpath);
//f.mkdir();//fullpath中带有目录时这样创建方式是不行的
}
int inbyte = 0;
try {
   FileOutputStream fout = new FileOutputStream(f);
   BufferedOutputStream bout = new BufferedOutputStream(fout);

try {
   bout.write(content.getBytes());
} catch (IOException ex) {
   ex.printStackTrace();
}
try {
   bout.close();
} catch (IOException ex) {
   ex.printStackTrace();

System.out.println("Writing Stream Success!");
} catch (FileNotFoundException ex) {
   ex.printStackTrace();
}
}

public void createfile(String fullpath)
{
System.out.println("---------In CreateFile Fouction---------");
String[] paths = null;
String propath = null;
File f;
int i;
if(fullpath.indexOf("")!=-1)
{
System.out.println("?????");
//paths = fullpath.split("");此函数执行有问题:java.util.regex.PatternSyntaxException: Unexpected internal error near index 1
fullpath = fullpath.replace('\','/');
}
if(fullpath.indexOf("/")!=-1)
{
System.out.println("!!!!!");
paths = fullpath.split("/");
}
//if(paths[0].indexOf(":")!=-1)

if(paths!=null)
{
for(i = 0; i
{
if(propath!=null)
propath=propath+"/"+paths[i];
else
propath = paths[0];
f = new File(propath);
if(!f.exists())
f.mkdir();
}
}
else
{
f = new File(fullpath);
}
}
}

后记:一位朋友对我的文章给出了很好的答案^_^顶了,谢谢

当操作的对象不需要关心编码问题时应该使用InputStream/OutputStream系列的类。例如读取二进制文件(EXE等)。

当需要关心编码问题时应该使用Reader/Writer系列的类。

String.split()的参数是正则表达式。所以想以分割字符串的话应该这样:str.("");


二.简单的代码分析PrintWriter


五. OutputStream -> PrintWriter


class MySendCommondThread extends Thread{
    private String commond;
    public MySendCommondThread(String commond){
       this.commond=commond;
    }
    publicvoid run(){
       //实例化Socket 
try {
           Socket socket=new Socket(serverUrl,serverPort);
           PrintWriter out = new PrintWriter(socket.getOutputStream());

           out.println(commond);
           out.flush();
       } catch (UnknownHostException e) {
       } catch (IOException e) {
       } 
    }
 

    }
1.android手册
public PrintWriter (OutputStream out)

Constructs a new PrintWriter with out as its target stream. By default, the new print writer does not automatically flush its contents to the target stream when a newline is encountered.

Parameters
out the target output stream
2.分析
这里使用的是PrintWriter的out构造函数,这个构造函数的唯一参数是OutputStream,在这个实例中是sockt.getOutPutStream(),即将字符流写入到socket的输出流中。
然后在PrintWriter实例化对象out中,调用了println(String str)方法即out.println(commond),也就是实现将commond换行显示出来。
在使用out的构造函数中,由于不能像
public PrintWriter (OutputStream out, boolean autoFlush)

一样将缓冲区的数据挤出,因此需要用到out.flush()方法将缓冲区的数据手动挤出。



另外,关于从inputStream中读取的例子,可参考下面从Socket中读取数据的示例。


public class ServerThread implements Runnable {  
    // 定义当前线程所处理的Socket  
    Socket s = null;  
    // 该线程所处理的Socket所对应的输入流  

六. InputStream -> InputStreamReader->BufferedReader


    BufferedReader br = null;  
  
    public ServerThread(Socket s) throws IOException {  
        this.s = s;  
        // 初始化该Socket对应的输入流  
        br = new BufferedReader(new InputStreamReader(s.getInputStream(),  
                "uft-8"));  

    }  
  
    @Override  
    public void run() {  
        try  
        {  
            String content = null;  
            //采用循环不断从Socket中读取客户端发送过来的数据  
            while((content = readFromClient()) != null)  
            {  
                //遍历socketList中的每个socket  
                //将读到的内容向每一个socket发送一次  
                for(Socket s : MyServer.socketList)  
                {  
                    OutputStream os = s.getOutputStream();  
                    os.write((content + "\n").getBytes("utf-8"));  
                }  
            }  
        }  
        catch(IOException e)  
        {  
            e.printStackTrace();  
        }  
    }  
  
    //定义读取客户端数据的方法  
    private String readFromClient() {  
        try  
        {  
            return br.readLine();  
        }  
        //如果捕捉到异常,表明该Socket对应的客户端已经关闭  
        catch(IOException e)  
        {  
            //删除该Socket  
            MyServer.socketList.remove(s);  
        }  
        return null;  
    }  
}  

java中BufferedInputStream类相比InputStream类,提高了输入效率,增加了输入缓冲区的功能


七. BufferedOutputStream和ByteArrayOutputStream区别

也有一部分是自己加上去的,以备后用。

众所周知BufferedOutputStream是一个缓冲数据输出流接口, ByteArrayOutputStream则是字节数组输出流接口. 这2个输出流都是我们经常用到的, 它们都是OutputStream的子类,而什么时候选择用它们呢, 这个就要看你运用到什么应用场景下了. 

下来先来看下源码吧. 

1.BufferedOutputStream会首先创建一个默认的容器量, capacity = 8192 = 8KB, 每次在写的时候都会去比对capacity是否还够用, 如果不够用的时候, 就flushBuffer(), 把buf中的数据写入对应的outputStream中, 然后将buf清空, 一直这样等到把内容写完. 在这过程中主要起到了一个数据缓冲的功能. 

  1. public synchronized void write(byte b[], int off, int len) throws IOException {  
  2.       // 在这判断需要写的数据长度是否已经超出容器的长度了,如果超出则直接写到相应的outputStream中,并清空缓冲区  
  3.       if (len >= buf.length) {  
  4.           flushBuffer();  
  5.           out.write(b, off, len);  
  6.           return;  
  7.       }  
  8.       // 判断缓冲区剩余的容量是否还够写入当前len的内容,如果不够则清空缓冲区  
  9.       if (len > buf.length - count) {  
  10.           flushBuffer();  
  11.       }  
  12.       // 将要写的数据先放入内存中,等待数据达到了缓冲区的长度后,再写到相应的outputStream中  
  13.       System.arraycopy(b, off, buf, count, len);  
  14.       count += len;  
  15.     }  
flushBuffer () 这个方法干了些什么呢, 来看看源码  
Java代码   收藏代码
  1. private void flushBuffer() throws IOException {  
  2.        if (count > 0) {  
  3.           // 把写入内存中的数据写到构造方法里传入的OutputStream句柄里, 并把容量大小清楚  
  4.     out.write(buf, 0, count);  
  5.     count = 0;  
  6.        }  
  7.    }  
这个类最重要的就是这2个方法, 这样节省了大量的内存空间, 合理的分配内存来完成数据输出, 当你资源不是那么充沛时, 选择这个类来实现你想要的东西是不是很合适呢?  

2.普通的OutputStream, 例如ByteArrayOutputStream也会首先创建一个默认的容器量, capacity = 32 = 32b, 每次在写的时候都会去比对capacity是否还够用, 如果不够用的时候, 就重新创建buf的容量, 一直等到内容写完, 这些数据都会一直处于内存中. 
Java代码   收藏代码
  1. public synchronized void write(byte b[], int off, int len) {  
  2.       if ((off < 0) || (off > b.length) || (len < 0) ||  
  3.             ((off + len) > b.length) || ((off + len) < 0)) {  
  4.           throw new IndexOutOfBoundsException();  
  5.       } else if (len == 0) {  
  6.           return;  
  7.       }  
  8.         // 不断对自己的容量进行相加  
  9.         int newcount = count + len;  
  10.         // 如果新的容量大小已经超过了现有的大小时,则重新开辟新的内存区域来保存当前的数据  
  11.         if (newcount > buf.length) {  
  12.             buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));  
  13.         }  
  14.         System.arraycopy(b, off, buf, count, len);  
  15.         count = newcount;  
  16.     }  


注释中已经对这个类进行了详细的解释 

总结 : 当你资源不足够用时,选择BufferedOutputStream是最佳的选择, 当你选择快速完成一个作业时,可以选择ByteArrayOutputStream之类的输出流

你可能感兴趣的:(File)