对象流(处理流的一种)
-
ObjectInputStream 和 ObjectOutputStream
-
用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据中,也能把对象从数据中还原回来。
-
序列化:用ObjectOutputStream类保存基本数据或对象的机制
-
反序列化:用ObjectInputStream类读取基本数据类型或对象的机制
-
ObjectInputStream 和 ObjectOutputStream 不能序列化 static 和 transinet修饰的成员变量。
对象的序列化
- 对象的序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象(面试题 重点)。
- 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
- 序列化是RMI(Remole Method Invoke - 远程方法调用)过程的参数和返回值都必须实现的机制,而RMI 是JavaEE的基础。因此序列化机制是JavaEE平台的基础。
- 如果需要让某个对象支持序列化机制,则必须让对象所属的类其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则会抛出NotSerializableException异常。
- Serializable
- Externallizable
对象流的使用
-
ObjectInputStream 和 ObjectOutputStream
-
作用:用于存储和读取数据类型或对象的处理流,它的强大之处就是可以用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据中,也能把对象从数据中还原回来。
-
要想一个Java对象是可序列化的,需要满足相应的要求:
- 需要实现接口:Serializable
- 当前类提供一个全局变量:serialVersionUID
- 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
-
ObjectInputStream 和 ObjectOutputStream 不能序列化 static 和 transinet修饰的成员变量。
//自定义类实现序列化
public class Person implements Serializable {
public static final long serialVersionUID = 421521531521L;
序列化
/*
对象流的使用
*/
@Test
public void test1(){
/*
序列化过程:将内存中的Java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
*/
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("我爱北京天安门"));
oos.flush();//刷新操作
oos.writeObject(new Person("Tom",22));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos!=null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
反序列化
@Test
public void test2(){
/*
反序列化:将磁盘文件中的对象还原为内存中的一个Java对象。
使用ObjectInputStream
*/
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("object.dat"));
Object o = ois.readObject();
String str = (String) o;
Object o1 = ois.readObject();
Person str1 = (Person) o1;
System.out.println(str);//我爱北京天安门
System.out.println(str1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
随机存取文件流
RandomAccessFile类
-
RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且实现了DataInput , DataOutput这两个接口,也意味着这个类既可以读也可以写。
-
RandomAccessFile类支持“随机访问”的方试,程序可以直接跳到文件的任意地方来读,写文件
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
-
RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置RandomAccessFile类对象可以自由移动记录指针:
- long getFilePointer():获取文件记录指针当前位置
- void seek(long pos):将文件记录指针定位到pos位置
-
构造器
- public RandomAccessFile(File file,string mode)
- public RandomAccessFile(String name,String mode)
-
创建RandomAccessFile类实例需要指定一个mode参数,该参数指定RandomAccessFile的访问模式:
- r:以只读方式打开
- rw:打开以便读取和写入
- rwd:打开以便读取和写入:同步文件内容的更新
- rws:打开以便读取和写入:同步文件内容和元数据的更新
-
如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常,如果模式为rw读写。如果文件不存在则会区创建文件,如果存在则不会创建。
@Test
public void test1(){
RandomAccessFile raf1 = null;//读
RandomAccessFile raf2 = null;//读写,文件不存在会创建文件
try {
//1.
raf1 = new RandomAccessFile(new File("hello01.txt"),"r");
raf2 = new RandomAccessFile(new File("hello02.txt"),"rw");
//2.
byte[] by =new byte[1024];
int len;
while ((len = raf1.read(by))!=-1){
raf2.write(by,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (raf1!=null){
try {
//3.
raf1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (raf2!=null){
try {
raf2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test2(){
RandomAccessFile rw = null;
try {
rw = new RandomAccessFile("hello01.txt", "rw");
rw.seek(new File("hello01.txt").length());//指定写入的指针位置(插入的效果),获取文件的长度,在文件的最后进行添加元素
rw.write("a".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (rw != null) {
try {
rw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test3(){
/*
使用RandomAccessFile实现数据的插入效果插入前"abcdefg"插入后"abcxyzdefg"
*/
RandomAccessFile rw = null;
try {
rw = new RandomAccessFile("hello02.txt", "rw");
//指定指针位置为3(从3处将后面的内容取出来)
rw.seek(3);
StringBuilder sb =new StringBuilder((int) new File("hello02.txt").length());
byte[] by = new byte[20];
int len;
while ((len = rw.read(by))!=-1){
//将3后面的数据都存放到sb中了
String str = new String(by, 0, len);
sb.append(str);
}
//调回指针为3,进行覆盖
rw.seek(3);
//将”xyz“作为byte数组写入
rw.write("xyz".getBytes());
//在将sb里的数据写入就行
rw.write(new String(sb).getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (rw!=null){
//关闭流
try {
rw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Java NIO 概述(了解)
- Java NIO (New IO , Non - Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO 有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的),基于通道的IO 操作。NIO将以更加高效的方式进行文件的读写操作。
- Java API中提供了两套NIO,一套是针对标准输入流输出NIO,另一套就是网络编程NIO。
- java.nio.channels.Channel(通道)
- FileChannel:处理本地文件
- SocketChannel:TCP网络编程的客户端的Channel
- ServerSocketChannel:TCP网络编程的服务端的Channel
- DatagramChannel:UDP网络编程中发送端和接收端的Channel
- java.nio.channels.Channel(通道)
多线程
线程简介
Process(进程)与Thread(线程)
- 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
- 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
注意:很多线程是模拟出来,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
核心概念
- 线程就是独立的执行;路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
普通方法调用和多线程
线程创建(重点)
三种创建方式
- Thread 类 ----->继承Thread类(重点)
- Runnable 接口------>实现Runnable接口 (重点中的重点)
- Callable接口 -------> 实现Callable接口 (了解)
Thread
- 自定义线程类Thraed类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
- 不建议使用:避免OOP单继承局限性
//创建线程方式一:继承Thread类,重写run()方法,调用start()开启线程
//总结:注意,线程开启不一定;立即执行,由cpu调度执行
public class Demo01 extends Thread{
@Override
public void run() {
//run()方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----->"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程----->"+i);
}
//创建一个线程对象
Demo01 demo01 = new Demo01();
//调用start()方法
demo01.start();
}
}
练习:
//练习Thread,实现多线程下载图片
public class Demo02 extends Thread{
private String url;//网络图片地址
private String name;//保存的文件名
public Demo02(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
Demo02 d1 = new Demo02("https://img-blog.csdnimg.cn/20210718200416738.jpg","1.jpg");
Demo02 d2 = new Demo02("https://img-blog.csdnimg.cn/20210718200437720.jpg","2.jpg");
Demo02 d3 = new Demo02("https://img-blog.csdnimg.cn/20210718200446725.jpg","3.jpg");
d1.start();
d2.start();
d3.start();
//并不是按照先后顺序的,而是同时执行。
}
}
//下载器
class WebDownLoader{
//下载方法
public void downLoader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Runnable
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
推荐使用Runnable接口,因为Java单继承局限性
//创建线程方式二:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类,
//调用start方法
public class Demo03 implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类对象
Demo03 demo03 = new Demo03();
//创建线程对象,通过线程对象来开启我们的线程,代理
// Thread thread = new Thread(demo03);
// thread.start();
new Thread(demo03).start();
for (int i = 0; i < 50; i++) {
System.out.println("我在学习线程"+i);
}
}}