Posts Tagged ‘IO’

解决乱码思路—Java读写文件中遇到的问题

July 26th, 2016

最近在做一个大型Java项目的移植工作,遇到大量字符编码问题,传统的Windows平台主要使用GBK编码,而linux上面为了保持字符的统一性,使用UTF8编码,这个就导致从windows迁移过来的配置文件和资源文件乱码。之前我写过一篇《字符编码浅析》,简单介绍了下这几种编码的不同。

这里我首先解决Linux平台下的乱码问题:如果只是要看一个不同编码的文件,我们可以用vim打开,然后:set fileencoding=utf8  gbk….的方式改变查看文档的编码。如果我们想转换文档编码形式,那么使用以下的命令(将一个GBK编码的文件转换成UTF-8编码):

$enconv -L zh_CN -x UTF-8 filename

文档编码转换完成,这里我从Java的角度阐述如果读写。因为网上关于Java读写的博文足够多,我主要来总结一下:

Java 的IO有两种流的类型:那就是字节流(InputStream、OutputStream)和字符流(Reader、Writer)。我们在读写中文的时候务必使用字符流,因为中文是2个字节组成,如果读取一个字节必然出现问题。

InputStream常用的类是如下,直接对读数据的是节点流,浅色的是处理流,依靠InputStream对数据进行读写,拥有更强大的功能。

20160808200550

OutputStream常用类如下,直接写的是节点流,浅色的是处理流。

20160808200918

二进制读写的时候要使用Stream的方式进行,而读写字符就使用Reader、Writer。

20160808201307

20160808201400

总结一下两种类型,节点流为以下表格,通过这些对象直接对数据源进行读写。

类型 字符流 字节流
File FileReader、FileWriter FileInputStream、FileOutputStream
Memory Array CharArrayReader、CharArrayWriter ByteArrayInputStream、ByteArrayOutputStream
Memory String StringReader、StringWriter
Pipe PipedReader、PipedWriter PipedInputStream、PipedOutputStream
  • 但是节点流提供功能单一,需要使用处理流对其进行拓展,其中尤其是 BufferedReader、BufferedWriter最为常用,先看一个表格:

 

处理类型 字符流 字节流
Buffering BufferedReader、BufferedWriter BufferedInputStream、BufferedOutputStream
 Filtering  FilterReader、FilterWriter FilterInputStream、FilterOutputStream
字节流字符流转换 InputStreamReader、OutputStreamWriter
Object序列化 ObjectInputStream、ObjectOutputStream
数据转换 DataInputStream、DataOutputStream
计数 LineNumberReader LineNumberInputStream
Peeking ahead PushbackReader PushbackInputStream
Print PrintWriter PrintStream
  • BufferedReader和BufferedWriter来读写文件(字符读写,只要不是二进制文件,都可以用字符,字节是给二进制文件使用的),这种方式读写效率最好(而且可以选择想要的字符集),这个主要将数据先缓冲起来,然后一起写入或者读取出来。经常使用的是readLine()方法,一次读取一行数据。其中我们可以根据装饰者模式修改BufferReader和BufferWriter中的类,比如new InputStreamReader(new FileInputStream(…), StandardCharsets.UTF_8),通过这种方式,就可以以UTF-8的方式读取文件,或者以new OutputStreamWriter(new FileOutputStream(“test.txt”), “UTF-8”)写文件

使用方式 FileReader->BufferedReader->readLine、FileWriter->BufferedWriter->write

private static void read() throws FileNotFoundException, IOException {  
        File file = new File("a.txt");// 指定要读取的文件  
        // 获得该文件的缓冲输入流  
        BufferedReader bufferedReader = new BufferedReader(new FileReader(file));  
        String line = "";// 用来保存每次读取一行的内容  
        while ((line = bufferedReader.readLine()) != null) {  
            System.out.println(line);  
        }  
        bufferedReader.close();// 关闭输入流  
    }  
private static void write() throws IOException {  
        File file = new File("a.txt");// 指定要写入的文件  
        if (!file.exists()) {// 如果文件不存在则创建  
            file.createNewFile();  
        }  
        // 获取该文件的缓冲输出流  
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));  
        bufferedWriter.write("你好世界");  
        bufferedWriter.newLine();
        bufferedWriter.write("hello world");  
        bufferedWriter.flush();// 清空缓冲区  
        bufferedWriter.close();// 关闭输出流  
    }  

与他类似的就是BufferedInputStream和BufferedOuputStream,用这两个读取二进制文件,不同的在于读取的是二进制文件。

FileInputStream ——>BufferedInputStream -> read、FileOutputStream->BufferedOutputStream->write

        // 指定要读取文件的缓冲输入字节流  
        BufferedInputStream in = new BufferedInputStream(new FileInputStream("a.jpg"));  
        File file = new File("b.jpg");  
        if (file != null) {  
            file.createNewFile();  
        }  
        // 指定要写入文件的缓冲输出字节流  
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));  
        byte[] bb = new byte[1024];// 用来存储每次读取到的字节数组  
        int n;// 每次读取到的字节数组的长度  
        while ((n = in.read(bb)) != -1) {  
            out.write(bb, 0, n);// 写入到输出流  
        }  
        out.close();// 关闭流  
        in.close();  

有的时候我们还要使用File对象,这个时候可以查看FileInputStream、FileOutputStream、FileReader、FileWriter的构造方法。类似于FileReader(File file)。

字符流到字节流的转换也很重要,类似于:

InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
    String s = null;
    try {
      s = br.readLine();
...

OutputStreamWriter osw = new OutputStreamWriter(
           new FileOutputStream("...."));
osw.write("mircosoftibmsunapplehp");

Java中io类太多了,有时候容易眼花,不知道用哪个,这个时候只要记住字符流和字节流的异同,合理使用类,并查询java api即可。

参考:

http://blog.csdn.net/dangnianmingyue_gg/article/details/47361323

阻塞,非阻塞访问与异步通知的比较

August 25th, 2015

最近在编写字符设备驱动,在使用场景上面存在不同的实现:阻塞I/O,非阻塞I/O和异步通知三种,之前都是朦朦胧胧知道三者区别,而没有认真的学习三者不同,这这篇文章中我会仔细的比较三者的区别。

设备的阻塞访问

指的是执行设备操作时如果无法回去资源,那么挂起进程,挂起的进程进入休眠状态,kernel将其从rq中移出,直到条件满足,示例代码:

char buf;
fd = open("/dev/ttyS1",O_RDWR);
...
res = read(fd,&buf,1);
if(res == 1)
   printf("%c\n",buf);

20150825112857

阻塞访问的优点就是节省CPU资源,资源没有得到满足,那么挂起即可,进程进入休眠状态,将cpu资源让给其他进程(当然如果进入休眠,那么当资源满足,我们需要一种方式唤醒这个休眠进程,可以使用信号)。阻塞I/O 一般使用等待队列来实现。

设备的非阻塞访问

指的是如果得不到资源,那么立即返回,并不挂起这个进程,我们可以不断的轮训这个设备,直到这个设备满足资源。

char buf;
fd = open("/dev/ttyS1",O_RDWR | O_NONBLOCK);
...
while(read(fd,&buf,1)!= 1)
   printf("%c\n",buf);

20150825112920

非阻塞访问的最大缺点是因为要不停的轮训设备,会浪费大量的cpu时间,但是我们可以借助sigaction通过异步通知的方式访问串口提高cpu利用率,说到非阻塞,通常会用到select() poll() 系统调用,这两个调用最后都会调用到驱动设备中的poll函数。

poll函数原型是unsigned int (* poll)(struct file *filp,struct poll_table *wait),在驱动里面,调用poll_wait() 向poll_table注册等待队列,当字符设备中存在数据时,return POLLIN,POLLRDNORM,POLLOUT。这里我们要注意:设备驱动的poll函数本身并不会阻塞,但是poll和select()系统调用会阻塞等待文件描述符集合中的至少一个可访问或者超时。

异步通知

异步通知的全程是“信号驱动的异步I/O”,也就是说一旦设备准备就绪,主动通知应用程序,这样应用程序根本就不需要查询设备状态。

20150825112911

我们可以使用信号来通知设备处理,其中STDIN_FILENO是int类型,不同于STDIN 的FILE * 类型,使用signal添加信号处理函数,使用fcntl()设置SIGIO信号被STDIN_FILENO接收,之后使用O_ASYNC 使得IO具有异步特性。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#define MAX_LEN 100

void input_handler(int num)
{
        char data[MAX_LEN];
        int len;

        len = read(STDIN_FILENO,&data,MAX_LEN);
        data[len] = 0;
        printf("input:%s\n",data);
}

int main()
{
        int oflags;
        signal(SIGIO,input_handler);
        fcntl(STDIN_FILENO,F_SETOWN,getpid());
        oflags = fcntl(STDIN_FILENO,F_GETFL);
        fcntl(STDIN_FILENO,F_SETFL,oflags | O_ASYNC);

        while(1);
}

 

 

[1] UNIX 高级编程

[2] Linux 设备驱动开发

[3] http://stackoverflow.com/questions/15102992/what-is-the-difference-between-stdin-and-stdin-fileno

[4] http://www.c4learn.com/c-programming/c-reference/fread-function/

具有健壮性的文件IO函数

October 13th, 2014

在理想环境下,我们使用linux下的read()write()函数可以依照我们想读的字节数读到buffer中。并没有考虑函数返回值的问题。

以read为例

static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
    int cnt;

    while (rp->rio_cnt <= 0) {  /* refill if buf is empty */
	rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, 
			   sizeof(rp->rio_buf));
	if (rp->rio_cnt < 0) {
	    if (errno != EINTR) /* interrupted by sig handler return */
		return -1;
	}
	else if (rp->rio_cnt == 0)  /* EOF */
	    return 0;
	else 
	    rp->rio_bufptr = rp->rio_buf; /* reset buffer ptr */
    }

    /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */
    cnt = n;          
    if (rp->rio_cnt < n)   
	cnt = rp->rio_cnt;
    memcpy(usrbuf, rp->rio_bufptr, cnt);
    rp->rio_bufptr += cnt;
    rp->rio_cnt -= cnt;
    return cnt;
}
ssize_t rio_readn(int fd, void *usrbuf, size_t n) 
{
 size_t nleft = n;
 ssize_t nread;
 char *bufp = usrbuf;

 while (nleft > 0) {
 if ((nread = read(fd, bufp, nleft)) < 0) {
 if (errno == EINTR) /* interrupted by sig handler return */
 nread = 0; /* and call read() again */
 else
 return -1; /* errno set by read() */ 
 } 
 else if (nread == 0)
 break; /* EOF */
 nleft -= nread;
 bufp += nread;
 }
 return (n - nleft); /* return >= 0 */
}

我们看到read返回值是有可能小于要求sizoof(buffer)的值,这种现象在kernel character device与network中非常普遍!

另外,read()应该也要处理用户发来的信号,如果遇到sigal信号,要使得返回值置0,并使得buffer指针的移动。

所以我们要注意,并进行比较。

比如在http://www.lizhaozhong.info/archives/1066中read也是实现了类似的思想。

维护一个len与count的关系,每次调用read函数都是确保len减去一个count大小的buffer,直到len<count,然后len赋值为count。

write函数也是类似,只不过len与count之间主要做加法。