JAVA基础知识之NIO——Buffer.Channel,Charset,Channel文件锁
NIO机制
NIO即NEW IO的意思,是JDK1.4提供的针对旧IO体系进行改进之后的IO,新增了许多新类,放在java.nio包下,并对java.io下许多类进行了修改,以便使用与nio.
在java.io中,无论上层通过什么方式访问数据,在底层都是通过字节的方式来读取,
即使是BufferedReader也一样,虽然是先集中将OS中的数据读取到buffer中,再由上层应用从buffer中读取,或者是上层先将数据写入buffe中,再批量写入OS中去,
buffer机制只是减少了IO的切换次数,但是访问数据的方式并没有变化,在OS底层依然是一个字节一个字节地操作。
而在NIO中则出现了全新的访问数据的方式,NIO引入了Buffer和Channel的概念,可以通过Channel将OS中一块或者全部数据直接映射成Buffer,应用程序则从Buffer中读取数据,
NIO这种类似虚拟内存映射的机制不仅仅是减少了与OS的IO切换次数,而且真正减少了直接读取OS的次数,因此NIO在性能上比传统IO要高得多。
也就是说传统的IO读取的是数据流,而NIO读取的是数据块。
Buffer
Buffer的作用就是用来装载数据,然后输出数据,其内部类似于一个数组,但是又不是完全相同。
针对不同的功能/数据类型,有不同的Buffer,ByteBuffer, CharBuffer, ShortBuffer, IntBuffer等等。ByteBuffer和CharBuffer是最常用的Buffer.
Buffer是一个抽象类,要得到一个Buffer对象,需要用具体的Buffer子类的allocate(int i)方法。
Buffer的内部结构类似一个数组,因此也提供了一些类似数组的属性,
capacity表示Buffer的容量;
limit表示界限,用来表示读取数据时的最大的下标。
position则类似一个游标,随着读和写而变化。
mark是一个标志位,相当于一个绝对坐标,position可以直接定位到mark位置。
Buffer的工作过程:
- 写Buffer
初始状态的Buffer的position位置为0,limit等于capacity, 当put进数据到Buffer的时候,position会随之后移,
当所有数据装载完毕之后,调用Buffer的flip()方法,会将limit设置为position位置,position则置0,
这就做好了读的准备,此时position就充当数组下标的角色,而limit就是数组的上限,最多只能读到这里。
- 读Buffer
当Buffer输出数据结束后,此时的position应该已经读到了limit位置,调用Buffer的clear()方法,会将position再次置0,同时会将limit置为capacity,
这又恢复了Buffer写数据前的状态,相当于重置了Buffer,为下一次写进Buffer做好了准备。但注意的是,clear()这个过程并没有清空Buffer中的数据。
下面演示Buffer的使用,
1 package nio; 2 3 import java.nio.CharBuffer; 4 5 public class BufferTest { 6 public static void main(String[] args) { 7 //创建Buffer 8 CharBuffer buff = CharBuffer.allocate(8); 9 System.out.println("capacity: "+buff.capacity()); 10 System.out.println("limit: "+buff.limit()); 11 System.out.println("position: "+buff.position()); 12 //放入元素 13 buff.put('a'); 14 buff.put('b'); 15 buff.put('c'); 16 System.out.println("放入3个元素后,position: "+buff.position()); 17 buff.flip(); 18 System.out.println("执行flip()后,limit: "+buff.limit()+", position: "+buff.position()); 19 //取出第一个元素 20 System.out.println("第一个元素(position=0): "+buff.get()); 21 System.out.println("取出第一个元素后,position:" +buff.position()); 22 buff.clear(); 23 System.out.println("执行clear()后,limit: "+buff.limit()+" ,position: "+buff.position()); 24 System.out.println("执行clear()后,buffer并没有被清除。第三个元素为: "+buff.get(2)); 25 System.out.println("执行绝对读取后, position: "+buff.position()); 26 } 27 }
注意从Buffer中读数据的时候,有两种方式,一种是读相对读取,即从当前position位置读取,读完之后,position会随之后移。另一种是绝对位置读取,直接读取具体位置的数据,position不会改变。
下面是执行结果,
1 capacity: 8 2 limit: 8 3 position: 0 4 放入3个元素后,position: 3 5 执行flip()后,limit: 3, position: 0 6 第一个元素(position=0): a 7 取出第一个元素后,position:1 8 执行clear()后,limit: 8 ,position: 0 9 执行clear()后,buffer并没有被清除。第三个元素为: c 10 执行绝对读取后, position: 0
从上面程序看出,比起普通的数组,Buffer更智能一点,读取数据的时候不需要关心Buffer中有多少数据,读取到limit位置即可,不会出现越界问题。
(PS:上面是书上说的,我本人并没有看出单纯使用Buffer能比单纯使用数组强大到哪去,倒是麻烦了不少,Buffer只有在结合Channel使用的时候才有用吧)
Channel
NIO中真正有革命性改动的应该是引入了Channel机制,Channel可以将文件的部分或全部映射成Buffer进行读取,直接减少了读取文件的次数。
不过Channel的映射文件机制需要借助Buffer,Channel需要将文件映射成Buffer,应用程序再从Buffer中读取数据,就相当于从文件中读数据,或者应用程序将数据写入Buffer,就相当于写入文件,
这不过这个过程相对底层OS来说,是一块一块进行的,而不是一个字节一个字节进行的。
Channle也不是直接创建,而是由传统io中的流对象的getChannel()返回的。
不同功能的流对象返回不同的changnel(),例如FileInputStream(),FileOutputstream()返回的是FileChannel, 而PipeInputStream返回的是PipeChannel.
下面演示Channel的用法,
1 package nio; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 import java.io.RandomAccessFile; 9 import java.nio.ByteBuffer; 10 import java.nio.CharBuffer; 11 import java.nio.MappedByteBuffer; 12 import java.nio.channels.FileChannel; 13 import java.nio.charset.Charset; 14 import java.nio.charset.CharsetDecoder; 15 16 public class FileChannelTest { 17 public static void fileChannel() { 18 File f = new File("tmp.txt"); 19 try ( 20 //FileInputStream获取的channel只能读 21 FileChannel inChannel = new FileInputStream(f).getChannel(); 22 //FileOutputStream获取的channel只能写 23 FileChannel outChannel = new FileOutputStream("a.txt").getChannel() 24 ) { 25 //将整个文件映射成buffer 26 MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); 27 Charset charset = Charset.forName("GBK"); 28 //输出buffer里的全部数据,就直接写入了a.txt 29 outChannel.write(buffer); 30 //复位buffer的limit和position 31 buffer.clear(); 32 //解码器 33 CharsetDecoder decoder = charset.newDecoder(); 34 //用解码器将ByteBuffer解码成CharBuffer 35 CharBuffer charBuffer = decoder.decode(buffer); 36 //charBuffer的toString()方法可以获取对应的字符串 37 System.out.println(charBuffer); 38 } catch (IOException e) { 39 e.printStackTrace(); 40 } 41 } 42 43 public static void randomFileChannel() throws FileNotFoundException, IOException { 44 File f = new File("a.txt"); 45 try ( 46 RandomAccessFile raf = new RandomAccessFile(f, "rw"); 47 //random Channel的读写模式取决于RandomAccessFile的打开模式 48 FileChannel randomChannel = raf.getChannel() 49 ) { 50 ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); 51 //把channel的指针移动到最后,程序就可以让ByteBuffer的数据追加到文件后面 52 randomChannel.position(f.length()); 53 //写入buffer,就直接写入了文件 54 randomChannel.write(buffer); 55 buffer.clear(); 56 Charset charset = Charset.forName("GBK"); 57 //解码器 58 CharsetDecoder decoder = charset.newDecoder(); 59 //用解码器将ByteBuffer解码成CharBuffer 60 CharBuffer charBuffer = decoder.decode(buffer); 61 //charBuffer的toString()方法可以获取对应的字符串 62 System.out.println(charBuffer); 63 } 64 } 65 public static void main(String[] args) throws FileNotFoundException, IOException { 66 //fileChannel(); 67 randomFileChannel(); 68 } 69 }
在RandomAccessFile中也包含了一个getChannel()方法返回FileChannel,但是它的读写属性则是取决于RandomAccessFile的打开方式,如上面的randomFileChannel()所示。
字符集和Charset简介
字符集
所谓字符集,就是对每个字符进行编号,所有编号的集合就是一种字符集,例如对汉字‘刚’的十进制编码是65,但是计算机存储的是二进制,65的二进制是01000001,这样就实现了字符到二进制的映射关系,这个过程也叫编码。
目前存在很多种字符集,java默认使用Unicode字符集,使用两个字节来表示一个字符。但很多操作系统使用的其他字符集,如果直接与java读写数据,就会乱码。
因此java.nio中,提供了一个Charset类,实现字节与字符之间的互相转换。
通过Charset我们可以查看平台支持什么字符集,
1 public static void charsetTest() { 2 SortedMap<String,Charset> map = Charset.availableCharsets(); 3 for (String key : map.keySet()) { 4 System.out.println(key+" : "+map.get(key)); 5 } 6 }
我的电脑上会输出以下字符集,
1 Big5 : Big5 2 Big5-HKSCS : Big5-HKSCS 3 CESU-8 : CESU-8 4 EUC-JP : EUC-JP 5 EUC-KR : EUC-KR 6 GB18030 : GB18030 7 GB2312 : GB2312 8 GBK : GBK 9 hp-roman8 : hp-roman8 10 IBM-Thai : IBM-Thai 11 IBM00858 : IBM00858 12 IBM00924 : IBM00924 13 IBM01140 : IBM01140 14 IBM01141 : IBM01141 15 IBM01142 : IBM01142 16 IBM01143 : IBM01143 17 IBM01144 : IBM01144 18 IBM01145 : IBM01145 19 IBM01146 : IBM01146 20 IBM01147 : IBM01147 21 IBM01148 : IBM01148 22 IBM01149 : IBM01149 23 IBM037 : IBM037 24 IBM1026 : IBM1026 25 IBM1047 : IBM1047 26 IBM1047_LF : IBM1047_LF 27 IBM1141_LF : IBM1141_LF 28 IBM1153 : IBM1153 29 IBM273 : IBM273 30 IBM277 : IBM277 31 IBM278 : IBM278 32 IBM280 : IBM280 33 IBM284 : IBM284 34 IBM285 : IBM285 35 IBM290 : IBM290 36 IBM297 : IBM297 37 IBM420 : IBM420 38 IBM424 : IBM424 39 IBM437 : IBM437 40 IBM500 : IBM500 41 IBM775 : IBM775 42 IBM850 : IBM850 43 IBM852 : IBM852 44 IBM855 : IBM855 45 IBM857 : IBM857 46 IBM860 : IBM860 47 IBM861 : IBM861 48 IBM862 : IBM862 49 IBM863 : IBM863 50 IBM864 : IBM864 51 IBM865 : IBM865 52 IBM866 : IBM866 53 IBM868 : IBM868 54 IBM869 : IBM869 55 IBM870 : IBM870 56 IBM871 : IBM871 57 IBM918 : IBM918 58 IBM924_LF : IBM924_LF 59 ISO-2022-CN : ISO-2022-CN 60 ISO-2022-JP : ISO-2022-JP 61 ISO-2022-JP-2 : ISO-2022-JP-2 62 ISO-2022-KR : ISO-2022-KR 63 ISO-8859-1 : ISO-8859-1 64 ISO-8859-10 : ISO-8859-10 65 ISO-8859-13 : ISO-8859-13 66 ISO-8859-14 : ISO-8859-14 67 ISO-8859-15 : ISO-8859-15 68 ISO-8859-16 : ISO-8859-16 69 ISO-8859-2 : ISO-8859-2 70 ISO-8859-3 : ISO-8859-3 71 ISO-8859-4 : ISO-8859-4 72 ISO-8859-5 : ISO-8859-5 73 ISO-8859-6 : ISO-8859-6 74 ISO-8859-7 : ISO-8859-7 75 ISO-8859-8 : ISO-8859-8 76 ISO-8859-9 : ISO-8859-9 77 JIS_X0201 : JIS_X0201 78 JIS_X0212-1990 : JIS_X0212-1990 79 KOI8-R : KOI8-R 80 KOI8-U : KOI8-U 81 PTCP154 : PTCP154 82 RK1048 : RK1048 83 Shift_JIS : Shift_JIS 84 TIS-620 : TIS-620 85 US-ASCII : US-ASCII 86 UTF-16 : UTF-16 87 UTF-16BE : UTF-16BE 88 UTF-16LE : UTF-16LE 89 UTF-32 : UTF-32 90 UTF-32BE : UTF-32BE 91 UTF-32LE : UTF-32LE 92 UTF-8 : UTF-8 93 windows-1250 : windows-1250 94 windows-1251 : windows-1251 95 windows-1252 : windows-1252 96 windows-1253 : windows-1253 97 windows-1254 : windows-1254 98 windows-1255 : windows-1255 99 windows-1256 : windows-1256 100 windows-1257 : windows-1257 101 windows-1258 : windows-1258 102 windows-31j : windows-31j 103 x-Big5-HKSCS-2001 : x-Big5-HKSCS-2001 104 x-Big5-Solaris : x-Big5-Solaris 105 x-compound-text : x-compound-text 106 x-EUC-TW : x-EUC-TW 107 x-EUC_CN : x-EUC_CN 108 x-EUC_JP_LINUX : x-EUC_JP_LINUX 109 x-eucJP-Open : x-eucJP-Open 110 x-IBM-udcJP : x-IBM-udcJP 111 x-IBM1006 : x-IBM1006 112 x-IBM1025 : x-IBM1025 113 x-IBM1027 : x-IBM1027 114 x-IBM1041 : x-IBM1041 115 x-IBM1043 : x-IBM1043 116 x-IBM1046 : x-IBM1046 117 x-IBM1046S : x-IBM1046S 118 x-IBM1088 : x-IBM1088 119 x-IBM1097 : x-IBM1097 120 x-IBM1098 : x-IBM1098 121 x-IBM1112 : x-IBM1112 122 x-IBM1114 : x-IBM1114 123 x-IBM1115 : x-IBM1115 124 x-IBM1122 : x-IBM1122 125 x-IBM1123 : x-IBM1123 126 x-IBM1124 : x-IBM1124 127 x-IBM1130 : x-IBM1130 128 x-IBM1164 : x-IBM1164 129 x-IBM1165 : x-IBM1165 130 x-IBM1166 : x-IBM1166 131 x-IBM1351 : x-IBM1351 132 x-IBM1362 : x-IBM1362 133 x-IBM1363 : x-IBM1363 134 x-IBM1363C : x-IBM1363C 135 x-IBM1364 : x-IBM1364 136 x-IBM1370 : x-IBM1370 137 x-IBM1371 : x-IBM1371 138 x-IBM1377 : x-IBM1377 139 x-IBM1379 : x-IBM1379 140 x-IBM1380 : x-IBM1380 141 x-IBM1381 : x-IBM1381 142 x-IBM1382 : x-IBM1382 143 x-IBM1383 : x-IBM1383 144 x-IBM1385 : x-IBM1385 145 x-IBM1386 : x-IBM1386 146 x-IBM1388 : x-IBM1388 147 x-IBM1390 : x-IBM1390 148 x-IBM1390A : x-IBM1390A 149 x-IBM1399 : x-IBM1399 150 x-IBM1399A : x-IBM1399A 151 x-IBM16684 : x-IBM16684 152 x-IBM16684A : x-IBM16684A 153 x-IBM29626 : x-IBM29626 154 x-IBM29626C : x-IBM29626C 155 x-IBM300 : x-IBM300 156 x-IBM300A : x-IBM300A 157 x-IBM301 : x-IBM301 158 x-IBM33722 : x-IBM33722 159 x-IBM33722A : x-IBM33722A 160 x-IBM33722C : x-IBM33722C 161 x-IBM420S : x-IBM420S 162 x-IBM4933 : x-IBM4933 163 x-IBM720 : x-IBM720 164 x-IBM737 : x-IBM737 165 x-IBM808 : x-IBM808 166 x-IBM833 : x-IBM833 167 x-IBM834 : x-IBM834 168 x-IBM835 : x-IBM835 169 x-IBM836 : x-IBM836 170 x-IBM837 : x-IBM837 171 x-IBM856 : x-IBM856 172 x-IBM859 : x-IBM859 173 x-IBM864S : x-IBM864S 174 x-IBM867 : x-IBM867 175 x-IBM874 : x-IBM874 176 x-IBM875 : x-IBM875 177 x-IBM897 : x-IBM897 178 x-IBM921 : x-IBM921 179 x-IBM922 : x-IBM922 180 x-IBM927 : x-IBM927 181 x-IBM930 : x-IBM930 182 x-IBM930A : x-IBM930A 183 x-IBM933 : x-IBM933 184 x-IBM935 : x-IBM935 185 x-IBM937 : x-IBM937 186 x-IBM939 : x-IBM939 187 x-IBM939A : x-IBM939A 188 x-IBM942 : x-IBM942 189 x-IBM942C : x-IBM942C 190 x-IBM943 : x-IBM943 191 x-IBM943C : x-IBM943C 192 x-IBM947 : x-IBM947 193 x-IBM948 : x-IBM948 194 x-IBM949 : x-IBM949 195 x-IBM949C : x-IBM949C 196 x-IBM950 : x-IBM950 197 x-IBM951 : x-IBM951 198 x-IBM954 : x-IBM954 199 x-IBM954C : x-IBM954C 200 x-IBM964 : x-IBM964 201 x-IBM970 : x-IBM970 202 x-IBM971 : x-IBM971 203 x-ISCII91 : x-ISCII91 204 x-ISO-2022-CN-CNS : x-ISO-2022-CN-CNS 205 x-ISO-2022-CN-GB : x-ISO-2022-CN-GB 206 x-iso-8859-11 : x-iso-8859-11 207 x-ISO-8859-6S : x-ISO-8859-6S 208 x-JIS0208 : x-JIS0208 209 x-JISAutoDetect : x-JISAutoDetect 210 x-Johab : x-Johab 211 x-KOI8_RU : x-KOI8_RU 212 x-KSC5601 : x-KSC5601 213 x-MacArabic : x-MacArabic 214 x-MacCentralEurope : x-MacCentralEurope 215 x-MacCroatian : x-MacCroatian 216 x-MacCyrillic : x-MacCyrillic 217 x-MacDingbat : x-MacDingbat 218 x-MacGreek : x-MacGreek 219 x-MacHebrew : x-MacHebrew 220 x-MacIceland : x-MacIceland 221 x-MacRoman : x-MacRoman 222 x-MacRomania : x-MacRomania 223 x-MacSymbol : x-MacSymbol 224 x-MacThai : x-MacThai 225 x-MacTurkish : x-MacTurkish 226 x-MacUkraine : x-MacUkraine 227 x-MS932_0213 : x-MS932_0213 228 x-MS950-HKSCS : x-MS950-HKSCS 229 x-MS950-HKSCS-XP : x-MS950-HKSCS-XP 230 x-mswin-936 : x-mswin-936 231 x-mswin-936A : x-mswin-936A 232 x-PCK : x-PCK 233 x-SJIS_0213 : x-SJIS_0213 234 x-UTF-16LE-BOM : x-UTF-16LE-BOM 235 X-UTF-32BE-BOM : X-UTF-32BE-BOM 236 X-UTF-32LE-BOM : X-UTF-32LE-BOM 237 x-UTF_8J : x-UTF_8J 238 x-windows-1256S : x-windows-1256S 239 x-windows-50220 : x-windows-50220 240 x-windows-50221 : x-windows-50221 241 x-windows-874 : x-windows-874 242 x-windows-949 : x-windows-949 243 x-windows-950 : x-windows-950 244 x-windows-iso2022jp : x-windows-iso2022jp
通过Charset的forName()方法,可以指定当前以什么字符集进行编码或者解码,
Charset中的 CharsetEncoder可以将Unicode字符集以forName()指定的字符集进行编码,并转换成ByteBuffer(字节序列).
Charset中的 CharsetDecoder可以将其他字符集的ByteBuffer以Unicode字符集进行解码,并转换成CharBuffer(字符序列).
以上两行待验证。
下面演示Charset用法,
1 package nio; 2 3 import java.nio.ByteBuffer; 4 import java.nio.CharBuffer; 5 import java.nio.charset.CharacterCodingException; 6 import java.nio.charset.Charset; 7 import java.nio.charset.CharsetDecoder; 8 import java.nio.charset.CharsetEncoder; 9 import java.util.SortedMap; 10 11 public class CharsetTest { 12 public static void charsetTest() { 13 SortedMap<String,Charset> map = Charset.availableCharsets(); 14 for (String key : map.keySet()) { 15 System.out.println(key+" : "+map.get(key)); 16 } 17 } 18 19 public static void charsetTransForm() throws CharacterCodingException { 20 //简体中文编码 21 Charset cn = Charset.forName("GBK"); 22 CharsetEncoder cnEncoder = cn.newEncoder(); 23 CharsetDecoder cnDecoder = cn.newDecoder(); 24 CharBuffer cbuff = CharBuffer.allocate(8); 25 cbuff.put("A"); 26 cbuff.put("孙"); 27 cbuff.put("悟"); 28 cbuff.put("空"); 29 //调用flip(), lim设置为pos位置,pos恢复起始位置, 读就绪 30 cbuff.flip(); 31 //字符转字节 32 ByteBuffer bbuff = cnEncoder.encode(cbuff); 33 for (int i = 0; i < bbuff.limit(); i++) { 34 //直接输出字节 35 System.out.print(bbuff.get(i)+" "); 36 } 37 System.out.println("\n"); 38 //字节转字符 39 System.out.println(cnDecoder.decode(bbuff)); 40 } 41 public static void main(String[] args) throws CharacterCodingException { 42 charsetTest(); 43 //charsetTransForm(); 44 } 45 }
上面的charsetTransForm()方法输出如下,
1 65 -53 -17 -50 -14 -65 -43 2 3 A孙悟空
在String类里,也提供了一个getBytes(String charset)的方法,可以返回byte[],可以将指定字符集的字符串转换成字节码。
文件锁
Channel中提供了lock(阻塞)和trylock(非阻塞)来在多进程并发时锁定文件,这两个方法返回一个FileLock对象。 FileLock对象的release()方法可以释放文件锁。
下面是一个简单例子,
1 package nio; 2 3 import java.io.FileNotFoundException; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.nio.channels.FileChannel; 7 import java.nio.channels.FileLock; 8 9 public class FileLockTest { 10 public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException { 11 try ( 12 FileChannel channel = new FileOutputStream("a.txt").getChannel()) { 13 //使用非阻塞方式加锁 14 FileLock lock = channel.lock(); 15 Thread.sleep(10000); 16 lock.release(); 17 } 18 } 19 }
Channel提供的文件锁并不常用,部分原因原因如下,
- 并发控制性能不高,还是推荐用数据库
- 某些平台上文件锁无效
- 如果两个java程序同时使用一个Java虚拟机运行,则不能同时对一个文件加锁。
- 某些平台上,关闭FlieLock时会释放锁,因此同一个文件被锁定应该避免打开多个FileLock