服务端会有个ServerSocket,每一个ServerSocket都有一个与之对应ClientSocket,每一个连接都需要有一个线程来维护
1.Java NIO 全称 Java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 NewIO),是同步非阻塞的。
Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
缓冲区(ByteBuffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。ByteBuffer的指针有postion
NIO 的通道类似于流,主要的区别是:通道可以同时进行读写,而流只能读或者只能写。通道可以实现异步读写数据。通道可以从缓冲读数据,也可以写数据到缓冲。BIO的流是单向的,NIO的通道是双向的,可以读也可以写操作。
Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)。Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。
这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。避免了多线程之间的上下文切换导致的开销。
// 将流整合起来以便实现更高级的输入和输出操作。如可以把InputStream包装到BufferedInputStream中以实现缓冲,即从磁盘中一次读取一大块数据
InputStream input = new BufferedInputStream(new FileInputStream("c:\data\input-file.txt"));
OutputStream output = new BufferedOutputStream(new FileOutputStream("c:\data\output-file.txt"));
Java的IO流共涉及40多个类,实际上非常规则,都是从以上4个抽象基类派生的
java.io.InputStream
int read()
从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
int read(byte[] b)
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。
int read(byte[] b, int off, int len)
将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。
java.io.OutputStream
void write(int b)
将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。
void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b,int off,int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
public void flush()throws IOException
刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。
java.io.Reader
int read()
读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1
int read(char[] cbuf)
将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
int read(char[] cbuf,int off,int len)
将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
java.io.Writer
void write(int c)
写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。
void write(char[] cbuf)
写入字符数组。
void write(char[] cbuf,int off,int len)
写入字符数组的某一部分。从off开始,写入len个字符
void write(String str)
写入字符串。
void write(String str,int off,int len)
写入字符串的某一部分。
void flush()
刷新该流的缓冲,则立即将它们写入预期目标
用来将文件转换为二进制字节流或字符流
//二进制流范例,打开一个文件的输入流,读取到字节数组,再写入另一个文件的输出流
public void test1() {
try {
FileInputStream fileInputStream = new FileInputStream(new File("a.txt"));
FileOutputStream fileOutputStream = new FileOutputStream(new File("b.txt"));
byte []buffer = new byte[128];
while (fileInputStream.read(buffer) != -1) {
fileOutputStream.write(buffer);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 字符流范例
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("file.txt");
FileWriter fileWriter = new FileWriter("file2.txt");
//一次拷贝4个字节
char[] chars = new char[1024*1024];
int read;
while ((read = fileReader.read(chars)) != -1) {
fileWriter.write(chars, 0, read);
}
fileWriter.flush();
if (fileReader != null) {
fileReader.close();
}
if (fileWriter != null) {
fileWriter.close();
}
}
Java IO中的管道为运行在同一个JVM中的两个线程提供通信的能力,所以管道也可以作为数据源以及目标媒介。不同的JVM中线程不能利用管道进行通信
//使用管道来完成两个线程间的数据点对点传递
@Test
public void test2() throws IOException {
PipedInputStream pipedInputStream = new PipedInputStream();
PipedOutputStream pipedOutputStream = new PipedOutputStream(pipedInputStream);
new Thread(new Runnable() {
@Override
public void run() {
try {
pipedOutputStream.write("hello input".getBytes());
pipedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
byte []arr = new byte[128];
while (pipedInputStream.read(arr) != -1) {
System.out.println(Arrays.toString(arr));
}
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
不仅可以用来做缓存数据的临时存储空间,同时也可以作为数据来源或者写入目的
//字符数组和字节数组在io过程中的作用
public void test4() {
//arr和brr分别作为数据源
char []arr = {'a','c','d'};
CharArrayReader charArrayReader = new CharArrayReader(arr);
byte []brr = {1,2,3,4,5};
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(brr);
}
通过ObjectInputStream转换InputStream为Object
ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) input.readObject();
input.close();
@Test
public void BufferedStreamTest() throws FileNotFoundException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情3.jpg");
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
// bos.flush();//刷新缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
// fos.close();
// fis.close();
}
}
通过InputStreamReader、OutputStreamWriter
@Test
public void test1() throws IOException {
FileInputStream fis = new FileInputStream("dbcp.txt");
// InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
//参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
String str = new String(cbuf,0,len);
System.out.print(str);
}
isr.close();
}
System.in和System.out分别代表了系统标准的输入和输出设备
默认输入设备是:键盘,输出设备是:显示器
System.in的类型是InputStream
System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
BufferedReader br = null;
InputStreamReader isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);
while (true) {
System.out.println("请输入字符串:");
String data = br.readLine();
if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
System.out.println("程序结束");
break;
}
字节流:处理字节和字节数组或二进制对象(stream命名); 字符流:处理字符、字符数组或字符串(Reader、Writer命名)。
public class 序列化和反序列化 {
//注意,内部类不能进行序列化,因为它依赖于外部类
@Test
public void test() throws IOException {
A a = new A();
a.i = 1;
a.s = "a";
FileOutputStream fileOutputStream = null;
FileInputStream fileInputStream = null;
try {
//将obj写入文件
fileOutputStream = new FileOutputStream("temp");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(a);
fileOutputStream.close();
//通过文件读取obj
fileInputStream = new FileInputStream("temp");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
A a2 = (A) objectInputStream.readObject();
fileInputStream.close();
System.out.println(a2.i);
System.out.println(a2.s);
//打印结果和序列化之前相同
} catch (IOException e)
{ e.printStackTrace();
} catch (ClassNotFoundException e)
{ e.printStackTrace(); } } }
class A implements Serializable {
int i;
String s;
}
反序列化的前提是序列化ID得相同,Eclipse提供两种产生序列化ID的方法,一种是:属性名+时间戳,另一种是我们一般用1L表示。
// 虽然两个类的路径和功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化
public class A implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
public class A implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
Serializable
接口类,如果没有则抛NotSerializableException
异常(这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。),如果实现了则会创建ObjectOutputStream
对象,绑定到输出流上static(因为其为类的状态)
、 transient
关键字的字段,不对其进行序列化,Object对象流不写入,也不读出。为什么ArrayList要通过重写writeObject 和 readObject 方法来实现序列化呢?
为什么数组elementData是transient的
ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient
为什么要重写writeObject 和 readObject 方法
前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用transient来声明elementData。
但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObject 和 readObject方法的方式把其中的元素保留下来。
writeObject方法把elementData数组中的元素遍历的保存到输出流(ObjectOutputStream)中。
readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中。
序列化引擎:Dubbo 框架中的 Hession、JDK 自带的 Serializable、跨语言的 Hessian、ProtoBuf、ProtoStuff