NIO:
Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO 。
NIO与原来的IO同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于
通道的IO操作。
NIO将以更加高效的方式进行文件的读写操作。
1. Buffer(缓冲区):
属性:
-
limit 缓冲区大小限制,默认指向最后一的元素的下一位
-
position 指针位置,默认指向当前元素的下一位
-
capacity 缓冲区容量,在初始化缓冲区的时候可以设置缓冲区大小
清理,翻转和倒带
-
clear() 使缓冲区准备好信道读取或相对放置操作的一个新的序列:它设置了limit和position为零。
-
flip() 使缓冲区准备好新的通道写入或相对获取操作序列:它将limit设置为当前位置,然后将position设置为零。
-
rewind()使缓冲区准备好重新读取已经包含的数据:它保持limit不变,并将position设置为零。
一些方法:
-
mark() 将此缓冲区的当前position做个标记
-
reset() 使position返回到mark()标记的位置
-
remaining() 返回当前position和limit之间的元素数。
-
hasRemaining() 返回当前position和limit之间的是否有元素。
-
get() 相对获取,从position处开始读取数据,然后position自增
-
get (int index),绝对获取,不会影响position
总结:
0<=mark<=position<=limit<=position
直接缓冲区和非直接缓冲区
直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率。
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中。
allocateDirect(int capacity)分配一个新的直接字节缓冲区
allocate(int capacity)分配一个新的非直接字节缓冲区。
isDirect()判断这个字节缓冲区是否是直接缓冲区。
代码示例:
public class NIOTest {
@Test
public void test01() {
//初始化缓冲区并且规定容量
CharBuffer cb = CharBuffer.allocate(10);
//往缓冲区加入元素
cb.put('a');
cb.put("123");
cb.put('b');
cb.put('c');
System.out.println("capaticy : " + cb.capacity());
System.out.println("limit : " + cb.limit());
System.out.println("position : " + cb.position());
cb.flip(); //使缓冲区准备好读取和写入操作,limit指向指针位置,指针指向0
System.out.println(cb.get());
System.out.println(cb.get());
System.out.println(cb.get());
System.out.println("limit : " + cb.limit());
System.out.println("position : " + cb.position());
}
}
运行结果:
2. Channel(通道):
Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作
Channel本身不能直接访问数据,而只能与Buffer进行交互(可以将channel想象成轨道,而把buffer想象成装载数据的地铁)
Channel接口的主要实现类:
-
FileChannel 本地文件传输通道
-
SocketChannel/ServerSocketChannel TCP协议数据传输通道
-
DatagramChannel UDP协议传输通道
使用 channel+直接缓冲区(物理内存映射文件) 完成文件的复制:
通道之间的数据传输 (直接缓冲区)
-
transferFrom() :从源通道传输数据
-
transferTo():传输数据到目标通道
代码示例:
public class ChannelTest {
@Test
public void test() {
try {
//文件输入通道
//操作方式只读
FileChannel fr = FileChannel.open(Paths.get("abc.rar"), StandardOpenOption.READ);
//文件输出通道
//操作方法,读写创建
FileChannel fw = FileChannel.open(Paths.get("c.rar"),StandardOpenOption.READ,
StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//fr输出数据到fw
//fr.transferTo(0, fr.size(), fw);
//fw从fr接受数据
fw.transferFrom(fr, 0, fr.size());
fr.close();
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果(先生成a.rar,再生成b.rar):
3. Path:
Path用替换原有的File类
实例化
Paths类提供静态的get()方法用来获取Path对象
-
static Path get(String first, String …more) : 用于将多个字符串成路径
-
static Path get(URI uri) : 返回指定uri对应的path路径
常用方法:
4. Files工具类
操作文件或文件目录的工具类
复制方法代码举例:
@Test
public void test() throws FileNotFoundException, IOException {
Files.copy(new FileInputStream("a.txt"), Paths.get("b.txt"),StandardCopyOption.REPLACE_EXISTING);
}
判断是否为隐藏文件
@Test
public void test() throws FileNotFoundException, IOException {
//判断文件是否为隐藏文件
Path path = Paths.get("a.txt");
System.out.println("该文件是隐藏文件" + ":" + Files.isHidden(path));
}
5.Charset
NIO提供了Charset用于字符编码和解码
输出支持的字符集
利用字符集编码和解码
不太重要,直接上代码:
@Test
public void test() throws CharacterCodingException {
//初始化转码对象
Charset forname = Charset.forName("UTF-8");
//初始化一个字符缓冲区对象,设置容量为10
CharBuffer cb = CharBuffer.allocate(10);
//放置字符到缓冲区
cb.put("你好NIO");
//编码
CharsetEncoder encoder = forname.newEncoder();
//进入读取模式
cb.flip();
//编码之后得到byteBuffer
ByteBuffer bb = encoder.encode(cb);
//遍历byteBuffer
for(int i=0;i < bb.limit();i++){
System.out.println(bb.get());
}
//初始化解码对象
CharsetDecoder decoder = forname.newDecoder();
bb.flip();
//解码
CharBuffer cbf = decoder.decode(bb);
System.out.println(cbf.toString());
}
运行结果:
多线程:
理解说明:
-
程序:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
-
进程:资源分配的基本单位,程序的一次执行过程,或是正在运行的一个程序。
-
线程:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),进程可进一步细化为线程,是一个程序内部的一条执行路径。
并行与并发的理解:
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
创建线程类
一. 继承Thread
创建一个继承于Thread类的子类
重写Thread类的run() --> 将此线程执行的操作声明在run()中
创建Thread类的子类的对象
通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
代码示例:
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i < 100;i++){
System.out.println(getName() + ":" + i);
}
}
}
@Test
public void test01() {
//初始化两个线程对象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
if (i == 20) {
thread1.start();
thread2.start();
}
}
}
部分运行结果:
可以看到,两个线程对象打印的顺序是未知的,因为线程会抢占CPU的时间片,谁抢的时间片多谁就有运行得久一点
二. 实现Runnable接口的方式:
创建一个实现了Runnable接口的类
实现类去实现Runnable中的抽象方法:run()
创建实现类的对象
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()
代码示例:
public class MyThread2 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i < 30;i++){
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
@Test
public void test02() {
MyThread2 mt2 = new MyThread2();
Thread t1 = new Thread(mt2, "线程1");
t1.start();
Thread t2 = new Thread(mt2, "线程2");
t2.start();
}
}
部分运行结果:
同样是乱序
两种方式的对比:
开发中:优先选择:实现Runnable接口的方式
-
实现的方式没类的单继承性的局限性
-
实现的方式更适合来处理多个线程共享数据的情况
相同点:
两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
目前两种方式,要想启动线程,都是调用的Thread类中的start()。
大部分笔记都转载于尚硅谷,有兴趣想自学Java得朋友可以去看一看:Java零基础入门教程