《Thinking in Java》学习——18章Java I/O系统(三)

ppe#标准I/O

一.从标准输入中读取

1.按照标准I/O模型,Java提供了System.inSystem.outSystem.err。其中System.out已经事先被包装成了PrintStream对象。System.err同样也是PrintStream,但是System.in却是一个没有被包装过的未经加工的InputStream。这意味着尽管我们可以立刻使用System.outSystem.err,但是在读取System.in之前必须对其进行包装。
2.为了使用readLine()一行一行地读取,我们将System.in包装成BufferedReader来使用:

public class Echo {
    public static void main(String... args) throws IOException {
        BufferedReader stdin = new BufferedReader(
            new InputStreamReader(System.in));
        String in;
        while ((s = stdin.readLine()) != null && s.length != 0) {
            System.out.println(s);
        }
    }
}

注意,System.in和大多数流一样,通常应该对它进行缓冲。

二.将System.out转换成PrintWriter

PrintWriter有一个可以接受OutputStream作为参数的构造器。因此,只要需要,就可以使用那个那个构造器把System.out转换成PrintWriter

public class ChangeSystemOut {
    public static void main(String... args) {
        PrintWriter out = new PrintWriter(System.out, true);
        out.println("Hello, world");
    }
}

第二个参数需要设置为true,以便开启自动清空功能;否则,你可能看不到输出。

三.标准I/O重定向

1.Java的System类提供了一些简单的静态方法调用,以允许我们对标准I/O流进行重定向:

setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)

2.下面是简单实例:

public class Redirecting {
    public static void main(String... args) throws IOException {
        PrintStream console = System.out;
        BufferedInputStream in = new BufferedInputStream(
            new FileInputStream("Redirecting.java"));
        PrintStream out = new PrintStream(
            new BufferedOutputStream(
                new FileOutputStream("test.out")));
        System.setIn(in);
        System.setOut(out);
        System.setErr(out);
        BufferedReader br = new BufferedReader(
            new InputStreamReader(System.in));
        String s;
        while (s = br.readLine() != null) {
            System.out.println(s);
        } 
        out.close();
        System.setOut(console);
    }
}

I/O重定向操纵的是字节流,而不是字符流;因此我们使用的是InputStreamOutputStream,而不是ReaderWriter

进程控制

1.对于需要在Java内部之行其他 操作系统程序的需求,Java类库提供了执行这些操作的类。
2.下面的程序的作用是运行程序,并将产生的输出发送到控制台:

class OSExecuteException extends RuntimeException {
    public OSExecuteException(String why) { super(why); }
}

class OSExecute {
    public static void command(String command) {
        boolean err = false;
        try {
            Process process = new ProcessBuilder(command.split(" ")).start();
            BufferedReader results = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
            String s;
            while ((s = results.readLine()) != null) {
                System.out.println(s);
            }
            BufferedReader errors = new BufferedReader(
                new InputStreamReader(process.getErrorStream()));
            while ((s = results.readLine) != null){
                System.err.println(s);
                err = true;
            }
        } catch (Exception e) {
            if (!command.startWith("CMD /C"))
                command("CMD /C" +   command);
            else 
                throw new RuntimeException(e);
        }
        if (err) {
            throw new OSExecuteException("Errors executing" + command);
        }
    }
}

要想运行一个程序,你需要传递一个字符串,它与你在控制台上运行该程序所键入的命令相同。这个命令被传递给java.lang.ProcessBuilder构造器,然后所产生的ProcessBuilder对象被启动。程序执行过程中调用getInputStream()getErrorStream()获取标准输出流和标准错误流。

新I/O

1.JDK1.4引入了新的Java I/O类库java.nio.,其目的在于提高速度。速度的提高在文件I/O和网络I/O中都有实现,这里我们只研究前者。
2.速度的提高来自于所使用的结构:
通道缓冲器。但是,我们并没有必要直接和通道交互,我们只和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
3.唯一直接与通道交互的缓冲器是
ByteBuffer:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法集,用于以原始的字节形式或几本数据类型输出和读取数据。
4.
FileInputStream* 、FileOutputStreamRandomAccessFile提供了方法用以产生可写的、可读的及可读可写的通道:

public class GetChannel {
    private static final int BSIZE = 1024;
    public static void main(String... args) throws Exception {
        FileChannel fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("Some text ".getBytes()));
        fc.close();
        fc = RandomAccessFile("data.txt", "rw").getChannel();
        fc.position(fc.size());
        fc.write(ByteBuffer.wrap("Some more".getBytes()));
        fc.close();
        ByteBuffer buff = ByteBuffer.allocate(BSIZE);
        fc.read(buff);
        buff.flip();
        while (buff.hasRemaining())
            System.out.println((char) buff.get());
    }
}
/*
Output:
Some text Some more
*/

5.通道时一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
6.将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。也可以使用warp()方法将已经存在的字节数组“包装到”ByteBuffer中。
7.对于只读访问,我们必须显式地使用静态的allocate()方法来分配ByteBuffer
8.一旦调用read()方法来告知FileChannelByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备,如果我们打算使用缓冲器执行进一步的read()操作,我们也必须得调用clear()来为每个read()做好准备:

public class ChannelCopy {
    private static final int BSIZE = 1024;
    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            System.out.println("arguments: sourcefile destfile");
            System.exit(1);
        }
        FileChannel
            in = new FileInputStream(args[0]).getChannel();
            out = new FileOutputStream(args[1]).getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
        while (in.read(buffer) != -1) {
            buffer.flip();
            out.write(buffer);
            buffer.clear();
        }
    }
}

每次read()操作之后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲器中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接受数据的准备。

一.转换数据

1.缓冲器容纳的是普通字节,为了把它们转换成字符,我们要不在输入它们的时候对其进行编码,要么在将其从缓冲器输出对它们进行解码。可以使用java.nio.charset.Charset类实现这些功能,该类提供来把数据编码成多种不同类型的字符集的工具。如果我们想对缓冲器调用rewind()方法(该方法是为了回到数据开始的部分),接着使用平台的默认字符集对数据进行decode(),那么作为结果的CharBuffer可以很好地输出打印到控制台:

public class BufferToText {
    private static final int BSIZE = 1024;
    public static void main(String[] args) {
        FileChannel fc = new FileOutputStream("data2.txt").getChannel();
        fc.write(ByteBuffer.wrap("Some text".getBytes()));
        fc.close();
        fc = new FileInputStream("data2.txt").getChannel();
        ByteBuffer buff = ByteBuffer.allocate(BSIZE);
        fc.read(buff);
        buff.flip();
        System.out.println(buff.asCharBuffer());
        buff.rewind();
        String encoding = System.getProprety("file.encoding");
        System.out.println("Decoded using " + encoding + ": " 
            + Charset.forName(encoding).decode(buff));
        fc = new FileOutputStream("data2.txt").getChannel();
        fc.write(ByteBuffer.wrap("Some text".getBytes("UTF-16BE")));
        fc.close();
        fc = new FileInputStream("data2.txt").getChannel();
        buff.clear();
        fc.read(buff);
        buff.flip();
        System.out.println(buff.asCharBuffer());
        fc = new FileOutputStream("data2.txt").getChannel();
        buff = ByteBuffer.allocate(24);
        buff.asCharBuffer().put("Some text");
        fc.write(buff);
        fc.close();
        fc = new FileInputStream("data2.txt").getChannel();
        buff.clear();
        fc.read(buff);
        buff.flip();
        System.out.println(buff.asCharBuffer());
    }
}
/*
Output:
????
Decoded using Cp1252: Some text
Some text
Some text
*/

2.System.getProperty("file.encoding")可以用来发现默认字符集,它会产生代表字符集名称的字符串。把该字符串传送给Charset.forName()用以产生Charset对象,可以用它对字符串进行解码。

二.获取基本类型

1.尽管ByteBuffer只能保存字节类型的数据,但是它可以具有可以从所容纳的字节中产生出各种不同基本类型值的get方法:

ByteBuffer bb = ByteBuffer.allocate(1024);
System.out.println(String.valueof(bb.getInt()));

2.向ByteBuffer插入基本数据类型的方法是:利用asCharBuffer()asShortBuffer()等获得该缓冲器上的视图,然后使用视图的put()方法。此方法适用于所有基本数据类型的转换,唯一的例外就是使用asShortBuffer()方法等时候,需要进行数据类型转换:

bb.asCharBuffer().put("ByteBuffer");
bb.asShortBuffer().put((short) 1111111111);
bb.asIntBuffer().put(1);

3.ByteBuffer中提供了limit()方法,以便获取ByteBuffer可使用空间的上限。
4.当分配完一个ByteBuffer之后,缓冲器的分配方式会将其内容自动置零。

三.视图缓冲器

1.视图缓冲器可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBufferByteBuffer依然是实际存储数据的地方,支持着前面的视图,因此,对视图的任何修改都会映射成对ByteBuffer中数据的修改:

public class IntBufferDemo {
    private static final int BSIZE = 1024;
    public static void main(String[] args) {
        ByteBuffer bb = ByteBuffer.allocate(BSIZE);
        IntBuffer ib = bb.asIntBuffer();
        ib.put(new int[]{ 11, 42, 47, 99, 143, 811, 1016});
        System.out.println(ib.get(3));
        ib.put(3, 1811);
        ib.flip();
        while (ib.hasRemaining()) {
            int i = ib.get();
            System.out.println(i);
        }
    }
}
/*
Output:
99
11
42
47
1811
143
811
1016
*/

先用重载后的put()方法存储一个数组,接着get()put()方法调用直接访问底层ByteBuffer中的某个整数位置。
2.不同的机器可能会使用不同的字节排序方法来存储数据。“big endian”(高位优先)将最重要的字节存放在地址最低的存储器单元。而“little endian”(低位优先)则是将最重要的字节放在地址最高的存储器单元。因此,当存储量大于一个字节的时候,就要考虑字节的顺序问题了。如有两个字节b1:00000000,b2:01100001,如果我们以short(ByteBuffer.asShortBuffer())形式读取数据,得到的数字是97(二进制形式为000000000110010),如果在读取之前将排序方式改为低位优先,得到的数字为24832(二进制形式为011001000000000)。
3.改变排序方式可以使用order()方法,这里需要传入一个参数,为ByteOrder.BIG_ENDIANByteOrder.LITTLE_ENDIAN

四.用缓冲器操纵数据

1.
nio类之间的关系.png
五.缓冲器的细节

1.Buffer由数据和可以高效地访问及操纵这些数据的四个索引组成,这四个索引是:mark(标记),position(位置),limit(界限),和capacity(容量):

方法 描述
capacity() 返回缓冲器的容量
clear() 清空缓冲区,将position设置为0,limit设置为容量。我们可以调用此方法覆盖缓冲区
flip() 将limit设置为position,position设置为0.此方法用于准备葱缓冲区读取已经写入的数据
limit() 返回limit值
limit(int num) 设置limit的值
mark() 将mark设置为position
position() 返回position()的值
position(int pos) 设置position的值
remaining() 返回(limit - positon)
hasRemaining() 若有介于position和limit之间的元素,则返回true
六.内存映射文件

1.内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都放在内存中,而且王权可以把它当作非常大的数组来访问:

public class LargeMappedFiles {
    static int length = 0x8FFFFFF;
    public static void main(String... args) {
        MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel()
            .map(FileChannel.MapMode.READ_WRITE, 0, length);
        for (int i = 0; i < length; i ++) {
            out.put((byte)'x');
        }
        fro (int i = length / 2; i < length / 2 + 6; i ++) {
            System.out.print((char) out.get(i));
        }
    }
}

MappedByteBufferByteBuffer继承而来,可以通过调用获取到的文件上的通道的map()方法获得,它具有ByteBuffer的所有方法。
2.尽管“映射写”似乎要用到FileOutputStream,但是映射文件中的所有输出必须使用RandomAccessFile
3.尽管“旧”的I/O流在使用nio实现后性能有所提高,但是“映射文件访问”往往可以更加显著地加快速度,即使简历映射文件的话费很大。

七.文件加锁

1.JDK1.4引入了文件加锁机制,它允许我们同步访问某个作为共享资源的文件。为了解决竞争统一文件的两个线程可能在不同的进程里的问题,文件锁被设定为对其他的操作系统的进程是可见的,因为Java的文件加锁直接映射到了本地操作系统的加锁工具。
2.通过对FileChannel调用tryLock()lock(),就可以获得整个问价的FileLocktryLock()是非阻塞式的,它设法获取锁,但是如果不能获得,它将直接从方法调用返回。lock()则是阻塞式的,它要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。使用FileLock.release()可以释放锁。
3.tryLock()lock()方法也有其重载方法提供使用对文件等一部分上锁:

tryLock(long position, long size, boolean shared)
lock(long position, long size, boolean shared)

4.文件映射通常应用于极大的文件。我们可能需要对这种巨大的文件进行部分加锁,以便其他进程可以修改文件中未被加锁的部分:

public class LockAndModify extends Thread {
    private ByteBuffer buff;
    private int start, end;
    public LockAndModify(ByteBuffer mob, int start, int end) {
        this.start = start;
        this.end = end;
        mbb.limit(end);
        mbb.position(start);
        buff = mbb.slice();
        start();
    }
    public void run() {
        try {
            FileLock fl = fc.lock(start, end, false);
            System.out.println("Locked: " + start + " to " + end);
            while (buff.position() < buff.limit() - 1) {
                buff.put((byte) (byte.get() + 1));
            }
            fl.release();
            System.out.println("Release: " + start + " to " + end);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

在上面的程序中,线程类LockAndModify创建了缓冲区和用于修改的slice,然后在run()方法中,获得文件通道上的锁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 转自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的猫阅读 2,259评论 0 22
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,494评论 18 139
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,137评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,493评论 18 399
  • 终于,我可以提笔记录那些年 你熟悉却已陌生的脸 分手时我们没有愉快地说再见 这是我留有的遗憾 你手机里她灿烂的笑脸...
    朗秋阅读 287评论 0 3