String是一个类,属于引用类型。Java程序中一切使用""引起来的内容都属于String类的实例,称为字符串对象。
字符串定义后是一个常量,值不可更改,String字符串是一个不可变字符串字符串实际是一个字符数组。
String str="hello";//这句话在执行时,创建一个hello字符串对象,将其地址保存在str变量中 str="abc";//这里看似是在改变字符串的值,实际是又创建了一个字符串对象abc,将其地址保存在str变 量中 //以上两句话,在内存中会有两个字符串对象hello和abc,str只引用最后赋值的字符串地址abc String str2="abc"; //字符串实际是一个字符数组 char[] list = {'a','b','c'}; String str3= new String(list); //这里str2和str3中保存的内容是一样的
常用构造方法 | 说明 |
---|---|
String() | 创建一个空白字符串对象,实际创建一个空字符数组 |
String(String str) | 创建一个指定的字符串对象,实际是创建一个形参字符 串的副本 |
String(char[] list) | 创建一个指定字符数组的字符串对象。 |
String(byte[] list) | 按默认编码格式创建一个指定字节数组的字符串对象。 |
String(byte[] list,String charsetName) | 按指定的编码格式创建一个指定字节数组的字符串对 象。 |
用于表示可变字符串的一个类,是非线程安全的,建议在单线程环境下使用,效率略高于StringBuffer。
StringBuffer类用于表示可变字符串的一个类,是线程安全的,建议在多线程环境下使用,效率略低于StringBuilder。
StringBuilder和StringBuffer中的方法作用都一致,只不过StringBuffer中的方法使用了synchronized关键字修饰,表示一个同步方法,在多线程环境下不会出现问题。
以StringBuilder为例:
常用构造方法 | 说明 |
---|---|
String() | 创建一个空白字符串对象,实际创建一个空字符数组 |
String(String str) | 创建一个指定的字符串对象,实际是创建一个形参字符 串的副本 |
String(char[] list) | 创建一个指定字符数组的字符串对象。 |
String(byte[] list) | 按默认编码格式创建一个指定字节数组的字符串对象。 |
String(byte[] list,String charsetName) | 按指定的编码格式创建一个指定字节数组的字符串对 象。 |
有些方法如indexOf()、charAt()等,在String和StringBuilder中都存在,可以不用转换。
但有些方法如getBytes()、contains()等,只能通过String调用,这时就需要进行转换。
不可变字符串转换为可变字符串
通过创建一个可变字符串对象,将不可变字符串作为参数实现转换
//定义一个不可变字符串对象 String str="hello"; //创建一个可变字符串对象,将不可变字符串对象作为参数 StringBuilder sb = new StringBuilder(str);
可变字符串转换为不可变字符串
通过调用可变字符串的toString()方法实现转换
//创建一个可变字符串对象 StringBuilder sb = new StringBuilder("hello"); //调用toString()转换为String类型 String str=sb.toString();比较String、StringBuilder、StringBuffer这三个类的区别
相同点
- 这三个类都可以用于表示或操作一个字符串
- 这三个类中有公共的方法,如indexOf()、charAt()等、
- 这三个类都是被final修饰的类,不能被继承
不同点
- String定义的字符串是一个常量,不可变;StringBuilder和StringBuffer定义的字符串是一个变量,可以改变。
- String类中的方法调用后,会创建字符串副本,不会影响原字符串;可变字符串对象调用的方法,直接操作原字符串,会影响。
- StringBuilder是非线程安全的,StringBuffer是线程安全的,其中的方法使用synchronized修饰为同步方法。在多线程情景下,需要使用StringBuffer。StringBuilder效率略高于StringBuffer。
总结
在程序中,如果要频繁操作某个字符串,一定使用可变字符串类StringBuilder或StringBuffer创建对象。
java是纯面向对象语言,宗旨是将一切事物视为对象处理。但原始类型不属于对象,不满足面向对象的思想,但原始类型使用时无需创建对象,保存在栈中。为了让原始类型也有对象的类类型,达到"万物皆对象"的思想,所以就有了包装类的概念。
包装类就是原始类型对应的类类型。包装类通常用于将字符串转换为对应的原始类型。在web应用中,从浏览器中获取到后台的数据,全是String类型,一定要使用转换的方法。
包装类 | 原始类型 | 将字符串转换为原始类型 |
---|---|---|
Byte | byte | Byte.parseByte(String str) |
Short | short | Short.parseShort(String str) |
Integer | int | Integer.parseInt(String str) |
Long | long | Long.parseLong(String str) |
Float | float | Float.parseFloat(String str) |
Double | duoble | Double.parseDouble(String str) |
Boolean | boolean | Boolean.parseBoolean(String str) |
Character | char | 无 |
特点
- 八个原始类型中,除了int和char之外,其余类型的包装类,都是将首字母改为大写。int为Integer,char为Character。
- 除了Character类之外,其余类都有至少两个构造方法:参数为原始类型或字符串的构造方法。Character的构造方法只有一个,参数为char变量。
- 除了Character类之外,其余类都有静态方法parse原始类型(String str),用于将字符串转换为相
应的原始类型- 数值型的包装类的parseXXX()方法,如果不是一个真正的对应类型的数,转换时会抛出NumberFormatException异常。如"123abc"、"123.456"都不能使用Integer.parseInt()转换
- Boolean类型中的parseBoolean()方法,参数如果是"true"这个单词,无论大小写,都能转换为真正的boolean值的true,只要不是"true"这个单词,转换结果都为false
- 除了Boolean类之外,其余类都有MAX_VALUE和MIN_VALUE这两个静态属性,用于获取对应原始类型支持的最大最小范围
- 所有包装类中都有一个compareTo(参数1,参数2)方法,用于比较两个参数
- 如果是数值型,参数1>参数2返回1,参数1<参数2返回-1,相同返回0
- 如果是Boolean型,两个参数相同返回0,不同时,如果参数1为true返回1,否则返回-1
- 如果是Character型,返回参数1-参数2的值。
- 所有包装类中都有toString()方法,用于将包装类对象转换为String字符串对象
装箱和拆箱
- 所有包装类都有一个静态方法valueOf(原始类型),将某个原始类型的数据转换为相应的包装类对象,这个过程称为装箱。
//手动装箱 int i=123;//定义一个原始类型的数据 Integer aInteger=Integer.valueOf(i);//调用包装类的valueOf()方法将原始类型转换为包 装类对象
- 所有包装类都有一个原始类型Value()方法,用于将包装类对象转换为原始类型,这个过程称为拆箱
//手动拆箱 Integer aInteger=new Integer(123);//创建一个包装类对象 int i = aInteger.intValue();//调用包装类的"原始类型Value()"方法将其转换为原始类型
- 自动装箱拆箱。在jdk1.5之后,加入了自动装箱拆箱的特性,可以直接在原始类型和对应的包装类中互相赋值
//自动装箱 Integer aInteger=123; //自动拆箱 int i=aInteger;字符串与原始类型之间的转换
原始类型转换为字符串:
String.valueOf(原始类型参数);
int num=123; String str=String.valueOf(num); System.out.println(str.length());
字符串转换为原始类型:
使用原始类型对应的包装类,调用其pareseXXX(字符串)方法
String num="123";Date类
用于表示日期时间的类,位于java.util包下
构造方法
常用构造方法 | 说明 |
---|---|
Date() | 创建当前瞬间的日期对象 |
Date(long l) | 创建指定毫秒数对应的日期对象。(从1970.1.1起经过了的毫秒数) |
Date(int year,int month,int date) | 该构造方法已过时。创建指定年月日的日期对象。(年从1900年起经过 |
常用方法
常用方法 | |
---|---|
getTime() | 得到对应Date对象指定的毫秒数。 |
setTime(long l) | 设置毫秒数 |
after(Date when) | 判断调用日期对象是否在when之后 |
before(Date when) | 判断调用日期对象是否在when之前 |
是一个用于格式化日期的类。
构造方法
//参数为一个日期模板 SimpleDateFormat(String pattern); //如定义这样的一个模板对象 SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); //创建一个Date对象 Date now = new Date(); //调用格式化方法,参数为一个Date对象 String res=sdf.formart(now); //输出的形式为2022/07/28 System.out.println(res);
日期格式化的模板
当程序没有按程序员的意愿正常执行,中途出错导致程序中断,出现这种情况,就称为异常。学习异常就是认识异常的种类和如何避免异常出现。为了让程序能正常执行,就需要学会解决异常。
异常的产生
异常在程序中以对象的形式存在。当代码执行时出现异常,虚拟机会自动创建一个相应的异常对象,如果没有对该异常进行处理,就会导致程序中断。
异常的分类
异常在程序中以对象的形式存在,所以异常有对应的类。
“异常家族”
Throwable是异常类的根类,通常所说的异常,其实指的是Exception子类
如果出现XXXXXError,如StackOverflowError栈空间溢出时,无法通过额外的代码去解决,只能修改源码。
Exception异常如果出现XXXXException,如NullPointerException空指针异常时,可以通过额外代码去避免。
运行时异常和非运行时异常如果一个异常属于RuntimeException异常类的子类,称为运行时异常,可以通过编译,运行时可能抛出异常对象。
如果一个异常属于Exception异常类的子类,称为非运行时异常,无法通过编译,只有处理后才能编译运行。
-
运行时异常(RuntimeException),可以通过编译,可以不用处理,但在运行时有可能会抛出异常。
常见运行时异常 说明 出现的情景 NullPointerException 空指针异常 用空对象null调用方法或属性 ArrayIndexOutOfBoundsException 数组下标越界异常 使用数组时,下标超出范围 ClassCastException 对象转型异常 Dog dog=(Dog)new Person();(Person类和Dog类不是继承关系) InputMismatchException 输入类型不匹配异常 如需要int类型的数据,输入String类型的数据。 ArithmeticException 算术运算异常 0当分母 IllegalArgumentException 非法参数异常 调用方法的参数不合理 -
非运行时异常(除RuntimeException),无法通过编译,必须要处理后才能编译运行。
常见非运行时异常 说明 出现的情景 IOException 输入输出流异常 当使用流对象 SQLException 数据库SQL相关异常 操作数据库时 FileNotFoundException 文件未找到异常 方法的参数为文件
只要处理Exception异常类的子类时,都称为处理异常。处理异常的目的就是为了保证程序正常运行,不要中断。
方式一:try-catch-finally语句
这种方式能成功地处理异常,无论会不会抛出异常,都能让程序保证正常执行。
try{ //可能出现异常的代码 }catch(异常类 异常对象){ //如果出现异常对象,且与catch中的异常类匹配,则执行 }catch(异常类 异常对象){//后续的catch中的异常类型只能与之前的异常类型同级或是父类 //如果出现异常对象,且与catch中的异常类匹配,则执行 }finally{ //无论程序是否会抛出异常,一定要执行的代码 }
执行流程:先执行try中的内容,当出现异常,与后续的catch进行匹配,如果匹配对应的异常类型或异常父类型,则执行大括号中的内容,最终一定执行finally中的内容
try-catch注意
- 执行try中的内容时,当某行代码抛出异常后,不再执行try中该行代码后续的内容。
- 无论try中是否会抛出异常,finally中的内容一定执行。通常finally用于释放资源。
- 如果有多个catch,需要将异常子类放在最前,异常父类放在最后
- try、catch、finally都不能单独使用,try需要配合catch或finally或catch和finally一起使用
- try中定义的内容,无法在try之外的地方使用
- try中如果有return,不影响finally的执行,并且finally优先于return执行
方式二:throws关键字
这种方式,可以让非运行时异常通过编译,定义方法的时候,声明可能抛出的异常。
用法:方法的参数小括号之后,添加"throws 异常类型1,异常类型2…"
public class Test{ public void fun() throws InterruptException{//在定义方法时,声明可能抛出的异常类型 //如果直接写这句话,会报错,因为sleep()方法会抛出一个非运行时异常,必须要处理 Thread.sleep(5000); } }
throw和throws
- throws表示用于方法声明异常。使用时写在方法的小括号之后
//用于声明方法的异常 public void fun() throws Exception{ }
- throw用于手动抛出异常对象。使用时写在{}中,通常用于满足某种条件时,强制中断程序。
public void fun(){ //用于手动抛出一个异常对象 RuntimeException e = new RuntimeException(); throw e; }自定义异常
可以自定义异常,在满足某种条件下,手动通过throw关键字抛出异常,人为中断程序。
自定义异常步骤
- 定义一个类,继承某个异常类。
- 如果继承的是RuntimeException,表示自定义的异常类属于运行时异常,该异常对象可以不用处理。
- 如果继承的是非RuntimeException,表示自定义的异常类属于非运行时异常,该异常对象必须要处理。
- [可选操作]定义一个无参数的构造方法,调用父类中无参的构造方法,定义一个带字符串参数的构造方法,调用父类带字符串参数的构造方法。
数组的特点
- 数组中保存的元素都是有序的,可以通过下标快速访问
- 数组中保存的数据都是同一种类型
- 数组的长度在定义后,无法改变
- 数组无法获取其中保存的实际元素数量
集合的特点
- 能保存一组数据,可以有序也可以无序
- 集合的容量可变
- 集合中可以保存不同类型的数据
- 可以获取集合中实际的元素数量
集合框架(集合家族)
Iterator接口并不算严格意义上的集合的"根",它称为迭代器,用于遍历集合元素的一个工具接口。
所以集合的根接口为:Collection接口和Map接口,位于java.util包中
核心的两个子接口:Set和List。
这两个接口都可以保存一组数据,Set接口保存数据时,是无序不重复的;List接口保存数据时,是有序可重复的。
List接口(有序可重复)有序集合,元素可以重复,允许保存null,可以通过索引获取对应位置上的元素。
在接口中定义了一些操作元素的方法,如获取元素数量、添加、删除、替换、截取等。
ArrayList实现类(掌握)- 采用数组实现的集合
- 可以通过索引访问元素、可以改变集合大小。如果要在其中插入或删除元素时,会影响其余元素。该集合查询效率高、增删中间元素效率低。
- 该集合对象中保存的元素,都是引用类型(对象的内存地址)。即使保存了123,其实不是保存的int类型的123,而是Integer类型的123.
构造方法
常用构造方法 | 说明 |
---|---|
ArrayList() | 创建一个无参的ArrayList对象。实际是创建了一个空的Object数组。在调用添加元素方法时,才会初始化该数组大小为10。 |
ArrayList(int | 创建一个指定容量的ArrayList对象,实际是创建一个指定大小的Object数组,如果参数为负,会抛出IllegalArgumentException异常 |
- 采用双向链表实现的集合
- 集合中保存的每个元素也称为节点,除首尾节点外,每个节点即保存了自己的数据,也保存了其前一个和后一个节点的地址
- 如果在其中进行插入和删除的操作时,不影响其他元素的位置,只需要重新定义新节点的前后节点位置即可。
- 如果要查询某个节点的索引,需要从头结点或尾结点开始一步步得到目标节点位置
所以中间进行插入和删除的效率高,随机读取的效率低
构造方法
常用构造方法 | 说明 |
---|---|
LinkedList() | 创建一个空链表 |
ArrayList和LinkedList的区别
- 两者都是List接口的实现类,保存的元素有序可重复,允许保存null,拥有一些公共的方法,如
size(),isEmpty(),subList(int from,int to)等 - ArrayList采用数组实现,对于随机读取效率更高,通常用于查询;LinkedList采用双向链表实现,插入删除不影响其他元素位置,通常用于中间插入删除。
无序集合,元素不可以重复,允许保存null,没有索引。
在接口中定义了一些操作元素的方法,如获取元素数量、添加、删除、替换、截取等。
哈希表hash table
哈希表,也称为散列表,是一种数据结构,能更快地访问数据。
要保存的数据称为原始值,这个原始值通过一个函数得到一个新的数据,这个函数称为哈希函数,这个新数据称为哈希码,哈希码和原始值之间有一个映射关系,这个关系称为哈希映射,可以构建一张映射表,称为哈希表。在哈希表中,可以通过哈希码快速访问对应的原始值。
哈希码的特点
-
如果两个对象的hashCode不同,这两个对象一定不同
-
如果两个对象的hashCode相同,这两个对象不一定相同
- 如"通话"和"重地"这两个字符串的hashCode相同,但是两个对象
- hashCode相同,对象不同,称为哈希冲突
-
本质是一个HashMap对象,调用add()方法,实际调用的也是HashMap中的put()方法,参数作为put()方法的键,new Obejct()作为put()方法的值
-
采用哈希表实现
-
元素不能重复,无序保存,允许保存null
-
本质是一个HashMap对象,调用add()方法,实际调用的也是HashMap中的put()方法,参数作为put()方法的键,new Obejct()作为put()方法的值
构造方法
常用构造方法 | 说明 |
---|---|
HashSet() | 创建一个默认的集合对象,实际是创建了一个大小为16,加载因子为0.75的HashMap对象 |
HashSet(int capacity) | 创建一个指定容量的集合对象,实际是创建了一个指定大小,加载因子为0.75的HashMap对象 |
HashSet(int capacity,floatloadFactor) | 创建一个指定容量和指定加载因子的集合对象。 |
HashSet添加数据的原理
if(两个对象hashCode相同&&两个对象equals相同){视为同一对象,不能添加}
每次向集合中添加元素时,先判断该元素的hashCode是否存在,
- 如果不存在,视为不同对象,直接添加
- 如果存在,再判断equals方法的结果
- 如果为false,视为不同对象,直接添加
- 如果为true,视为同一对象,不能添加
可见不能添加的条件是两个对象的hashCode相同并且equals结果为true。如果每次都只判断equals的话,过程可能会很久,效率不高,如果每次只判断hashCode的话,有可能会有哈希冲突,所以先判断hashCode,再判断equals,既能保证效率又能保证添加进去的元素都是不相同的元素。
equals方法和hashCode的关系
- 如果两个对象的equlas方法比较结果为true,在没有重写equals方法的前提下,hashcode相同吗?
- 相同
- 如果两个对象的hashCode不同,在没有重写equals方法的前提下,equals方法比较结果为?
- false
- 如果两个对象的hashCode相同,equals方法比较结果为?
- 可能为true也可能是false
String str1="通话"; String str2="重地"; //以上两个字符串对象的hashCode相同,但equals方法结果为false String str3="abc"; String str4="abc"; //以上两个字符串的hashCode相同,equals方法结果为trueTreeSet实现类
- 特殊的Set实现类,数据可以有序保存,可以重复,不能添加null元素
- 采用红黑树(自平衡二叉树)实现的集合
- 二叉树表示某个节点最多有两个子节点
- 某个节点右侧的节点值都大于左侧节点值
- 只能添加同一种类型的对象且实现了Comparable接口的对象
- 实现Comparable接口后必须要重写compareTo方法
- 每次调用添加时,参数会自动调用该方法
- 添加的元素可以自动排序
- compareTo方法的返回值决定了能否添加新元素和新元素的位置
- 如果返回0,视为每次添加的是同一个对象,不能重复添加
- 如果返回正数,将新元素添加到现有元素之后
- 如果返回负数,将新元素添加到现有元素之前
构造方法
常用构造方法 | 说明 |
---|---|
TreeSet() | 实际会创建一个TreeMap对象 |
Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。
其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序。
HashMap实现类- JDK1.8之后,HashMap的数据结构采用"数组+链表+红黑树"实现
- 当没有哈希冲突时,元素保存到数组中
- 如果哈希冲突,在对应的位置上创建链表,元素保存到链表中
- 当链表元素数量大于8,转换为红黑树
- 数据采用"键值对"的形式保存,键称为key,值称为value ,键不能重复,允许null,值没有限
制,键和值都是引用类型 - 在哈希表中,哈希码就是键,保存的数据就是值,可以通过键得到相应的值。
常用构造方法 | 说明 |
---|---|
HashMap() | 创建一个空的集合对象,默认大小为16,加载因子为0.75 |
方式一:普通for循环
List list= new ArrayList(); list.add("123"); list.add("123"); list.add("123"); list.add("123"); for(int i=0;iObject obj= list.get(i); }
方式二:增强for循环
List list= new ArrayList(); list.add("123"); list.add("123"); list.add("123"); list.add("123"); for(Object o:list){ Object obj= o; }
方式三:迭代器
List list= new ArrayList(); //获取迭代器对象 Iterator it= list.iterator(); //hasNext判断是否还有下一个元素 while(it.hasNext()){ //获取读到的元素 Object obj=it.next(); }遍历Set
方式一:增强for循环
Set list= new HashSet(); list.add("123"); list.add("qwe"); list.add("hello"); list.add(null); for(Object o:list){ Object obj= o; }
方式二:迭代器
Set list= new HashSet(); //获取迭代器对象 Iterator it= list.iterator(); //hasNext判断是否还有下一个元素 while(it.hasNext()){ //获取读到的元素 Object obj=it.next(); }泛型
一种规范,常用于限制集合中元素的类型,省去遍历元素时转型的过程。
//定义集合,默认不限制元素类型 List list = new ArrayList(); list.add("hello"); list.add("world"); list.add(123); //如果没有限制集合的类型,要通过循环做某个事情时,可能对于某些元素并不适用 for(int i=0;i//String有length属性,Integer没有,运行时就会抛出异常 System.out.println(list.get(i).length); }
用法:
在定义集合时,在接收集合变量的数据类型后,写上<数据类型>。
集合类或接口<数据类型> 集合变量名 = new 集合实现类<>();
ListCollections集合工具类list = new ArrayList<>(); //这样只能在集合中保存String //list.add(123);//不能通过编译 //如果要限制集合保存int类型的数据,不能写int,要写int的包装类Integer。因为集合中保存的都是引 用类型。 //List list2=new ArrayList();//无法通过编译 List list2=new ArrayList();
- Collection是集合的根接口,定义了集合的方法
- Collections是集合的工具类,定义了很多静态方法,直接通过类名使用
常用方法
常用方法 | |
---|---|
Collections.shuffle(List list) | 打乱有序集合中的元素顺序 |
Collections.swap(List list,int i,int j) | 交换有序集合中i和j索引上的元素 |
Collections.replaceAll(List list,ObjectoldVal,Object newVal) | 将有序集合中的旧元素替换为新元素 |
Collections.fill(List list,Object obj) | 将有序集合使用obj填充 |
Collections.rotate(List list,int distance) | 将最后distance个元素放在最前 |
Collections.sort(List list) | 对有序集合中的元素进行排序,元素必须实现了Comparable接口 |
Collections.max(Collection list) | 得到有序集合中的最小元素,元素必须实现了Comparable接口 |
Collections.min(Collection list) | 得到有序集合中的最大元素,元素必须实现了Comparable接口 |
-
集合转换为数组
Object[] 数组 = 集合对象.toArray(); List list = new ArrayList(); list.add(123); Object[] obj = list.toArray(); System.out.println((Integer)obj[0]);
-
数组转换为数组集合
int[] ints={1,2,3,9,22,16}; //将数组保存到一个数组集合中,上边定义的那个数组就是集合中的第一个元素 List
ints1 = Arrays.asList(ints); //遍历转换后的数组 for (int[] ints2 : ints1) { //遍历数组中的元素 for (int i : ints2) { System.out.println(i); } }
Java中可以将本地硬盘中的文件(文件和目录)以对象的形式表示。
就能通过Java代码对本地文件进行读取或操作。
构造方法
常用构造方法 | 说明 |
---|---|
File(String pathName) | 根据文件的完整路径创建对象 |
File(String parent,String name) | 根据文件所在的父目录路径和自身文件名创建对象 |
File(File parent,String name) | 根据文件所在父目录文件对象和自身文件夹创建对象 |
//创建一个File对象 new File(String pathName); File file1 = new File("d:/xiaopw84in1111"); //创建一个File对象 new File(String parent,String child); File file2 = new File("D:\xiaopw84in1111","Readme-说明.htm"); //创建一个File对象 new File(File parent,String child); File parent = new File("D:/xiaopw84in1111"); File file3 = new File(parent, "Readme-说明.htm");IO、流(Stream)
IO
I:Input输入
O:Output输出
流
在Java中,流用于表示计算机硬盘与内存之间传输数据的通道。
将内存中的数据存入到硬盘中,称为写write,也称为输出
将硬盘中的数据存入到内存中,称为读read,也称为输入
流的分类
Java中将流定义成了类,以对象的形式保存。流有"四大家族",是所有流的父类。
InputStream字节输入流 FileInputStream 文件字节输入流按字节读取本地文件,本地文件必须存在
FileInputStrream构造方法
常用构造方法 | 说明 |
---|---|
FileInputStream(String pathName) | 根据文件名创建文件字节输入流对象 |
FileInputStream(File file) | 根据文件对象创建文件字节输入流对象 |
反序列化:将文件转换为对象的过程
ObjectInputStream构造方法
常用构造方法 | 说明 |
---|---|
ObjectInputStream(InputStream | 创建一个对象字节输入流对象,参数为一个字节输入流对象,由于是抽象类,所以要使用其子类,如FileInputStream对象,在其中定义要读取的文件 |
按字节将数据写入到本地文件中,本地文件可以不存在
FileOutputStrream构造方法
常用构造方法 | 说明 |
---|---|
FileOutputStream(String pathName) | 根据文件名创建文件字节输出流对象,写入内容时覆盖原内容 |
FileOutputStream(StringpathName,boolean append) | 根据文件名创建文件字节输出流对象,写入内容时追加在原内容之后 |
FileOutputStream(File file) | 根据文件对象创建文件字节输出流对象,写入内容时覆盖原内容 |
FileOutputStream(File file,booleanappend) | 根据文件对象创建文件字节输出流对象,写入内容时追加在原内容之后 |
序列化:将对象转换为文件的过程
被序列化的对象,必须要实现Serializable接口。
这个接口是一个特殊的接口,没有定义方法,只是给这个类打上标记,表示该类可以被序列化。
ObjectOutputStream构造方法
常用构造方法 | 说明 |
---|---|
ObjectOutputStream(InputStream | 创建一个对象字节输入流对象,参数为一个字节输入流对象,由于是抽象类,所以要使用其子类,如FileOutputStream对象,在其中定义要写入的文件 |
按字符写入。
FileWriter构造方法
常用构造方法 | 说明 |
---|---|
FileWriter(String fileName) | 按文件名创建字符输出流对象 |
FileWriter(String fileName,boolean append) | 按文件名创建字符输出流对象,允许追加写入 |
FileWriter(File file) | 按文件对象创建字符输出流对象 |
FileWriter(File file,boolean append) | 按文件对象创建字符输出流对象,允许追加写入 |
自带字符数组(缓冲区)的字符输出流。
BufferedWriter构造方法
常用构造方法 | 说明 |
---|---|
BufferedWriter(Writer writer) | 创建一个带有缓冲区(字符数组)的字符输出流对象,参数为一个Writer对象,但Writer是一个抽象类,所以要使用其子类,如FileWriter对象,在FileWriter中定义要写入的文件 |
BufferedWriter(Writer writer,int size) | 创建一个指定缓冲区(字符数组)大小的字符输出流对象 |
按字符读取文件。
FileReader构造方法
常用构造方法 | 说明 |
---|---|
FileReader(String fileName) | 根据文件名创建文件字符输入流对象 |
FileReader(File file) | 根据文件对象创建文件字符输入流对象 |
自带字符数组(缓冲区)的字符输入流。默认字符数组大小为8192,每次最多读取8192个字符
在读取纯文本文件(txt或md等)时,首选该类。
BufferedReader构造方法
常用构造方法 | 说明 |
---|---|
BufferedReader(Reader reader) | 创建一个带有缓冲区(字符数组)的字符输入流对象,默认缓冲区大小为8192。参数注意是一个Reader类型的对象,但Reader是一个抽象类,所以要使用其子类,如FileReader,然后在FileReader中定义要读取的文件信息 |
BufferedReader(Reader reader,int size) | 创建一个指定缓冲区(字符数组)大小的字符输入流对象 |
输入流:InputStream、Reader
作用:将硬盘中的数据读取到内存中
输出流:OutputStream、Wrtiter
作用:将内存中的数据写入到硬盘中
字节流:InputStream、OutputStream
作用:适用于非文本类型,如图片、文件等的读写
字符流:Reader、Writer
作用:适用于文本类型,尤值txt格式文件的读写
流的四个父类的特点
- 以上四个类都在java.io包下,都是抽象类,不能直接创建其对象,要使用其子类
- 这四个类都有close()方法,用于关闭流对象释放资源
- 输出流(OutputStream和Write)都有flush()方法,用于将流中的数据到冲刷到硬盘中
- 在使用输出流对象时,一定要调用flush()或close()方法后,才能真正将数据写到硬盘中
- 输入流(InputStream和Reader)都有read()方法读取数据到内存中,输出流都有write()方法写入数据到硬盘中
- 所有的流类中,以Stream结尾的,都是字节流,以Reader或Writer结尾的都是字符流
- 读取硬盘中的数据时,使用输入流;将数据写入到硬盘中时,使用输出流
- 读取或写入文本文件时,使用字符流;读取或写入非文本文件时,使用字节流
使用FileInputStream和FileOutputStream读写时的注意事项
- 在使用read(byte[] bytes)方法时,每次都会读取指定数组大小的字节,将读取到的字节保存到数组中,该方法返回读取到的字节数量。如果最后一次读取的字节数不足字节数组大小时,只会用读取到的内容覆盖最前的几个元素。所以会导致读取到的内容多于实际内容。
- 在使用write(byte[] bytes)方法时,会将字节数组中的所有内容写入到输出流中,在最后一次写入时,可能会写入多余的内容。所以在写入时,最好使用write(byte[] bytes,int off,int len)方法,表示将字节数组中的元素,从off开始,写入len个字节。如有文件test.txt,其中保存aaabbbccc
FileInputStream fis = new FileInuptStream("d:/test1.txt"); FileOutputStream fos = new FileOutptStream("d:/test2.txt"); byte[] bytes=new bytes[4]; //第一次能读取4个装满数组,所以是aaab int count=fis.read(bytes); //使用这个方法写入实际读取到的字节数 fos.write(bytes,0,count); //第二次能读取4个装满数组,所以是bbcc count=fis.read(bytes); //使用这个方法写入实际读取到的字节数 fos.write(bytes,0,count); //第三次读取只能读取c,其余的元素依然是上次的元素,所以的是cbcc count=fis.read(bytes); //使用这个方法写入实际读取到的字节数 fos.write(bytes,0,count); //fos.write(bytes);//如果这样写,最后一次会多写入bcc fis.close(); fos.close();
使用FileInputStream和FileOutputStream实现单个文件的复制
package com.hqyj.IOTest; import java.io.*; public class Test3 { public static void main(String[] args) throws IOException { //创建源文件对象 File source = new File("d:/SteamTools-develop.zip"); //创建文件字节输入流对象,用于读取源文件中的内容 FileInputStream fis = new FileInputStream(source); //创建目标文件对象 File target = new File("C:\Users\Administrator\Desktop", source.getName()); //创建文件字节输出流对象,用于写入内容到该文件中 FileOutputStream fos = new FileOutputStream(target); //创建一个字节数组,按数组读写,这里表示8M byte[] bytes = new byte[1024 * 1024 * 8]; //整个数组读取,i表示读取到的字节数量 int i=fis.read(bytes); while(i!=-1){ //循环读取的同时,按字节数组写入,实际读到了多少字节,就写入多少字节 fos.write(bytes,0,i); //下次读取 i=fis.read(bytes); } fos.close(); fis.close(); if (target.exists()) { System.out.println("复制成功"); }else{ System.out.println("复制失败"); } } }
文件夹的复制
package com.hqyj.homework; import java.io.*; public class FileCopy { public static void main(String[] args) { File source = new File("d:/xiaopw84in1111"); File target = new File("C:\Users\Administrator\Desktop/copy"); copyDir(source, target); //source: d:/信息 假设该文件夹下只有一个info.txt文件 //target: 桌面:/copy //1.调用copyDir方法,判断source是一个文件夹,创建目标文件夹target"桌面:/copy" //2.遍历source文件夹,发现info.txt文件,用child表示 // 此时的source是"d:/信息/info.txt",即child,target是"桌面:/copy/info.txt" // 所以要按File(File parent,String child)构造方法创建目标文件对象,即File("桌 面/copy","info.txt"),对应代码为File(target,child.getName()) //3.递归调用copyDir方法,参数为copyDir(child,File(target,child.getName())) } public static void copyDir(File source,File target) { //判断如果是文件,直接按单文件复制的方式调用 if(source.isFile()){ copyFile(source,target); }else{//如果是文件夹 //创建目标文件夹 target.mkdir(); //展开原文件夹 for (File child : source.listFiles()) { //当前新的目标文件是 保存在目标文件夹下的源文件 //如原文件为"d:/source/info.txt" 新目标为"d:/target/info.txt" //所以按File(File parent,String child)构造方法创建目标文件对象 File newTarget = new File(target, child.getName()); //递归调用时的源文件依然是当前遍历出来的子文件 copyDir(child,newTarget); } } } public static void copyFile(File source, File target) { FileInputStream fis = null; FileOutputStream fos = null; try { //创建用于输入输出的流对象 fis = new FileInputStream(source); fos = new FileOutputStream(target); //定义字节数组 byte[] bytes = new byte[1024 * 1024 * 8]; //按字节读取,将读取到的内容马上写入 int count = fis.read(bytes); while (count != -1) { fos.write(bytes, 0, count); count = fis.read(bytes); } } catch (FileNotFoundException e) { System.out.println("文件不存在" + e); } catch (IOException e) { System.out.println("读写异常" + e); } finally { try { if (fis != null) { fis.close(); } if (fos != null) { fos.close(); } } catch (IOException e) { System.out.println("关闭流对象异常" + e); } } } }
序列化与反序列化的案例
-
Person类,必须要实现Serializable接口才可序列化
package com.hqyj.ObjectStreamTest; import java.io.Serializable; //如果要序列化某个类的对象,要将该类实现Serializable接口 //这个接口中没有任何方法,只是一个标记,表示该类的对象可以被序列化 public class Person implements Serializable { private String name; private int age; private String sex; @Override public String toString() { return "Person{" + "name='" + name + ''' + ", age=" + age + ", sex='" + sex + ''' + '}'; } public Person(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } //这里省略get/set }
-
Main类
package com.hqyj.ObjectStreamTest; import java.io.*; import java.util.ArrayList; public class Main { public static void main(String[] args) throws IOException, ClassNotFoundException { //创建一个对象 Person p = new Person("张敏", 22, "男"); //最终保存的文件 File file = new File("d:/person.p"); //创建一个字节输出流对象,参数为要写入的文件对象,即上一步的file OutputStream fos = new FileOutputStream(file); //创建一个对象输出流对象,用于序列化,参数为OutputStream对象,即上一步的fos ObjectOutputStream oos = new ObjectOutputStream(fos); //可以简写为 // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/person.p"))); //调用writeObject(Object obj)方法,将对象写入到文件中 oos.writeObject(p); //关闭 oos.close(); //创建一个对象字节输入流对象,用于反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:/person.p"))); //readObject();读取内容,返回对象类型 Object o = ois.readObject(); //要经过转型后才能当做Person对象使用 Person person = (Person) o; System.out.println(person.getName()); } }
a.util.ArrayList;
public class Main {
public static void main(String[] args) throws IOException,
ClassNotFoundException {
//创建一个对象
Person p = new Person(“张敏”, 22, “男”);
//最终保存的文件
File file = new File(“d:/person.p”);
//创建一个字节输出流对象,参数为要写入的文件对象,即上一步的file
OutputStream fos = new FileOutputStream(file);
//创建一个对象输出流对象,用于序列化,参数为OutputStream对象,即上一步的fos
ObjectOutputStream oos = new ObjectOutputStream(fos);
//可以简写为
// ObjectOutputStream oos = new ObjectOutputStream(new
FileOutputStream(new File(“d:/person.p”)));
//调用writeObject(Object obj)方法,将对象写入到文件中
oos.writeObject§;
//关闭
oos.close();
//创建一个对象字节输入流对象,用于反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new
File(“D:/person.p”)));
//readObject();读取内容,返回对象类型
Object o = ois.readObject();
//要经过转型后才能当做Person对象使用
Person person = (Person) o;
System.out.println(person.getName());
}
}