Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
Object 类位于 java.lang 包中,编译时会自动导入,我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为 Object 的子类。
Object toString() 方法用于返回对象的字符串表示形式。
语法
object.toString()
参数
返回值
返回对象的字符串表示形式。
默认返回格式:对象的 class 名称 + @ + hashCode 的十六进制字符串。
import java.util.Objects; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } /* 直接打印对象的地址值没有意义,需要重写object类的toString方法 打印对象的属性(name , age) */ /* @Override public String toString() { return "Person{name = " + name + " , age = " + age + "}"; } */ @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } /* java.lang.Object类 类Object是类层次结构的根(最顶层)类,每个类都使用Object作为超(父)类。 所有对象(包括数组)都实现这个类的方法 */ public class Demo01ToString { public static void main(String[] args) { /* Person类默认继承了Object类,所以可以使用Object类中的toString方法 String toString() 返回该对象的字符串表示 */ Person p = new Person("张三", 18); String s = p.toString(); System.out.println(s);//com.itheima.demo01.Object.Person@50cbc42f,重写后Person{name = 张三 , age = 18} //直接打印对象的名字,其实就是调用对象的toString方法p = p.toString(); System.out.println(p);//com.itheima.demo01.Object.Person@50cbc42f,重写后Person{name = 张三 , age = 18} /* 看一个类是否重写了toString方法,直接打印这个类对应对象的名字即可 如果没有重写toString方法,那么打印的就是对象的地址值(默认) 如果重写toString方法,那么就按照重写的方式打印 */ } }
Object equals() 方法用于比较两个对象是否相等。
equals() 方法比较两个对象,是判断两个对象引用指向的是同一个对象,即比较 2 个对象的内存地址是否相等。
**注意:**如果子类重写了 equals() 方法,就需要重写 hashCode() 方法,比如 String 类就重写了 equals() 方法,同时也重写了 hashCode() 方法。
语法
object.equals(Object obj)
参数
返回值
如果两个对象相等返回 true,否则返回 false。
import java.util.Objects; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } /* object类的equals方法黑认比较的是两个对象的地址值,没有意义 所以我们需要重写equals方法,比较两个对象的属性值(name, age) 对象的属性值一样,返回true,否则返回false 问题: 隐含着一个多态 object obj = new Person("古力娜扎"",18); 多态弊端:无法使用子类特有的内容(属性,方法) 解决:可以使用向下转型(强转)把object类型转换为Person */ /* @Override public boolean equals(Object obj) { //增加一个判断,传递的参数obj是null,直接返回false,提高程序的效率 if (obj == null){ return false; } //增加一个判断,是Person类型在转换,防止类型转换异常ClassCastException if(obj instanceof Person){ //使用向下转型(强转)把object类型转换为Person Person p = (Person)obj; // boolean b = this.name.equals(p.name) && this.age == p.age; return b; } return false; } */ /* @Override public boolean equals(Object o) { if (this == o) return true; //getClass() != o.getClass() 使用反射技术,判断o是否为Person类型 等效于 obj instanceof Person if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; if (age != person.age) return false; return name != null ? name.equals(person.name) : person.name == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; return result; } */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
在刚才IDEA自动重写equals代码中,使用到了java.util.objects类,那么这个类是什么呢?
在JDK7添加了一个Objects工具类,它提供了一些方法来操作对象,它由一些静态的实用方法组成,这些方法是null-save (空指针安全的)或null-tolerant (容忍空指针的),用于计算对象的hashcode、返回对象的字符串表示形式、比较两个对象。
在比较两个对象的时候,Object的equals方法容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题。方法如下:
public static boolean equals(object a,object b);
判断两个对象是否相等。
我们可以查看一下源码,学习一下:
public static boolean equals(object a,object b) { return (a == b)ll (a != null && a.equals(b)); }
/* java.util.Date;表示日期和时间的类 类Date表示特定的扬间,精确到毫秒。 毫秒:千分之一秒 1000毫秒=1秒 特定的瞬间:一个时间点,一刹那时间 2088-08-08 09:55:33:333瞬间 2088-08-08 09:55:33:334瞬间 2088-08-08 09:55:33:334瞬间 ... 毫秒值的作用:可以对时间和日期进行计算 2099-01-03到2088-01-01中间一共有多少天 可以日期转换为毫秒进行计算,计算完毕,在把毫秒转换为曰期 把日期转换为豪秒: 当前的日期:2088-01-01 时间原点(0毫秒):1970 年 1 月 1 日 00 : 00 : 00(英国格林威治) 就是计算当前日期到时间原点之间一共经历了多少毫秒 注意: 中国属于东八区,会把时间增加8小时 1970年1月1日 08:00:00 把毫秒转换为日期: 1天 = 24 * 60 * 60 = 86400 秒 = 86400 * 1000 = 86400000毫秒 */ public class Demo01Date { public static void main(String[] args) { System.out.println(System.currentTimeMillis());//获取当前系统时间的到1970年1月1日00:00:00经历了多少毫秒 } }
import java.util.Date; public class Demo02Date { public static void main(String[] args) { demo01(); demo02(); demo03(); } /* Date类的成员方法 Long getTime()把日期转换为毫秒(相当于system.currentTimeMillis()) 返回自1970年1月1日00:00:00 GNT以来此Date对象表示的毫秒数。 */ private static void demo03() { Date date = new Date(); long time = date.getTime(); System.out.println(time);//1642232462963 } /* Date类的带参数构造方法: Date(Long date):传递毫秒值,把毫秒转换为Date日期 */ private static void demo02() { Date date = new Date(0L); System.out.println(date);//Thu Jan 01 08:00:00 CST 1970 Date date1 = new Date(15811111111111111L); System.out.println(date1);//Fri Mar 02 12:38:31 CST 503004 } /* Date类的空参数构造方法: Date()获取的就是当前系统的日期和时间 */ private static void demo01() { Date date = new Date(); System.out.println(date);//Sat Jan 15 15:35:13 CST 2022 } }
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /* java.text.DateFormat:是日期、时间格式化子类的抽象类 作用: 格式化(也就是日期->文本)、解析(文本->日期) 成员方法: String format(Date date) 按照指定的模式,把Date日期格式化为符合模式的字符串 Date parse(String source) 把符合模式的字符串,解析为Date日期 DateFormat类是一个抽象类,无法直接创建对象使用,可以使用DateFormat的子类 java.text.SimpLeDateFormat extends DateFormat 构造方法: SimpleDateFormat(String pattern)用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。 参数: String pattern :传递指定的模式 模式:区分大小写的 y 年 M 月 d 日 H 时 m 分 s 秒 写对应的模式,会把模式替换为对应的日期和时间 “yyyy-MM-dd HH:mm:ss" "yyyy年MM月dd日 HH时mm分ss秒” 注意: 模式中的字母不能更改,连接模式的符号可以改变 */ public class Demo01DateFormat { public static void main(String[] args) throws ParseException { demo01(); demo02(); } /* 使用DateFormat类中的方法parse,把文本解析为日期 Date parse(String source)把符合模式的字符串,解析为Date日期 使用步骤: 1.创建simpLeDateFormat对象,构造方法中传递指定的模式 2.调用SimpleDateFormat对象中的方法parse,把符合构造方法中模式的字符串,解析为Date日期 注意: public Date parse(String source) throws ParseException parse方法声明了一个异常叫ParseException解析异常 如果字符串和构造方法中的模式不一样,那么程序就会抛出此异常 调用一个抛出了异常的方法,就必须的处理这个异常,要么throws继续声明抛出这一个异常,要么try ...catch自己处理这个异常 */ private static void demo02() throws ParseException { //1.创建simpLeDateFormat对象,构造方法中传递指定的模式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //2.调用SimpleDateFormat对象中的方法parse,把符合构造方法中模式的字符串,解析为Date日期 Date date = sdf.parse("2022-01-15 16:01:58"); System.out.println(date);//Sat Jan 15 16:01:58 CST 2022 } /* 使用DateFormat类中的方法format,把日期格式化为文本 String format(Date date)按照指定的模式,把Date日期,格式化为符合模式的字符串 使用步骤: 1.创建simpleDateFormat对象,构造方法中传递指定的模式 2.调用SimpLeDateFormat对象中的方法format,按照构造方法中指定的模式,把Date日期格式化为符合模式的字符串(文本) */ private static void demo01() { //1.创建simpleDateFormat对象,构造方法中传递指定的模式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //2.调用SimpLeDateFormat对象中的方法format,按照构造方法中指定的模式,把Date日期格式化为符合模式的字符串(文本) Date date = new Date(); String text = sdf.format(date); System.out.println(date);//Sat Jan 15 16:01:58 CST 2022 System.out.println(text);//2022-01-15 16:01:58 } }
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Scanner; /* 练习: 请使用日期时间相关的API,计算出一个人已经出生了多少天。 分析: 1.使用Scanner类中的方法next,获取出生日期 2.使用DateFormat类中的方法parse,把字符串的出生日期解析为Date格式 3.把Date格式的出生日期转换为毫秒值 4.获取当前的日期,转换为毫秒值 5.使用当前日期的毫秒值-出生日期的毫秒值 6.把毫秒值得差值转换为天(s/1000/60/60/24) */ public class Demo02Test { public static void main(String[] args) throws ParseException { //1.使用Scanner类中的方法next,获取出生日期 Scanner scanner = new Scanner(System.in); System.out.println("请输入您的出生日期,格式为yyyy-MM-dd"); String text = scanner.next(); //2.使用DateFormat类中的方法parse,把字符串的出生日期解析为Date格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = sdf.parse(text); //3.把Date格式的出生日期转换为毫秒值 long birthTime = date.getTime(); //4.获取当前的日期,转换为毫秒值 long todayTime = new Date().getTime(); //5.使用当前日期的毫秒值-出生日期的毫秒值 long time = todayTime - birthTime; //6.把毫秒值得差值转换为天(s/1000/60/60/24) System.out.println("活了" + time/1000/60/60/24 + "天"); }
/* java.util.Calendar类:日历类 Calendar类是一个抽象类,里面提供了很多操作日历字段的方法(YEAR、MONTH、DAY_OF_MONTH、HOUR) Calendar类无法直接创建对象使用,里边有一个静态方法叫getInstance(),该方法返回了Calendar类的子类对象 static Calendar getInstance() 使用默认时区和语言环境获得一个日历 */ public class Demo01Calendar { public static void main(String[] args) { Calendar c = Calendar.getInstance();//多态 System.out.println(c); } }
import java.util.Calendar; import java.util.Date; /* Calendar类的成员方法: - public int get(int field):返回给定日历字段的值。 - public void set(int field, int value):将给定的日历字段设置为给定值。 - public abstract void add(int field,int amount):根据日历的规则,为给定的日历字段添加或减去指定的时间量。 - public Date getTime( ):返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量〉的Date对象。 成员方法的参数: int fieLd:日历类的字段,可以使用Calendar类的静态成员变量获取 public static final int YEAR = 1;年 public static final int MONTH = 2;月 public static final int DATE = 5;月中的某一天 public static final int DAY_OF_MONTH = 5;月中的某一天 public static final int HOUR = 10;时 public static final int MINUTE = 12;分 public static final int SECOND = 13;秒 */ public class Demo02Calendar { public static void main(String[] args){ demo01(); demo02(); demo03(); demo04(); } /* public Date getTime():返回一个表示此Calendar时间值(从历元到现在的毫秒偏移量〉的Date对象。 把日历对象,转换为日期对象 */ private static void demo04() { Calendar c = Calendar.getInstance(); Date date = c.getTime(); System.out.println(date); } /* public abstract void add(int field,int amount);根据日历的规则,为给定的日历字段添加或减去指定的时间量。把指定的字段增加/减少指定的值 参数: int field:传递指定的日历字段(YEAR,MONTH. . . ) int amount:增加/减少的值 正数:增加 负数:减少 */ private static void demo03() { Calendar c = Calendar.getInstance(); //把年增加2年 c.add(Calendar.YEAR, 2); //把月减少3个月 c.add(Calendar.MONTH, -3); int year = c.get(Calendar.YEAR); System.out.println(year); int month = c.get(Calendar.MONTH); System.out.println(month); //int date = c.get(Calendar.DATE); int date = c.get(Calendar.DAY_OF_MONTH); System.out.println(date); } /* public void set(int field, int value):将给定的日历字段设置为给定值。 参数: int field:传递指定的日历字段(YEAR,MONTH……) int value:传递的字段设置的具体的值 */ private static void demo02() { Calendar c = Calendar.getInstance(); //设置年为9999 c.set(Calendar.YEAR, 9999); //设置月为9 c.set(Calendar.MONTH, 9); //设置日为9 c.set(Calendar.DATE, 9); //同时设置年月日,可以使用set的重载方法 c.set(8888, 8, 8); int year = c.get(Calendar.YEAR); System.out.println(year); int month = c.get(Calendar.MONTH); System.out.println(month); //int date = c.get(Calendar.DATE); int date = c.get(Calendar.DAY_OF_MONTH); System.out.println(date); } /* public int get(int field):返回给定日历字段的值。 参数:传递指定的白历字段(YEAR , MONTH. . . ) 返回值:日历字段代表具体的值 */ private static void demo01() { Calendar c = Calendar.getInstance(); int year = c.get(Calendar.YEAR); System.out.println(year); int month = c.get(Calendar.MONTH); System.out.println(month + 1);// 西方的月份0-11,东方1-12 //int date = c.get(Calendar.DATE); int date = c.get(Calendar.DAY_OF_MONTH); System.out.println(date); } }
import java.util.Arrays; /* java.lang.System类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作,在system类的API文档中,常用的方法有: - public static long currentTimeMillis( ):返回以毫秒为单位的当前时间。 - public static void arraycopy(Object src, int srcPos, object dest, int destPos, int length); 将数组中指定的数据拷贝到另一个数组中。 */ public class Demo01System { public static void main(String[] args){ demo01(); demo02(); } /* public static void arraycopy(Object src, int srcPos, object dest, int destPos, int length); 将数组中指定的数据拷贝到另一个数组中。 参数: src -源数组。 srcPos -源数组中的起始位置。 dest -目标数组。 destPos -自标数据中的起始位置。 Length -要复制的数组元素的数量。 练习: 将src数组中前3个元素,复制到dest数组的前3个位置上 复制元素前:src数组元素[1,2,3,4,5], dest数组元素[6,7,8,9,10] 复制元素后:src数组元素[1,2,3,4,5],dest数组元素[1,2,3,9,10] */ private static void demo02() { int[] src = {1, 2, 3, 4, 5}; int[] dest = {6, 7, 8, 9, 10}; System.out.println("复制前:" + Arrays.toString(src));//[1, 2, 3, 4, 5] System.arraycopy(src, 0, dest, 0,3); System.out.println("复制后:" + Arrays.toString(dest));//[1, 2, 3, 9, 10] } /* public static Long currentTimeMillis():返回以毫秒为单位的当前时间。 用来测试程序的效率 练习: 验证for循环打印数字1-9999所需要使用的时间(毫秒) */ private static void demo01() { //程序执行前,获取一次毫秒值 long s = System.currentTimeMillis(); //执行for循环 for (int i = 1; i <= 9999; i++) { System.out.println(i); } long e = System.currentTimeMillis(); System.out.println("程序共耗时:" + (e - s) + "毫秒");//程序共耗时:97毫秒 } }
/* java .Lang. stringBuilder类:字符串缓冲区,可以提高字符串的效率构造方法: - public StringBuilder():构造一个空的StringBuilder容器。 - public StringBuilder(String str):构造一个StringBuilder容器,并将字符串添加进去。 */ public class Demo01StringBuilder { public static void main(String[] args) { StringBuilder bu1 = new StringBuilder(); System.out.println("bu1 = " + bu1);//bu1 = StringBuilder bu2 = new StringBuilder("Hello"); System.out.println("bu2 = " + bu2);//bu2 = Hello } }
/* StringBuilder类的成员方法: public StringBuilder append(...):添加任意类型数据的字符串形式,并返回当前对象自身。 参数: 可以是任意的数据类型 */ public class Demo02StringBuilder { public static void main(String[] args) { StringBuilder bu1 = new StringBuilder(); //使用append方法返回的是this,调用方法的对象是bu1 StringBuilder bu2 = bu1.append("abc"); System.out.println(bu1);//abc System.out.println(bu2);//abc System.out.println(bu1 == bu2);//true 两个对象是同一个对象 } }
/* StringBuilder和String可以相互转换: String->StringBuilder:可以使用StringBuilder的构造方法 StringBuilder(String str)构造一个字符串生成器,并初始化为指定的字符串内容。 StringBuilder->String:可以使用StringBuilder中的toString方法 public String toString( ):将当前stringBuilder对象转换为String对象。 */ public class Demo03StringBuilder { public static void main(String[] args) { String str = "Hello"; System.out.println("str: " + str); StringBuilder bu = new StringBuilder(str); bu.append("World"); System.out.println("bu: " + bu); String s = bu.toString(); System.out.println("s: " + s); } }
Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:
基本类型 | 对应的包装类(位于java.lang包中) |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
包装类概念:
基本数据类型的数据,使用起来非常的方便,但是没有对应的方法来操作这些数据,所以我们可以使用一个类把基本类型的数据包装起来这个类叫包装类,在包装类中可以定义—些方法用来操作基本类型的数据。
基本类型与对应的包装类对象之间,来回转换的过程称为"装箱"与"拆箱”︰
/* 装箱:把基本类型的数据,包装到包装类中(基本类型的数据->包装类) 构造方法: Integer(int value)构造一个新分配的 Integer对象,它表示指定的 int 值。 Integer(String s)构造一个新分配的Integer对象,它表示String参数所指示的int 值。 注意: 传递的字符串,必须是基本类型的字符串,否则会抛出异常 “100”正确 “a”抛异常 静态方法: static Integer valueOf(int i)返回一个表示指定的 int 值的Integer 实例。 static Integer valueOf(String s)返回保存指定的 String 的值的Integer对象。 拆箱:在包装类中取出基本类型的数据(包装类-->基本类型的数据) 成员方法: int intValue();以int类型返回该Integer的值。 */ public class Demo01Integer { public static void main(String[] args) { Integer in1 = new Integer(1);//方法上有横线,说明方法过时了 System.out.println(in1);//1 重写了toString方法 Integer in2 = new Integer("1"); System.out.println(in2);//1 //静态方法 Integer in3 = Integer.valueOf(1); System.out.println(in3);//1 Integer in4 = Integer.valueOf("1"); //Integer in4 = Integer.valueOf("a");//NumberFormatException数字格式化异常 System.out.println(in4);//1 //拆箱:在包装类中取出基本类型的数据(包装类-->基本类型的数据) int i = in1.intValue(); System.out.println(i);//1 } }
import java.util.ArrayList; /* 自动装箱与自动拆箱: 基本类型的数据和包装类之间可以自动的相互转换 JDKi.5之后出现的新特性 */ public class Demo02Integer { public static void main(String[] args) { /* 自动装箱:直接把int类型的整数赋值给包装类 Integer in = 1;就相当于Integer in = new Integer(1); */ Integer in = 1; /* 自动拆箱:in是包装类,无法直接参与运算,可以自动转换为基本类型的数据,再参与计算 in + 2;就相当于in.intValue() + 3 = 3; in - in +2;就相当于in - new Integer(3)自动装箱 */ in = in + 2; System.out.println(in);//3 //ArrayList集合无法直接存储整数,可以存储Integer包装类 ArrayList<Integer> list = new ArrayList<>(); list.add(1);//自动装箱list.add(new Integer(1)); int a = list.get(0);//自动拆箱List.get(0).intValue(); } }
** 基本类型转换为String**
基本类型转换String总共有三种方式,查看课后资料可以得知,这里只讲最简单的一种方式:
基本类型直接与""相连接即可;如:34+""
String转换成对应的基本类型
除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型︰
/* 基本类型与字符串之间的转换 基本类型-->字符串 1.基本类型数据的值+""最简单的方式(工作中常用) 2.使用包装类中的静态方法 static String toString(int i)返回一个表示指定整数的 String 对象。 3.使用String类中的静态方法 static String valueOf(int i)返回int参数的字符串表示形式。 字符串-->基本类型 使用包装类的静态方法parseXxx("字符串") Integer类: static int parseInt( String s) Double类: static double parseDouble(String s) …… */ public class Demo03Integer { public static void main(String[] args) { //基本类型-->字符串 String s1 = 100 + ""; System.out.println(s1 + 200);//100200 String s2 = Integer.toString(100); System.out.println(s2 + 200);//100200 String s3 = String.valueOf(100); System.out.println(s3 + 200);//100200 //字符串-->基本类型 int i = Integer.parseInt("100"); System.out.println(i + 200);//300 // int i2 = Integer.parseInt("a");//NumberFormatException数字化格式异常 // System.out.println(i2); } }
在前面基础班我们已经学习过并使用过集合ArrayList,那么集合到底是什么呢?
早在 Java 2 中之前,Java 就提供了特设类。比如:Dictionary, Vector, Stack, 和 Properties 这些类用来存储和操作对象组。
虽然这些类都非常有用,但是它们缺少一个核心的,统一的主题。由于这个原因,使用 Vector 类的方式和使用 Properties 类的方式有着很大不同。
集合框架被设计成要满足以下几个目标。
集合框架定义了一些接口。本节提供了每个接口的概述:
序号 | 接口描述 |
---|---|
1 | Collection 接口 Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。 Collection 接口存储一组不唯一,无序的对象。 |
2 | List 接口 List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。 List 接口存储一组不唯一,有序(插入顺序)的对象。 |
3 | Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。 Set 接口存储一组唯一,无序的对象。 |
4 | SortedSet 继承于Set保存有序的集合。 |
5 | Map Map 接口存储一组键值对象,提供key(键)到value(值)的映射。 |
6 | Map.Entry 描述在一个Map中的一个元素(键/值对)。是一个 Map 的内部接口。 |
7 | SortedMap 继承于 Map,使 Key 保持在升序排列。 |
8 | Enumeration 这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。 |
Set和List的区别
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
public boolean add(E e);
把给定的对象添加到当前集合中。public void clear();
清空集合中所有的元素。public boolean remove(E e);
把给定的对象在当前集合中删除。public boolean contains(E e);
判断当前集合中是否包含给定的对象。public boolean isEmpty();
判断当前集合是否为空。public int size();
返回集合中元素的个数。public object[ ] toArray();
把集合中的元素,存储到数组中。import java.util.ArrayList; import java.util.Collection; /* java.utiL.collection接口 所有单列集合的最顶层的接口,里边定义了所有单列集合共性的方法 任意的单列集合都可以使用collection接口中的方法 共性的方法: public booLean add(E e):把给定的对象添加到当前集合中。 public void clear():清空集合中所有的元素。 public booLean remove(E e):把给定的对象在当前集合中删除。 public boolean contains(E e):判断当前集合中是否包含给定的对象。 public boolean isEmpty():判断当前集合是杏为空。 public int size():返回集合中元素的个数。 public Object[] toArray ():把集合中的元素,存储到数组中。 */ public class Demo01Collection { public static void main(String[] args) { //创建集合对象,可以使用多态 Collection<String> coll = new ArrayList<>(); System.out.println(coll);//重写了toString方法[] /* public boolean add(E e):把给定的对象添加到当前集合中。 返回值是一个boolean值,一般都返回true,所以可以不用接收 */ boolean b1 = coll.add("张三"); System.out.println("b1:" + b1);//b1:true System.out.println(coll);//[张三] coll.add("李四"); coll.add("赵六"); coll.add("田七"); System.out.println(coll);//[张三, 李四, 赵六, 田七] /* public boolean remove(E e):把给定的对象在当前集合中删除。 返回值是一个booLean值,集合中存在元素,删除元素,返回true 集合中不存在元素,册除失败,返回false */ boolean b2 = coll.remove("赵六"); System.out.println("b2:" + b2);//b2:true boolean b3 = coll.remove("赵四"); System.out.println("b3:" + b3);//b3:false System.out.println(coll);//[张三, 李四, 田七] /* public boolean contains(E e):判断当前集合中是否包含给定的对象。 包含返回true 不包含返回false */ boolean b4 = coll.contains("李四"); System.out.println("b4:" + b4);//b4:true boolean b5 = coll.contains("赵四");//b5:false System.out.println("b5:" + b5); /* public boolean isEmpty():判断当前集合是杏为空。 集合为空返回true,集合不为空返回false */ boolean b6 = coll.isEmpty(); System.out.println("b6:" + b6);//b6:false //public int size():返回集合中元素的个数。 int size = coll.size(); System.out.println("size:" + size);//size:3 //public Object[] toArray ():把集合中的元素,存储到数组中。 Object[] arr = coll.toArray(); for (int i = 0; i < arr.length; i++){ System.out.println(arr[i]); } //public void clear():清空集合中所有的元素。但是不删除集合,集合还存在 coll.clear(); System.out.println(coll);//[] System.out.println(coll.isEmpty());//true } }
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /* java.util.Iterator接口:迭代器(对集合进行遍历) 有两个常用的方法 boolean hasNext()如果仍有元素可以迭代,则返回true. 判断集合中还有没有下一个元素,有就返回true,没有就返回false E next() 返回迭代的下一个元素。 取出集合中的下一个元素 Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊 Collection接口中有一个方法,叫iterator(),这个方法返回的就是迭代器的实现类对象 Iterator<E> iterator()返回在此 collection的元素上进行迭代的迭代器。 迭代器的使用步骤(重点): 1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态) 2.使用Iterator接由中的方法hasNext岁断还有没有下一个元素 3.使用Iterator接口中的方法next取出集合中的下一个元素 */ public class Demo01Iterator { public static void main(String[] args) { //创建一个集合对象 Collection<String> coll = new ArrayList<>(); //往集合中添加元素 coll.add("姚明"); coll.add("科比"); coll.add("麦迪"); coll.add("詹姆斯"); /* 1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态) 注意: Iterator<E>接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型 */ //多态 接口 实现类对象 Iterator<String> it = coll.iterator(); //2.使用Iterator接口中的方法hasNext判断还有没有下一个元素 boolean b = it.hasNext(); System.out.println(b);//true //3.使用Iterator接口中的方法next取出集合中的下一个元素 /* String s = it.next(); System.out.println(s); b = it.hasNext(); System.out.println(b); s = it.next(); System.out.println(s); b = it.hasNext(); System.out.println(b); s = it.next(); System.out.println(s); b = it.hasNext(); System.out.println(b); s = it.next(); System.out.println(s); b = it.hasNext(); System.out.println(b);//false s = it.next();//没有元素,在取出元素会抛出NoSuchElementException没有元素异常 */ while (b){ String s = it.next(); System.out.println(s); b = it.hasNext(); } } }
迭代器实现原理
import java.util.ArrayList; /* 增强for循环:底层使用的也是迭代器,使用for循环的格式,简化了迭代器的书写 是JDK1.5之后出现的新特性 collection<E> extends Iterable<E>:所有的单列集合都可以使用增强for public interface Iterable<T>实现这个接口允许对象成为“foreach”语句的目标。 增强for循环:用来遍历集合和数组 格式: for(集合/数组的数据类型 变量名 : 集合名/数组名){ sout(变量名) } */ public class Demo02Foreach { public static void main(String[] args) { demo01(); demo02(); } //使用增强for循环遍历集合 private static void demo02() { ArrayList<String> list = new ArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); for(String s : list){ System.out.println(s); } } //使用增强for循环遍历数组 private static void demo01() { int[] arr = {1, 2, 3, 4, 5}; for(int i : arr){ System.out.println(i); } } }
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
/* 定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间 格式: 修饰符 <泛型> 返回值类型 方法名 (参数列表(使用泛型)) { 方法体; } 含有泛型的方法,在调用方法的时候确定泛型的数据类型 传递什么类型的参数,泛型就是什么类型 */ public class GenericMethod { //定义一个含有泛型的方法 public <M> void method01 (M m){ System.out.println(m); } //定义一个含有泛型的静态方法 public static <S> void method02(S s){ System.out.println(s); } } /* 测试含有泛型的方法 */ public class Demo03GenericMethod { public static void main(String[] args) { GenericMethod gm = new GenericMethod(); gm.method01(10); gm.method01("abc"); System.out.println("======================="); gm.method02("静态方法,不建议创建对象使用"); //静态方法,通过类名.方法名(参数)可以直接使用 GenericMethod.method02("静态方法"); GenericMethod.method02(1); } }
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
/* 定义一个含有泛型的类,模拟ArrayList集合 泛型是一个未知的数据类型,当我们不确定什么什么数据类型的时候,可以使用泛型 泛型可以接收任意的数据类型,可以使用Integer, string,student... 创建对家的时候确定泛型的数据类型 */ public class GenericClass<E>{ private E name; public GenericClass() { } public GenericClass(E name) { this.name = name; } public E getName() { return name; } public void setName(E name) { this.name = name; } } public class Demo02GenericClass { public static void main(String[] args) { GenericClass gc = new GenericClass(); gc.setName("不写泛型默认为Object类型"); Object obj = gc.getName(); System.out.println(obj); //创建GenericClass对象,泛型使用Integer类型 GenericClass<Integer> gc2 = new GenericClass<>(); gc2.setName(1); Integer name = gc2.getName(); System.out.println(name); //创建GenericClass对象,泛型使用String类型 GenericClass<String> gc3 = new GenericClass<>(); gc3.setName("小明"); String name1 = gc3.getName(); System.out.println(name1); } }
/* 定义含有泛型的接口 */ public interface GenericInterface<I> { public abstract void method(I i); } /* 含有泛型的接口,第一种使用方式:定义接口的实现类,实现接口,指定接口的泛型 public interface Iterator<E> { E next( ); } Scanner类实现了Iterator接口,并指定接口的泛型为String,所以重写的next方法泛型默认就是String public final class Scanner implements Iterator<String>{ public string next() {} } */ public class GenericInterfaceImpl1 implements GenericInterface<String>{ @Override public void method(String s) { System.out.println(s); } } /* 含有泛型的接口第二种使用方式:接口使用什么泛型,实现类就使用什么泛型,类跟着接口走 就相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型 public interface List<E>{ boolean add(E e); E get(int index); } public class ArrayList<E> implements list<E>{ public booLean add(E e){} public E get(int index){} } */ public class GenericInterfaceImpl2<I> implements GenericInterface<I>{ @Override public void method(I i) { System.out.println(i); } } /* 测试含有泛型的接口 */ public class Demo04GenericInterface { public static void main(String[] args) { //创建GenericInterfaceImpl1对象 GenericInterfaceImpl1 gi1 = new GenericInterfaceImpl1(); gi1.method("字符串"); //创建GenericInterfaceImpl2对象 GenericInterfaceImpl2<Integer> gi2 = new GenericInterfaceImpl2<>(); gi2.method(10); } }
类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List,List 等所有 List<具体类型实参> 的父类。
import java.util.ArrayList; import java.util.Iterator; /* 泛型的通配符: ?:代表任意的数据类型 使用方式: 不能创建对象使用 只能作为方法的参数使用 */ public class Demo05Generic { public static void main(String[] args) { ArrayList<Integer> list01 = new ArrayList<>(); list01.add(1); list01.add(2); ArrayList<String> list02 = new ArrayList<>(); list02.add("a"); list02.add("b"); printArray(list01); printArray(list02); } /* 定义一个方法,能遍历所有类型的ArrayList集合 这时候我们不知道ArrayList集合使用什么数据类型,可以泛型的通配符?来接收数据类型 */ public static void printArray(ArrayList<?> list){ //使用迭代器遍历集合 Iterator<?> it = list.iterator(); while(it.hasNext()){ //it.next()方法,取出的元素是自动向上转型为Object,可以接收任意的数据类型 Object o = it.next(); System.out.println(o); } } }
import java.util.ArrayList; import java.util.Collection; /* 泛型的上限限定:?extends E 代表使用的泛型只能是E类型的子类/本身 泛型的上限限定:?super E 代表使用的泛型只能是E类型的父类/本身 */ public class Demo06Generic { public static void main(String[] args) { Collection<Integer> list1 = new ArrayList<Integer>(); Collection<String> list2 = new ArrayList<String>(); Collection<Number> list3 = new ArrayList<Number>(); Collection<Object> list4 = new ArrayList<Object>(); getElement1(list1); //getElement1(list2);//报错 getElement1(list3); //getElement1(list4);//报错 //getElement2(list1);//报错 //getElement2(list2);//报错 getElement2(list3); getElement2(list4); /* 类与类之间的继承关系 Integer extends Number extends Object String extends Object */ } //泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类 private static void getElement1(Collection<? extends Number> coll) { } //泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类 private static void getElement2(Collection<? super Number> coll) { } }
题目
按照斗地主的规则,完成洗牌发牌的动作。
具体规则∶
使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
分析
import java.util.ArrayList; import java.util.Collection; import java.util.Collections; /* 斗地主综合案例: 1.准备牌 2.洗牌 3.发牌 4.看牌 */ public class DouDiZhu { public static void main(String[] args) { //1.准备牌 //定义一个存储54张牌的ArrayList集合,泛型使用String ArrayList<String> poker = new ArrayList<>(); //定义两个数组,一个数组存储牌的花色,一个数组存储牌的序号 String[] colors = {"♠", "♥", "♣", "♦"}; String[] numbers = {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"}; //先把大王和小王存储到poker集合中 poker.add("大王"); poker.add("小王"); //循环嵌套遍历两个数组,组装52张牌 for (String number : numbers){ for (String color : colors){ //System.out.println(color + number); //把组装好的牌存储到poker集合中 poker.add(color + number); } } /* 2.洗牌 使用集合的工具类Collections中的方法 static void shuffle(List<?> list)使用默认随机源对指定列表进行置换 */ Collections.shuffle(poker); //System.out.println(poker); ArrayList<String> player01 = new ArrayList<>(); ArrayList<String> player02 = new ArrayList<>(); ArrayList<String> player03 = new ArrayList<>(); ArrayList<String> dipai = new ArrayList<>(); /* 遍历poker集合,获取每一张牌 使用poker集含的索引%3给3个玩家轮流发牌 剩余3张牌给底牌 注意: 先判断底牌(i>=51),否则牌就发没了 */ for (int i = 0; i < poker.size(); i++){ //获取每一张牌 String p = poker.get(i); //轮流发牌 if (i >= 51){ //给底牌发牌 dipai.add(p); }else if (i % 3 == 0){ //给玩家1发牌 player01.add(p); }else if (i % 3 == 1){ //给玩家2发牌 player02.add(p); }else if (i % 3 == 2){ //给玩家3发牌 player03.add(p); } } //4.看牌 System.out.println("刘德华:" + player01); System.out.println("周润发:" + player02); System.out.println("周星驰:" + player03); System.out.println("底牌:" + dipai); } }
栈是一种具有 「先入后出」 特点的抽象数据结构,可使用数组或链表实现。
Stack<Integer> stack = new Stack<>();
如下图所示,通过常用操作「入栈 push()」,「出栈 pop()」,展示了栈的先入后出特性。
stack.push(1); // 元素 1 入栈 stack.push(2); // 元素 2 入栈 stack.pop(); // 出栈 -> 元素 2 stack.pop(); // 出栈 -> 元素 1
**注意:**通常情况下,不推荐使用 Java 的 Vector 以及其子类 Stack ,而一般将 LinkedList 作为栈来使用。详细说明请见:Stack,ArrayDeque,LinkedList 的区别
LinkedList<Integer> stack = new LinkedList<>(); stack.addLast(1); // 元素 1 入栈 stack.addLast(2); // 元素 2 入栈 stack.removeLast(); // 出栈 -> 元素 2 stack.removeLast(); // 出栈 -> 元素 1
队列是一种具有 「先入先出」 特点的抽象数据结构,可使用链表实现。
Queue<Integer> queue = new LinkedList<>();
如下图所示,通过常用操作「入队 offer()」,「出队 poll()」,展示了队列的先入先出特性。
queue.offer(1); // 元素 1 入队 queue.offer(2); // 元素 2 入队 queue.poll(); // 出队 -> 元素 1 queue.poll(); // 出队 -> 元素 2
数组是将相同类型的元素存储于连续内存空间的数据结构,其长度不可变。
数组特点:查询快,增删慢
如下图所示,构建此数组需要在初始化时给定长度,并对数组每个索引元素赋值,代码如下
// 初始化一个长度为 5 的数组 array int[] array = new int[5]; // 元素赋值 array[0] = 2; array[1] = 3; array[2] = 1; array[3] = 0; array[4] = 2;
或者可以使用直接赋值的初始化方式,代码如下:
int[] array = {2, 3, 1, 0, 2};
「可变数组」是经常使用的数据结构,其基于数组和扩容机制实现,相比普通数组更加灵活。常用操作有:访问元素、添加元素、删除元素。
// 初始化可变数组 List<Integer> array = new ArrayList<>(); // 向尾部添加元素 array.add(2); array.add(3); array.add(1); array.add(0); array.add(2);
链表以节点为单位,每个元素都是一个独立对象,在内存空间的存储是非连续的。链表的节点对象具有两个成员变量:「值 val」,「后继节点引用 next」 。
链表特点:查询慢,增删快
class ListNode { int val; // 节点值 ListNode next; // 后继节点引用 ListNode(int x) { val = x; } }
如下图所示,建立此链表需要实例化每个节点,并构建各节点的引用指向。
// 实例化节点 ListNode n1 = new ListNode(4); // 节点 head ListNode n2 = new ListNode(5); ListNode n3 = new ListNode(1); // 构建引用指向 n1.next = n2; n2.next = n3;
树是一种非线性数据结构,根据子节点数量可分为 「二叉树」 和 「多叉树」,最顶层的节点称为「根节点 root」。以二叉树为例,每个节点包含三个成员变量:「值 val」、「左子节点 left」、「右子节点 right」 。
class TreeNode { int val; // 节点值 TreeNode left; // 左子节点 TreeNode right; // 右子节点 TreeNode(int x) { val = x; } }
如下图所示,建立此二叉树需要实例化每个节点,并构建各节点的引用指向。
// 初始化节点 TreeNode n1 = new TreeNode(3); // 根节点 root TreeNode n2 = new TreeNode(4); TreeNode n3 = new TreeNode(5); TreeNode n4 = new TreeNode(1); TreeNode n5 = new TreeNode(2); // 构建引用指向 n1.left = n2; n1.right = n3; n2.left = n4; n2.right = n5;
排序树/查找树
在二叉树的基础上,元素是有大小顺序的
左子树小,右子树大
平衡树
平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
红黑树是一种特定类型的二叉树,它是在计算机科学中用来组织数据比如数字的块的一种结构。
红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但 对之进行平衡的代价较低, 其平均统计性能要强于 AVL 。
由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法,在查找过程中不需要颜色信息。
红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 结点是红色或黑色。
性质2. 根结点是黑色。
性质3. 所有叶子都是黑色。(叶子是NIL结点)
性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
性质5. 从任一节结点其每个叶子的所有路径都包含相同数目的黑色结点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
是性质4导致路径上不能有两个连续的红色结点确保了这个结果。最短的可能路径都是黑色结点,最长的可能路径有交替的红色和黑色结点。因为根据性质5所有最长的路径都有相同数目的黑色结点,这就表明了没有路径能多于任何其他路径的两倍长。
因为红黑树是一种特化的二叉查找树,所以红黑树上的只读操作与普通二叉查找树相同。
java.util.List
接口继承自collection
接口,是单列集合的一个重要分支,习惯性地会将实现了List
接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
import java.util.ArrayList; import java.util.Iterator; import java.util.List; /* java. utiL.List接口extends collection接口 list接口的特点: 1.有序的集合存储元素和取出元素的顺序是一致的(存储123取出123) 2.有索引包含了一些带索引的方法 3.允许存储重复的元素 List接口中带索引的方法(特有) - public void add(int index,E element):将指定的元素,添加到该集合中的指定位置上。 - public E get(int index):返回集合中指定位置的元素。 - public E remove(int index):移除列表中指定位置的元素,返回的是被移除的元素。 - public E set(int index,E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。 注意: 操作索引的时候,一定要防止索引越界异常 IndexOutOfBoundsException:索引越界异常 ArrayIndexOutOfBoundsException:数组索引越界异常 StringIndexOutOfBoundsException:字符串索引越界异常 */ public class Demo01List { public static void main(String[] args) { //创建一个List集合对象,多态 List<String> list = new ArrayList<>(); //使用add方法往集合中添加元素 list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("a"); //打印集合 System.out.println(list);//[a, b, c, d, a] //public void add(int index,E element):将指定的元素,添加到该集合中的指定位置上。 //在c和d之间添加一个itheima list.add(3, "itheima"); System.out.println(list);//[a, b, c, itheima, d, a] //public E remove(int index):移除列表中指定位置的元素,返回的是被移除的元素。 String remove = list.remove(2); System.out.println("被移除的元素:" + remove);//被移除的元素:c System.out.println(list);//[a, b, itheima, d, a] //public E set(int index,E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。 //把最后一个a替换为A String setE = list.set(4, "A"); System.out.println("被替换的元素:" + setE);//被替换的元素:a System.out.println(list);//[a, b, itheima, d, A] //List集合遍历有3种方法 //使用普通的for循环 for (int i = 0; i < list.size(); i++) { //public E get(int index):返回集合中指定位置的元素。 String s = list.get(i); System.out.println(s); } System.out.println("======================"); //使用迭代器 Iterator<String> it = list.iterator(); while (it.hasNext()){ String s = it.next(); System.out.println(s); } System.out.println("======================"); //使用增强for for (String s : list){ System.out.println(s); } } }
java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。
许多程序员开发时非常随意地使用ArrayList
完成任何需求,并不严谨,这种用法是不提倡的。
java.util.LinkedList
集合数据存储的结构是链表结构。链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
链表可分为单向链表和双向链表。
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。
一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。
Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。
与 ArrayList 相比,LinkedList 的增加和删除的操作效率更高,而查找和修改的操作效率较低。
以下情况使用 ArrayList :
特有方法实现如下:
import java.util.LinkedList; /* java.utiL.LinkedList集合implements list接口 LinkedList集合的特点: 1.底层是一个链表结构:查询慢,增删快 2.里边包含了大量操作首尾元素的方法 注意:使用LinkedList集合特有的方法,不能使用多态 - public void addFirst(E e):将指定元素插入此列表的开头。 - public void addLast(E e):将指定元素添加到此列表的结尾。此方法等效于与add(E). - public void push(E e):将元素推入此列表所表示的堆栈。此方法等效于addFirst(E). - public E getFirst():返回此列表的第一个元素。 - public E getLast():返回此列表的最后一个元素。 - public E removeFirst():移除并返回此列表的第一个元素。 - public E removeLast():移除并返回此列表的最后一个元素。 - public E pop():从此列表所表示的推钱处弹出一个元素。此方法相当于removeFirst - public boolean isEmpty():如果列表不包含元素,则返回true。 */ public class Demo02LinkedList { public static void main(String[] args) { show01(); show02(); show03(); } /* public E removeFirst():移除并返回此列表的第一个元素。 public E removeLast():移除并返回此列表的最后一个元素。 public E pop():从此列表所表示的推钱处弹出一个元素。 */ private static void show03() { //创建LinkedList集合对象 LinkedList<String> linked = new LinkedList<>(); //使用add方法往集合中添加元素 linked.add("a"); linked.add("b"); linked.add("c"); System.out.println(linked); //String first = linked.removeFirst(); String first = linked.pop(); System.out.println("被移除的第一个元素:" + first);//被移除的第一个元素:a String last = linked.removeLast(); System.out.println("被移除的最后一个元素:" + last);//被移除的最后一个元素:c System.out.println(linked);//[b] } private static void show02() { //创建LinkedList集合对象 LinkedList<String> linked = new LinkedList<>(); //使用add方法往集合中添加元素 linked.add("a"); linked.add("b"); linked.add("c"); //linked.clear();//清空集合中的元素,在获取集合中的元素会抛出NoSuchElementException //public boolean isEmpty();//如果列表不包含元素,则返回true。 if (linked.isEmpty()){ String first = linked.getFirst(); System.out.println(first);//a String last = linked.getLast(); System.out.println(last);//c } } /* public void addFirst(E e):将指定元素插入此列表的开头。 public void addLast(E e):将指定元素添加到此列表的结尾。 public void push(E e):将元素推入此列表所表示的堆栈。 */ private static void show01() { //创建LinkedList集合对象 LinkedList<String> linked = new LinkedList<>(); //使用add方法往集合中添加元素 linked.add("a"); linked.add("b"); linked.add("c"); System.out.println(linked);//[a, b, c] //public void addFirst(E e):将指定元素插入此列表的开头。 //linked.addFirst("www"); //System.out.println(linked);//[www, a, b, c] //public void push(E e):将元素推入此列表所表示的堆栈。此方法等效于addFirst(E). linked.push("www"); System.out.println(linked);//[www, a, b, c] //public void addLast(E e):将指定元素添加到此列表的结尾。此方法等效于与add(E). linked.addLast("www"); System.out.println(linked);//[www, a, b, c, www] } }
java.util.Set
接口和java.util.List
接口一样,同样继承自Collegtion
接口,它与Collection
接口中的方法基本一致,并没有对Collection
接口进行功能上的扩充,只是比 Collection
接口更加严格了。与List 接口不同的是,Set 接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set集合有多个子类,这里我们介绍其中的java.util.HashSet
、 java.util.LinkedHashSet
这两个集合。
tips:Set集合取出元素的方式可以采用:迭代器、增强for。
import java.util.HashSet; import java.util.Iterator; import java.util.Set; /* java.util.Set接口 extends Collection set接口的特点: 1.不允许存储重复的元素 2.没有索引没有带索引的方法,也不能使用普通的for循环遍历 java.util.HashSet集合 implements set接口 HashSet特点: 1.不允许存储重复的元素 2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历 3.是一个无序的集合,存储元素和取出元素的顺序有可能不一致 4.底层是一个哈希表结构(查询的速度非常的快) */ public class Demo01Set { public static void main(String[] args) { Set<Integer> set = new HashSet<>(); //使用add方法往集合中添加元素 set.add(1); set.add(3); set.add(2); set.add(1); //使用迭代器遍历set集合 Iterator<Integer> it = set.iterator(); while (it.hasNext()){ Integer n = it.next(); System.out.println(n);//1 2 3 } System.out.println("=================="); //使用增强for遍历set集合 for(Integer n : set){ System.out.println(n);//1 2 3 } System.out.println("=================="); System.out.println(set);//[1, 2, 3] } }
hashCode()方法
/* 哈希值:是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,不是数据实际存储的物理地址) 在Object类有一个方法,可以获取对象的哈希值 int hashCode返回该对象的哈希码值。 hashCode()方法的源码: public native int hashCode(); native:代表该方法调用的是本地操作系统的方法 */ public class Demo01HashCode { public static void main(String[] args){ Person p1 = new Person(); int h1 = p1.hashCode(); System.out.println(h1);//1355531311 Person p2 = new Person(); int h2 = p2.hashCode(); System.out.println(h2);//1967205423 /* toString()方法的源码: public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } */ System.out.println(p1);//com.itheima.demo03.hashCode.Person@50cbc42f System.out.println(p2);//com.itheima.demo03.hashCode.Person@75412c2f /* String类的哈希值 String类重写Object类的hashCode方法 */ String s1 = new String("abc"); String s2 = new String("abc"); System.out.println(s1.hashCode());//96354 System.out.println(s2.hashCode());//96354 System.out.println("重地".hashCode());//1179395 System.out.println("通话".hashCode());//1179395这两个是巧合 } }
HashSet集合存储数据的结构(哈希表)
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
import java.util.Objects; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } } import java.util.HashSet; /* HashSet存储自定义类型元素 set集合报错元素唯一: 存储的元素(String,Integer, . . .student , Person.. .) ,必须重写hashcode方法和equals方法 要求: 同名同年龄的人,视为同一个人,只能存储一次 */ public class Demo03HashSetSavePerson { public static void main(String[] args) { //创建HashSet集合存储Person HashSet<Person> set = new HashSet<>(); Person p1 = new Person("小美女", 18); Person p2 = new Person("小美女", 18); Person p3 = new Person("小美女", 19); System.out.println(p1.hashCode());//重写hashCode()方法前 1355531311 | 重写hashCode()方法后 734175839 System.out.println(p2.hashCode());//重写hashCode()方法前 1967205423 | 重写hashCode()方法后 System.out.println(p1==p2);//false System.out.println(p2.equals(p2));//重写equals()方法前 false | 重写equals()方法后 true set.add(p1); set.add(p2); set.add(p3); System.out.println(set); } }
import java.util.HashSet; import java.util.LinkedHashSet; /* java.util.LinkedHashSet集合 extends HashSet集合 LinkedHashSet集合特点: 底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(记录元素的存储顺序),保证元素有序 */ public class Demo04LinkedHashSet { public static void main(String[] args) { HashSet<String> set = new HashSet<>(); set.add("www"); set.add("abc"); set.add("abc"); set.add("itcast"); System.out.println(set);//[abc, www, itcast] 无序的,不允许重复 LinkedHashSet<String> linked = new LinkedHashSet<>(); linked.add("www"); linked.add("abc"); linked.add("abc"); linked.add("itcast"); System.out.println(linked);//[www, abc, itcast] 有序的,不允许重复 } }
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
其实这个书写完全等价与
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。
JDK1.5以后。出现了简化操作。…用在参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
/* 可变参数:是JDK1.5之后出现的新特性 使用前提: 当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数. 使用格式:定义方法时使用 修饰符 返回值类型 方法名(数据类型...变量名){} 可变参数的原理: 可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数 传递的参数个数,可以是0个(不传递),1,2...多个 */ public class Demo01VarArgs { public static void main(String[] args) { //int i = add(); //int i = add(10); //int i = add(10, 20); int i = add(10, 20, 30, 40, 50, 60, 70, 80, 90, 100); System.out.println(i); } /* 可变参数的注意事项 1.一个方法的参数列表,只能有一个可变参数 2.如果方法的参数有多个,那么可变参数必须写在参数列表的末尾 */ //可变参数的特殊(终极)写法 public static void method(Object ...obj){ } /* 定义计算(0-n)整数和的方法 已知:计算整数的和,数据类型已经确定int 但是参数的个数不确定,不知道要计算几个整数的和,就可以使用可变参数 add();就会创建一个长度为0的数组,new int[0] add(10);就会创建一个长度为1的数组,存储传递过来的参数 new int[]{10} add(10, 20);就会创建一个长度为2的数组,存储传递过来的参数 new int[]{10, 20} add(10, 20, 30, 40, 50, 60, 70, 80, 90, 100);就会创建一个长度为10的数组,存储传递过来的参数 new int[]{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; */ public static int add(int...arr){ //System.out.println(arr);//[I@1c53fd30 底层是一个数组 //System.out.println(arr.length);//0, 1, 2 ,10 int sum = 0; for (int i : arr) { sum += i; } return sum; } //定义一个方法,计算三个个int类型整数的和 /* public static int add(int a, int b, int c){ return a + b + c; } */ //定义一个方法,计算两个int类型整数的和 /* public static int add(int a, int b){ return a + b; } */ }
java.util.Collections
是集合工具类,用来对集合进行操作。部分方法如下︰
public static <T> boolean addAl)(Collection<T> c, T... elements)
:往集合中添加一些元素。
public static void shuffle(List<?> list)
:打乱顺序:打乱集合顺序。
public static <T> void sort(List<T> list)
:将集合中元素按照默认规则排序。
public static <T> void sort(List<T> list , comparator<? super T>)
:将集合中元素按照指定规则排序。
import java.util.ArrayList; import java.util.Collections; /* - java.util.collections是集合工具类,用来对集合进行操作。部分方法如下: - public static <T> boolean addAll(collection<T>c, T... elements):往集合中添加一些元素。 - public static void shuffle(List<?> list)打乱顺序:打乱集合顺序。 */ public class Demo01Collections { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); //public static <T> boolean addAll(collection<T>c, T... elements):往集合中添加一些元素。 Collections.addAll(list, "a", "b", "c", "d", "e"); System.out.println(list);//[a, b, c, d, e] //public static void shuffle(List<?> list)打乱顺序:打乱集合顺序。 Collections.shuffle(list); System.out.println(list);//[b, c, a, d, e] } }
public class Person implements Comparable<Person>{ private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } //重写排序的规则 @Override public int compareTo(Person o) { // return 0; //认为元素都是相同的 //自定义比较的规则,比较两个人的年龄(this,参数Person) //return this.getAge() - o.getAge();//年龄升序排序 return o.getAge() - this.getAge();//年龄降序排序 } } import java.util.ArrayList; import java.util.Collections; /* - java.util.collections是集合工具类,用来对集合进行操作。部分方法如下: - public static <T> void sort(list<T> list) :将集合中元素按照默认规则排序。 注意: sort(List<T> list)使用前提 被排序的集合里边存储的元素,必须实现Comparable,重写接口中的方法compareTo() Comparable接口的排序规则: 自己(this) - 参数:就是升序 参数 - 自己(this):就是降序 */ public class Demo02Sort { public static void main(String[] args) { ArrayList<Integer> list01 = new ArrayList<>(); list01.add(1); list01.add(3); list01.add(2); System.out.println(list01);//[1, 3, 2] //public static <T> void sort(list<T> list) :将集合中元素按照默认规则排序。 Collections.sort(list01);//默认是升序的 System.out.println(list01);//[1, 2, 3] /* public final class String implements java.io.Serializable, Comparable<String>, CharSequence String重写了Comparable接口compareTo方法,比较器 */ ArrayList<String> list02 = new ArrayList<>(); list02.add("a"); list02.add("c"); list02.add("b"); System.out.println(list02);//[a, c, b] Collections.sort(list02);//默认是升序的 System.out.println(list02);//[a, b, c] ArrayList<Person> list03 = new ArrayList<>(); list03.add(new Person("张三", 18)); list03.add(new Person("李四", 20)); list03.add(new Person("王五", 19)); System.out.println(list03); Collections.sort(list03); System.out.println(list03); } }
public class Student { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } } import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; /* - java.utils.collections是集合工具类,用来对集合进行操作。部分方法如下: - public static <T> void sort(List<T> list,comparator<? super T〉):将集合中元素按照指定规则排序。 Comparator和Comparable的区别 Comparable:自己(this )和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法 Comparator:相当于找一个第三方的裁判,比较两个 Comparator的排序规则: o1 - o2:升序 o2 - o1:降序 */ public class Demo03Sort { public static void main(String[] args) { ArrayList<Integer> list01 = new ArrayList<>(); list01.add(1); list01.add(3); list01.add(2); System.out.println(list01);//[1, 3, 2] Collections.sort(list01, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { //return o1 - o2;//升序 return o2 - o1;//降序 } }); System.out.println(list01); ArrayList<Student> list02 = new ArrayList<>(); list02.add(new Student("张三", 18)); list02.add(new Student("李四", 20)); list02.add(new Student("王五", 19)); list02.add(new Student("张三", 19)); System.out.println(list02); Collections.sort(list02, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { //按升序 int result = o1.getAge() - o2.getAge(); //如果两个人年龄相同,再使用姓名的第一个字比较 if (result == 0){ result = o1.getName().charAt(0) - o2.getName().charAt(0); } return result; } }); System.out.println(list02); } }
Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象。其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素还可以是数组一样。
Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序。
java.util. Map<k, v>集合
Map集合的特点:
/* java.util.Map<k, v>集合 Map集合的特点: 1. Map集合是一个双列集合,一个元素包含两个值(一个key,一个value) 2. Map集合中的元素,key和value的数据类型可以相同,也可以不同 3. Map集合中的元索,key是不允许重复的,value是可以重复的 4. Map集合中的元素,key和vaLue是一一对应 java. utiL.HashMap<k, v>集合implements Map<k , v>接口 HashMap集合的特点: 1.HashMap集合底层是哈希表:查询的速度特别的快 JDK1.8之前:数组+单向链表 JDK1.8之后:数组+单向链表/红黑树(链表的长度超过8):提高查询的速度 2.HashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不—致 java.utiL.LinkedHashMap<k , v>集合extends HashMap<k ,v>集合 LinkedHashMap的特点: 1.LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序) 2.LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的 */ public class Demo01Map { }
Map接口中定义了很多方法,常用的如下:
public v put(K key,v value)
:把指定的键与指定的值添加到Map集合中。public v remove(Object key)
:把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。public v get(0bject key)
:根据指定的键,在Map集合中获取对应的值。boolean containsKey(Object key)
:判断集合中是否包含指定的键。public Set<K> keySet()
:获取Map集合中所有的键,存储到Set集合中。public Set<Map.Entry<K,v>> entrySet()
:获取到Map集合中所有的键值对对象的集合(Set集合)。public class Demo01Map { public static void main(String[] args) { show01(); show02(); show03(); show04(); } /* public v put(K key, v value):把指定的键与指定的值添加到wap集合中。 返回值:v 存储键值对的时候, key不重复,返回值v是null 存储键值对的时候, key重复,会使用新的value替换map中重复的value,返回被替换的value值 */ private static void show01() { //创建Map集合对象,多态 Map<String, String> map = new HashMap<>(); String v1 = map.put("李晨", "范冰冰1"); System.out.println("v1:" + v1);//v1:null String v2 = map.put("李晨","范冰冰2");//v2:范冰冰1 System.out.println("v2:" + v2); System.out.println(map);//{李晨=范冰冰2} map.put("冷锋", "龙小云"); map.put("杨过", "小龙女"); map.put("尹志平", "小龙女"); System.out.println(map);//{杨过=小龙女, 尹志平=小龙女, 李晨=范冰冰2, 冷锋=龙小云} } /* public V remove(object key):把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。 返回值:v key存在, v返回被删除的值 key不存在, v返回null */ private static void show02() { Map<String, Integer> map = new HashMap<>(); map.put("赵丽颖" ,168); map.put("杨幂" ,169); map.put("迪丽热巴" ,165); System.out.println(map);//{赵丽颖=168, 迪丽热巴=165, 杨幂=169},无序的 Integer v1 = map.remove("迪丽热巴"); System.out.println("v1:" + v1);//v1:165 //int v2 = map.remove("迪丽热巴");//自动拆箱,NullPointerException(空指针异常) Integer v2 = map.remove("迪丽热巴"); System.out.println("v2:" + v2);//v2:null } /* public v get(object key)根据指定的键,在Nap集合中获取对应的值。 返回值: key存在,返回对应的value值 key不存在,返回null */ private static void show03() { Map<String, Integer> map = new HashMap<>(); map.put("赵丽颖" ,168); map.put("杨幂" ,169); map.put("迪丽热巴" ,165); Integer v1 = map.get("杨幂"); System.out.println("v1:" + v1);//v1:169 Integer v2 = map.get("郭德纲"); System.out.println("v2:" + v2);//v2:null } /* boolean containsKey (object key)判断集合中是否包含指定的键。 包含返回true,不包含返回false */ private static void show04() { Map<String, Integer> map = new HashMap<>(); map.put("赵丽颖" ,168); map.put("杨幂" ,169); map.put("迪丽热巴" ,165); boolean b1 = map.containsKey("赵丽颖"); System.out.println("b1:" + b1);//b1:true boolean b2 = map.containsKey("郭德纲"); System.out.println("b2:" + b2);//b2:false } }
键找值方式:即通过元素中的键,获取键所对应的值
分析步骤∶
keyset()
get(K key)
import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /* Map集合的第一种遍历方式:通过键找值的方式 Map集合中的方法: set<K> keySet()返回此映射中包含的键的Set视图。 实现步骤: 1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中 2.遍历set集合,获取Map集合中的每一个key 3.通过Map集含中的方法get(key),通过key找到value */ public class Demo02KeySet { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("赵丽颖" ,168); map.put("杨幂" ,169); map.put("迪丽热巴" ,165); //1.使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中 Set<String> set = map.keySet(); //2.遍历set集合,获取Map集合中的每一个key //使用迭代器遍历Set集合 Iterator<String> iterator = set.iterator(); while (iterator.hasNext()){ String key = iterator.next(); //3.通过Map集含中的方法get(key),通过key找到value Integer value = map.get(key); System.out.println(key + " = " + value); } System.out.println("======================"); for (String key : set){ Integer value = map.get(key); System.out.println(key + " = " + value); } } }
我们已经知道,Map
中存放的是两种对象,一种称为key(键),一种称为value(值),它们在Map中是一一对应关系,这一对对象又称做Map中的一个Entry(项)
。Entry
将键值对的对应关系封装成了对象。即键值对对象,这样我们在遍历Map
集合时,就可以从每一个键值对(Entry
)对象中获取对应的键与对应的值。
既然Entry表示了一对键和值,那么也同样提供了获取对应键和对应值得方法︰
public K getKey()
:获取Entry对象中的键。public v getValue()
:获取Entry对象中的值。在Map集合中也提供了获取所有Entry对象的方法∶public Set<Map.Entry<K,v>> entrySet()
:获取到Map集合中所有的键值对对象的集合(Set集合)。import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /* Map集合遍历的第二种方式:使用Entry对象遍历 Map集合中的方法: set<Map.Entry<K,v>> entrySet()返回此映射中包含的映射关系的Set视图。 实现步骤: 1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中 2.遍历set集合,获取每一个Entry对象 3.使用Entry对象中的方法getKey( )和getValue()获取键与值 */ public class Demo03EntrySet { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("赵丽颖" ,168); map.put("杨幂" ,169); map.put("迪丽热巴" ,165); //1.使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中 Set<Map.Entry<String, Integer>> set = map.entrySet(); //2.遍历set集合,获取每一个Entry对象 //使用迭代器遍历Set集合 Iterator<Map.Entry<String, Integer>> it = set.iterator(); while(it.hasNext()){ Map.Entry<String, Integer> entry = it.next(); //3.使用Entry对象中的方法getKey( )和getValue()获取键与值 String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + " = " + value); } System.out.println("===================="); for (Map.Entry<String, Integer> entry : set){ String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + " = " + value); } } }
import java.util.Objects; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } } import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /* HashMap存储自定义类型键值 Map集合保证key是唯一的: 作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一 */ public class Demo01HashMapSavePerson { public static void main(String[] args) { show01(); show02(); } /* HashMap存储自定义类型键值 key : String类型 String类重写hashCode方法和equals方法,可以保证key唯一 value: Person类型 value可以重复(同名同年龄的人视为同一个) */ private static void show01() { HashMap<String, Person> map = new HashMap<>(); map.put("北京", new Person("张三", 18)); map.put("上海", new Person("李四", 19)); map.put("广州", new Person("王五", 20)); map.put("北京", new Person("赵六", 18)); Set<String> set = map.keySet(); for (String key : set){ Person value = map.get(key); System.out.println(value); } } /* HashMap存储自定义类型键值 key : Person类型 person类就必须重写hashcode方法和equals方法,以保证key唯一 value :String类型 可以重复 */ private static void show02() { HashMap<Person,String> map = new HashMap<>(); map.put(new Person("女王", 18), "英国"); map.put(new Person("秦始皇", 18), "秦国"); map.put(new Person("普京", 18), "俄罗斯"); map.put(new Person("女王", 18), "毛里求斯"); Set<Map.Entry<Person,String>> set = map.entrySet(); for(Map.Entry<Person,String> entry : set){ Person key = entry.getKey(); String value = entry.getValue(); System.out.println(key + "--->" + value); } } }
我们知道HashMap保证成对元素唯一,并且查询速度很快,可是成对元素存放进去是没有顺序的,那么我们要保证有序,还要速度快怎么办呢?
在HashMap下面有一个子类LinkedHashMap,它是链表和哈希表组合的一个数据存储结构。
import java.util.HashMap; import java.util.LinkedHashMap; /* java.util.LinkedHashMap<K,V> extends HashMap<K,V> Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。 底层原理: 哈希表+链表(记录元素的顺序) */ public class Demo01LinkedHashMap { public static void main(String[] args) { HashMap<String, String> map = new HashMap<>(); map.put("a", "a"); map.put("c", "c"); map.put("b", "b"); map.put("a", "d"); System.out.println(map);//key不允许重复,无序 {a=d, b=b, c=c} LinkedHashMap<String, String> linked = new LinkedHashMap<>(); linked.put("a", "a"); linked.put("c", "c"); linked.put("b", "b"); linked.put("a", "d"); System.out.println(linked);//key不允许重复,有序 {a=d, c=c, b=b} } }
import java.util.HashMap; import java.util.Hashtable; /* java.util.Hashtable<K,V>集合implements Map<K,V>接口 Hashtable:底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢 HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程的集合,速度快 HashMap集合(之前学的所有的集合):可以存储null值, nuLl键 HashtabLe集合,不能存储null值, null键 Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap ,ArrayList)取代了 Hashtable的子类Properties依然活跃在历史舞台 Properties集合是一个唯一和Io流相结合的集合 */ public class Demo02Hashtable { public static void main(String[] args) { HashMap<String, String> map = new HashMap<>(); map.put(null, "a"); map.put("b", null); map.put(null, null); System.out.println(map);//{null=null, b=null} Hashtable<String,String> table = new Hashtable<>(); //table.put(null, "b");//NullPointerException //table.put("b", null);//NullPointerException //table.put(null, null);//NullPointerException } }
问题:
计算一个字符串中每个字符出现次数。
分析:
import java.util.HashMap; import java.util.Map; import java.util.Scanner; import java.util.Set; /* 练习: 计算一个字符串中每一个字符出现次数 分析: 1.使用Scanner获取用户输入的字符串 2.创建Map集合,key是字符串中的字符, value是字符的个数 3.遍历字符串,获取每一个字符 4.使用获取到的字符,去Map集合判断key是否存在 key存在: 通过字符(key),获取value(字符个数) value++ put( key , vaLue)把新的value存储到Map集合中 key不存在: put( key , 1) 5.遍历Map集合,输出结果 */ public class Demo03Test { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入一个字符串:" ); String str = sc.next(); Map<Character, Integer> map = new HashMap<>(); char[] ch = str.toCharArray(); for (int i = 0; i < ch.length; i++) { if(map.containsKey(ch[i])){ map.put(ch[i], map.get(ch[i]) + 1); }else{ map.put(ch[i], 1); } } Set<Map.Entry<Character, Integer>> set = map.entrySet(); for (Map.Entry<Character, Integer> entry : set){ Character c = entry.getKey(); Integer i = entry.getValue(); System.out.println(c + "出现" + i + "次"); } } }
import java.util.List; import java.util.Map; import java.util.Set; /* JDK9的新特性: List接口,Set接口, Map接口:里边增加了一个静态的方法of ,可以给集合一次性添加多个元素 static <E> List<E> of (E.. . eLements) 使用前提: 当集合中存储的元素的个数已经确定了,不在改变时使用 注意: 1.of方法只适用于List接口, set接口,Map接口,不适用于接接口的实现类 2.of方法的返回值是一个不能改变的集合,集合不能再使用add, put方法添加元素,会抛出异常 3.set接口和Mp接口在调用of方法的时候,不能有重复的元素,否则会抛出异常 */ public class Dmeo01JDK9 { public static void main(String[] args){ List<String> list = List.of("a", "b", "c", "d"); System.out.println(list);//[a, b, c, d] //list.add("w");//UnsupportedOperationException不支持操作异常 //Set<String> set = Set.of("a", "b", "c", "d", "a");//IllegalArgumentException非法参数异常 Set<String> set = Set.of("a", "b", "c", "d"); System.out.println(set);//[a, b, c, d] //set.add("f");//UnsupportedOperationException不支持操作异常 //Map<String, Integer> map = Map.of("张三", 18, "李四", 19, "王五", 20,"张三", 20);//IllegalArgumentException非法参数异常 Map<String, Integer> map = Map.of("张三", 18, "李四", 19, "王五", 20); System.out.println(map);//{张三=18, 王五=20, 李四=19} //map.add("张三", 18);//UnsupportedOperationException不支持操作异常 } }
/* Debug调试程序: 可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug 使用方式: 在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里) 右键,选择Debug执行程序 程序就会停留在添加的第一个断点处 执行程序: f8:逐步执行程序 f7:进入到方法中 shift + f8:跳出方法 f9:跳到下一个断点,如果没有下一个断点,那么就结束程序 ctrl + f2:退出debug模式,停止程序 console:切换到控制台 */ public class Demo01Debug { public static void main(String[] args){ // int a = 10; // int b = 20; // int sum = a + b; // System.out.println(sum); for (int i = 0; i < 3; i++){ System.out.println(i); } } }
import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; public class DouDiZhu { public static void main(String[] args) { HashMap<Integer, String> poker = new HashMap<>(); List<Integer> list = new ArrayList<>(); String[] color = {"♠", "♥", "♣", "♦"}; String[] number = {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"}; int index = 0; list.add(index); poker.put(index++, "大王"); list.add(index); poker.put(index++, "小王"); for (int i = 0; i < color.length; i++) { for (int j = 0; j < number.length; j++) { list.add(index); poker.put(index++, color[i] + number[j]); } } //System.out.println(poker); Collections.shuffle(list); //System.out.println(list); ArrayList<Integer> player01 = new ArrayList<>(); ArrayList<Integer> player02 = new ArrayList<>(); ArrayList<Integer> player03 = new ArrayList<>(); ArrayList<Integer> dipai = new ArrayList<>(); for (int i = 0; i < list.size(); i++){ Integer in = list.get(i); if (i >= 51){ dipai.add(in); }else if (i % 3 == 0){ player01.add(in); }else if (i % 3 == 1){ player02.add(in); }else if (i % 3 == 2){ player03.add(in); } } Collections.sort(player01); Collections.sort(player02); Collections.sort(player03); Collections.sort(dipai); lookPoker("刘德华",poker,player01); lookPoker("周润发",poker,player02); lookPoker("周星驰",poker,player03); lookPoker("底牌",poker,dipai); } /* 定义一个看牌的方法,提高代码的复用性 参数: String name:玩家名称 HashMap<Integer,String> poker :存储牌的poker集合 ArrayList<Integer> list:存储玩家和底牌的List集合 查表法: 遍历玩家或者底牌集合,获取牌的索引 使用牌的索引,去Map集合中,找到对应的牌 */ public static void lookPoker(String name, HashMap<Integer, String> poker, ArrayList<Integer> list){ System.out.println(name + ": "); for (Integer key : list){ String value = poker.get(key); System.out.print(value + " "); } System.out.println(); } }
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
异常发生的原因有很多,通常包含以下几大类:
所有的异常类是从 java.lang.Exception 类继承的子类。
Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
Error 用来指示运行时环境发生的错误。
例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
Java 语言定义了一些异常类在 java.lang 标准包中。
标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。
Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。
异常 | 描述 |
---|---|
ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 请求的变量不存在 |
NoSuchMethodException | 请求的方法不存在 |
/* throw关键字 作用: 可以使用throw关键字在指定的方法中抛出指定的异常使用 格式: throw new XXXException(”异常产生的原因"); 注意: 1.throw关键字必须写在方法的内部 2.throw关键字后边nev的对象必须是Exception或者Exception的子类对象 3.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象 throw关键字后边创建的是RuntimeException或者是RuntimeException的子类对象,我们可以不处理,默认交给JWw处理(打印异常对象,中断程序) throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try. ..catch */ public class Demo03Throw { public static void main(String[] args) { int[] arr = new int[3]; int e = getElement(arr, 3); System.out.println(e); } /* 定义一个方法,获取数组指定索引处的元素 参数: int[] arr int index 以后(工作中)我们首先必须对方法传递过来的参数进行合法性校验 如果参数不合法,那么我们就必须使用批出异常的方式,告知方法的调用者,传递的参数有问题 */ public static int getElement(int[] arr, int index){ /* 我们可以对传递过来的参数数组,进行合法性校验 如果数组arr的值是nulL 那么我们就抛出空指针异常,告知方法的调用者"传递的数组的值是nuLl" */ if (arr == null){ throw new NullPointerException("传递的数组的值是null"); } /* 我们可以对传递过来的参数index进行合法性校验 如果index的范围不在数组的索引范围内 那么我们就抛出数组索引越界异常,告知方法的调用者“传递的索引超出了数组的使用范围” */ if(index < 0 || index > arr.length - 1){ throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围"); } int ele = arr[index]; return ele; } }
Objects非空判断
还记得我们学习过一个类Objects吗,曾经提到过它由一些静态的实用方法组成,这些方法是null-save (空指针安全的)或null-tolerant(容忍空指针的),那么在它的源码中,对对象为null的值进行了抛出异常操作。
public static <T> T requireNonNull(T obj)
:查看指定引用对象不是null。public static <T> T requireNonNull(T obj) { if (obj -= null) throw new NullPointerException( ) ; return obj; }
import java.util.Objects; /* Objects类中的静态方法: public static <T> T requireNonNull(T obj):查看指定引用对象不是null。 查看源码发现这里对为null的进行了抛出异常操作︰ public static <T> T requireNonNull(T obj) { if (obj -= null) throw new NullPointerException( ) ; return obj; } */ public class Demo04Objects { public static void main(String[] args) { method(null); } public static void method(Object obj){ //对传递过来的参数进行合法判断,判断是否为null /*if(obj == null){ throw new NullPointerException("传递的对象值是null"); }*/ //Objects.requireNonNull(obj); Objects.requireNonNull(obj, "空指针异常"); } }
声明异常︰将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws进行声明,让调用者去处理。
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)
声明异常格式︰
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2...{}
import java.io.FileNotFoundException; import java.io.IOException; /* throws关键字:异常处理的第—种方式,交给别人处理 作用: 当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象 可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JW处理-->中断处理 使用格式:在方法声明时使用 修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{ throw new AAAException("产生原因"); throw new 8BBException( "产生原因"); } 注意: 1.throws关键字必须写在方法声明处 2.throws关键字后边声明的异常必须是Exception或者是Exception的子类 3.方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常 如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可 4.调用了一个声明抛出异常的方法,我们就必须的处理声明的异常 要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM 要么try...catch自己处理异常 */ public class Demo05Throws { /* FileNotFoundException extends IOException 如果抛出的多个异常有子父类关系,那么直接声明父类异常即可 */ public static void main(String[] args) throws FileNotFoundException, IOException { readFile("c:\\a.xt"); } /* 定义一个方法,对传递的文件路径进行合法性判断 如果路径不是"c:\\a.txt",那么我们就抛出文件找不到异常对象,告知方法的调用者 注意: FileNotFoundException是编译异常,抛出了编译异常,就必须处理这个异常 可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法的调用者处理 */ public static void readFile(String fileName) throws FileNotFoundException, IOException { if (!fileName.equals("c:\\a.txt")){ throw new FileNotFoundException("传递的文件路径不是c:\\a.txt"); } /* 如果传递的路径,不是.txt结尾 那么我们就抛出Io异常对象,告知方法的调用者,文件的后缀名不对 */ if (!fileName.endsWith(".txt")){ throw new IOException("文件的后缀名不对"); } System.out.println("路径没有问题,读取文件"); } }
如果异常出现的话,会立刻终止程序,所以我们得处理异常:
try{ 编写可能会出现异常的代码 }catch(异常类型 e){ 处理异常的代码 //记录日志/打印异常信息/继续抛出异常 }
Throwable类中定义了3个异常处理的方法
String getMessage()
返回此 throwable的简短描述。
String toString()
返回此throwable 的详细消息字符串。
void printStackTrace()
JVM打印异常对象,默认此方法,打印的异常信息是最全面的
import java.io.FileNotFoundException; import java.io.IOException; /* try……catch:异常处理的第二种方式,自己处理异常 格式; try{ 可能产生异常的代码 }catch(定义一个异常的变量,用来接收try中抛出的异常对象){ 异常的处理逻辑,异常异常对象之后,怎么处理异常对象 一般在工作中,会把异常的信息记录到一个日志中 } ... catch (异常类名 变量名){ } 注意: 1.try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象 2.如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try ...catch之后的代码 如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继!续执行try. ..catch之后的代码 */ public class Demo01TryCatch { public static void main(String[] args) { try{ //可能产生异常的代码 readFile("d:\\a.tx"); }catch (IOException e){//try中抛出什么异常对象,catch就定义什么异常变量,用来接收这个异常对象 //异常的处理逻辑,异常对象之后,怎么处理异常对象 //System.out.println("catch - 传递的文件后缀不是.txt"); /* Throwable类中定义了3个异常处理的方法 String getMessage() 返回此 throwable的简短描述。 String toString() 返回此throwable 的详细消息字符串。 void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息是最全面的 */ System.out.println(e.getMessage());//文件的后缀名不对 System.out.println(e.toString());//重写Object类的toString,java.io.IOException: 文件的后缀名不对 System.out.println(e);//java.io.IOException: 文件的后缀名不对 /* java.io.IOException: 文件的后缀名不对 at demo02.Exception.Demo01TryCatch.readFile(Demo01TryCatch.java:61) at demo02.Exception.Demo01TryCatch.main(Demo01TryCatch.java:29) */ e.printStackTrace(); } System.out.println("后续代码"); } /* 如果传递的路径,不是.txt结尾 那么我们就抛出Io异常对象,告知方法的调用者,文件的后缀名不对 */ public static void readFile(String fileName) throws FileNotFoundException, IOException { /*if (!fileName.equals("c:\\a.txt")){ throw new FileNotFoundException("传递的文件路径不是c:\\a.txt"); }*/ if (!fileName.endsWith(".txt")){ throw new IOException("文件的后缀名不对"); } System.out.println("路径没有问题,读取文件"); } }
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
多重捕获块的语法如下所示:
try{ // 程序代码 }catch(异常类型1 异常的变量名1){ // 程序代码 }catch(异常类型2 异常的变量名2){ // 程序代码 }catch(异常类型3 异常的变量名3){ // 程序代码 }
上面的代码段包含了 3 个 catch块。
可以在 try 语句后面添加任意数量的 catch 块。
如果保护代码中发生异常,异常被抛给第一个 catch 块。
如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。
如果不匹配,它会被传递给第二个 catch 块。
如此,直到异常被捕获或者通过所有的 catch 块。
实例
该实例展示了怎么使用多重 try/catch。
try { file = new FileInputStream(fileName); x = (byte) file.read(); } catch(FileNotFoundException f) { // Not valid! f.printStackTrace(); return -1; } catch(IOException i) { i.printStackTrace(); return -1; }
注意:
catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会报错
finally 关键字用来创建在 try 代码块后面执行的代码块。
无论是否发生异常,finally 代码块中的代码总会被执行。
在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
import java.io.FileNotFoundException; import java.io.IOException; /* finaLLy代码块格式: try{ 可能产生异常的代码 }catch(定义一个异常的变量,用来接收try中抛出的异常对象){ 异常的处理逻辑,异常异常对象之后,怎么处理异常对象 一般在工作中,会把异常的信息记录到一个日志中 } catch(异常类名 变量名){ }finally{ 无论是否出现异常都会执行 } 注意: 1.finalLy不能单独使用,必须和try一起使用 2.finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO) */ public class Demo02TryCatchFinally { public static void main(String[] args){ try { //可能会产生异常的代码 readFile("c:\\a.xt"); } catch (IOException e) { //异常的处理逻辑 e.printStackTrace(); }finally { //无论是否出现异常,都会执行 System.out.println("资源释放"); } } public static void readFile(String fileName) throws FileNotFoundException, IOException { if (!fileName.equals("c:\\a.txt")){ throw new FileNotFoundException("传递的文件路径不是c:\\a.txt"); } /* 如果传递的路径,不是.txt结尾 那么我们就抛出Io异常对象,告知方法的调用者,文件的后缀名不对 */ if (!fileName.endsWith(".txt")){ throw new IOException("文件的后缀名不对"); } System.out.println("路径没有问题,读取文件"); } }
注意下面事项:
/* 如果finally有return语句,永远返回finally中的结果,避免该情况 */ public class Demo02Exception { public static void main(String[] args) { System.out.println(getA());//100 } public static int getA(){ int a = 10; try{ return a; }catch (Exception e){ System.out.println(e); }finally { a = 100; return a; } } }
/* 子父类的异常: -如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。 -父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出 注意: 父类异常时什么样,子类异常就什么样 */ public class Fu { public void show01() throws NullPointerException, ClassCastException{} public void show02() throws IndexOutOfBoundsException{} public void show03() throws IndexOutOfBoundsException{} public void show04() {} } class Zi extends Fu{ //子类重写父类方法时,抛出和父类相同的异常 public void show01() throws NullPointerException, ClassCastException{} //子类重写父类方法时,抛出父类异常的子类 public void show02() throws ArrayIndexOutOfBoundsException{} //子类重写父类方法时,不抛出异常 public void show03() {} /* 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常 此时子类产生该异常,只能捕获处理,不能声明抛出 */ public void show04() { try{ throw new Exception ("编译器异常"); } catch (Exception e) { e.printStackTrace(); } } }
在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
/* 自定义异常类: java提供的异常类,不够我们使用,需要自己定义一些异常类 格式: public class XXXException extends Exception / RuntimeException{ 添加一个空参数的构造方法 添加一个带异常信息的构造方法 } 注意: 1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类 2.自定义异常类,必须的继承Exception或者RuntimeException 继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch 继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理) */ public class RegisterException extends Exception{ //添加一个空参数的构造方法 public RegisterException() { } //添加一个带异常信息的构造方法 //查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息 public RegisterException(String message) { super(message); } }
import java.util.Scanner; /* 要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。 分析: 1.使用数组保存已经注册过的用户名(数据库) 2.使用Scanner获取用户输入的注册的用户名(前端,页面) 3.定义一个方法,对用户输入的中注册的用户名进行判断 遍历存储已经注册过用户名的数组,获取每一个用户名 使用获取到的用户名和用户输入的用户名比较 true: 用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册""; false: 继续遍历比较 如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功中; */ public class Demo01RegisterException { //1.使用数组保存已经注册过的用户名(数据库) static String[] userNames = {"张三","李四","王五"}; //2.使用Scanner获取用户输入的注册的用户名(前端,页面) public static void main(String[] args) /*throws RegisterException*/ { Scanner sc = new Scanner(System.in); System.out.println("请输入您要注册的用户名:"); String username = sc.next(); checkUserName(username); } //3.定义一个方法,对用户输入的中注册的用户名进行判断 public static void checkUserName(String username) /*throws RegisterException*/ { //遍历存储已经注册过用户名的数组,获取每一个用户名 for (String name : userNames){ if (name.equals(username)){ //true:用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册""; try { throw new RegisterException("亲,该用户已经被注册"); } catch (RegisterException e) { e.printStackTrace(); return; } } } //如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功中; System.out.println("恭喜你注册成功"); } }
在Java中定义了两种类型的异常和错误。
**并发:**指两个或多个事件在同一个时问段内发生。
**并行:**指两个或多个事件在同一时刻发生(同时发生).
**进程︰**是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位﹔系统运行一个程序即是一个进程从创建、运行到消亡的过程。
**线程︰**线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程调度:
Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
主线程: 执行主(main)方法的线程
单线程程序: java程序中只有一个线程,执行从main方法开始,从上到下依次执行,JVM执行main方法, main方法会进入到栈内存,JVM会找操作系统开辟一条main方法通向cpu的执行路径,cpu就可以通过这个路径来执行main方法,而这个路径有一个名字,叫main (主)线程。
创建一个线程
Java 提供了三种创建线程的方法:
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
/* 创建多线程程序的第一种方式:创建Thread类的子类 java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类 实现步骤: 1.创建一个Thread类的子类 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?) 3.创建Thread类的子类对象 4.调用Thread类中的方法start方法,开启新的线程,执行run方法 void start()使该线程开始执行;Java虚拟机调用该线程的run方法。 结果是两个线程并发地运行;当前线程( main线程)和另一个线程(创建的新线程,执行其run 方法) 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。 java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行 */ //1.创建一个Thread类的子类 public class MyThread extends Thread { //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?) @Override public void run() { for (int i = 0; i < 20; i++){ System.out.println("run" + i); } } } public class Demo01Thread { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); for (int i = 0; i < 20; i++) { System.out.println("main" + i); } } }
多线程原理_随机打印
多线程原理_多线程内存图解
在我们完成操作过程中用到了java.lang.Thread类,API中该类中定义了有关线程的一些方法,具体如下∶
构造方法:
public Thread()
:分配一个新的线程对象。public Thread(String name)
:分配一个指定名字的新的线程对象。public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。public string getName()
:获取当前线程名称。public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。public void run()
:此线程要执行的任务在此处定义代码。public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。/* 获取线程的名称: 1.使用Thread类中的方法getName() String getName() 返回该线程的名称。 2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称 static Thread currentThread()返回对当前正在执行的线程对象的引用。 */ public class MyThread extends Thread{ @Override public void run() { // String name = getName(); // System.out.println(name); Thread t = currentThre(); System.out.println(t); String name = t.getName(); System.out.println(name); } } /* 线程的名称: 主线程: main 新线程: Thread-0 , Thread-1, Thread-2…… */ public class Demo01GetThreadName { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); //获取主线程的main System.out.println(Thread.currentThread().getName()); } }
/* 设置线程的名称:(了解) 1.使用Thread类中的方法setName(名字) void setName (String name)改变线程名称,使之与参数name相同。 ⒉.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字 Thread (String name)分配新的 Thread 对象。 */ public class MyThread extends Thread{ public MyThread(){} public MyThread(String name){ super(name); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } } public class Demo01SetThreadName { public static void main(String[] args) { MyThread mt = new MyThread(); mt.setName("小强"); mt.start(); //开启多线程 new MyThread("旺财").start(); } }
/* public static void sleep(Long millis):使当前正在执行的线程以指定的毫秒数暂停〈暂时停止执行)。 毫秒数结束之后,线程继续执行 */ public class Demoi01Sleep { public static void main(String[] args) { for (int i = 0; i < 60; i++) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
/* 创建多线程程序的第二种方式:实现Runnable接口 java.Lang. Runnable Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参数方法。 java.Lang. Thread类的构造方法 Thread(Runnable target)分配新的 Thread 对象。 Thread ( Runnable target,string name)分配新的 Thread 对象。 实现步骤: 1.创建一个Runnable接口的实现类 2.在实现类中重写Runnable接口的run方法,设置线程任务 3.创建一个Runnable接口的实现类对象 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 5.调用Thread类中的start方法,开启新的线程执行run方法 实现Runnable接口创建多线程程序的好处: 1.避免了单继承的局限性 一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类 实现了Runnable接口,还可以继承其他的类,实现其他的接口 2.增强了程序的扩展性,降低了程序的耦合性(解耦) 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦) 实现类中,重写了run方法:用来设置线程任务 创建Thread类对象,调用start方法:用来开启新线程 */ //1.创建一个Runnable接口的实现类 public class RunnableImpl implements Runnable { //2.在实现类中重写Runnable接口的run方法,设置线程任务 @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + "-->" + i); } } } public class Demo01Runnable { public static void main(String[] args) { //3.创建一个Runnable接口的实现类对象 RunnableImpl run = new RunnableImpl(); //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 Thread t = new Thread(run); //5.调用Thread类中的start方法,开启新的线程执行run方法 t.start(); for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + "-->" + i); } } }
public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i); if(i==20) { new Thread(ft,"有返回值的线程").start(); } } try { System.out.println("子线程的返回值:"+ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } return i; } }
创建线程的三种方式的对比
/* 匿名内部类方式实现线程的创建 匿名:没有名字 内部类:写在其他类内部的类 匿名内部类作用:简化代码 把子类继承父类,重写父类的方法,创建子类对象合一步完成 把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字 格式: new 父类/接口 (){ 重复父类/接口中的方法 }; */ public class Demo01InnerClassThread { public static void main(String[] args) { //线程的父类实Thread //new MyThread().start() new Thread(){ //重写run方法,设置线程任务 @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+ "-->" + i); } } }.start(); //线程的接口Runnable //Runnable r = new RunnableImpl();//多态 Runnable r = new Runnable(){ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+ "-->黑马" + i); } } }; new Thread(r).start(); //简化接口方式 new Thread(new Runnable(){ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+ "-->白马" + i); } } }).start(); } }
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
根据案例简述∶
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执行原子操作Java引入了线程同步机制。那么怎么去使用呢?
有三种方式完成同步操作︰
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。synchronized(同步锁){ 需要同步操作的代码 }
/* 卖票案例出现了线程安全问题 卖出了不存在的票和重复的票 解决线程安全问题的一种方案:使用同步代码块 格式: synchronized(锁对象){ 可能会出现线程安全问题的代码(访问了共享数据的代码) } 注意: 1.通过代码块中的锁对象,可以使用任意的对象 2.但是必须保证多个线程使用的锁对象是同一个 3.锁对象作用: 把同步代码块锁住,只让一个线程在同步代码块中执行 */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100; //创建一个锁对象 Object obj = new Object(); //设置线程任务:卖票 @Override public void run() { synchronized (obj){ while(true){ //先判断是否有票 if(ticket > 0){ //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票"); ticket--; } } } } } /* 模拟卖票案例 创建3个线程,同时开启,对共享的票进行出售 */ public class Demo01Ticket { public static void main(String[] args) { RunnableImpl run = new RunnableImpl(); Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); t1.start(); t2.start(); t3.start(); } }
同步原理:
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式︰
public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步方法的锁对象是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
/* 卖票案例出现了线程安全问题 卖出了不存在的票和重复的票 解决线程安全问题的第二种方案:使用同步方法 使用步骤: 1.把访问了共享数据的代码抽取出来,放到一个方法中 2.在方法上添加synchronized修饰符 格式:定义方法的格式 修饰符 synchronized 返回值类型 方法名(参数列表){ 可能会出现线程安全问题的代码(访向了共享数据的代码) } */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private static int ticket = 100; //设置线程任务:卖票 @Override public void run() { System.out.println("this:" + this);//this:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1 while(true) { //payTicket(); payTicketStatic(); } } /* 静态的同步方法 锁对象是谁? 不能是this this是创建对象之后产生的,静态方法优先于对象 静态方法的锁对象是本类的class属性-->class文件对象 */ public static /*synchronized*/ void payTicketStatic(){ synchronized (RunnableImpl.class){ //先判断是否有票 if(ticket > 0){ //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票"); ticket--; } } } /* 定义一个同步方法 同步方法也会把方法内部的代码锁住 只让一个线程执行 同步方法的锁对象是谁? 就是实现类对象 new RunnableImpl() 也是就是this */ public /*synchronized*/ void payTicket(){ synchronized (this){ //先判断是否有票 if(ticket > 0){ //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票"); ticket--; } } } } /* 模拟卖票案例 创建3个线程,同时开启,对共享的票进行出售 */ public class Demo01Ticket { public static void main(String[] args) { RunnableImpl run = new RunnableImpl(); System.out.println("run:" + run);//run:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1 Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); t1.start(); t2.start(); t3.start(); } }
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁。
public void unlock()
:释放同步锁。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* 卖票案例出现了线程安全问题 卖出了不存在的票和重复的票 解决线程安全问题的三种方案:使用Lock锁 java.util.concurrent.locks.Lock接口 Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。 Lock接口中的方法: void Lock()获取锁。 void unLock()释放锁。 java.util.concurrent.Locks.ReentrantLock implements Lock接口 使用步骤: 1.在成员位置创建一个ReentrantLock对象 2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁 3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁 */ public class RunnableImpl implements Runnable{ //定义一个多个线程共享的票源 private int ticket = 100; //1.在成员位置创建一个ReentrantLock对象 Lock lock = new ReentrantLock(); //设置线程任务:卖票 @Override public void run() { while(true){ //2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁 lock.lock(); //先判断是否有票 if(ticket > 0){ //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); } finally { //3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁 lock.unlock(); } } } } } /* 模拟卖票案例 创建3个线程,同时开启,对共享的票进行出售 */ public class Demo01Ticket { public static void main(String[] args) { RunnableImpl run = new RunnableImpl(); Thread t1 = new Thread(run); Thread t2 = new Thread(run); Thread t3 = new Thread(run); t1.start(); t2.start(); t3.start(); } }
线程状态。 线程可以处于以下状态之一:
Timed Waiting在API中的描述为∶一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢?
在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠(暂停执行),以”减慢线程"。
其实当我们调用了sleep方法之后,当前执行的线程就进入到"休眠状态”,其实就是所谓的Timed Waiting(计时等待),那么我们通过一个案例加深对该状态的一个理解。
Timed Waiting线程状态图
Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
我们已经学完同步机制,那么这个状态是非常好理解的了。比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态,而这部分内容作为扩充知识点带领大家了解一下。
Blocked线程状态图
Wating状态在API中介绍为∶一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。那么我们之前遇到过这种状态吗?答案是并没有,但并不妨碍我们进行一个简单深入的了解。我们通过一段代码来学习一下:
等待唤醒案例:
/* 等待唤醒案例:线程之间的通信 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子 注意: 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行 同步使用的锁对象必须保证唯一 只有锁对象才能调用wait和notify方法 Obejct类中的方法 void wait() 在其他线程调用此对象的notify()方法或 notifyAll()方法前,导致当前线程等待。 void notify() 唤醒在此对象监视器上等待的单个线程。 会继续执行wait方法之后的代码 */ public class Demo01WaitAndNotify { public static void main(String[] args) { //创建锁对象,保证唯一 Object obj = new Object(); //创建一个顾客线程(消费者) new Thread(){ @Override public void run() { //保证等待和唤醒只能有一个在执行 synchronized (obj){ System.out.println("告知老板要的包子的种类和数量"); //调用wait方法,放弃CPU的执行,进入到WAITING状态(无限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println("吃包子"); } } }.start(); //创建一个老板线程(生产者) new Thread(){ @Override public void run() { //花了5秒做包子 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized (obj){ System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了"); //做好包子之后,调用notify方法,唤醒顾客吃包子 obj.notify(); } } }.start(); } }
/* 进入到TimeWaiting(计时等待)有两种方式 1.使用sleep(Long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/BLocked状态 2.使用wait(Long m)方法, wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态 唤醒的方法: void notify ()唤醒在此对象监视器上等待的单个线程。 void notifyAll()唤醒在此对象监视器上等待的所有线程。 */ public class Demo02WaitAndNotify { public static void main(String[] args) { //创建锁对象,保证唯一 Object obj = new Object(); //创建一个顾客线程(消费者) new Thread(){ @Override public void run() { while (true) { //保证等待和唤醒只能有一个在执行 synchronized (obj) { System.out.println("顾客1告知老板要的包子的种类和数量"); //调用wait方法,放弃CPU的执行,进入到WAITING状态(无限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println("顾客1吃包子"); System.out.println("====================================="); } } } }.start(); //创建一个顾客线程(消费者) new Thread(){ @Override public void run() { while (true) { //保证等待和唤醒只能有一个在执行 synchronized (obj) { System.out.println("顾客2告知老板要的包子的种类和数量"); //调用wait方法,放弃CPU的执行,进入到WAITING状态(无限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println("顾客2吃包子"); System.out.println("=================================="); } } } }.start(); //创建一个老板线程(生产者) new Thread(){ @Override public void run() { while(true) { //花了5秒做包子 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized (obj) { System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了"); //做好包子之后,调用notify方法,唤醒顾客吃包子 //obj.notify(); obj.notifyAll(); } } } }.start(); } }
**概念︰**多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
为什么要处理线程间通信∶
多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源∶
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即――等待唤醒机制。
什么是等待唤醒机制
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争( race ),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态( wait()),等待其他线程执行完他们的指定代码过后再将其唤醒( notify() ) ;在有多个线程进行等待时,如果需要,可以使用notifyAl)来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下︰
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用wait方法之后的地方恢复执行。
总结如下:
- 如果能获取锁,线程就从WAITING状态变成RUNNABLE状态;
- 否则,从wait set出来,又进入entry set,线程就从WAITING状态又变成BLOCKED状态
调用wait和notify方法需要注意的细节
等待唤醒机制其实就是经典的“"生产者与消费者"的问题。
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源∶
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false ),吃货线程等待,包子铺线程生产包子(即包子状态为true ),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false ),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
分析:
/* 资源类:包子类 设置包子的属性 皮 馅 包子的状态:有true,没有false */ public class BaoZi { //皮 String pi; //馅 String xian; //包子的状态:有true,没有false boolean flag = false; }
/* 生产者(包子铺)类:是一个线程类,可以继承Thread 设置线程任务(run):生产包子 对包子的状态进行判断 true:有包子 包子铺调用wait方法进入等待状态 false:没有包子 包子铺生产包子 增加一些趣味性:交替生产两种包子 有两种状态(i%2==0) 包子铺生产好了包子 修改包子的状态为true有 唤醒吃货线程,让吃货线程吃包子 注意: 包子铺线程和包子线程关系-->通信(互斥) 必须同时同步技术保证两个线程只能有一个在执行 锁对象必须保证唯一,可以使用包子对象作为锁对象 包子铺类和吃货的类就需要把包子对象作为参数传递进来 1.需要在成员位置创建一个包子变量 2.使用带参数构造方法,为这个包子变量赋值 */ public class BaoZiPu extends Thread{ //1.需要在成员位置创建一个包子变量 private BaoZi bz; //2.使用带参数构造方法,为这个包子变量赋值 public BaoZiPu(BaoZi bz) { this.bz = bz; } //设置线程任务(run):生产包子 @Override public void run() { //定义一个变量 int count = 0; //让包子铺一直生产包子 while (true) { //必须同时同步技术保证两个线程只能有一个在执行 synchronized (bz) { //对包子的状态判断 if (bz.flag == true) { try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //别唤醒后执行,包子铺生产包子 //增加一些趣味性:交替生产两种包子 if (count % 2 == 0) { //生产 薄皮三鲜馅包子 bz.pi = "薄皮"; bz.xian = "三鲜馅"; } else { //生产 冰皮牛肉大葱馅包子 bz.pi = "冰皮"; bz.xian = "牛肉大葱馅"; } count++; System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子"); //生产包子需要3秒钟 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //包子铺生产好了包子 //修改包子的状态为true有 bz.flag = true; //唤醒吃货线程,让吃货线程吃包子 bz.notify(); System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,吃货可以开始吃了"); } } } }
/* 消费者(吃货)类:是一个线程类,可以继承Thread 设置线程任务(run) :吃包子 对包子的状态进行判断 false:没有包子 吃货调用wait方法进入等待状态 true :有包子 吃货吃包子 吃货吃完包子 修改包子的状态为false没有 吃货唤醒包子铺线程,生产包子 */ public class ChiHuo extends Thread{ //1.需要在成员位置创建一个包子变量 private BaoZi bz; //2.使用带参数构造方法,为这个包子变量赋值 public ChiHuo(BaoZi bz) { this.bz = bz; } //设置线程任务(run):吃包子 @Override public void run() { //使用死循环,让吃货一直吃包子 while (true) { //必须同时同步技术保证两个线程只能有一个在执行 synchronized (bz){ //对包子的状态进行判断 if (bz.flag == false){ //吃货调用wait方法进入等待状态 try{ bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被唤醒之后执行的代码,吃包子 System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子"); //吃货吃完包子 //修改包子的状态为false没有 bz.flag = false; //吃货唤醒包子铺线程,生产包子 bz.notify(); System.out.println("吃货已经把:" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子"); System.out.println("=========================================="); } } } }
/* 测试类: 包含main方法,程序执行的入口,启动程序 创建包子对象; 创建包子铺线程,开启,生产包子; 创建吃货线程,开启,吃包子; */ public class Demo { public static void main(String[] args) { //创建包子对象 BaoZi bz = new BaoZi(); //创建包子铺线程,开启,生产包子; new BaoZiPu(bz).start(); //创建吃货线程,开启,吃包子; new ChiHuo(bz).start(); } }
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行Future接口∶用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池中线程对象的步骤︰
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /* 线程池:JDK1.5之后提供的 java.util.concurrent.Executors :线程池的工厂类,用来生成线程池 Executors类中的静态方法; static ExecutorService newFixedThreadPooL(int nThreads)创建一个可重用固定线程数的钱线程池 参数: int nThreads:创建线程池中包含的线程数量 返回值: ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程) java.utiL.concurrent.ExecutorService:线程池接口 用来从线程池中获取线程,调用start方法,执行线程任务 submit (Runnable task)提交一个Runnable任务用于执行 关闭/销毁线程池的方法 void shutdown () 线程池的使用步骤: 1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池 2.创建一个类,实现Runnable接口,重写run方法,设置线程任务 3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行) */ public class Demo01ThreadPool { public static void main(String[] args) { //1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池 ExecutorService es = Executors.newFixedThreadPool(2); //3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 es.submit(new RunnableImpl());//创建了一个新的线程pool-1-thread-2 //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用 es.submit(new RunnableImpl());//创建了一个新的线程pool-1-thread-1 es.submit(new RunnableImpl());//创建了一个新的线程pool-1-thread-1 //4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行) es.shutdown(); es.submit(new RunnableImpl());//RejectedExecutionException,抛出异常,线程池没有线程了,就不能获取线程了。 } } /* 2.创建一个类,实现Runnable接口,重写run方法,设置线程任务 */ public class RunnableImpl implements Runnable{ @Override public void run() { System.out.println("创建了一个新的线程" + Thread.currentThread().getName()); } }
在数学中,函数就是有输入量、输出量的一套计算方案,也就是"拿什么东西做什么事情"。相对而言,面向对象过分强调"必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法―—强调做什么,而不是以什么形式做。
面问对家的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
传统写法
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用
java.lang.Thread
类来启动该线程。代码如下∶
/* 创建Runnable接口的实现类,重写run方法,设置线程任务 */ public class RunnableImpl implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "创建了线程" ); } } /* 使用实现Runnable接口的方式实现多线程程序 */ public class Demo01Runnable { public static void main(String[] args) { //创建Runnable接口的实现类对象 RunnableImpl run = new RunnableImpl(); //创建Thread类对象,构造方法中传递Runnable接口的实现类 Thread t = new Thread(run); //调用start方法开启新线程,执行run方法 t.start(); //简化代码,使用匿名内部类,实现多线程程序 Runnable r = new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName() + "创建了线程"); } }; new Thread(r).start(); new Thread(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName() + "创建了线程"); } }).start(); } }
对于Runnable的匿名内部类用法,可以分析出几点内容∶
借助Java8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:
public class Demo02Lambda { public static void main(String[] args) { new Thread(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName() + "创建了线程"); } }).start(); //使用Lambda表达式,实现多线程 new Thread(()->{ System.out.println(Thread.currentThread().getName() + "创建新线程"); } ).start(); } }
Lambda省去面向对象的条条框框,格式由3部分组成:
一些参数
一个箭头
一段代码
Lambda表达式的标准格式为:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
/* 需求: 给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参数、无返回值。 使用Lambda的标准格式调用invokeCook方法,打印输出“吃饭啊!”字样 */ /* 定一个厨子Cook接口,内含唯一的抽象方法makeFood */ public interface Cook { //定义无参数无返回值的方法makeFood public abstract void makeFood(); } public class Demo01Cook { public static void main(String[] args) { //调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类对象 invokeCook(new Cook() { @Override public void makeFood() { System.out.println("吃饭了"); } }); //使用Lambda表达式,简化匿名内部类的书写 invokeCook(()->{ System.out.println("吃饭了"); }); } //定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood public static void invokeCook(Cook cook){ cook.makeFood(); } }
需求: 使用数组存储多个Person对象 对数组中的Person对象是使用Arrays的sort方法通过年龄进行升序排序
下面举例演示java.util.Comparator接口的使用场景代码,其中的抽象方法定义为:
public abstract int compare(T o1, T o2);
Arrays.sort
方法需要一个Comparator
接口实例来指定排序的规则。假设一个Person
类,含有String name
和int age
两个成员变量:public class Person{ private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } import java.util.Arrays; import java.util.Comparator; /* Lambda表达式有参数有返回值的练习 需求: 使用数组存储多个Person对象 对数组中的Person对象是使用Arrays的sort方法通过年龄进行升序排序 */ public class Demo01Arrays { public static void main(String[] args) { //使用数组存储多个Person对象 Person[] arr = { new Person("柳岩", 38), new Person("迪丽热巴", 18), new Person("古力娜扎", 19) }; //对数组中的Person对象是使用Arrays的sort方法通过年龄进行升序排序 // Arrays.sort(arr, new Comparator<Person>() { // @Override // public int compare(Person o1, Person o2) { // return o1.getAge() - o2.getAge(); // } // // }); //使用Lambda表达式,简化匿名内部类 Arrays.sort(arr, (Person o1, Person o2)->{ return o1.getAge() - o2.getAge(); }); //遍历数组 for (Person person : arr) { System.out.println(person); } } }
题目
给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值:
public interface Calculator { //定义一个计算两个int整数和的方法并返回结果 public abstract int calc(int a, int b); } /* Lambda表达式有参数有返回值的练习 需求: 给定一个计算器calculator接口,内含抽象方法calc可以将两个int数字相加得到和值 使用Lambda的标准格式调用invokeCalc方法,完成120和130的相加计算 */ public class Demo01Calculator { public static void main(String[] args) { //调用invokeCalc方法,方法的参数是一个接口,可以使用匿名内部类 invokeCalc(10, 20, new Calculator() { @Override public int calc(int a, int b) { return a + b; } }); //使用Lambda表达式简化匿名内部类的书写 invokeCalc(100, 20, (int a, int b)->{ return a + b; }); } /* 定义一个方法 参数传递两个int类型的整数 参数传递Calculator接口 方法内部调用Calculator中的方法calc计算两个整数的和 */ public static void invokeCalc (int a, int b, Calculator c){ int sum = c.calc(a, b); System.out.println(sum); } }
可推导即可省略
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。例如上例还可以使用Lambda的省略写法:
public static void main(String[] args){ invokeCalc(120, 130, (a, b) -> a + b); }
省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意∶
无论是JDK内置的
Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
2.使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为"函数式接口”。
java.io.File类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。
import java.io.File; /* java.io.File类 文件和目录路径名的抽象表示形式。 java把电脑中的文件和文件夹(目录)封装为了一个File类,我们可以使用File类对文件和文件夹进行操作 我们可以使用File类的方法 创建一个文件/文件夹 删除文件/文件夹 获取文件/文件夹 判断文件/文件夹是否存在 对文件夹进行遍历 获取文件的大小 File类是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法 重点:记住这三个单词 fiLe:文件 directory:文件夹/目录 path:路径 */ public class Demo01File { public static void main(String[] args) { /* static String pathSeparator 与系统相关的路径分隔符字符,为方便起见,表示为字符串。 static char pathSeparatorChar 与系统相关的路径分隔符。 static String separator 与系统相关的默认名称分隔符字符,以方便的方式表示为字符串。 static char separatorChar 与系统相关的默认名称分隔符。 操作路径:路径不能写死了 C:\develop\a\a.txt windows C:/develop/a/a.txt Linux "C:" + File.separator + "develop" + File.separator + "a" + File.separator + "a.txt" */ String pathSeparator = File.pathSeparator; System.out.println(pathSeparator);//路径分隔符 Windows : 分号(;) Linux : 冒号(:) String separator = File.separator; System.out.println(separator);//文件名称分隔符 Windows : 反斜杠(\) Linux : 正斜杠(/) } }
public File(String pathname)
:通过将给定的路径名字符串转换为抽象路径名来创建新的File实例。import java.io.File; /* 路径: 绝对路径:是一个完整的路径 以盘符(C:, D:)开始的路径 c:\\a.txt c:\\users\\itcast\\IdeaProjects\\shungyuan\\123.txt D:\\demo\\b.txt 相对路径:是一个简化的路径 相对指的是相对于当前项目的根目录(C: \\users\\itcast\\IdeaProjects\\shungyuan) 如果使用当前项自的根目录,路径可以简化书写 c:\\Users\\itcast\\IdeaProjects\\shungyuan\\123.txt-->简化为:123.txt(可以省略项目的根目录) 注意: 1.路径是不区分大小写 2.路径中的文件名称分隔符windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠 */ public class Demo02File { public static void main(String[] args) { show01(); show02("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory", "a.txt"); show03(); } /* File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新File实例。 参数: String pathname:字符串的路径名称 路径可以是以文件结尾,也可以是以文件夹结尾 路径可以是相对路径,也可以是绝对路径 路径可以是存在,也可以是不存在 创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况 */ private static void show01() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\a.txt"); System.out.println(f1);//重写了Object类的toString方法 E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt } /* File(String parent,String child)根据 parent 路径名字符串和child路径名字符串创建一个新 File实例 参数:把路径分成了两部分 String parent:父路径 String child :子路径 好处: 父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化 */ private static void show02(String parent, String child) { File file = new File(parent, child); System.out.println(file);//E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt } /* File(File parent,String child)根据parent抽象路径名和child路径名字符串创建一个新File实例。 参数:把路径分成了两部分 File parent:交路径 String child:子路径 好处: 父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化 父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象 */ private static void show03() { File parent = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\");//最后两个反斜杠加和不加都可以,结果都一样 File file = new File(parent, "hello.java"); System.out.println(file);//E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\hello.java } }
public string getAbsolutePath()
:返回此File的绝对路径名字符串。public string getPath()
:将此File转换为路径名字符串。public string getName()
:返回由此File表示的文件或目录的名称。public long length()
:返回由此File表示的文件的长度。import java.io.File; /* FiLe类获取功能的方法 - public string getAbsolutePath():返回此File的绝对路径名字符串。 - public string getPath() :将此File转换为路径名字符串。 - public string getName():返回由此File表示的文件或目录的名称。 - public long length():返回由此File表示的文件的长度。 */ public class Demo03File { public static void main(String[] args) { show01(); show02(); show03(); show04(); } /* public string getAbsolutePath() :返回此File的绝对路径名字符串。 获取的构造方法中传递的路径 无论路径是绝对的还是相对的, getAbsolutePath方法返回的都是绝对路径 */ private static void show01() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\a.txt"); String absolutePath1 = f1.getAbsolutePath(); System.out.println(absolutePath1);//E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt File f2 = new File("a.txt"); String absolutePath2 = f2.getAbsolutePath(); System.out.println(absolutePath2);//E:\Java\JavaWorkSpace\enhance_code\a.txt } /* public string getPath():将此File转换为路径名字符串。 获取的构造方法中传递的路径 toString方法调用的就是getPath方法 源码: public String toString() { return getPath(); } */ private static void show02() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\a.txt"); File f2 = new File("a.txt"); String path1 = f1.getPath(); System.out.println(path1);//E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt String path2 = f2.getPath(); System.out.println(path2);//a.txt System.out.println(f1);//E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt System.out.println(f1.toString());//E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt } /* public string getName():返回由此File表示的文件或目录的名称。 获取的就是构造方法传递路径的结尾部分(文件/文件夹) */ private static void show03() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\a.txt"); String name1 = f1.getName(); System.out.println(name1);//a.txt File f2 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); String name2 = f2.getName(); System.out.println(name2);//file_directory } /* public Long Length():返回由此File表示的文件的长度。 获取的是构造方法指定的文件的大小,以字节为单位 注意: 文件夹是没有大小概念的,不能获取文件夹的大小 如果构造方法中给出的路径不存在,那么length方法返回0 */ private static void show04() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\a.txt"); long length = f1.length(); System.out.println(length);//4字节 File f2 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\b.txt"); System.out.println(f2.length());//0字节 File f3 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); System.out.println(f3.length());//文件夹没有大小概念 } }
public boolean exists()
: 此File表示的文件或目录是否实际存在public boolean isDirectory()
: 此File表示的是否为目录public boolean isFile()
: 此File表示的是否为文件import java.io.File; /* FiLe判断功能的方法 - public boolean exists():此File表示的文件或目录是否实际存在。 - public boolean isDirectory():此File表示的是否为目录。 - public boolean isFile():此File表示的是否为文件。 */ public class Demo04File { public static void main(String[] args) { show01(); show02(); } /* public booLean exists :此File表示的文件或目录是否实际存在。用于判断构造方法中的路径是否存在 存在:true 不存在:false */ private static void show01() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code"); System.out.println(f1.exists());//true File f2 = new File("E:\\Java\\JavaWorkSpace\\enhance"); System.out.println(f2.exists());//false File f3 = new File("exam08\\file_directory\\a.txt");//相对路径 E:\Java\JavaWorkSpace\enhance System.out.println(f3.exists());//true } /* - public boolean isDirectory():此File表示的是否为目录。 用于判断构造方法中的路径是否以文件夹结尾 是:true 否:false - public boolean isFile():此File表示的是否为文件 用于判断构造方法中的路径是否以文件结尾 是:true 否:false 注意: 电脑的硬盘中只有文件/文件夹,两个方法是互斥 这两个方法使用前提,路径必须是存在的,否则都返回false */ private static void show02() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance"); //不存在,就没有必要获取 if (f1.exists()){ System.out.println(f1.isDirectory()); System.out.println(f1.isFile()); } File f2 = new File("E:\\Java\\JavaWorkSpace\\enhance_code"); if (f2.exists()){ System.out.println(f2.isDirectory());//true System.out.println(f2.isFile());//false } File f3 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\a.txt"); if (f3.exists()){ System.out.println(f3.isDirectory());//false System.out.println(f3.isFile());//true } } }
import java.io.File; import java.io.IOException; /* File类创建删除功能的方法 - public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。 - public boolean delete() :删除由此File表示的文件或目录。 - public boolean mkdir() :创建由此File表示的目录。 - public boolean mkdirs( )﹔创建由此File表示的目录,包括任何必需但不存在的父目录。 */ public class Demo05File { public static void main(String[] args) throws IOException { show01(); show02(); show03(); } /* public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。 创建文件的路径和名称在构造方法中给出(构造方法的参数) 返回值:布尔值 true:文件不存在,创建文件,返回true false:文件存在,不会创建,返回false 注意 1.此方法只能创建文件,不能创建文件夹 2.创建文件的路径必须存在,否则会抛出异常 public boolean createNewFile() throws IOException createNewFiLe声明抛出了IOException,我们调用这个方法,就必须的处理这个异常,要么throws ,要么try……catch */ private static void show01() throws IOException { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\b.txt"); boolean b1 = f1.createNewFile(); System.out.println("b1:" + b1); File f2 = new File("exam08\\file_directory\\c.txt"); System.out.println(f2.createNewFile()); //File f3 = new File("exam\\a.txt"); //System.out.println(f3.createNewFile());//路径不存在会抛出IOException } /* public boolean mkdir() :创建单级空文件夹 创建文件夹的路径和名称在构造方法中给出(构造方法的参数) 返回值:布尔值 true:文件夹不存在,创建文件夹,返回true false:文件夹存在,不会创建,返回false;构造方法中给出的路径不存在返回false 注意: 1.此方法只能创建文件夹,不能创建文件 public boolean mkdirs( ):既可以创建单级空文件夹,也可以创建多级文件夹 */ private static void show02() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\aaa"); boolean b1 = f1.mkdir(); System.out.println("b1:" + b1); File f2 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\bbb\\ccc"); boolean b2 = f2.mkdirs(); System.out.println("b2:" + b2); } /* public booLean delete():删除由此FiLe表示的文件或目录。 此方法,可以删除构造方法路径中给出的文件/文件夹 返回值:布尔值 true:文件/文件夹删除成功,返回true false:文件夹中有内容,不会删除返回false;构造方法中路径不存在false 注意: delete方法是直接在硬盘删除文件/文件夹,不走回收站,删除要谨慎 */ private static void show03() { File f1 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\aaa"); boolean b1 = f1.delete(); System.out.println(b1); File f2 = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\c.txt"); boolean b2 = f2.delete(); System.out.println(b2); } }
public String[] list()
: 返回一个String数组,表示该File目录中的所有子文件或者目录。public File[] listFiles()
: 返回一个File数组,表示该File目录中的所有子文件或者目录。import java.io.File; /* FiLe类遍历(文件夹)目录功能 - public String[] list():返回一个String数组,表示该File目录中的所有子文件或目录。隐藏的文件夹或文件也可以遍历到。 - public File[] listFiles():返回一个File数组,表示该File目录中的所有的子文件或目录。 注意: list方法和ListFiles方法遍历的是构造方法中给出的目录 如果构造方法中给出的自录的路径不存在,会抛出空指针异常 如果构造方法中给出的路径不是一个目录,也会抛出空指针异常 */ public class Demo06File { public static void main(String[] args) { show01(); show02(); } /* public String[] list():返回一个String数组,表示该File目录中的所有子文件或目录。 遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把获取到的多个名称存储到一个String类型的数组中 */ private static void show01() { //File file = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory\\a.txt");//如果访问的不是文件夹会报空指针异常NullPointerException File file = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); String[] arr = file.list(); for (String fileName : arr){ System.out.println(fileName); } } /* public File[] listFiles():返回一个FiLe数组,表示该FiLe目录中的所有的子文件或目录。 遍历构造方法中给出的目录,会获取目录中所有的文件/文件夹,把文件/文件夹封装为File对象,多个File对象存储到File数组中 */ private static void show02() { File file = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); File[] files = file.listFiles(); for (File f : files){ System.out.println(f); } } }
/* 递归:方法自己调用自己 -递归的分类: -递归分为两种,直接递归和间接递归。 -直接递归称为方法自身调用自己。 -间接递归可以A方法调用B方法,8方法调用c方法,c方法调用A方法。 -注意事项: -递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。 -在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。 -构造方法,禁止递归 递归的使用前提: 当调用方法的时候,方法的主体不变,每次调用方法的参数不同,可以使用递归 */ public class Demo01Recursion { public static void main(String[] args) { a(); b(1); } /* 构造方法,禁止递归 编译报错:构造方法是创建对象使用的,一直递归会导致内存中有无数多个对象,直接编译报错 */ /*public Demo01Recursion(){ Demo01Recursion(); } */ /* 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。 Exception in thread "main" java.Lang. StackOverflowError a方法会在栈内存中一直调用a方法,就会导致栈内存中有无数多个a方法 方法太多了,超出栈内存的大小,就会导致内存溢出的错误 注意: 当一个方法调用其他方法的时候,被调用的方法没有执行完毕, 当前方法会一直等待调用的方法执行完毕才会继续执行 */ private static void a(){ System.out.println("a方法!"); a(); } /* 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。 11157 Exception in thread "main" java.Lang.StackOverflowError */ private static void b(int i) { System.out.println(i); if (i == 20000) { return; } b(++i); } }
/* 练习: 使用递归计算1-n之间的和 */ public class Demo02Recurison { public static void main(String[] args) { /* 定义一个方法,使用递归计算1-n之间的和 1+2+3+...+n n+(n-1)+(n-2)+...+1 已知: 最大值:n 最小值:1 使用递归必须明确: 1.递归的结束条件 获取到1的时候结束 2.递归的目的 获取下一个被加的数字(n-1) */ int s = sum (100); System.out.println(s); } private static int sum(int n){ if (n == 1) return 1; return n + sum(n - 1); } }
/* 使用递归计算阶乘 n的阶乘:n! = n * (n-1) *……* 1; */ public class Demo03Recursion { public static void main(String[] args) { int ans = jc(5); System.out.println(ans); } private static int jc(int i) { if (i == 1){ return 1; } return i * jc (i - 1); } }
import java.io.File; /* 练习: 递归打印多级目录 需求: 遍历:E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory文件夹,及file_directory文件夹的子文件夹 E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\b.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\bbb.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\ccc E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\ccc\ccc.txt */ public class Demo04Recursion { public static void main(String[] args) { File file = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); getAllFile(file); } private static void getAllFile(File dir){ System.out.println(dir); File[] files = dir.listFiles(); for (File f : files){ if (f.isDirectory()) getAllFile(f); else{ System.out.println(f); } } } }
import java.io.File; /* 练习: 递归打印多级目录 需求: 只要.txt结尾的文件 遍历:E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory文件夹,及file_directory文件夹的子文件夹 E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\b.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\bbb.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\ccc E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\ccc\ccc.txt */ public class Demo05Recursion { public static void main(String[] args) { File file = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); getAllFile(file); } private static void getAllFile(File dir){ File[] files = dir.listFiles(); for (File f : files){ if (f.isDirectory()) getAllFile(f); else{ /* 只要.txt结尾的文件 1.把f转为字符串对象 */ //String s = f.getName(); //String s = f.getPath(); String s = f.toString(); //把字符串,转换为小写 s = s.toLowerCase(); //2.调用String类中的方法endswith判断字符串是否是以.txt结尾 boolean b = s.endsWith(".txt"); //3.如果是以.txt结尾的文件,这输出 if (b){ System.out.println(f); } } } } }
import java.io.File; import java.io.FileFilter; /* 创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则 */ public class FileFilterImpl implements FileFilter { @Override public boolean accept(File pathname) { /* accept方法返回值是一个布尔值 true;就会记传递过去的File对象保存到File数组中 false:就不会把传递过去的File对象保存到File数组中 过滤的规则: 在accept方法中,判断File对象是否是以.java结尾; 是就返回true 不是就返回false */ if (pathname.isDirectory()) return true; String s = pathname.toString(); if(s.endsWith(".txt")) return true; return false; } } import java.io.File; /* 练习: 递归打印多级目录 需求: 只要.txt结尾的文件 遍历:E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory文件夹,及file_directory文件夹的子文件夹 E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\a.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\b.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\bbb.txt E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\ccc E:\Java\JavaWorkSpace\enhance_code\exam08\file_directory\bbb\ccc\ccc.txt 我们可以使用过滤器来实现 在File类中有两个和listFiles重载的方法,方法的参数传递的就是过滤器 File[] listFiles (FileFilter filter) java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器。 作用:用来过滤文件(File对象) 抽象方法:用来过滤文件的方法 boolean accept(File pathname)测试指定抽象路径名是否应该包含在某个路径名列表中。 参数: File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象 File[] listFiles ( FilenameFilter filter) java.io.FilenameFilter接口:实现此接口的类实例可用于过滤器文件名。 作用:用于过滤文件名称 抽象方法:用来过滤文件的方法 boolean accept(File dir,String name)测试指定文件是否应该包含在某一文件列表中。 参数: File dir:构造方法中传递的被遍历的目录 String name:使用ListFiles方法遍历目录,获取的每一个文件/文件夹的名称 注意: 两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则 */ public class demo01Filter { public static void main(String[] args) { File file = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); getAllFile(file); } private static void getAllFile(File dir){ File[] files = dir.listFiles(new FileFilterImpl()); /* listFiles方法一共做了3件事情: 1.listFiles方法会对构造方法中传递的目录进行遍历,获取目录中的每一个文件/文件夹-->封装为File对象 2.listFiles方法会调用参数传逞的过活器中的方法accept 3.listFiles方法会把遍历得到的每一个File对象,传递过accept方法的参数pathname */ for (File f : files){ if (f.isDirectory()) getAllFile(f); else{ System.out.println(f); } } } }
import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; public class demo02Filter { public static void main(String[] args) { File file = new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam08\\file_directory"); getAllFile(file); } private static void getAllFile(File dir){ /*File[] files = dir.listFiles(new FileFilter(){ @Override public boolean accept(File pathname) { return pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".txt"); } });*/ // File[] files = dir.listFiles(new FilenameFilter() { // @Override // public boolean accept(File dir, String name) { // return new File(dir, name).isDirectory() || name.toLowerCase().endsWith(".txt"); // } // }); //使用Lambda表达式优化匿名内部类(接口中只有一个抽象方法) // File[] files = dir.listFiles((File d, String name)->{ // return new File(d, name).isDirectory() || name.toLowerCase().endsWith(".txt"); // }); File[] files = dir.listFiles((d, name)->new File(d, name).isDirectory() || name.toLowerCase().endsWith(".txt")); /* listFiles方法一共做了3件事情: 1.listFiles方法会对构造方法中传递的目录进行遍历,获取目录中的每一个文件/文件夹-->封装为File对象 2.listFiles方法会调用参数传逞的过活器中的方法accept 3.listFiles方法会把遍历得到的每一个File对象,传递过accept方法的参数pathname */ for (File f : files){ if (f.isDirectory()) getAllFile(f); else{ System.out.println(f); } } } }
IO流用来处理设备之间的数据传输,Java程序中,对于数据的输入/输出操作 都是以“流”的方式进行的。java.io包下提供了各种“流”类的接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
对于计算机来说,数据都是以二进制形式读出或写入的。我们可以把文件想象为一个桶,我们可以通过管道将桶里的水抽出来。这里的管道也就相当于Java中的流。流的本质是一种有序的数据集合,有数据源和目的地。
根据数据的流向分为:输入流和输出流
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close()
︰关闭此输出流并释放与此流相关联的任何系统资源。public void flush()
︰刷新此输出流并强制任何缓冲的输出字节被写出。public void write(byte[] b)
:将b.length字节从指定的字节数组写入此输出流。(一次写多个字节)public void write(byte[] b,int off,int len)
:从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。(一次写多个字节)public abstract void write(int b)
︰将指定的字节输出流。(一次写一个字节)import java.io.FileOutputStream; import java.io.IOException; /* java.io.OutputStream:字节输出流 此抽象类是表示输出字节流的所有类的超类。 定义了一些子类共性的成员方法: - public void close() :关闭此输出流并释放与此流相关联的任何系统资源。 - public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。 - public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流。 - public void write(byte[] b, int off, int len) :从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。 - public abstract void write(int b):将指定的字节输出流。 java.io.FileOutputStream extends OutputStream FileOutputStream:文件字节输出流 作用:把内存中的数据写入到硬盘的文件中 构造方法: FileOutputStream(String name)创建一个向具有指定名称的文件中写入数据的输出文件流。 FileOutputStream(File file)创建一个向指定File对象表示的文件中写入数据的文件输出流。 参数:写入数据的目的 String name:目的地是一个文件的路径 File file:目的地是一个文件 构造方法的作用: 1.创建一个FileOutputStream对象 2.会根据构造方法中传递的文件/文件路径,创建一个空的文件 3.会把FiLeOutputStream对象指向创建好的文件 写入数据的原理(内存-->硬盘) java程序-->JVM(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中 字节输出流的使用步骤(重点): 1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地 2.调用FileOutputStream对象中的方法write,把数据写入到文件中 3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率) */ public class Demo01OutputStream { public static void main(String[] args) throws IOException { //1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地 FileOutputStream fos = new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\a.txt"); //2.调用FileOutputStream对象中的方法write,把数据写入到文件中 fos.write(97); //3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率) fos.close(); } }
文件储存原理:
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; /* 一次写多个字节的方法; - public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流。 - public void write(byte[] b, int off, int len) :从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。 */ public class Demo02OutputStream { public static void main(String[] args) throws IOException { //创建FileOutputStream对象,构造方法中绑定要写入数据的目的地 FileOutputStream fos = new FileOutputStream(new File("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\b.txt")); //调用FileOutputStream对象中的方法write,把数据写入到文件中 //在文件中显示100,写个字节 fos.write(49);//1 fos.write(48);//0 fos.write(48);//0 /* public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流。 一次写多个字节: 如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII表 如果写的第一个字节是负数,那第一个字节会和第二个字节,两个字节组成一个中文显示,查询系统认码表(GBK) */ byte[] bytes = {65, 66, 67, 68, 69, 70};//ABCDEF //byte[] bytes = {-65, -66, -67, 68, 69, 70};//100烤紻EF fos.write(bytes); /*public void write(byte[] b, int off, int len) :把字节数组的一部分写入到文件中 int off:数组的开始索引 int len:写几个字节 */ fos.write(bytes, 1, 2);//BC /* 写入字符的方法:可以使用String类中的方法把字符串,转换为字节数组 byte[] getBytes() 把字符串转换为字节数组 */ byte[] bytes1 = "你好".getBytes(); System.out.println(Arrays.toString(bytes1));//[-28, -67, -96, -27, -91, -67] fos.write(bytes1); //释放资源 fos.close(); } }
经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?
public FileoutputStream(File file,boolean append)
:创建文件输出流以写入由指定的File对象表示的文件。public FileoutputStream( String name,boolean append)
:创建文件输出流以指定的名称写入文件。true
表示追加数据,false
表示清空原有数据。import java.io.FileOutputStream; import java.io.IOException; /* 追加写/续写:使用两个参数的构造方法 FileOutputStream(String name, boolean append)创建一个向具有指定name的文件中写入数据的输出文件流。 FileOutputStream(File file, boolean append)创建一个向指定File对象表示的文件中写入数据的文件输出流。 参数: String name, File file:写入数据的目的地 boolean append:追加写开关 true:创建对象不会覆盖源文件,继续在文件的末尾追加写数据 false:创建一个新文件,覆盖源文件 写换行:写换行符号 Windows:\r\n linux: /n mac: /r */ public class Demo03OutputStream { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\c.txt", true); fos.write("你好".getBytes()); for (int i = 0; i < 10; i++) { fos.write("你好".getBytes()); fos.write("\r\n".getBytes()); } fos.close(); } }
java.io.InputStream
: 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public void close()
∶关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read()
:从输入流读取数据的下一个字节。
public int read(byte[] b)
∶从输入流中读取一些字节数,并将它们存储到字节数组b中。
小贴士: close方法,当完成流的操作时,必须调用此方法,释放系统资源。
/* java.io.InputStream:字节输入流 此抽象类是表示字节输入流中的所有类的超类 定义了所有子类共性的方法: int read()从输入流中读取数据的下一个字节 int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓存区数组b中。 void close()关闭此输入流并释放与该流关联的所有系统资源 java.io.FileInputStream extends InputStream FileInputStream:文件字节输入流 作用:把硬盘文件中的数据,读取到内存中使用 构造方法: FileInputStream(String name) FileInputStream(File file) 参数:读取文件的数据源 String name:文件的路径 File file:文件 构造方法的作用: 1.会创建一个FileInputStream对象 2.会把FileInputStream对象指定构造方法中要读取的文件 读取数据的原理(硬盘-->内存) java程序-->JVM-->OS-->OS读取数据的方法-->读取文件 字节输入流的使用步骤(重点): 1.创建FileInputStream对象,构造方法中绑定要读取的数据源 2.使用FileInputStream对象中的方法read,读取文件 3.释放资源 */ public class Demo01InputStream { public static void main(String[] args) throws IOException { //1.创建FileInputStream对象,构造方法中绑定要读取的数据源 FileInputStream fis = new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\b.txt"); //2.使用FileInputStream对象中的方法read,读取文件 //int read()读取文件中的一个字节并返回,读取到文件的末尾返回-1 /* int len = fis.read();//每次读完指针会往后移一位 System.out.println(len);//97 a len = fis.read(); System.out.println(len);//98 b len = fis.read(); System.out.println(len);//99 c len = fis.read(); System.out.println(len);// -1 len = fis.read(); System.out.println(len);// -1 */ /* 发现以上读取文件是一个重复的过程,所以可以使用循环优化 不知道文件中有多少字节,使用while循环 while循环结束条件,读取到-1的时候结束 布尔表达式(len = fis.read())!=-1 1.fis.read():读取一个字节 2.len = fis.read():把读取到的字节赋值给变量len 3.(len = fis.read())!=-1:判断变量len是否不等于-1 */ int len = 0; while ((len = fis.read()) != -1){ System.out.println((char)len); } //3.释放资源 fis.close(); } }
一次读取一个字节原理:
import java.io.FileInputStream; import java.io.IOException; /* 字节输入流一次读取多个自己的方法: int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中 明确两件事情: 1.方法的参数byte[]的作用? 起到缓冲作用,存储每次读取到的多个字节 数组的长度一把定义为1024(1kb)或者1024的整数倍 如果byte[]的容量够大的话,第一次会把所有的内容读取并返回有效字节个数,第二次读取则返回-1 如果byte[]的容量比内容少,则第一次读取读到数组最大容量返回有效字节个数,第二次从上一次结束的下一个位置开始读取 2.方法的返回值int是什么? 每次读取的有效字节个数 String类的构造方法 String(byte[] bytes):把字节数组转换为字符串 String(byte[] bytes, int offset, int length)把字节数组的一部分转换为字符串offset:数组的开始索引length:转换的字节个数 */ public class Demo02InputStream { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\b.txt"); /* byte[] bytes = new byte[2]; int len = fis.read(bytes); System.out.println(len);//2 //System.out.println(Arrays.toString(bytes));//[97, 98] System.out.println(new String(bytes));//ab len = fis.read(bytes); System.out.println(len);//1 System.out.println(new String(bytes));//cb len = fis.read(bytes); System.out.println(len);//-1 System.out.println(new String(bytes));//cb */ /* 发现以上读取时一个重复的过程,可以使用循环优化 不知道文件中有多少字节,所以使用while循环 while循环结束的条件,读取到-1结束 */ byte[] bytes = new byte[1024];//存取读取到的多个字节 int len = 0;//每次读取的有效字节个数 while ((len = fis.read(bytes)) != -1){ System.out.println(new String(bytes, 0, len)); } fis.close(); } }
一次读取多个字节原理:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /* 文件复制练习:一读一写 明确: 数据源:E:\Java\JavaWorkSpace\enhance_code\exam09\1.jpg 数据的目的地:E:\Java\JavaWorkSpace\enhance_code\exam09\file_directory\1.jpg 文件复制的步骤: 1.创建一个字节输入流对象,构造方法中绑定要读取的数据源 2.创建一个字节输出流对象,构造方法中绑定要写入的目的地 3.使用字节输入流对象中的方法read读取文件 4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中 5.释放资源 */ public class Demo01CopyFile { public static void main(String[] args) throws IOException { //1.创建一个字节输入流对象,构造方法中绑定要读取的数据源 FileInputStream fis = new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\1.jpg"); //2.创建一个字节输出流对象,构造方法中绑定要写入的目的地 FileOutputStream fos = new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\1.jpg"); int len = 0; byte[] bytes = new byte[1024]; //3.使用字节输入流对象中的方法read读取文件 while ((len = fis.read(bytes)) != -1){ // 4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中 fos.write(bytes, 0, len); } //5.释放资源(先关写的,后关闭读的;如果写完了,肯定读取完毕了) fos.close(); fis.close(); } }
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
:从输入流读取一个字符。public int read(char[ ] cbuf)
︰从输入流中读取一些字符,并将它们存储到字符数组cbuf中。FileReader(File file)
:创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
:创建一个新的FileReader ,给定要读取的文件的名称。当你创建一个流对象时,必须传入一个文件路径。类似于FilelnputStream。import java.io.FileReader; import java.io.IOException; /* java.io.Reader:字符输入流,是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类 共性的成员方法: int read()读取单个字符并返回。 int read(char[] cbuf)一次读取多个字符,将字符读入数组。 void close()关闭该流并释放与之关联的所有资源。 java.io.FileReader extends InputStreamReader extends Reader FiLeReader:文件字符输入流 作用:把硬盘文件中的数据以字符的方式读取到内存中 构造方法: FileReader(String fiLeName) FileReader(File file) 参数:读取文件的数据源 String fileName:文件的路径 File file:一个文件 FileReader构造方法的作用: 1.创建一个FileReader对象 2.会把FileReader对象指向要读取的文件 字符输入流的使用步骤: 1.创建FileReader对象,构造方法中绑定要读取的数据源 2.使用FileReader对象中的方法read读取文件 3.释放资源 */ public class demo02Reader { public static void main(String[] args) throws IOException { //1.创建FileReader对象,构造方法中绑定要读取的数据源 FileReader fr = new FileReader("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\c.txt"); //2.使用FileReader对象中的方法read读取文件 //int read()读取单个字符并返回。 /* int len = 0; while ((len = fr.read()) != -1){ System.out.print((char)len); } */ // int read(char[] cbuf)一次读取多个字符,将字符读入数组。 char[] cs = new char[1024]; int len = 0; while ((len = fr.read(cs)) != -1){ /* String类的构造方法 String(char[] value)把字符数组转换为字符串 String(char[] value, int offset, int count)把字符数组的一部分转换为字符串offset数组的开始索引,count转换的个数 */ System.out.println(new String(cs, 0, len)); } //3.释放资源 fr.close(); } }
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
void write(int c)
写入单个字符。void write(char[] cbuf)
写入字符数组。abstractvoid write(char[ ] cbuf,int off,int len)
写入字符数组的某一部分,off数组的开始索引len写的字符个数。void write(String str)
写入字符串。void write(String str, int off,int len)
写入字符串的某一部分,off字符串的开始索引len写的字符个数。void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。import java.io.FileWriter; import java.io.IOException; /* java.io.writer:字符输出流,是所有字符输出流的最顶层的父类,是一个抽象类 共性的成员方法: - void write(int c)写入单个字符。 - void write(char[] cbuf)写入字符数组。 - abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分, off数组的开始索引,Len写的字符个 - void write(String str)写入字符串。 - void write(String str, int off, int len)写入字符串的某一部分, off字符串的开始索引, Len写的字符个数。- void flush()刷新该流的缓冲。 - void close(关闭此流,但要先刷新它。 java.io.FileWriter extends OutputStreamWriter extends Writer FileWriter :文件字符输出流 作用:把内存中字符数据写入到文件中 构造方法: FileWriter(File file)根据给定的 File对象构造一个 FileWriter对象。 FileWriter(String fiLeName)根据给定的文件名构造一个FileWriter对象。 参数:写入数据的目的地 string fileName:文件的路径 File file:是一个文件 构造方法的作用: 1.会创建一个FileWriter对象 2.会根据构造方法中传递的文件/文件的路径,创建文件 3.会把FileWriter对象指向创建好的文件 字符输出流的使用步骤(重点): 1.创建FileWriter对象,构造方法中绑定要写入数据的目的地 2.使用FileWriter中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程) 3.使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中 4.释放资源(会先把内存缓冲区中的数据刷新到文件中) flush方法和close方法的区别 - flush:刷新缓冲区,流对象可以继续使用。 - close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。 */ public class Demo01Writer { public static void main(String[] args) throws IOException { //1.创建FileWriter对象,构造方法中绑定要写入数据的目的地 FileWriter fw = new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\e.txt"); //2.使用FileWriter中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程) //void write(int c)写入单个字符 fw.write(97); //3.使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中 fw.flush(); //刷新之后流可以继续使用 fw.write(98); //4.释放资源(会先把内存缓冲区中的数据刷新到文件中) fw.close(); //close方法之后流已经关闭了,已经从内存中消失了,流就不能再使用了 //fw.write(99);//IOException: Stream closed } }
输出字符,和字符串数据
import java.io.FileWriter; import java.io.IOException; /* 字符输出流写数据的其他方法 - void write(char[] cbuf)写入字符数组。 - abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分, off数组的开始索引,Len写的字符个 - void write(String str)写入字符串。 - void write(String str, int off, int len)写入字符串的某一部分, off字符串的开始索引, Len写的字符个数。 */ public class Demo03Writer { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\f.txt"); char[] cs = {'a', 'b', 'c', 'd', 'e'}; //void write(char[] cbuf)写入字符数组。 fw.write(cs);//abcde //void write(char[] cbuf, int off, int len)写入字符数组的某一部分, off数组的开始索引,Len写的字符个 fw.write(cs, 1, 3);//bcd //void write(String str)写入字符串。 fw.write("传智播客"); //void write(String str, int off, int len)写入字符串的某一部分, off字符串的开始索引, Len写的字符个数。 fw.write("黑马程序员", 2, 3); fw.close(); } }
续写和换行:操作类似于FileOutputStream
import java.io.FileWriter; import java.io.IOException; /* 续写和换行 续写,追加写;使用两个参数的构造方法 FileWriter(string fiLeName, boolean append) FileWriter(File file, boolean append) 参数: String fileName, File file:写入数据的目的地 boolean append:续写开关 true:不会创建新的文件覆盖源文件,可以续写; false:创建新的文件覆盖源文件 换行:换行符号 windows:\r\n linux:/n mac:/r */ public class Demo04Writer { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\g.txt",true); for (int i = 0; i < 10; i++) { fw.write("HolleWorld" + i + "\r\n"); } fw.close(); } }
import java.io.FileWriter; import java.io.IOException; /* 在jdk1.7之前使用try……catch……finally处理流中的异常 格式: try{ 可能会产生异常的代码 }catch(异常类变量 变量名){ 异常的处理逻辑 }finally{ 一定会执行的代码 释放资源 } */ public class Demo01TryCatch { public static void main(String[] args){ //提高变量fw的作用域,让finally可以使用 //变量在定义的时候,可以没有值,但是使用的时候必须有值 //fw = new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\g.txt", true);执行失败,fw没有值,fw》close会报错 FileWriter fw = null; try { //可能会产生异常的代码 fw = new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\g.txt", true); for (int i = 0; i < 10; i++) { fw.write("HolleWorld" + i + "\r\n"); } }catch (IOException e){ //异常的处理逻辑 System.out.println(e); }finally{ //一定会指定的代码 //创建对象失败了,fw的默认值是null,null是不能调用方法的,会抛出NULLPointerException,需要增加一个判断,不是null在把资源释放 if (fw != null){ try { //fw.close方 法声明抛出了IOException异常对象,所以我们就的处理这个异常对象,要么throws,要么try catch fw.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /* JDK7的新特性 在try的后边可以增加一个(),在括号中可以定义流对象 那么这个流对象的作用城就在try中有效 try中的代码执行完毕,会自动把浇对家释放,不用写finaLLy 格式: try(定义流对象;定义流对象……){ 可能会产生异常的代码 }catch(异常类变量 变量名){ 异常的处理逻辑 } */ public class Demo02JDK7 { public static void main(String[] args) { try(//1.创建一个字节输入流对象,构造方法中绑定要读取的数据源 FileInputStream fis = new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\1.jpg"); //2.创建一个字节输出流对象,构造方法中绑定要写入的目的地 FileOutputStream fos = new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\1.jpg");){ int len = 0; byte[] bytes = new byte[1024]; //3.使用字节输入流对象中的方法read读取文件 while ((len = fis.read(bytes)) != -1){ // 4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中 fos.write(bytes, 0, len); } }catch (IOException e){ System.out.println(e); } } }
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; /* JDK9新特性 try的前边可以定义流对象 在try后边的()中可以直接引入流对象的名称(变量名) 在try代码执行完毕之后,流对家也可以释放掉,不用写finally 格式: A a = new A(); B b = new B( ); try(a,b){ 可能会产生异常的代码 }catch(异常类变量 变量名){ 异常的处理逻辑 } */ public class Demo03JDK9 { public static void main(String[] args) throws FileNotFoundException { //1.创建一个字节输入流对象,构造方法中绑定要读取的数据源 FileInputStream fis = new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\1.jpg"); //2.创建一个字节输出流对象,构造方法中绑定要写入的目的地 FileOutputStream fos = new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\1.jpg"); try(fis; fos){ int len = 0; byte[] bytes = new byte[1024]; //3.使用字节输入流对象中的方法read读取文件 while ((len = fis.read(bytes)) != -1){ // 4.使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中 fos.write(bytes, 0, len); } }catch (IOException e){ System.out.println(e); } } }
java.util .Properties
继承于Hashtable
,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties
方法就是返回—个Properties
对象。
构造方法
public Properties()
:创建一个空的压性列表。基本的存储方法public object setProperty (String key,String value)
:保存一对属性。public String getProperty (String key)
:使用此属性列表中指定的键搜索属性值public set<String> stringPropertyNames()
:所有键的名称的集合。import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; import java.util.Set; /* java.utiL.Properties集合extends Hashtable<k, v> implements Map<k, v> Properties类表示了一个持久的属性集。Properties可保存在流中或从流中加载。 Properties集合是一个唯一和io流相结合的集合 可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储 可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对)读取到集合中使用 属性列表中每个键及其对应值都是一个字符串。 Properties集合是一个双列集合,key和value默认都是字符串 */ public class Demo01Properties { public static void main(String[] args) throws IOException { show01(); show02(); show03(); } /* 使用Properties集合存储数据,遍历取出Properties集合中的数据 Properties集合是一个双列集合,key和value黑认都是字符串 Properties集合有一些操作字符串的特有方法 Object setProperty (String key,String value)调用Hashtable 的方法 put。 String getProperty(String key)通过key找到value值,此方法相当于Map集合中的get(key)方法 Set<String> stringPropertyNames()返回此属性列表中的键集,其中该键及其对应值是字符串,,此方法相当于Map集合中的keyset方法 */ private static void show01() { //创建Properties集合对象 Properties properties = new Properties(); //使用setProperties往集合中添加数据 properties.setProperty("赵丽颖", "168"); properties.setProperty("迪丽热巴", "165"); properties.setProperty("古力娜扎", "160"); //使用stringPropertyNames把Properties集合中的键取出,存储到一个set集合中 Set<String> set =properties.stringPropertyNames(); //遍历Set集合,取出Properties集合的每一个键 for (String key : set) { //使用getProperty方法通过key获取value String value = properties.getProperty(key); System.out.println(key + "=" + value); } } /* 可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储 void store (OutputStream out, String comments) void store (Writer writer, String comments)参数: OutputStream out:字节输出流,不能写入中文 Writer writer:字符输出流,可以写中文 String comments:注释,用来解释说明保存的文件是做什么用的 不能使用中文,会产生乱码,默认是unicode编码 一般使用""空字符串 使用步骤: 1.创建Properties集合对象,添加数据 2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地 3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储 4.释放资源 */ private static void show02( ) throws IOException { //1.创建Properties集合对象,添加数据 Properties properties = new Properties(); properties.setProperty("赵丽颖", "168"); properties.setProperty("迪丽热巴", "165"); properties.setProperty("古力娜扎", "160"); //2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地 FileWriter fw = new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\prop.txt"); //3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储 properties.store(fw, "save data"); //4.释放资源 fw.close(); } /* 可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用 void load(InputStream inStream) void load(Reader reader) 参数︰ InputStream inStream:字节输入流,不能读取含有中文的键值对 Reader reader:字符输入流,能读取含有中文的键值对 使用步骤: 1.创建Properties集合对象 2.使用Properties集合对象中的方法load读取保存键值对的文件 3.遍历Properties集合 注意: 1.存储键值对的文件中,键与值默认的连接符号可以使用=,空格(其他符号) 2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取 3.存储键值对的文件中,键与值黑默认都是字符串,不用再加引号 */ private static void show03() throws IOException { //1.创建Properties集合对象 Properties prop = new Properties(); //2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取 prop.load(new FileReader("E:\\Java\\JavaWorkSpace\\enhance_code\\exam09\\file_directory\\prop.txt")); //3.遍历Properties集合 Set<String> set = prop.stringPropertyNames(); for (String key : set) { String value = prop.getProperty(key); System.out.println(key + "=" + value); } } }
缓冲流,也叫高效流,是对4个基本的FileXxx流的增强,所以也是4个流,按照数据类型分类:
BufferedInputStream
, BufferedOutputStream
BufferedReader
,BufferedWriter
public BufferedInputStream(InputStream in)
:创建一个新的缓冲输入流。public BufferedOutputStream(OutputStream out)
:创建一个新的缓冲输出流。import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; /* java.io.BufferedOutputStream extends OutputStream BufferedOutputStream:字节缓冲输出流 继承自父类的共性成员方法: - public void close():关闭此输出流并释放与此流相关联的任何系统资源。 - public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。 - public void write(byte[ ] b):将 b.length字节从指定的字节数组写入此输出流。 - public void write(byte[ ] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。 - public abstract void write(int b):将指定的字节输出流。 构造方法: BufferedOutputStream(OutputStream out)创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 BufferedOutputStream(OutputStream out,int size)创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流 参数: OutputStream out:字节输出流 我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率 int size:指定缓冲流内部缓冲区的大小,不指定默认 使用步骤(重点) 1.创建FileOutputStream对象,构造方法中绑定要输出的目的地 2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率 3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中 4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中 5.释放资源(会先调用flush方法刷新数据,第4步可以省略) */ public class Demo01BufferedOutputStream { public static void main(String[] args) throws IOException { //1.创建FileOutputStream对象,构造方法中绑定要输出的目的地 FileOutputStream fos = new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\a.txt"); //2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率 BufferedOutputStream bos = new BufferedOutputStream(fos); //3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中 bos.write("我把数据写入到内部缓冲区中".getBytes()); //4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中 bos.flush(); //5.释放资源(会先调用flush方法刷新数据,第4步可以省略) bos.close(); } }
import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; /* java.io.BufferedInputStream extends InputStream BufferedInputStream:字节缓冲输入流 继承自父类的成员方法: int read()从输入流中读取数据的下一个字节。 int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。 void close()关闭此输入流并释放与该流关联的所有系统资源。 构造方法: BufferedInputStream(InputStream in)创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用。 BufferedInputStream(InputStream in,int size)创建具有指定缓冲区大小的BufferedInputStream并保存其参数,即输入流in,供以后使用。 参数: InputStream in:字节输入流 我们可以传递FileInputStream,缓冲流会给FileInputStream增加一个缓冲区,提高FiLeInputStream的读取效率 int size:指定缓冲流内部缓冲区的大小,不指定默认 使用步骤(重点): 1.创建FileInputStream对象,构造方法中绑定要读取的数据源 2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率 3.使用BufferedInputStream对象中的方法read,读取文件 4 .释放资源 */ public class Demo02BufferedInputStream { public static void main(String[] args) throws IOException { //1.创建FileInputStream对象,构造方法中绑定要读取的数据源 FileInputStream fis = new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\a.txt"); //2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率 BufferedInputStream bis = new BufferedInputStream(fis); //3.使用BufferedInputStream对象中的方法read,读取文件 //int read()从输入流中读取数据的下一个字节。 /*int len = 0; while ((len = bis.read()) != -1){ System.out.println(len); }*/ //int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。 byte[] bytes = new byte[1024]; int len = 0; while ((len = bis.read(bytes))!= -1){ System.out.println(new String(bytes, 0, len)); } //4 .释放资源 bis.close(); } }
构造方法
public BufferedReader(Reader in)
:创建一个新的缓冲输入流。public Bufferedwriter(writer out)
:创建一个新的缓冲输出流。import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; /* java.io.BufferedWriter extends writer BufferedWriter:字符缓冲输出流 继承自父类的共性成员方法: - void write(int c)写入单个字符。 - void write(char[] cbuf)写入字符数组。 - abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分, of数组的开始索引,len写的字符个数。 - void write(String str)写入字符串。 - void write(String str, int off, int len)写入字符串的某一部分, off字符串的开始索引, Len写的字符个数。 - void flush()刷新该流的缓冲。 - void close()关闭此流,但要先刷新它。 构造方法: BufferedWriter(Writer out)创建一个使用默认大小输出缓冲区的缓冲字符输出流。 BufferedWriter(writer out,int sz)创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 参数: writer out:字符输出流 我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率 int sz:指定缓冲区的大小,不写默认大小 特有的成员方法: void newLine() 写入一个行分隔符。会根据不同的操作系统,获取不同的行分隔符 换行符号: Windows:\r\n Linux: /n mac: /r 使用步骤: 1.创建字符缓冲输出流对象,构造方法中传递字符输出流 2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中 3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中 4.释放资源 */ public class Demo03BufferedWriter { public static void main(String[] args) throws IOException { //1.创建字符缓冲输出流对象,构造方法中传递字符输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\b.txt")); //2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中 for (int i = 0; i < 10; i++){ bw.write("传智播客"); //bw.write("\r\n"); bw.newLine(); } //3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中 bw.flush(); //4.释放资源 bw.close(); } }
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /* java.io.BufferedReader extends Reader 继承自父类的共性成员方法: int read()读取单个字符并返回。 int read(char[] cbuf)一次读取多个字符,将字符读入数组。 void close()关团该流并释放与之关联的所有资源。 构造方法: BufferedReader(Reader in)创建一个使用默认大小输入缓冲区的缓冲字符输入流。 BufferedReader(Reader in, int sz)创建一个使用指定大小输入缓冲区的缓冲字符输入流。 参数: Reader in :字符输入流 我们可以传递FileReader ,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率 特有的成员方法: String readline()读取一个文本行。读取一行数据 行的终止符号:通过下列字符之一即可认为某行已终止:换行('\n ')、回车('\r')或回车后直接跟着换行(\r\n)。 返回值: 包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回null 使用步骤: 1.创建字符缓冲输入流对象,构造方法中传递字符输入流 2.使用字符缓冲输入流对象中的方法read/readLine读取文本 3.释放资源 */ public class Demo04BufferedReader { public static void main(String[] args) throws IOException { //1.创建字符缓冲输入流对象,构造方法中传递字符输入流 BufferedReader br = new BufferedReader(new FileReader("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\b.txt")); //2.使用字符缓冲输入流对象中的方法read/readLine读取文本 /* int len = 0; char[] bytes = new char[1024]; while ((len = br.read(bytes)) != -1){ System.out.println(new String(bytes, 0, len)); } */ String line; while ((line = br.readLine()) != null){ System.out.println(line); } br.close(); } }
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为言中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。 8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨课善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。 4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。 2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。 1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。 9.今当远离,临表涕零,不知所言。 6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,泰命于危难之间,尔来二十有一年矣。 7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。 5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
import java.io.*; import java.util.HashMap; import java.util.Set; /* 练习; 对文本的内容进行排序 按照(1,2,3....)顺序排序 分析: 1.创建一个HashMap集合对象,可以:存储每行文本的序号(1,2,3,. .) ; value:存储每行的文本 2.创建字符缓冲输入流对象,构造方法中绑定字符输入流 3.创建字符缓冲输出流对象,构造方法中绑定字符输出流 4.使用字符缓冲输入流中的方法readLine,逐行读取文本 5.对读取到的文本进行切割,获取行中的序号和文本内容 6.把切害好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序1,2,3,4..) 7.通历HashMap集合,获取每一个键值对 8.把每一个键值对,拼接为一个文本行 9.把拼接好的文本,使用字符缓冲输出流中的方法write,写入到文件中 10.释放资源 */ public class Demo05Test { public static void main(String[] args) throws IOException { //1.创建一个HashMap集合对象,可以:存储每行文本的序号(1,2,3,. .) ; value:存储每行的文本 HashMap<String, String> map = new HashMap<>(); //2.创建字符缓冲输入流对象,构造方法中绑定字符输入流 BufferedReader br = new BufferedReader(new FileReader("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\sort.txt")); //3.创建字符缓冲输出流对象,构造方法中绑定字符输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\sort1.txt")); //4.使用字符缓冲输入流中的方法readLine,逐行读取文本 String line; while ((line = br.readLine()) != null){ //5.对读取到的文本进行切割,获取行中的序号和文本内容 String[] arr = line.split("\\."); //6.把切害好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序1,2,3,4..) map.put(arr[0], arr[1]); } //7.通历HashMap集合,获取每一个键值对 Set<String> set = map.keySet(); for (String key : set){ String value = map.get(key); //8.把每一个键值对,拼接为一个文本行 line = key + "." + value; //9.把拼接好的文本,使用字符缓冲输出流中的方法write,写入到文件中 bw.write(line); bw.newLine(); } //10.释放资源 bw.close(); br.close(); } }
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
编码:字符(能看懂的)–字节(看不懂的)
解码:字节(看不懂的)–>字符(能看懂的)
Character Encoding
:就是一套自然语言的字符与二进制数之间的对应规则。字符集Charset
:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。
在IDEA中,使用FileReader读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
/* FileReader可以读取IDEA默认编码格式(UTF-8)的文件 FileReader读取系统黑t认编码(中文GBK)会产生乱码 */ public class Demo01FileReader { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\GBK.txt"); int len = 0; while ((len = fr.read()) != -1){ System.out.println((char) len); } fr.close(); } } 输出结果: � � �
那么如何读取GBK编码的文件呢?
转换流java.io.InAputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
InputstreamReader(InputStream in)
:创建一个使用默认字符集的字符流。InputStreamReader(InputStream in,String charsetName)
:创建一个指定字符集的字符流。package com.itheima.demo03ReverseStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; /* java.io.InputStreamReader extends Reader InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。(解码:把看不懂的变成能看懂的) 继承自父类的共性成员方法: int read()读取单个字符并返回。 int read(char[] cbuf)一次读取多个字符,将字符读入数组。 void close()关团该流并释放与之关联的所有资源。 构造方法: InputStreamReader(InputStream in)创建一个使用默认字符集的InputStreamReader。 InputStreamReader(InputStream in,String charsetName)创建使用指定字符集的InputStreamReader。 参数: InputStream in :字节输入流,用来读取文件中保存的字节 String charsetName:指定的编码表名称,不区分大小写,可以是utf-8/UTF-8,gbk/GBK ...不指定默认使用uTF-8 使用步骤: 1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称 2.使用InputStreamReader对象中的方法read读取文件 3.释放资源 注意事项: 构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码 */ public class Demo03InputStreamReader { public static void main(String[] args) throws IOException { read_utf_8(); read_gbk(); } /* 使用InputStreamReader读取UTF-8格式的文件 */ private static void read_gbk() throws IOException { //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称 InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\gbk1.txt"), "gbk"); //2.使用InputStreamReader对象中的方法read读取文件 int len = 0; while ((len = isr.read()) != -1){ System.out.println((char) len); } //3.释放资源 isr.close(); } /* 使用InputStreamReader读取UTF-8格式的文件 */ private static void read_utf_8() throws IOException { //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称 // InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\utf-8.txt"), "utf-8"); InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\utf-8.txt"));//不指定编码格式,默认为utf-8 //2.使用InputStreamReader对象中的方法read读取文件 int len = 0; while ((len = isr.read()) != -1){ System.out.println((char) len); } //3.释放资源 isr.close(); } }
OutputStreamWriter:是字符流通向字节流的桥梁:向其写入的字符编码成使用指定的字节charset 。 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
OutputStreamWriter(outputStream out)
创建使用黑认字符编码的 OutputStreamWriter。OutputStreamWriter(OutputStream out,String charsetName)
创建使用指定字符集的 OutputStreamWriter。import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; /* java.io.OutputStreamWriter extends Writer OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset将要写入流中的字符编码成字节。(编码:把能看懂的变成看不懂) 继续自父类的共性成员方法: - void write(int c)写入单个字符。 - void write(char[] cbuf)写入字符数组。 - abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分, off数组的开始索引, len写的字符个数。 - void write(String str)写入字符串。 - void write(String str, int off, int len)写入字符串的某一部分, off字符串的开始索引Len写的字符个数。 - void flush()刷新该流的缓冲。 - void close()关闭此流,但要先刷新它。 构造方法: OutputStreamWriter(outputStream out)创建使用黑认字符编码的 OutputStreamWriter。 OutputStreamWriter(OutputStream out,String charsetName)创建使用指定字符集的 OutputStreamWriter。 参数: OutputStream out:字节输出流,可以用来写转换之后的字节到文件中 String charsetName:指定的编码表名称,不区分大小写,可以是utf-8/UTF-8,gbk/GBK ...不指定默认使用UTF-8 使用步骤: 1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称 2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码) 3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程) 4.释放资源 */ public class Demo02OutputStreamWriter { public static void main(String[] args) throws IOException { //write_utf_8(); write_gbk(); } private static void write_gbk() throws IOException { //1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\gbk1.txt"), "GBK"); //2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码) osw.write("你好"); //3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程) osw.flush(); //4.释放资源 osw.close(); } /* 使用转换流OutputStreamWriter写UTF-8格式的文件 */ private static void write_utf_8() throws IOException { //1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称 // OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\utf-8.txt"));不指定默认使用UTF-8 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\utf-8.txt"), "utf-8"); //2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码) osw.write("你好"); //3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程) osw.flush(); //4.释放资源 osw.close(); } }
package com.itheima.demo03ReverseStream; import java.io.*; /* 练习:转换文件编码 将GBK编码的文本文件,转换为UTF-8编码的文本文件。 分析: 1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK 2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8 3.使用InputStreamReader对象中的方法read读取文件 4.使用OutputStreamWriter对象中的方法Write,把读取的数据写入到文件中 5.释放资源 */ public class Demo04Test { public static void main(String[] args) throws IOException { //1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\GBK.txt"), "gbk"); //2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\utf-8_1.txt"), "utf-8"); //3.使用InputStreamReader对象中的方法read读取文件 int len = 0; while ((len = isr.read()) != -1){ //4.使用OutputStreamWriter对象中的方法Write,把读取的数据写入到文件中 osw.write(len); } //5.释放资源 osw.close(); isr.close(); } }
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据
、对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。看图理解序列化:
注意:一个类的对象要想序列化成功,必须满足两个条件:
ObjectOutputStream 类用来序列化一个对象,如下的 SerializeDemo 例子实例化了一个 Person 对象,并将该对象序列化到一个文件中。
该程序执行后,就创建了一个名为 Person.ser 文件。该程序没有任何输出,但是你可以通过代码研读来理解程序的作用。
注意: 当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名。
import java.io.Serializable; /* 序列化和反序列化的时候,会抛出NotSerializableException没有序列化异常 类通过实现 java.io.Serializable接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。 Serializable接口也叫标记型接口 要进行序列化和反序列化的类必须实现Serializable接口,就会给类添加一个标记 当我们进行序列化和反序列化的时候,就会检测类上是否有这个标记 有:就可以序列化和反序列化 没有:就会出NotSerializableException异常 static关键字:静态关键字 静态优先于非静态加载到内存中(静态优先于对象进入到内存中) 被static修饰的成员变量不能被序列化的,序列化的都是对象 private static int age; oos.writeObject(new Person("小美女”,18)); object o = ois.readObject(); Person{name="小美女',age=0} transient关键字:瞬态关键字 被transient修饰成员变量,不能被序列化 private transient int age; oos.writeObject(new Person("小美女”,18)); object o = ois.readObject(); Person{name="小美女',age=0} */ public class Person implements Serializable{ private String name; // private static int age; // private transient int age; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; /* java.io.ObjectOutputStream extends OutputStream ObjectOutputStream:对象的序列化流 作用:把对象以流的方式写入到文件中保存 构造方法: ObjectOutputStream(OutputStream out)创建写入指定OutputStream的 ObjectOutputStream。 参数: OutputStream out:字节输出流 特有的成员方法: void writeObject(Object obj)将指定的对象写入ObjectOutputStream。 使用步骤: 1.创建ObjectOutputStream对象,构造方法中传递字节输出流 2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中 3.释放资源 */ public class Demo01ObjectOutputStream { public static void main(String[] args) throws IOException { //1.创建ObjectOutputStream对象,构造方法中传递字节输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\person.ser")); //2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中 oos.writeObject(new Person("小美女", 18)); //3.释放资源 oos.close(); } }
下面的 DeserializeDemo 程序实例了反序列化,E:\Java\JavaWorkSpace\enhance_code\exam10\file_directory\person.ser 存储了Person 对象。
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; /* java.io.ObjectInputStream extends InputStream ObjectInputStream:对象的反序列化流 作用:把文件中保存的对象,以流的方式读取出来使用 构造方法: ObjectInputStream(InputStream in〉创建从指定InputStream 读取的 ObjectInputStream 参数: InputStream in :字节输入流 特有的成员方法: Object readObject()从ObjectInputStream读取对象。 使用步骤: 1.创建ObjectInputStream对象,构造方法中传递字节输入流 2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件 3.释放资源 4.使用读取出来的对象(打印) readObject方法声明抛出了ClassNotFoundException(class文件找不到异常) 当不存在对象的class文件时抛出此异常 反序列化的前提: 1.类必须实现Serializable 2.必须存在类对应的class文件 */ public class Demo02ObjectInputStream { public static void main(String[] args) throws IOException, ClassNotFoundException { //1.创建objectInputStream对象,构造方法中传递字节输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\person.ser")); //2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件 Object o = ois.readObject(); //3.释放资源 ois.close(); //4.使用读取出来的对象(打印) System.out.println(o); } }
这里要注意以下要点:
readObject() 方法中的 try/catch代码块尝试捕获 ClassNotFoundException 异常。对于 JVM 可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException 异常。
**另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidclassException
异常。**发生这个异常的原因如下︰
Serializable
接口给需要序列化的类,提供了一个序列版本号。serialversionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
import java.io.*; import java.util.ArrayList; /* 练习:序列化集合 当我们想在文件中保存多个对象的时候可以把多个对象存储到一个集合中对集合进序列化和反序列化 分析: 1.定义一个存储Person对象的ArrayList集合 2.往ArrayList集合中存储Person对象 3.创建一个序列化流ObjectOutputStream对象 4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化 5.创建一个反序列化ObjectInputStream对象 6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合 7.把Object类型的集合转换为ArrayList类型 8.遍历ArrayList集合 9.释放资源 */ public class Demo03Test { public static void main(String[] args) throws IOException, ClassNotFoundException { //1.定义一个存储Person对象的ArrayList集合 ArrayList<Person> list = new ArrayList<>(); //2.往ArrayList集合中存储Person对象 list.add(new Person("张三", 18)); list.add(new Person("李四", 19)); list.add(new Person("王五", 20)); //3.创建一个序列化流ObjectOutputStream对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\list.ser")); //4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化 oos.writeObject(list); //5.创建一个反序列化ObjectInputStream对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\list.ser")); //6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合 Object o = ois.readObject(); //7.把Object类型的集合转换为ArrayList类型 ArrayList<Person> list2 = (ArrayList<Person>)o; //8.遍历ArrayList集合 for (Person p : list2){ System.out.println(p); } //9.释放资源 ois.close(); oos.close(); } }
平时我们在控制台打印输出,是调用print
方法和println
方法完成的,这两个方法都来自于java.io.Printstream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
public PrintStream(String fileName)
:使用指定的文件名创建一个新的打印流。import java.io.FileNotFoundException; import java.io.PrintStream; /* java.io.PrintStream:打印流 PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。 PrintStream特点: 1.只负责数据的输出,不负责数据的读取 2.与其他输出流不同,PrintStream永远不会抛出IOException 3.有特有的方法, print , printLn void print(任意类型的值) void println(任意类型的值并换行) 构造方法: PrintStream(File file):输出的目的地是一个文件 PrintStream(OutputStream out):输出的目的地是一个字节输出流 PrintStream (String fileName):输出的目的地是一个文件路径 PrintStream extends OutputStream 继承自父类的成员方法: - public void close() :关闭此输出流并释放与此流相关联的任何系统资源。 - public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。 - public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流。 - public void write(byte[] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。 - public abstract void write(int b):将指定的字节输出流。 注意: 如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表97->a 如果使用自己特有的方法print/println方法写数据,写的数据原样输出97->97 */ public class Demo01PrintStream { public static void main(String[] args) throws FileNotFoundException { System.out.println("HelloWorld"); //创建打印流PrintStream对象,构造方法中绑定要输出的目的地 PrintStream ps = new PrintStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\print.txt"); //如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97 -> a ps.write(97); //如果使用自己特有的方法print/println方法写数据,写的数据原样输出97 -> 97 ps.println(97); ps.println(8.8); ps.println('a'); //释放资源 ps.close(); } }
System.out
就是PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏”,改变它的流向。
import java.io.FileNotFoundException; import java.io.PrintStream; /* 可以改变输出语句的目的地(打印流的流向) 输出语句,默认在控制台输出 使用System.setOut方法改变输出语句的目的地改为参数中传递的打印流的目的地 static void setOut(PrintStream out) 重新分配“标准”输出流。 */ public class Demo02PrintStream { public static void main(String[] args) throws FileNotFoundException { System.out.println("我是在控制台输出"); PrintStream ps = new PrintStream("E:\\Java\\JavaWorkSpace\\enhance_code\\exam10\\file_directory\\printStream_aim_space.txt"); System.setOut(ps); System.out.println("我在打印流的目的地中输出"); } }