IO流基础概念
把数据从数据源输送到数据目的地
1.数据可能存在的位置
1.1.键盘输入(从控制台输入)
1.2.文件输入
1.3.Java代码中
2.主要负责
2.1.输入与输出
2.2.当前代码
3.输入输出方式
3.1.字节---->字节流
3.2.字符----->字符流
4.细分
都是位于java.io包中
4.1.字节输入流:intputStream
4.2.字节输出流:outputStream
4.3.字符输入流:XxxxxxReader
4.4.字符输出流:XxxxxWriter
注:intputStream和outputStream是抽象类
IO流基础操作
1.输入流:
InputStream/Reader
1.1: read();会阻塞代码.从输入流读取一个字节(1字节是8位),把它转换为0-255之间的整数,并返回这一整数。如果遇到输入流的结尾,则返回-1;
1.2: read(byte[] but);会阻塞代码.从流中读取字节数据,把读取到的字节数据放到数组中,一直放满数组。返回的整数表示读取的字节数。如果遇到输入流的结尾,则返回-1;
1.3: read(byte[] b, int off, int len)会阻塞代码.从输入流读取若干个字节,把它们保存到参数b指定的字节数组中。返回的整数表示读取的字节数。参数off指定在字节数组中开始保存数据的起始下标,参数len指定读取的字节数目。返回的整数表示实现读取的字节数。如果遇到输入流的结尾,则返回-1;
注:第二个和第三个效率大于第一个。因为:减少进行物理读文件或键盘的次数,因此能提高I/O操作的效率。
1.4: available(): 返回可以从输入流中读取的字节数目;
1.5: skip(long): 从输入流中跳过参数n指定数目的字节/字符。
1.6: close(): 关闭输入流,不关闭流,资源被占用,其他进程不能使用
1.7: 字节流 读取到流尾 返回 -1;
1.8: 字符流 读取到流尾 返回 null;
以下是 不常用方法. 使用场景:从流中重复读入数据
1.9. boolean markSupported(): 返回true则表示这个流是否支持重复读入数据。
1.10.void mark(int):从流的当前位置开始设置标记,mark方法有个参数,通过这个整型参数,你告诉系统,希望在读出这么多个字符之前,这个mark保持有效。
1.11. void reset():该方法使输入流重新定位到刚才做了标记的起始位置。
mark祥解:
pos:读取的下一个位置。
markLimit: mark方法后reset方法前 最多允许读取的字节数,这个最大字节数,其实是由markLimit和buffer.size中较大的那个决定的。
当你调用mark方法时,内部会保存一个markPos标志,它的值为目前读取字节流的pos位置,倘若你调用reset方法,这时候会把pos重置为markPos的值,这样你就可以重读已经读过的字节.
eg:有一段字节流是helloworld, 当你读取完字母h调用mark方法(此时markPos指向字母e),接着你继续读取e,l,l,o 然后此时你调用reset方法(内部把pos重置为markPos),当你再读取下一个字节的时候,你会发现你读取到的是e而不是字母w,这样通过mark方法我们就是实现了重复读(re-read the same bytes)
2:输出流
OutputStream/writer
2.1: write(int b): 向输出流写入一个字节;
2.2: write(byte[] b): 把字节数组中的所有字节数据写到输出流;
2.3: write(byte[] b, int off, int len): 把字节数组中的字节从off位置开始的数据写len个长度到输出流.
2.4: flush(): flush()方法强制把缓冲区内的数据写到输出流中。
注:OutputStream类本身的flush()方法不执行任何操作,
但是,一些带有缓冲区的流,在write的时候,是先把数据保存到缓冲区,并不是把数据写到流中,所以需要flash把缓冲区中的数据刷到流中。
2.2.4: close(): 关闭输出流。
IO流 各种流的操作:
前提:io操作涉及到众多的流,接下来也需要学习众多的流,只要是IO操作,用任何一种流都可以完成功能,咱学这么多流的意义在于,可以选更好的流完成。
每个流都是用到上面的基础操作,每个流也有自己的特点。咱要学的就是每个流的特点。
选用原则:
1,效率, 缓冲区字节流 > 字节流 > 缓冲区字符流 > 字符流
2,是否需要操作:基本只能是字符流(只有字符流(每次读取的是字符) 我们才能看懂,才能操作)
3.1: 字节流:
InputStream/OutputStream:
一般是一次读取一个字节
Unicode:16位表示一个字符->两个字节表示一个字符。
utf-8:属于Unicode一个子类: 是根据字符对应的数字大小来确定.
注:在控制台输入的时候 需要注意控制台编码,右键->run as->common->Encoding
String msg = new String(byte[]);
String msg = new String(byte[],start,len);
3.2: 字节流:
ByteArrayInputStream/ByteArrayOutputStream:
从字节数组中读取数据/把数据写到字节数组中。
专门用来读取字节数组。
3.3: 字节流:
FileInputStream/FileOutputStream:
从文件中读取数据/把数据输出到文件中。
3.3.1: File 类:
3.3.1.1构造器1:new File(“src/aa”);创建src/aa新File 实例。
3.3.1.2构造器2:new File(file2,“a.aaa”);在file2目录下a.aaa新File 实例。file2不是目录会报错
3.3.1.3构造器3:new File(“src/ab”,“b”);创建 src/ab目录下的 b新File 实例。src/ab 目录不存在 会报错
3.3.1.4:方法1: boolean canExecute(); 是否可以执行此抽象路径名表示的文件。
3.3.1.5:方法2: boolean createNewFile() 创建一个新的空文件。
3.3.1.6:方法3: boolean delete() 删除此抽象路径名表示的文件或目录。
3.3.1.7:方法4: boolean exists() 此抽象路径名表示的 文件 或 目录 是否存在。
3.3.1.8:方法5: String getPath() 将此抽象路径名转换为一个路径名字符串。
3.3.1.9:方法6: boolean isDirectory() 文件是否是一个目录。
3.3.1.10:方法7: long length() 文件的长度。
3.3.1.11:方法8: boolean mkdir() 创建目录。
3.3.1.12:方法9: boolean mkdirs() 创建目录和其父类目录。
3.3.2: FileInputStream:读取文件中的数据放到这个流中。
3.3.2.1构造器1:new FileInputStream(“文件路径”);//读取字符串路径所表示文件的数据
3.3.2.2构造器2:new FileInputStream(new File(“文件路径”));//读取文件的数据
3.3.3: FileOutputStream: 把输出流的数据输出到文件中。
3.3.3.1构造器1:new FileOutputStream(new File"文件路径");//把流中的数据输出到文件
3.3.3.2构造器2:new FileOutputStream(new File(“文件路径”),是否追加);//读取文件的数据,true–>本次输出的数据在文件末尾追加 ;false–>不追加,覆盖。
3.4: 字节流:
PipedInputStream/PipedOutputStream
管道输入/输出流。一般结合线程来用。
问题一:管道输入/输出流,这个输入/输出是相对于谁来说。
答 :相对于当前代码来说。
问题二:管道输入流/输出流 怎么连接起来。
答 :创建对象的时候用构造器传值就可以了。
3.5: 字节包装流 :
BufferedInputStream/BufferedOutputStream
有缓冲区的包装流
特点:有缓冲区的包装流 效率 会高一些,可以使用mark和reset功能(标记 回读 功能)
提高效率的原因:BufferedInputStream对外提供滑动读取的功能实现,通过预先读入一整段原始输入流数据至缓冲区中,而外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,如果读取的数据超过了缓冲区的范围,那么BufferedInputStream负责重新从原始输入流中载入下一截数据填充缓冲区,然后外界继续通过缓冲区进行数据读取。这样的设计的好处是:避免了大量的磁盘IO,因为原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,那么下一次读取一般情况下就直接可以从缓冲区读取,减少了磁盘IO。
3.5.1:构造器 new BufferedInputStream(字节流,缓冲区大小);//默认缓冲大小是8192字节
3.5.2:构造器 new BufferedOutputStream(字节流,缓冲区大小)//默认缓冲大小是512字节
3.6: 字节包装流 :
DataInputStream/DataOutputStream
读取/写出 某个类型的数据
特点:可以一次写一个int/long/double 基本数据类型+String的数据流中,可以从流中一次读取一个基本数据类型的数据。
注意:是通过基本数据类型数据的 字节数 进行读取 ,如果a.txt中有一个字符6, 不能用readInt()去读取,因为这个6代表的字节数不是int所代表的字节数。
推荐:使用DataInputStream读取数据 最好是读取 用DataOutputStream 写出去的数据。不然读取不到想要的效果。
任务:利用DataOutputStream+ByteArrayOutputStream获得1000L所对应的字节数组。
3.7: 字符流 :
CharArrayReader/CharArrayWriter:
有缓冲区的字符输入/输出流.可使用 toCharArray() 和 toString() 获取数据。
3.8: 字符流 :
FileReader/FileWriter
每次读取的是字符,可以查看,可以操作。
3.9: 字符流 :
BufferedReader/PrintWriter :
读取一行/写出一行
3.10: 转换流 :
字节输入流转换为字符输流:InputStreamReader字节输入流转换为字符输入流:OutputStreamWriter
特 点 : 将字节流转换为字符流的时候可以 设置编码。
3.11 : 随机访问流 :
RandomAccessFile
特点:将文件中的数据全部读取到流中,对流可以一直反复操作。
构造器:new RandomAccessFile(new File(),“r”)—>只读方式(“r”)
构造器:new RandomAccessFile(new File(),“rw”)—>读写方式(“rw”)
注意:利用readLine()读取得到的字符串是乱码。这是因为从文件中读取是以ISO-8859-1编码读取,如果是从文件中读取字符串需要转码操作。
eg: String msg = in.readLine();//ä½ å¥½
String dat = new String(msg.getBytes(“ISO-8859-1”));//你好
将msg以ISO-8859-1编码转为字节数组,然后通过该字节数组得到String(当前文件编码)。
utf-8占用字节:
一个utf8数字占1个字节
一个utf8英文字母占1个字节
占2个字节的:〇
占3个字节的:基本等同于GBK,含21000多个汉字
占4个字节的:中日韩超大字符集里面的汉字,有5万多个
主要操作
3.11.1:seek() 定位,设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
3.11.2:getFilePointer() 获得文件的当前偏移量
3.11.3:skipBytes() 跳过多少字节数
3.11.4:length() 文件大小
详解:seek(index); 设置偏移量。
eg: a.txt文件中 abc你 占6字节 seek(3),表示将偏移量设置到 abc|你 下次读取就是读取 你 的第一个字节
getFilePointer();获得文件中的当前偏移量
eg: a.txt文件中 abc你 占6字节 读取了3个字节,代表当前偏移量为3 返回为3。
skipBytes(n);跳过n字节
eg: a.txt文件中 abc你 占6字节, skipBytes(1)跳过一个字节,偏移量在当前位置+1。
IO流 对象序列化和反序列化
4.1:序列化:
利用ObjectOutputStream 将对象输出流 将对象写到文件中。
在利用ObjectOutputStream写对象到文件的过程中,
eg1:ObjectOutputStream oos = new ObjectOutputStream(文件,追加);
oos.writeObject(对象1);
oos.writeObject(对象2);
oos.writeObject(对象3);
得到文件:
标记对象1对象2对象3
eg2:
上面代码执行两次
得到文件:
标记对象1对象2对象3标记对象1对象2对象3
4.1:反序列化:
利用ObjectInputStream将对象从文件中读取到流中。
在利用ObjectInputStream将文件中的对象读取过来的时候。
eg11:读取上eg1 所产生的文件
ObjectInputStream ois = new ObjectInputStream(文件);
ois.readObejct();
ois.readObject();
ois.readObject();
得到3个对象
eg22:读取上eg2所产生的文件
ObjectInputStream ois = new ObjectInputStream(文件);
ois.readObejct();
ois.readObject();
ois.readObject();
ois.readObject();
ois.readObject();
ois.readObject();
得到:—> 报错–>因为连续读取了6个对象,但是eg2文件中是 标记3对象标记3对象 在读取到第四个对象的时候会报错,readObject 读取到的是标记。
改:
ObjectInputStream ois = new ObjectInputStream(文件);
ois.readObejct();
ois.readObject();
ois.readObject();
ois = new ObjectInputStream(文件);//因为在调用构造器的时候回读取 标记
ois2.readObejct();
ois2.readObject();
ois2.readObject();
得到 6对象
注意:对象的序列化和反序列化操作的对象一定要实现Serializable接口。(这是空接口,只是一个标识);
0,进程
进程 : 每一个进程都有自己独立的内存空间,这个内存空间有操作系统分配。
360 : 进程
线程1 :安全监测
线程2 :垃圾清理
线程3 :病毒查杀
1,线程:
一个进程中的一条执行流程
线程不可以脱离进程独立执行(计算机最小处理的就是进程)
可以理解线程是某一个进程下多条不同的流程互不影响的执行代码流程
线程特点:
在对应的进程内存空间中运行,线程可以共享同一块内存和系统资源
在java虚拟机进程中,执行程序代码的任务是由 线程 来完成的。
每当用java命令启动一个Java虚拟机进程时,Java虚拟机都会创建一个主线程。
该线程从程序入口main()方法开始执行。也就是main线程
计算机中机器指令的真正执行者是CPU,线程必须获得CPU的使用权,才能执行一条指令。
代码是执行某一个时间片,执行完成以后会停下来等
2,线程的分类:
2.1:前台线程(执行线程)
看得到执行代码的线程,线程执行main–>main线程,只要有前台现在在执行,jvm就不会停。
2.2:后台线程(守护线程/精灵线程)
gc:
当 前台线程 结束 守护线程也会结束
注:每次写的main方法中代码是由一个主线程执行的
3,线程的使用:
3.1:使用线程去执行在非常耗时的代码
3.2:使用线程让多个代码同时执行
1 100
for(int i = 0;i<100;i++){
}
1 100
for(int i = 0;i<100;i++){
}
CPU执行代码,一个cpu 单核 单线程,同一时刻只能执行一段代码
cpu --> 指令 --> 调度 --> 硬件资源–> 指令 --> 调度
硬盘
1S 2分钟
4,线程的生命周期
4.1:新建:
新建了线程对象
4.1.1:一个类继承Thread类 重写run方法->得到了新的线程类
Thread t = new MyThread();
4.1.2:一个类实现接口Runnable重写run方法–>得到新的线程类
MyThread myt = new MyTeacher();
Thread t = new Thread(myt);
4.2:就绪:
调用完start方法以后
t.start();
4.3:运行:
线程得到cpu时间片以后执行代码
4.4:阻塞状态:
代码暂停运行
4.4.1:挂起:sleep(毫秒);join();yield();
恢复为就绪状态:
sleep():睡觉时间完了
join();目标线程执行完毕
yield();让出cup执行权限立刻回到就绪状态
注: 不会释放锁资源
4.4.2:锁池等待 : 在run方法中执行了加锁的代码,但是锁被其他线程获取走了,当前线程就会到锁池等待.
恢复为就绪状态:
当其他线程释放锁资源,立刻回到就绪状态
4.4.3:等待池等待 : 当执行wait()方法以后就会到等待池等待, 恢复为锁池等待,当其他线程执行了notify/notifyall方法的时候回从等待池回到锁池等待.
注: wait()方法会释放锁资源
4.5:死亡状态:
当线程执行完run方法中所有代码就会变成死亡状态.
run方法执行完毕
龟兔赛跑:
100米
t|----------------
r|----------------
线程
每个线程操作自己的数据
isWin 是公共数据 static
run(){
while(nun<100){
if(isWin){
break;
}
…
}
isWin = true;
syso(谁赢了)
}
火车卖票
k123 : 100票
窗口1 |----------
窗口2 |--------------
窗口3 |-------------
窗口4 |----
if(num>0){
}else{
break;
}
5. 线程调度
计算机通常只有一个CPU, 在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。
所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。
在可运行池中,会有多个处于就绪状态的线程在等待CPU,Java虚拟机的一项任务就是负责线程的调度。
线程的调度是指按照特定的机制为多个线程分配CPU的使用权。有两种调度模型:
. 分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
. 抢占式调度模型:优先让可运行池中优先级高的线程较多可能占用CPU(概率高),如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于可运行状态的线程会一直运行,直至它不得不放弃CPU。Java虚拟机采用这种。
一个线程会因为以下原因而放弃CPU:
. Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;
. 当前线程因为某些原因而进入阻塞状态;
. 线程运行结束;
线程的调度不是跨平台的,它不仅取决于Java虚拟机,还依赖于操作系统。
在某些操作系统中,只要运行中的线程没有阻塞,就不会放弃CPU;
在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行机会。
5,线程常用方法使用
5.0: t1.start();
线程开始执行
线程只能启动一次
5.1: Thread.sleep(1000);
//当前线程睡1000毫秒
(放弃cup执行权限)
5.2: Thread.currentThread();
//获取当前正在执行的线程
5.3: t1.getName();
//获取线程名
5.4: t1.setName(“线程名”);
//设置线程名
5.5: t1.join();
//当前线程(t1.join()这个代码在哪个线程里)等待t1线程执行完成以后再执行(当前线程,放弃cup执行权限)
5.6: this.yield();
//当前线程让出cpu执行权限回到就绪状态
5.7: this.notify();
//随机唤醒一个线程–>等待池
5.8: this.notifyAll();
//唤醒所有线程
5.9: this.isAlive();
//线程是否是活的
5.10: interrupt();
//设置线程中断状态标识位–>中断状态:true
5.11: isInterrupted();
//查看返回线程的中断状态标识位 true:有线程要中断当前线程
//不会清除线程的中断状态
5.12: interrupted();
//返回线程的中断状态标识位 true:有线程要中断当前线程
//清除线程的中断状态
5.13: setDaemon(true);
//把新建状态的线程设置为守护线程。只能是新建状态。
6,线程同步 :
synchronized
线程不安全:多个线程操作同一份数据就会导致线程不安全问题,线程同步就是解决这个问题的
通过加锁就可以达到线程同步
6.1:synchronized关键字可以出现的地方
6.2:synchronized(锁){//线程同步的代码}
6.3:public synchronized void say(){}–>this
6.4:public static synchronized void say(){}–>Hello.class
注意问题: 多个线程对象使用的锁是不是同一把锁
是,多个线程 对于 锁 上的代码按顺序运行
否, 多个线程 对于 锁 上的代码任意运行
补充:
synchronized实现可见性
可以实现互斥锁(原子性),即同步。但很多人都忽略其内存可见性这一特性
JMM关于synchronized的两条规定:
线程解锁前,必须把共享变量的最新值刷新到主内存中
线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
线程解锁前对共享变量的修改在下棋甲所示对其他线程可见
7,线程通信
使用 锁对象.wait(); 会释放锁资源 放弃cup执行权限
锁对象.notify();
锁对象.notifyAll();
8,线程的死锁
A线程需要的锁被B线程获取
B线程需要的锁被A线程获取
9,线程的让步
yield()方法
Thread.yield()静态方法,
如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。
yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
yield()转到就绪状态;
yield()不抛任何异常
yield()并不常用。
10. 调整线程优先级
注意:
优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果
线程优先级的使用原则与操作系统有着密切的联系因此在JAVA中的线程的调度是完全受其所运行平台的操作系统的线程调度程序控制的。所有虽然我们可以设置线程的优先级但是在运行的时候不一定能够确切的体现出来。
所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较多的运行机会。
Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。
优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。
. MAX_PRIORITY: 10, 最高;
. MIN_PRIORITY: 1, 最低;
. NORM_PRIORITY: 5, 默认优先级;
释放对象的锁:
. 执行完同步代码块;
. 执行同步代码块过程中,遇到异常而导致线程终止,释放锁;
. 执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;
线程不释放锁:
. Thread.sleep()方法,放弃CPU,进入阻塞状态;
. Thread.yield()方法,放弃CPU,进入就绪状态;