### 第三章:
### java高级API
### 1️⃣ 什么是集合
数组和集合的区别:
数组:是一个简单线性序列;可以快速访问数组种元素;效率高的;
定义数组:长度固定;类型固定;可以存储基本数据类型和引用数据类型;
一旦固定长度;不能随意修改;
数组的劣势:不灵活;容量必须提前定义;不能随着需求的变化而扩容;
集合:
集合就是一个放数据的容器,准确的说是放数据对象引用的容器
集合类存放的都是对象的引用,而不是对象的本身
集合的特点:
大小的可变的;
集合只能存储引用数据类型;
集合的特点
集合的特点主要有如下两点:
集合用于存储对象的容器,对象是用来封装数据,对象多了也需要存储集中式管理。
和数组对比对象的大小不确定。因为集合是可变长度的。数组需要提前定义大小
### 面试题:
```
集合和数组的区别
数组是固定长度的;集合可变长度的。
数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
```
### 集合 分为 2个顶级接口: 分别为Collection 和 Map
Collection英文含义(收集 、集中;)单列集合;每个元素只包含一个值;这个对象也称之为单列元素
Collection 接口 定义的是对集合里的元素一些常用方法:
```
public int size() 表示集合内存在多少元素;
public boolean isEmpty() 判断集合是否为空;
public boolean contains(Object o) 判断集合是否存在o 元素;
public Iterator iterator() 迭代器:用于迭代遍历;
public Object[] toArray() 把集合封装成数组;
public boolean add(Object o) 添加元素到集合内;
public boolean remove(Object o) 删除集合内的元素;
public void clear() 清空所有元素
```
并且Colletion 有2个更加具体的子接口:
LIst: 最重要的特点就是:有序,可重复 ;有索引
先入先出的效果;可重复 o1.euqles 02 == true; 允许存入;
```
public Object get(int index) 根据index 索引 去找值;
public Object set(int index, Object element) 根据传入参数索引;修改元素
public void add(int index, Object element) 根据传参索引;添加元素;
public int indexOf(Object o); 根据元素去找索引;如果没有当前元素返回一个 -1;
```
List接口 3个常用实现类:
ArrayList::ArrayList 是动态数组的数据结构实现;查询效率高;增删效率低;线程不安全;
LinkedList:底层数据双链表的 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
vector:底层数据结构也是数组;查询效率高;线程安全的;
![image-20220425164443289](C:\Users\ming\Desktop\image-20220425164443289.png)
### 面试题
```
ArrayList 和 LinkedList 的区别是什么?
数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
```
### 面试题2:
```
ArrayList 和 Vector 的区别是什么?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合
线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
性能:ArrayList 在性能方面要优于 Vector。
扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。
Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。
```
### 面试题3
```
插入数据时,ArrayList、LinkedList、Vector谁速度较快?
阐述 ArrayList、Vector、LinkedList 的存储性能和特性?
ArrayList和Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差。
LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快。
```
Set: 集合特点是无序的;值不可重复;无索引;
Set接口的实现类:
HashSet:无序;不可重复;无索引
```
HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为present,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
HashSet如何检查重复?HashSet是如何保证数据不可重复的?
向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。
HashSet 中的add ()方法会使用HashMap 的put()方法。
HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。
```
linkedHashSet:有序的;不重复;无索引
TreeSet:按照自然排序做一个升序排列;不重复;无索引;
Set 集合只可以使用迭代器或者增强型For循环遍历;
### Map接口:
Map代表所有双列集合的顶级接口:
他的结构为Key value 键值对;
这种关系叫做一一对应;映射关系;
元素都是成对出现的;
Map 几个常见的实现类:
HashMap;
linkedHashMap;
TreeMap
HashTable;
CurrentHashMap
# HashMap的数据结构
JDK1.7版本,采用数组+链表
JDK1.8版本,采用数组+红黑树
### 面试题:
```
HashMap的工作原理
HashMap底层是数组+链表实现,数组中的每个元素都是链表,由Node内部类(实现Map.Entry<K,V>接口)实现,HashMap通过put&get方法存储和获取。
```
> **存储对象时,将K/V键值传给put()方法:**
>
> ①、调用hash(K)方法计算K的hash值,然后结合数组长度,计算得数组下标;
>
> ②、调整数组大小(当容器中的元素个数大于capacity*loadfactor时,容器会进行扩容resize为原来的两倍);
>
> ③
>
> - i. 如果K的hash值在HashMap中不存在,则执行插入,若存在,则发生碰撞;
> - ii. 如果K的hash值在HashMap中存在,且它们两者equals返回true,则更新键值对;
> - iii.如果K的hash值在HashMap中存在,且它们两者equals返回false,则插入链表的尾部(尾插法)或者红黑树中(树的添加方式)。
>
> > (JDK1.7之前使用头插法、JDK1.8使用尾插法)
> > (注意:当碰撞导致链表大于TREEIFY_THRESHOLD=8时,且容量>64,就把链表转换成红黑树)
>
> **获取对象时,将K传给get()方法:**
>
> - ①、调用hash(K)方法(计算K的hash值)从而获取该键值所在链表的数组下标;
> - ②、顺序遍历链表,equals()方法查找相同Node链表中K值对应的V值。
>
> hashCode是定位的,存储位置;equals是定性的,比较两者是否相等。
>
>
### 面试题
```
HashMap中put方法的过程?
- 调用哈希函数获取Key对应的hash值,再计算其数组下标;
- 如果没有出现哈希冲突,则直接放入数组;如果出现哈希冲突,则以链表的方式放在链表后面;
- 如果链表长度超过阀值(TREEIFYTHRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;
- 如果结点的key已经存在,则替换其value即可;
- 如果集合中的键值对大于12,调用resize方法进行数组扩容。
```
### 面试题
```
HashMap红黑树原理分析
相比 jdk1.7 的 HashMap 而言,jdk1.8最重要的就是引入了红黑树的设计,红黑树除了插入操作慢其他操作都比链表快,当hash表的单一链表长度超过 8 个的时候,数组长度大于64,链表结构就会转为红黑树结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。
为什么要这样设计呢?好处就是避免在最极端的情况下链表变得很长很长,在查询的时候,效率会非常慢。
红黑树查询:其访问性能近似于折半查找,时间复杂度 O(logn);
链表查询:这种情况下,需要遍历全部元素才行,时间复杂度 O(n);
简单的说,红黑树是一种近似平衡的二叉查找树,其主要的优点就是“平衡“,即左右子树高度几乎一致,以此来防止树退化为链表,通过这种方式来保障查找的时间复杂度为 log(n)。
上面是比较重要的;
下面记住1~3即可;
关于红黑树的内容,网上给出的内容非常多,主要有以下几个特性:
1、每个节点要么是红色,要么是黑色,但根节点永远是黑色的;
2、每个红色节点的两个子节点一定都是黑色;
3、红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色);
4、从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;
5、所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);
在树的结构发生改变时(插入或者删除操作),往往会破坏上述条件 3 或条件 4,需要通过调整使得查找树重新满足红黑树的条件。
```
## 包装类:
```
8种包装类:
Byte Short Inttger Long
Folat Double
Character Boolean
都是一种引用数据类型;
```
```
注意事项
Boolean类构造方法参数为String类型时,若该字符串内容为true(不考虑大小写),则该Boolean对象表示true,否则表示false
当Number包装类构造方法参数为String 类型时,字符串不能为null,且该字符串必须可解析为相应的基本数据类型的数据,否则编译不通过,运行时会抛出NumberFormatException异常
```
```
**
* 对于包装类:主要用途:2种;
* 常用操作之一:就是基本数据类型于字符串之间转换;
* 包装类相关方法 查找最大值;最小值;及其相关操作;
*
*/
public static void main(String[] args) {
//intger chatater ;首字母大写;
//2种;
//
Integer integer = new Integer(10);
System.out.println(integer);//官方是不太推荐使用的;
Integer integer1 = Integer.valueOf(20);
System.out.println(integer1);
//Double Byte;
//Intger int;
//Double ;charater;Boolean;
//true false 可以放字符串;如果字符串为TURE 不分大小写;
//结果为true
Boolean aTrue = Boolean.valueOf("True");
System.out.println(aTrue);//
Boolean bTrue = Boolean.valueOf("love");
System.out.println(bTrue);
//最常用的;
// * 当包装类构造方法参数为String 类型的时候;
// * 字符串不能为null;且 该字符串必须可以解析;相对应的基本数据类型;
Integer integer2 = Integer.valueOf("19");
System.out.println(integer2);//NumberFormatException
Double aDouble = Double.valueOf("19.99");
System.out.println(aDouble);
//int 和 String 类型的相互抓换;
/**
* 注意事项
* Boolean类构造方法参数为String类型时,若该字符串内容为true(不考虑大小写),则该Boolean对象表示true,否则表示false
* 当Number包装类构造方法参数为String 类型时,字符串不能为null,且该字符串必须可解析为相应的基本数据类型的数据,否则编译不通过,运行时会抛出NumberFormatException异常
*/
}
```
```
/**
* todo int 和String类型的相互转换
* int 转化为String
* 转换方式:直接在数字后面+ 字符串(字符串拼接;数值类型+ 字符串类型 = 字符串)
* 通过String 类的静态方法 valueOF(数值)
*/
public static void main(String[] args) {
// int number = 10;
// String a =number+""; //"10"
// System.out.println(a+10);//字符串拼接数字;
// String s = String.valueOf(number);
// System.out.println(s);
/**
* String 类型转换为int 类型:
* 先将字符串转换为intnger类型;在调用valueOf 方法;
* 直接使用intger .parseInt()进行转换’
*这个字符串 是数值码?
* 不是数值的字符串;会报异常;数字格式转换异常;
*/
String s = "abc";
Integer integer = Integer.valueOf(s);
System.out.println(integer+10);//如果可以进行数学运算;代表转换成功;
int i = Integer.parseInt(s);
System.out.println(i);
}
```
```
Integer i = 100;//装箱
int i1 =i;//拆箱;
// i+= 200;
// // i = i + 200; i+200 自动拆箱;
//
```
# JavaApi
System 类概述
System也是一个工具类,代表了当前系统,提供了一些与系统相关的方法。
| 方法名 | 说明 |
| ------------------------------------------------------------ | -------------------------------------------- |
| public static void exit(int status) | 终止当前运行的 Java 虚拟机,非零表示异常终止 |
| public static long currentTimeMillis() | 返回当前系统的时间毫秒值形式 |
| public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数) | 数组拷贝 |
| | |
```
//输⼊输出包含三个成员变量,分别是in,out,err
System.out //常⽤调试
System.in //⽤于数据读取,少⽤
System.err //⽤在错误输出,少⽤
System.currentTimeMillis()
```
tring 类、StringBuilder 类、StringBuffer 类是三个字符串相关类。
* String 类是的对 象代表不可变的字符序列,
* StringBuilder 类和 StringBuffer 类代表可变字符序列。关于这 三个类详细的用法,在笔试面试以及实际开发中经常用到,我们必须掌握好它们。
String 类是我们最常使用的类。字符串类的方法我们必须非常熟悉!我们列出常用的方 法,请大家熟悉。
```
char charAt(int index) 返回字符串中第 index 个字符
boolean equals(String other) 如果字符串与 other 相等,返回 true;否则,返回 false。
boolean equalsIgnoreCase(String other)
如果字符串与 other 相等(忽略大小写),则返回 true;否则, 返回 false。
int indexOf(String str) 返回从头开始查找第一个子字符串 str 在字符串中的索引位 置。如果未找到子字符串 str,则返回-1。
lastIndexOf() 返回从末尾开始查找第一个子字符串 str 在字符串中的索引位 置。如果未找到子字符串 str,则返回-1。 int length() 返回字符串的长度。
String replace(char oldChar,char newChar) 返回一个新串,它是通过用 newChar 替换此字符串中出现的 所有 oldChar 而生成的。
boolean startsWith(String prefix) 如果字符串以 prefix 开始,则返回 true。
boolean endsWith(String prefix) 如果字符串以 prefix 结尾,则返回 true。
String substring(int beginIndex) 返回一个新字符串,该串包含从原始字符串beginIndex到串尾。
String substring(int beginIndex,int endIndex) 返回一个新字符串,该串包含从原始字符串 beginIndex 到串尾 或 endIndex-1 的所有字符。
String toLowerCase() 返回一个新字符串,该串将原始字符串中的所有大写字母改成 小写字母。
String toUpperCase() 返回一个新字符串,
该串将原始字符串中的所有小写字母改成 大写字母。 S
tring trim() 返回一个新字符串,该串删除了原始字符串头部和尾部的空 格; //字符串去除空格
str1.trim();
```
```java
public class StringTest1 {
public static void main(String[ ] args) {
String s1 = "core Java";
String s2 = "Core Java";
System.out.println(s1.charAt(3));//提取下标为 3 的字符
System.out.println(s2.length());//字符串的长度
System.out.println(s1.equals(s2));//比较两个字符串是否相等
System.out.println(s1.equalsIgnoreCase(s2));//比较两个字符串(忽略大小写)
System.out.println(s1.indexOf("Java"));//字符串 s1 中是否包含 Java
System.out.println(s1.indexOf("apple"));//字符串 s1 中是否包含 apple
String s = s1.replace(' ', '&');//将 s1 中的空格替换成&
System.out.println("result is :" + s); } }
```
**字符串相等的判断**
```java
equals 方法用来检测两个字符串内容是否相等。如果字符串 s 和 t 内容相等,则 s.equals(t)返回 true,否则返回 false。
要测试两个字符串除了大小写区别外是否是相等的,需要使用 equalsIgnoreCase 方 法。
判断字符串是否相等不要使用"==
"Hello".equalsIgnoreCase("hellO");//true
public class TestStringEquals {
public static void main(String[ ] args) {
String g1 = "北大青鸟";
String g2 = "北大青鸟";
String g3 = new String("北大青鸟");
System.out.println(g1 == g2); // true 指向同样的字符串常量对象
System.out.println(g1 == g3); // false
g3 是新创建的对象
System.out.println(g1.equals(g3)); // true g1 和 g3 里面的字符串内容是一样的
```
**StringBuffer 和 StringBuilder**
StringBuffer 和 StringBuilder 非常类似,均代表可变的字符序列。 这两个类都是抽
象类 AbstractStringBuilder 的子类,方法几乎一模一样。我们打开 AbstractStringBuilder
```java
abstract class AbstractStringBuilder implements Appendable, CharSequence { /*** The value is used for character storage. */
char value[ ]; //以下代码省略 }
```
显然,内部也是一个字符数组,但这个字符数组没有用 final 修饰,随时可以修改。因 此,StringBuilder 和 StringBuffer 称之为“可变字符序列”。那两者有什么区别呢?
StringBuffer JDK1.0 版本提供的类,**线程安全,做线程同步检查, 效率较低**。
StringBuilder JDK1.5 版本提供的类,线程不安全,不做线程同步检查,因此效率较高。 建议采用该类。
| 方法名 | 说明 |
| ---------------------------------- | ------------------------------------------ |
| public StringBuilder() | 创建一个空白可变字符串对象,不含有任何内容 |
| public StringBuilder(String str) | 根据字符串的内容,来创建可变字符串对象 |
| 方法名 | 说明 |
| ---------------------------------------- | --------------------------------------------------- |
| public StringBuilder append (任意类型) | 添加数据,并返回对象本身 |
| public StringBuilder reverse() | 返回相反的字符序列 |
| public int length() | 返回长度,实际存储值 |
| public String toString() | 通过toString()就可以实现把StringBuilder转换为String |
- 示例代码
```java
public class TestStringBufferAndBuilder{
public static void main(String[ ] args) {
/**StringBuilder*/
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 7; i++) {
sb.append((char) ('a' + i));//追加单个字符 }
System.out.println(sb.toString());//转换成 String 输出
sb.append(", I can sing my abc!");//追加字符串
System.out.println(sb.toString()); /**StringBuffer,下面的方法同样适用 StringBuilder*/
StringBuffer sb2 = new StringBuffer("北大青鸟");
sb2.insert(0, "爱").insert(0, "我");//插入字符串
System.out.println(sb2);
sb2.delete(0, 2);//删除子字符串
System.out.println(sb2);
sb2.deleteCharAt(0).deleteCharAt(0);//删除某个字符
System.out.println(sb2.charAt(0));//获取某个字符
System.out.println(sb2.reverse());//字符串逆序 } }
```
## 输入流和输出流
输入输出的概念:
什么是输入-- input
什么是输出-- output 先别管什么概念 就记住这两个单词;
目前是怎么样存储数据的?弊端是什么?
```javascript
nt a = 10;
int [] arr = {1,2,3,4,5};
List<String> list = new ArrayList<>();
```
目前存储的数据--在内存中存储的数据是用来处理、修改、运算的,不能长久保存。
那怎么把数据长久的储存?---硬盘---
#### 会使用File类操作文件或目录
1、<u>先要定位文件</u>
File类可以定位文件:进行删除、获取文本本身信息等操作。
但是不能读写文件内容。
2、<u>读写文件数据</u>
IO流技术可以对硬盘中的文件进行读写
3、<u>今日总体学习思路</u>
先学会使用File类定位文件以及操作文件本身
然后学习IO流读写文件数据。
File类介绍
它是文件和目录路径名的抽象表示
文件和目录是可以通过File封装成对象的
对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的
* File类代表磁盘或网络中某个文件或目录的路径名称,如:/cmd/javase/io/1.jpg
但不能直接通过File对象读取和写入数据,如果要操作数据,需要IO流。File对象好比是到水库的“路线地址”,要“存取”里面的水到你“家里”,需要“管道”。
### File类的构造方法
* `public File(String pathname) ` :通过将给定的**路径名字符串**转换为抽象路径名来创建新的 File实例。
* `public File(String parent, String child) ` :从**父路径名字符串和子路径名字符串**创建新的 File实例。
* `public File(File parent, String child)` :从**父抽象路径名和子路径名字符串**创建新的 File实例。
```java
public class FileDemo01 {
public static void main(String[] args) {
//File(String pathname):通过将给定的路径名字符串转换为抽象路径名来创建新的 File
实例。
File f1 = new File("E:\\Demo\\java.txt");
System.out.println(f1);
//File(String parent, String child):从父路径名字符串和子路径名字符串创建新的
File实例。
File f2 = new File("E:\\Demo","java.txt");
System.out.println(f2);
//File(File parent, String child):从父抽象路径名和子路径名字符串创建新的 File
实例。
File f3 = new File("E:\\demo");
File f4 = new File(f3,"java.txt");
System.out.println(f4);
```
> 小贴士:
>
> 1. 一个File对象代表硬盘中实际存在的一个文件或者目录。
> 2. 无论该路径下是否存在文件或者目录,都不影响File对象的创建。
### IO流的分类
- I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。
- O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。
根据数据的流向分为:**输入流**和**输出流**。
- **输入流** :把数据从`其他设备`上读取到`内存`中的流。
- 以InputStream,Reader结尾
- **输出流** :把数据从`内存` 中写出到`其他设备`上的流。
- 以OutputStream、Writer结尾
根据数据的类型分为:**字节流**和**字符流**。
- **字节流** :以字节为单位,读写数据的流。
- 以InputStream和OutputStream结尾
- 字节流
字节输入流
字节输出流
- **字符流** :以字符为单位,读写数据的流。
- 以Reader和Writer结尾
- 字符流
字符输入流
字符输出流
根据IO流的角色不同分为:**节点流**和**处理流**。
* **节点流**:可以从或向一个特定的地方(节点)读写数据。如FileReader.
* **处理流**:是对一个已存在的流进行连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
**常用的节点流:**
* 文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流。
* 字符串 StringReader StringWriter 对字符串进行处理的节点流。
* 数 组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
* 管 道 PipedInputStream、PipedOutputStream、PipedReader、PipedWriter对管道进行处理的节点流。
**常用处理流:**
* 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter---增加缓冲功能,避免频繁读写硬盘。
* 转换流:InputStreamReader、OutputStreamReader---实现字节流和字符流之间的转换。
* 数据流:DataInputStream、DataOutputStream -提供读写Java基础数据类型功能
* 对象流:ObjectInputStream、ObjectOutputStream--提供直接读写Java对象功能
### 4大顶级抽象父类
| | **输入流** | 输出流 |
| :--------: | :-----------------------: | :------------------------: |
| **字节流** | 字节输入流**InputStream** | 字节输出流**OutputStream** |
| **字符流** | 字符输入流**Reader** | 字符输出流**Writer** |
对于如何选择:IO流的使用场景
- 如果操作的是纯文本文件,优先使用字符流
- 如果操作的是图片、视频、音频等二进制文件。优先使用字节流
- 如果不确定文件类型,优先使用字节流。字节流是万能的流
字节流是 8 位通用字节流,字符流是 16 位 Unicode 字符流
> > #### 字符集基础知识:
> >
> > * 计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0、1)
> >
> > * 二进制是可以转换成十进制的;计算机底层可以表示十进制编号。计算机可以给人类字符进行编号存储,这套编号规则就是字符集。
> >
> > * ASCII字符集:
> >
> > ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。
> >
> > ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。
> >
> > * 01100001 = 97
> >
> > * 01100010 = 98
> >
> > * 01100001 = 97 => a
> >
> > * 01100010 = 98 => b
> >
> > * GBK:
> > window系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
> >
> > * Unicode码表:
> >
> > * unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准。
> > 容纳世界上大多数国家的所有常见文字和符号。
> >
> > * 由于Unicode会先通过UTF-8,UTF-16,以及 UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8。
>
>
字节输入流
- 典型实现:**FileInputStream**
-
- | 构造器 | 说明 |
| --------------------------------------- | ---------------------------------- |
| public FileInputStream(File file) | 创建字节输入流管道与源文件对象接通 |
| public FileInputStream(String pathname) | 创建字节输入流管道与源文件路径接通 |
| 方法名称 | 说明 |
| ------------------------------ | ------------------------------------------------------ |
| public int read() | 每次读取一个字节返回,如果字节已经没有可读的返回-1 |
| public int read(byte[] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
```java
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 个字节,但读取
的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于
文件末尾而没有可用的字节,则返回值 -1。
public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源。
为什么使用close
程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资
源,所以应该显式关闭文件 IO 资源。
```
```java
public class FileInputStreamDemo01 {
public static void main(String[] args) throws IOException {
//创建字节输入流对象
//FileInputStream(String name)
FileInputStream fis = new FileInputStream("E:\IO\java.txt");
int by;
/*
fis.read():读数据
by=fis.read():把读取到的数据赋值给by
by != -1:判断读取到的数据是否是-1
System.out.println("可读取的字节数:"+fis.available());
System.out.print("文件内容为:");
//循环读数据 read()方法是从输入流读取1个8位的字节,
把它转化为0-255之间的整数返回。将返回的整数转换为字符
*/
while ((by=fis.read())!=-1) {
System.out.print((char)by);
}
//释放资源
fis.close();
1. 虽然读取了一个字节,但是会自动提升为int类型。
2. 流操作完毕后,必须释放系统资源,调用close方法,千万记得。
```
### 字节输出流写数据的步骤
创建字节输出流对象
作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流。
调用字节输出流对象的写数据方法
释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
| 构造器 | 说明 |
| -------------------------------------------------------- | ---------------------------------------------- |
| public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
| public FileOutputStream(File file,boolean append) | 创建字节输出流管道与源文件对象接通,可追加数据 |
| public FileOutputStream(String filepath) | 创建字节输出流管道与源文件路径接通 |
| public FileOutputStream(String filepath,boolean append) | 创建字节输出流管道与源文件路径接通,可追加数据 |
| 方法名称 | 说明 |
| ---------------------------------------------------- | ---------------------------- |
| public void write(int a) | 写一个字节出去 |
| public void write(byte[] buffer) | 写一个字节数组出去 |
| public void write(byte[] buffer , int pos , int len) | 写一个字节数组的一部分出去。 |
```java
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
}
}
输出结果:
abc
2. 写出字节数组:`write(byte[] b)`,每次可以写出数组中的数据,代码使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "你好北大青鸟".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
}
}
3. 写出指定长度字节数组:`write(byte[] b, int off, int len)` ,每次写出从off索引开始,len个字节,代码使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b,2,2);
// 关闭资源
fos.close();
}
}
输出结果:
cd
public FileOutputStream(String filepath,boolean append) 创建字节输出流管道与源文件路径接通,可追加数据
public class FOSWrite {
public static void main(String[] args) throws IOException {
// true 表示内容会追加到文件末尾;false 表示重写整个文件内容
FileOutputStream fos = new FileOutputStream("fos.txt",true);
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b);
// 关闭资源
fos.close();
}
}
文件操作前:cd
文件操作后:cdabcde
```
#### 流的关闭与刷新
| 方法 | 说明 |
| ------- | ------------------------------------------------------------ |
| flush() | 刷新流,还可以继续写数据 |
| close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
作用:缓冲流自带缓冲区、
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。可以提高原始字节流、字符流读写数据的性能
缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
**处理流之一:缓冲流**
当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
当使用BufferedInputStream读取字节文件时,
BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组
```
/**
* @Author Dm
* @Date 2022/5/8 15:58
* @Version 1.0
**/
public class BufferTest {
/**
* Bufferinputstream;字节缓冲流
* bufferReader;字符缓冲流
* 处理流:套接在节点流之上;
* 自带缓冲区:8192
* 通过缓冲区的读写;减少IO 的次数;提高读写的效率;
*
*/
public static void main(String[] args) throws IOException {
BufferedInputStream buf = new BufferedInputStream(new FileInputStream("E:\\IOTest\\py.bak"));
BufferedOutputStream buffer = new BufferedOutputStream(new FileOutputStream("Copy.bak"));
byte[] bytes = new byte[8 * 1024];
int len;
while ((len=buf.read(bytes))!=-1){
buffer.write(bytes,0,len);
}
}
}
```