【转】 【JavaSE】黑马程序员 刘意 基础部分笔记(一)
0:tips
Java 如何搭稳基础? - Java3y的回答 -
idea快捷键
psvm 回车,快速mai方法
sout 回车,快速输出语句
ctrl+alt+space 代码自动提示与补全
ctrl+alt+L 代码对齐格式化
⭐基础语法
Java 语言概述
Java 核心机制与 JVM 运行原理
Java 程序的执行原理
首先将用java语言编写的程序(即源代码.java文件)经过java编译器处理编译成字节码文件(.class文件),这个字节码文件可在任意一个平台的不同java虚拟机(jvm)上屏蔽平台差异而运行。在jvm中,程序首先被类装载器处理加载类文件,然后进入字节码校验器检查代码的语法规范性以及安全性,接着进入解释器把抽象的字节码指令映射到本地系统平台下的库引用或指令,最后到操作系统平台运行。
字节码反编译(jd-gui.exe 和 javap
变量内存空间分配与原理
进制转换与位运算
八大基本数据类型 与转换
变量的运算与底层运算原理
常见面试题讲解
⭐基础类型
强制类型转换
数组
堆内存问题
如图是两个数组通过赋值得写法实际是指向了相同得堆内存,在arr2修改值时也会牵动arr数组得值发送改变。
数组静态初始化
int [] arr={1,2,3}; int [] arr2=new int[]{1,2,3}; System.out.println(arr);//打印得是内存地址:[I@2d98a335
arr.length====3
ArrayIndexOutOfBoundsException:数组越界异常
NullPointerException空指针异常
方法重载
满足条件:
- 多个方法在同一个类中
- 多个方法具有相同的方法名
- 多个方法的参数不相同,类型不同或者数量不同
特点:
不能通过返回值来判定两个方法是否相互构成重载
注意引用类型的参数在传参时值的改变
final
final修饰引用类型时
⭐面向对象
接口、继承、多态
继承
只支持单继承、但是可以多层继承
继承中构造方法的调用特点
多态
多态概述
抽象类多态、接口多态
多态特性
- 成员变量
通过多态的形式访问animal的成员变量时,即便声明时是使用的cat,但是编译时、运行时都是将左边看成是animal的,只能访问animal内的成员变量。
- 成员方法
多态访问类内方法时,编译看左边,运行看右边,即运行的是猫的方法。(原因:成员方法有重写,而成员变量没有。)
向上转型、向下转型
向上转型:从子到父 ,父类引用指向子类对象
向下转型:从父到子, 父类转换为子类类型
//向上转型 Animal ac=new Cat(); ac.eat();//猫吃鱼 //ac.playball();//编译出错 //向下转型 Cat ca=(Cat)ac; ca.playball();//猫玩毛球
抽象类abstract
- 抽象类里里可以有抽象方法也可以有具体方法,
- 抽象类不可以被实例化,但是抽象类可以有构造方法:用于子类访问时数据的初始化。
- 父类里的抽象方法可以理解为限定了子类必须实现的方法。
抽象方法
抽象方法是没有方法体
子类继承抽象类时必须重写所有抽象方法,否则子类也必须定义为抽象类
接口interface
- 实现
接口不能直接实例化,可以通过实现类来间接实例化。
实现类必须重写接口中所有的抽象方法。
形如:
Interface i = new cat(); //cat类实现了接口
- 成员变量:
- 可以是变量、常量
- 默认静态常量;默认修饰符都是 public static final,
- 外部可以通过接口名直接访问,但是谁都不能修改接口内的初始值
- 成员方法:
- 没有构造方法
- 不能有非抽象的方法
- 默认全是抽象方法
- 类和接口的关系
- 抽象类和接口的区别
system类
object类
tostring方法
建议每个类都自动重写tostring方法,这样后打印出的对象就是内部成员变量的值了。
否则默认是对象实例的地址。
equals方法
默认比较的还是对象实例的地址,除非你在类里重写了equals方法。
下面默认比较的是s1与s2的地址值,需要我们再自动重写equals方法然后再比较就是true啦。
equals源码详解
Arrays排序题
原理
- 如果有n个数据进行排序,总共需要比较n-1次(外层)
- 每次比较完毕,下一次的比较就会少一个数据参与比较(内层)
外层循环是一共比较的次数,内层循环是一个成员不能超出参与比较的范围
例如,共5个值。第一次循环,一个成员要与其他4个人比较,
为了防止数组越界所以要求j < arr.length - 1
for(int i=0;i<arr.length-1;i++) { for (int j = 0; j < arr.length - 1-i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = temp; } } }
!以后可以使用Arrays自带的方法了,自动从小到大排序
基本类型与包装类
案例:字符串数字排序题
arr[i]=Integer.parseInt(strArray[i]);
{ //定义一个字符串 String s ="91 85 45 63 52"; //以空格分隔组成字符数组 String[] strArray =s.split(" "); //定义int数组接受String数组 int[] arr=new int[strArray.length]; for(int i=0;i<strArray.length;i++){ arr[i]=Integer.parseInt(strArray[i]); } Arrays.sort(arr);//排序 // System.out.println(arr.toString());地址 //标准化输出 StringBuilder sb =new StringBuilder(); for (int i=0;i<arr.length;i++){ if(i==arr.length-1){ sb.append(arr[i]); } else { sb.append(arr[i]).append(" "); } } System.out.print("\""); System.out.print(sb.toString()); System.out.print("\""); }
自动装箱与拆箱
Date类
日期类
simpleDateFormat类
案例
public class DateTils { private DateTils(){ }; //将date格式化为字符串 public static String dateToString (Date date, String format){ SimpleDateFormat sdf=new SimpleDateFormat(format); String s=sdf.format(date); return s; } //将字符串解析为date public static Date stringToDate(String s,String format) throws ParseException { SimpleDateFormat sdf=new SimpleDateFormat(format); Date date=sdf.parse(s); return date; } }
calendar类
{ public static void main(String[] args) { //键盘输入任意年份 Scanner sc=new Scanner(System.in); System.out.println("请输入年份:"); int year=sc.nextInt(); //设置日历对象的年月日 x年 3月:2 1日 Calendar cal=Calendar.getInstance(); cal.set(year,2,1); //往前减1天得到二月最后一天的号 cal.add(Calendar.DATE,-1); int date =cal.get(Calendar.DATE); //输出这一天即可 System.out.println(year +"年的2月份有 "+date+"天"); } }
⭐String类家族
String特性
“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
字符串常量池(重要)
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
字符串的比较
使用 == 做比较
- 基本类型:比较的是数据值是否相同
- 引用类型:比较的是地址值是否相同
使用object.equals(Object)方法比较文本内容
字符串遍历CharAt(i)
public static void main(String[] args) { Scanner sc =new Scanner(System.in); System.out.println("请输入字符串:"); String line =sc.nextLine(); for (int i=0;i<line.length();i++) { System.out.println(line.charAt(i)); } }
string常用方法
2 charAt()截取一个字符
String a = "Hello Word"; System.out.println(a.charAt(1));
输出的结果是字符串a的下标为1的字符e。
3 getchars()截取多个字符并由其他字符串接收
String a = "Hello Word"; char[] b = new char[10]; a.getChars(0, 5, b, 0); System.out.println(b);
输出的结果为Hello,其中第一个参数0是要截取的字符串的初始下标(int sourceStart),第二个参数5是要截取的字符串的结束后的下一个下标(int sourceEnd)也就是实际截取到的下标是int sourceEnd-1,第三个参数是接收的字符串(char target[]),最后一个参数是接收的字符串开始接收的位置。
4 getBytes()将字符串变成一个byte数组
String a = "Hello Word"; byte b[] = a.getBytes(); System.out.println(new String(b));
输出的结果为Hello Word的byte数组。
5 toCharArray()将字符串变成一个字符数组
String a = "Hello Word"; char[]b = a.toCharArray(); System.out.println(b);
输出的结果为Hello Word字符数组。
6 equals()和equalsIgnoreCase()
比较两个字符串是否相等,前者区分大小写,后者不区分
String a = "Hello Word"; String b = "hello word"; System.out.println(a.equals(b));
System.out.println(a.equalsIgnoreCase(b));
输出的结果为第一条为false,第二条为true。
7 startsWith()和endsWith()
判断字符串是不是以特定的字符开头或结束
String a = "Hello Word"; System.out.println(a.startsWith("ee")); System.out.println(a.endsWith("rd"));
输出的结果第一条为false,第二条为true。
8 toUpperCase()和toLowerCase()
将字符串转换为大写或小写
String a = "Hello Word"; System.out.println(a.toUpperCase()); System.out.println(a.toLowerCase());
输出的结果第一条为“HELLO WORD”,第二条为“hello word”。
9 concat() 连接两个字符串
String a = "Hello Word"; String b = "你好"; System.out.println(b.concat(a));
输出的结果为“你好Hello Word”。
10 trim()去掉起始和结束的空格
String a = " Hello Word "; System.out.println(a.trim());
输出的结果为“Hello Word”。
11 substring()截取字符串
String a = "Hello Word"; System.out.println(a.substring(0, 5)); System.out.println(a.substring(6));
输出的结果第一条为“Hello”,第一个参数0(beginIndex)是开始截取的位置,第二个参数5(endIndex)是截取结束的位置,输出的结果第二条是“Word”,参数6(beginIndex)是开始截取的位置。
12 indexOf()和lastIndexOf()
前者是查找字符或字符串第一次出现的地方,后者是查找字符或字符串最后一次出现的地方
String a = "Hello Word"; System.out.println(a.indexOf("o")); System.out.println(a.lastIndexOf("o"));
输出的结果第一条是4,是o第一次出现的下标,第二条是7,是o最后一次出现的下标。
13 compareTo()和compareToIgnoreCase()
按字典顺序比较两个字符串的大小,前者区分大小写,后者不区分
String a = "Hello Word"; String b = "hello word"; System.out.println(a.compareTo(b)); System.out.println(a.compareToIgnoreCase(b));
输出的结果第一条为-32,第二条为0,两个字符串在字典顺序中大小相同,返回0。
14 replace() 替换
String a = "Hello Word"; String b = "你好"; System.out.println(a.replace(a, b)); System.out.println(a.replace(a, "HELLO WORD"));
System.out.println(b.replace("你", "大家"));
输出的结果第一条为“你好”,第二条为“HELLO WORD”,第三条为“大家好”。
stringbudiler
可变字符串,避免了传统string的字符串常量空间的浪费和垃圾的产生。
- append(String str)/append(Char c):字符串连接
- toString():返回一个与构建起或缓冲器内容相同的字符串
- setCharAt(int i, char c):将第 i 个代码单元设置为 c(可以理解为替换)
- strB.setCharAt(2, 'd');
- insert(int offset, String str)/insert(int offset, Char c):在指定位置之前插入字符(串)
- delete(int startIndex,int endIndex):
- reverse()反转
string与stringbudiler的比较
连接符号 “+” 本质
字符串变量(非final修饰)通过 “+” 进行拼接,在编译过程中会转化为StringBuilder对象的append操作,注意是编译过程,而不是在JVM中。
public class StringTest { public static void main(String[] args) { String str1 = "hello "; String str2 = "java"; String str3 = str1 + str2 + "!"; String str4 = new StringBuilder().append(str1).append(str2).append("!").toString(); } }
上述 str3 和 str4 的执行效果其实是一样的,不过在for循环中,千万不要使用 “+” 进行字符串拼接。因为 +号 每次循环都会重新初始化StringBuilder对象,导致性能问题的出现。
StringBuffer类
StringBuilder类与StringBuffer类区别??
StringBuffer线程安全
还有一个StringBuilder类,它也是字符串缓冲区,StringBuilder与它和StringBuffer的有什么不同呢?它也是一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
从线程安全的角度去看
StringBuffer是线程安全的,而StringBuilder是线程不安全的,实验的具体方法见此链接:https://blog.csdn.net/litterfrog/article/details/76862435
string三足鼎立总结
String适用于少量的字符串操作的情况,因为它的速度最慢且会产生垃圾内存
StringBuilder适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer适用多线程下在字符缓冲区进行大量操作的情况
⭐集合
集合总览:
集合的体系结构
💙ArrayList
ArrayList底层使用的是数组。是List的可变数组实现,这里的可变是针对List而言,而不是底层数组。
数组有自身的特点,不变性,一旦数组被初始化,那么其长度就固定了,不可被改变。这就导致了ArrayList中的一个重要特性:扩容。
public static void main(String[] args) { ArrayList<String> arr=new ArrayList<>(); arr.add("hello"); arr.add("world"); arr.add("java"); //arr.add(3,"zy"); arr.add(4,"zy");//IndexOutOfBoundsException: Index: 4, Size: 3 System.out.println(arr); }
💙Collection
什么是Collection?
- 首先Collection是(单例)集合类的基类,就是可以存储多个对象的集合。
- JDK不提供此接口的任何直接实现,它提供更具体的子接口(如set 、list )实现。
创建collection集合的对象
- 多态的方式
- 具体的实现类arraylist
与数组的区别
存储数据我们之前学过数组,下面简单说一下两者的不同
● 数组是长度固定的,集合长度可变。
● 数组可以存储基本类型,集合只可以存储对象。
|
从API上可以看到Collection的构造方法
public interface Collection<E> extends Iterable<E>
/* 基本使用 *创建collection集合的对象 多态的方式 具体的实现类arraylist * */ public static void main(String[] args) { Collection<String> c=new ArrayList<String>(); c.add("hello"); c.add("world"); c.add("java"); System.out.println(c); } }
collection常用方法
collection的遍历
//得到迭代器来遍历数组。迭代器与数组是相辅相成的 Iterator it =c.iterator();//通过集合的方法返回迭代器对象 while (it.hasNext()){ String s= (String) it.next(); System.out.println(s); }
💙list有序集合
特点:有索引
功能
|
方法
|
添加
|
void add(int index, Object element)
|
获取
|
Object get(int index)
|
删除并返回被删的元素
|
Object remove(int index)
|
修改并返回被替代的元素
|
Obeject set(int index, Object element)
|
并发修改异常【应用】
出现的原因
普通迭代器在迭代期间不允许修改元素
迭代器遍历的过程中,通过集合对象修改了集合中的元素,造成了迭代器获取元素中判断预期修改值和实际
修改值不一致,则会出现:ConcurrentModificationException
解决的方案
用for循环遍历,然后用集合对象做对应的操作即可
解决方案:使用for循环+list.get方法获取下一个元素
列表迭代器【应用】
ListIterator 介绍
- 通过 List集合的listIterator()方法得到,所以说它是List集合特有的迭代器
- 用于允许程序员沿任一方向遍历的列表迭代器,
- 允许在迭代期间修改列表,并获取列表中迭代器的当前位置
不会发生并发修改异常:源码分析
增强for循环【应用】
for(String s : list) { System.out.println(s); } //内部原理是一个Iterator迭代器 /* for(String s : list) { if(s.equals("world")) { list.add("javaee"); //ConcurrentModificationException //和普通迭代器一样会抛出并发修改异常 } } */
List集合子类的特点【记忆】
ArrayList 集合
底层是数组结构实现,查询快、增删慢
LinkedList 集合
底层是链表结构实现,查询慢、增删快
LinkedList集合的特有功能【应用】
方法名 说明
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
功能
|
方法
|
在该列表开头插入指定的元素
|
public void addFirst(E e)
|
将指定的元素追加到此列表的末尾
|
public void addLast(E e)
|
返回此列表中的第一个元素
|
public E getFirst()
|
返回此列表中的最后一个元素
|
public E getLast()
|
从此列表中删除并返回第一个元素
|
public E removeFirst()
|
从此列表中删除并返回最后一个元素
|
public E removeLast()
|
💙set
Set集合概述和特点
- 无序:元素存储和取出没有顺序
- 遍历:没有索引、只能通过迭代器或增强 for循环遍历
- 不重复:不能存储重复元素
//创建集合对象 Set<String> set = new HashSet<String>();
哈希值【理解】
- 哈希值简介
- 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
- 如何获取哈希值
- Object类中的public int hashCode():返回对象的哈希码值
- 哈希值的特点
- 同一个对象多次调用 hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。
- 而重写 hashCode()方法,可以实现让不同对象的哈希值相同
public class HashDemo { public static void main(String[] args) { //创建学生对象 Student s1 = new Student("林青霞",30); //同一个对象多次调用hashCode()方法返回的哈希值是相同的 System.out.println(s1.hashCode()); //1060830840 System.out.println(s1.hashCode()); //1060830840 System.out.println("--------"); Student s2 = new Student("林青霞",30); //默认情况下,不同对象的哈希值是不相同的 //通过方法重写,可以实现不同对象的哈希值是相同的 System.out.println(s2.hashCode()); //2137211482 System.out.println("--------"); System.out.println("hello".hashCode()); //99162322 System.out.println("world".hashCode()); //113318802 System.out.println("java".hashCode()); //3254818 System.out.println("world".hashCode()); //113318802 System.out.println("--------"); System.out.println("重地".hashCode()); //1179395 System.out.println("通话".hashCode()); //1179395 } }
HashSet集合
HashSet 集合的特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通 for循环遍历
- 由于是 Set集合,所以是不包含重复元素的集合
HashSet 集合的基本使用
HashSet<String> hs = new HashSet<String>();
HashSet集合保证元素唯一性源码分析
- 1.根据对象的哈希值计算存储位置
- 如果当前位置没有元素则直接存入
- 如果当前位置有元素存在,则进入第二步
- 2.当前元素的元素和已经存在的元素比较哈希值
- 如果哈希值不同,则将当前元素进行存储
- 如果哈希值相同,则进入第三步
- 3.通过equals()方法比较两个元素的内容
- 如果内容不相同,则将当前元素进行存储
- 如果内容相同,则不存储当前元素
LinkedHashSet集合
LinkedHashSet 集合特点
- 哈希表和链表实现的 Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
TreeSet集合
TreeSet 集合概述
- 元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法
- TreeSet() :根据其元素的自然排序进行排序
- TreeSet(Comparator comparator) :根据指定的比较器进行排序
- 没有带索引的方法,所以不能使用普通 for循环遍历
- 由于是 Set集合,所以不包含重复元素的集合
public class TreeSetDemo01 { public static void main(String[] args) { //创建集合对象 TreeSet<Integer> ts = new TreeSet<Integer>();//默认无参构造方法,自然排序 //添加元素 ts.add(10); ts.add(40); ts.add(30); ts.add(50); ts.add(20); ts.add(30); //遍历集合 for(Integer i : ts) { System.out.println(i);//10 20 30 40 50 } } }
💙map
Map 集合的特点
MAP的键要保证唯一性,所以在用自定义的类做键时,一定要重写两个方法:hashmap equals方法
双列集合接口
interface Map<K,V> K:键的类型;V:值的类型
- 键值对映射关系
- 一个键对应一个值
- 键不能重复,值可以重复
- 元素存取无序
方法名 说明
V put(K key,V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
方法名 说明
V get(Object key) 根据键获取值
Set keySet() 获取所有键的集合
Collection values() 获取所有值的集合
Set<map.entry<k,v>> entrySet() 获取所有键值对对象的集合
public static void main(String[] args) { //创建集合对象 Map<String, String> map = new HashMap<String, String>(); //添加元素 map.put("张无忌", "赵敏"); map.put("郭靖", "黄蓉"); map.put("杨过", "小龙女"); System.out.println(map.get("张无忌")); System.out.println(map.get("张三丰"));//null Set<String> keySet = map.keySet();//将所有键转换为set集合 for(String key : keySet) { System.out.println(key);//张无忌 郭靖 杨过 } Collection<String> values = map.values();//获取所有值的集合 for(String value : values) { System.out.println(value); }
Map集合的遍历(方式1)
遍历思路
- 我们刚才存储的元素都是成对出现的,所以我们把 Map看成是一个夫妻对的集合
- 把所有的丈夫给集中起来
- 遍历丈夫的集合,获取到每一个丈夫
- 根据丈夫去找对应的妻子
//获取所有键的集合。用keySet()方法实现 Set<String> keySet = map.keySet(); //遍历键的集合,获取到每一个键。用增强for实现 for (String key : keySet) { //根据键去找值。用get(Object key)方法实现 String value = map.get(key); System.out.println(key + "," + value); } }
Map集合的遍历(方式2)
步骤分析
- 获取所有键值对对象的集合
- Set<map.entry<k,v>> entrySet() :获取所有键值对实体的集合
- 遍历键值对实体的集合,得到每一个键值对对象
- 用增强 for实现,得到每一个Map.Entry
- 根据键值对对象获取键和值
- 用 getKey()得到键
- 用 getValue()得到值
//创建集合对象 Map<String, String> map = new HashMap<String, String>();
//获取所有键值对对象的集合 Set<Map.Entry<String, String>> entrySet = map.entrySet(); //遍历键值对对象的集合,得到每一个键值对对象 for (Map.Entry<String, String> me : entrySet) { //根据键值对对象获取键和值 String key = me.getKey(); String value = me.getValue(); System.out.println(key + "," + value); }
集合嵌套之ArrayList嵌套HashMap
遍历时使用嵌套循环遍历
//创建ArrayList集合 ArrayList<HashMap<String, String>> array = new ArrayList<HashMap<String, String>>(); //创建HashMap集合,并添加键值对元素 HashMap<String, String> hm1 = new HashMap<String, String>(); hm1.put("孙策", "大乔"); hm1.put("周瑜", "小乔"); HashMap<String, String> hm2 = new HashMap<String, String>(); hm2.put("郭靖", "黄蓉"); hm2.put("杨过", "小龙女"); HashMap<String, String> hm3 = new HashMap<String, String>(); hm3.put("令狐冲", "任盈盈"); hm3.put("林平之", "岳灵珊"); /把HashMap作为元素添加到ArrayList集合 array.add(hm1); array.add(hm2); array.add(hm3); //遍历ArrayList集合 for (HashMap<String, String> hm : array) { Set<String> keySet = hm.keySet(); for (String key : keySet) { String value = hm.get(key); System.out.println(key + "," + value); }
集合嵌套之HashMap嵌套ArrayList
//创建HashMap集合 HashMap<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>(); //创建ArrayList集合,并添加元素 ArrayList<String> sgyy = new ArrayList<String>(); sgyy.add("诸葛亮"); sgyy.add("赵云"); ArrayList<String> xyj = new ArrayList<String>(); xyj.add("唐僧"); xyj.add("孙悟空"); ArrayList<String> shz = new ArrayList<String>(); shz.add("武松"); shz.add("鲁智深"); //把ArrayList作为元素添加到HashMap集合 hm.put("三国演义",sgyy); hm.put("西游记",xyj); hm.put("水浒传",shz); //遍历HashMap集合 Set<String> keySet = hm.keySet(); for(String key : keySet) { System.out.println(key); ArrayList<String> value = hm.get(key); for(String s : value) { System.out.println("\t" + s); } }
题型:统计字符串中每个字符出现的次数
案例需求
- 键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
- 举例:键盘录入 “aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)”
代码实现
//键盘录入一个字符串 Scanner sc = new Scanner(System.in); System.out.println("请输入一个字符串:"); String line = sc.nextLine(); //创建HashMap集合,键是Character,值是Integer // HashMap<Character, Integer> hm = new HashMap<Character, Integer>(); TreeMap<Character, Integer> hm = new TreeMap<Character, Integer>(); //遍历字符串,得到每一个字符 for (int i = 0; i < line.length(); i++) { char key = line.charAt(i); //拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值 Integer value = hm.get(key); if (value == null) { //如果返回值是null:说明该字符在HashMap集合中不存在,就把该字符作为键,1 作为值存储 hm.put(key,1); } else { //如果返回值不是null:说明该字符在HashMap集合中存在,把该值加1,然后重新 存储该字符和对应的值 value++; hm.put(key,value); } } //遍历HashMap集合,得到键和值,按照要求进行拼接 StringBuilder sb = new StringBuilder(); Set<Character> keySet = hm.keySet(); for(Character key : keySet) { Integer value = hm.get(key); sb.append(key).append("(").append(value).append(")"); } String result = sb.toString(); //输出结果 System.out.println(result); }
💙Collections 集合工具类
方法名 说明
public static void sort(List list) 将指定的列表按升序排序
public static void reverse(List list) 反转指定列表中元素的顺序
public static void shuffle(List list) 使用默认的随机源随机排列指定的列表
案例:使用工具类对arraylist进行排序
{ ArrayList<Student> array = new ArrayList<Student>(); //创建学生对象 Student s1 = new Student("lin", 30); Student s2 = new Student("wang", 32); Student s3 = new Student("liu", 33); Student s4 = new Student("zhang", 33); //把学生对象添加到集合 array.add(s1); array.add(s2); array.add(s3); array.add(s4); //使用Collections 对ArrayList集合进行排序 Collections.sort(array, new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { //先按年龄排序,后按名字排序 int num = s1.getAge() - s2.getAge(); int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num; return num2; } }); //遍历集合 for (Student s : array) { System.out.println(s.getName() + "," + s.getAge()); } }
案例:斗地主
public class PokerDemo { public static void main(String[] args) { //创建HashMap,键是编号,值是牌 HashMap<Integer,String> hm =new HashMap<Integer, String>(); //创建ArrayList,存储编号 ArrayList<Integer> array =new ArrayList<Integer>(); //创建花色数组和点数数组 String[] colors={"♦", "♣", "♥", "♠"}; String[] numbers={"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"}; //从0开始往HashMap里面存储编号,并存储对应的牌。同时往ArrayList里面存储编号 int index=0; for(String number:numbers){ for (String color:colors){ hm.put(index,color+number); array.add(index); index++; } } hm.put(index,"小王"); array.add(index); index++; hm.put(index,"大王"); array.add(index); //洗牌(洗的是编号),用Collections的shuffle()方法实现 Collections.shuffle(array); //发牌(发的也是编号,为了保证编号是排序的,创建TreeSet集合接收) TreeSet<Integer> lqxSet=new TreeSet<Integer>(); TreeSet<Integer> lySet = new TreeSet<Integer>(); TreeSet<Integer> fqySet = new TreeSet<Integer>(); TreeSet<Integer> dpSet = new TreeSet<Integer>();//底牌 for(int i=0;i<array.size();i++){ int x=array.get(i); if(i>=array.size()-3){ dpSet.add(x); }else if (i%3==0){ lqxSet.add(x); }else if(i%3==1){ lySet.add(x); }else if(i%3==2){ fqySet.add(x); } } //调用看牌方法 lookPoker("林青霞", lqxSet, hm); lookPoker("柳岩", lySet, hm); lookPoker("风清扬", fqySet, hm); lookPoker("底牌", dpSet, hm); } //定义方法看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌) public static void lookPoker(String name,TreeSet<Integer> ts,HashMap<Integer,String> hm){ System.out.println(name+"的牌是:"); for (Integer key:ts){ String poker=hm.get(key); System.out.print(poker+" "); } System.out.println(); } }
⭐泛型
⭐I/O
💜 File 类
File类概述和构造方法
- 它是文件和目录路径名的抽象表示
- 文件和目录是可以通过 File封装成对象的
- 对于 File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。
将来是要通过具体的操作把这个路径的内容转换为具体存在的
构造方法
File(String pathname)
|
通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
|
File(String parent, String child)
|
从父路径名字符串和子路径名字符串创建新的 File实例
|
File(File parent, String child)
|
从父抽象路径名和子路径名字符串创建新的 File实例
|
public boolean createNewFile()
|
当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件,如果存在该文件就不创建了
|
public boolean mkdir()
|
创建由此抽象路径名命名的目录
|
public boolean mkdirs()
|
可以创建多级目录
|
- 判断功能
public boolean isDirectory()
|
测试此抽象路径名表示的File是否为目录
|
public boolean isFile()
|
测试此抽象路径名表示的File是否为文件
|
public boolean exists()
|
测试此抽象路径名表示的File是否存在
|
- 获取功能
public String getAbsolutePath()
|
绝对路径名字符串
|
public String getPath()
|
抽象路径名转换为路径名字符串
|
public String getName()
|
返回由此抽象路径名表示的文件或目录的名称
|
public String[] list()
|
返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
|
public File[] listFiles()
|
返回此抽象路径名表示的目录中的文件和目录的File对象数组
|
- File类删除功能
public boolean delete()
|
删除由此抽象路径名表示的文件或目录
|
💜 递归
递归一定要有出口
- 斐波那契数列的第n个值
public static int f(int n) { if(n==1 || n==2) { return 1; } else { return f(n - 1) + f(n - 2); } }
- 求n的阶乘
//定义一个方法,用于递归求阶乘,参数为一个int类型的变量 public static int jc(int n) { //在方法内部判断该变量的值是否是1 if(n == 1) { //是:返回1 return 1; } else { //不是:返回n*(n-1)! return n*jc(n-1); } }
- 递归遍历目录
File srcFile = new File("E:\\itheima"); //调用方法 getAllFilePath(srcFile);
//定义一个方法,用于获取给定目录下的所有内容,参数为第1步创建的File对象 public static void getAllFilePath(File srcFile) { //获取给定的File目录下所有的文件或者目录的File数组 File[] fileArray = srcFile.listFiles(); //遍历该File数组,得到每一个File对象 if(fileArray != null) { for(File file : fileArray) { //判断该File对象是否是目录 if(file.isDirectory()) { //是:递归调用 getAllFilePath(file); } else { //不是:获取绝对路径输出在控制台 System.out.println(file.getAbsolutePath()); } } }
💜 IO 流
概述
- 常见的应用:
- 文件复制;文件上传;文件下载
- IO 流的分类
- 输入流:读数据
- 输出流:写数据
- 字符流
- 字节流
- IO 流的使用场景
- 如果操作的是纯文本文件,优先使用字符流
- 如果操作的是图片、视频、音频等二进制文件。优先使用字节流
- 如果不确定文件类型,优先使用字节流。字节流是万能的流
💜字节流FileOutputStream
字节流写数据FileOutputStream
字节流抽象基类
- InputStream :这个抽象类是表示字节输入流的所有类的超类
- OutputStream :这个抽象类是表示字节输出流的所有类的超类
- 子类名特点:子类名称都是以其父类名作为子类名的后缀
字节输出流
- FileOutputStream(String name) :创建文件输出流以指定的名称写入文件
等价于 //new File(name) // FileOutputStream fos =
使用字节输出流写数据的步骤
- 创建字节输出流对象 (调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法
- 释放资源 (关闭此文件输出流并释放与此流相关联的任何系统资源)
/*
做了三件事情:
A:调用系统功能自动帮我们创建了文件
B:创建了字节输出流对象
C:让字节输出流对象指向创建好的文件
*/
public class FileOutputStreamDemo01 { public static void main(String[] args) throws IOException { //创建字节输出流对象 //FileOutputStream(String name):创建文件输出流以指定的名称写入文件 FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt"); /* 做了三件事情: A:调用系统功能创建了文件 B:创建了字节输出流对象 C:让字节输出流对象指向创建好的文件 */ //void write(int b):将指定的字节写入此文件输出流 fos.write(97); // fos.write(57); // fos.write(55); //最后都要释放资源 //void close():关闭此文件输出流并释放与此流相关联的任何系统资源。 fos.close(); } }
写数据的方法分类
void write(int b)
将指定的字节写入此文件输出流 一次写一个字节数据
void write(byte[] b)
从指定的字节数组写入此文件输出流 一次写一个数组
void write(byte[] b, int off, int len)
从偏移量off开始写入此文件输出流, 指定长度len 一次写一个数组的部分数据
读数据
//读数据 //一次读取一个字节 FileInputStream fis=new FileInputStream("day04_IO\\fos.txt"); int by; while ((by=fis.read())!=-1){ System.out.print((char)by); } fos.close(); //一次读取一个数组 FileInputStream fis2=new FileInputStream("day04_IO\\test.txt"); byte[] bys=new byte[1024]; int len;//实际读取到的字节长度 while ((len=fis2.read(bys))!=-1){ String s=new String(bys,0,len); System.out.println(s); } //复制图片 FileInputStream fis3=new FileInputStream("G:\\图片\\头像\\16.jpeg"); FileOutputStream fos3=new FileOutputStream("day04_IO\\pic.png"); byte[] bys3=new byte[2048]; int len3; while ((len3=fis3.read(bys3))!=-1){ fos3.write(bys3,0,len3); } fis3.close(); fos3.close();
💜字节缓冲流BufferedOutputStream
字节缓冲流介绍
- lBufferOutputStream :该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写
- 入字节,而不必为写入的每个字节导致底层系统的调用
- lBufferedInputStream :创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
构造方法:
方法名
|
说明
|
BufferedOutputStream(OutputStream out)
|
创建字节缓冲输出流对象
|
BufferedInputStream(InputStream in)
|
创建字节缓冲输入流对象
|
注意:为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
实例:创建一个对象:
// FileInputStream fis=new FileInputStream("day04_IO\\test.txt"); // BufferedInputStream bis=new BufferedInputStream(fis); //等价于 BufferedInputStream bi=new BufferedInputStream(new FileInputStream("day04_IO\\test.txt"));
💜 字符流 & 编码
方法名 说明
byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节
byte[] getBytes(String charsetName) 使用指定的字符集将该 String编码为一系列字节
String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串
String(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来创建字符串
注意:不同的字节数组可以由不同的编码方式得到
应用:字符串中的编码与解码问题
String s="中国"; //编码 byte[] by=s.getBytes(); String s1 = Arrays.toString(by); System.out.println(s1);//默认UTF-8 [-28, -72, -83, -27, -101, -67] //解码 String ss=new String(by,"UTF-8"); System.out.println(ss);//中国 //编码 byte[] bys=s.getBytes("GBK"); String s2=Arrays.toString(bys);//[-42, -48, -71, -6] System.out.println(s2); //解码 String sss=new String(bys,"GBK"); System.out.println(sss);
💜字节流到字符流的桥梁
💜OutputStreamWriter& InputStreamReader
InputStreamReader :是从字节流到字符流的桥梁
OutputStreamWriter :是从字符流到字节流的桥梁
实际进行读写的还是基本字节流对象
OutputStreamWriter osw = new OutputStreamWriter(new
FileOutputStream("myCharStream\\osw.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt")); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"),"GBK"); osw.write("中国"); osw.close(); InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\osw.txt"),"GBK"); //一次读取一个字符数据 int ch; while ((ch=isr.read())!=-1) { System.out.print((char)ch); } isr.close();
方法名
|
说明
|
void write(int c)
|
写一个字符
|
void write(char[] cbuf)
|
写入一个字符数组
|
void write(char[] cbuf, int off, int len)
|
写入字符数组的一部分
|
void write(String str)
|
写一个字符串
|
void write(String str, int off, int len)
|
写一个字符串的一部分
|
int read()
|
一次读一个字符数据
|
int read(char[] cbuf)
|
一次读一个字符数组数据
|
flush()
|
刷新流,之后还可以继续写数据
|
close()
|
关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
|
char[] chs = {'a', 'b', 'c', 'd', 'e'}; osw.write(chs);//写入一个字符数组 osw.write(chs, 0, chs.length); // osw.write(chs, 1, 3);//写入字符数组的一部分 osw.write("abcde", 1, 3);//写一个字符串的一部分
//int read(char[] cbuf):一次读一个字符数组数据 char[] chs = new char[1024]; int len; while ((len = isr.read(chs)) != -1) { System.out.print(new String(chs, 0, len)); }
💜文件缓冲流 FileWriter &FileReader
💜 字符缓冲流 BufferedWriter &BufferedReader
注意:一般二者都是一起包裹使用
BufferedWriter bw=new BufferedWriter(new FileWriter("day05_net\\server.txt"));
如果文件不存在,它会自动创建出这个文件,因为它的底层还是调用的FileOutPutStream,实际上是它会检查文件是否存在然后创建
BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\bw.txt")); bw.write("hello\r\n"); bw.write("world\r\n"); bw.close(); BufferedReader br = new BufferedReader(new FileReader("myCharStream\\bw.txt")); //一次读取一个字符数据 // int ch; // while ((ch=br.read())!=-1) { // System.out.print((char)ch); // } //一次读取一个字符数组数据 char[] chs = new char[1024]; int len; while ((len=br.read(chs))!=-1) { System.out.print(new String(chs,0,len)); } br.close(); } }
💜字符缓冲流特有功能
void newLine()
|
写一行行分隔符,行分隔符字符串由系统属性定义
|
String readLine()
|
读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null
|
String line; while ((line=br.readLine())!=null) { System.out.println(line); } br.close();
💜IO流小结
💜 练习
集合到文件
把ArrayList集合中的学生数据写入到文本文件。要求:每一个学生对象的数据作为文件中的一行数据 格式:
学号,姓名,年龄,居住地 举例:itheima001,林青霞,30,西安
public class ArrayListToFileDemo { public static void main(String[] args) throws IOException { //创建ArrayList集合存储学生对象 ArrayList<Student> array=new ArrayList<Student>(); Student s1=new Student("20171303011","林青霞",18,"武汉"); Student s2=new Student("20171345658","张曼玉",25,"海南"); Student s3=new Student("20171389655","王祖贤",36,"北京"); array.add(s1); array.add(s2); array.add(s3); //创建字符缓冲输出流对象 BufferedWriter bw=new BufferedWriter(new FileWriter("day04_IO\\namelist.txt",true)); ////遍历集合,把学生对象的数据拼接成指定格式的字符串 for (Student student:array){ StringBuilder sb=new StringBuilder(); sb.append(student.getSid()+","+student.getName()+","+ student.getAge()+","+student.getAddress()); bw.write(sb.toString()); bw.newLine(); bw.flush(); } bw.close(); } }
文件到集合:点名器
public class CallNameDemo { public static void main(String[] args) throws IOException { //创建字符缓冲输入流对象 BufferedReader br=new BufferedReader(new FileReader("day04_IO\\namelist.txt")); //创建ArrayList集合对象 ArrayList<String> array=new ArrayList<String>(); //调用字符缓冲输入流对象的方法读数据 String line; while ((line=br.readLine())!=null){ array.add(line); } br.close(); //随机数读取集合 Random r=new Random(); int index=r.nextInt(array.size()); String name = array.get(index); System.out.println(name); } }
文件到集合
把文本文件中的数据读取到集合中,并遍历集合。要求:文件中每一行数据是一个学生对象的成员变量值 举
例:itheima001,林青霞,30,西安
把读取到的字符串数据用 split()进行分割,得到一个字符串数组
把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值
public class FileToArrayListDemo { public static void main(String[] args) throws IOException { //创建字符缓冲输入流对象 BufferedReader br=new BufferedReader(new FileReader("day04_IO\\namelist.txt")); ArrayList<Student> array=new ArrayList<Student>(); //调用字符缓冲输入流对象的方法读数据 String line; while ((line=br.readLine())!=null){ String[] straray=line.split(","); Student student=new Student(); //把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值 //itheima001,林青霞,30,西安 student.setSid(straray[0]); student.setName(straray[1]); student.setAge(Integer.parseInt(straray[2])); student.setAddress(straray[3]); array.add(student); } br.close(); //遍历集合 for (Student student:array){ System.out.println(student.getSid() + "," + student.getName() + "," +student.getAge() + "," + student.getAddress()); } } }
💜标准输入输出流
System 类中有两个静态的成员变量
- public static final InputStream in :标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
- public static final PrintStream out :标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
//public static final InputStream in:标准输入流 // InputStream is = System.in; // int by; // while ((by=is.read())!=-1) { // System.out.print((char)by); // } //如何把字节流转换为字符流?用转换流 // InputStreamReader isr = new InputStreamReader(is); // //使用字符流能不能够实现一次读取一行数据呢?可以 // //但是,一次读取一行数据的方法是字符缓冲输入流的特有方法 // BufferedReader br = new BufferedReader(isr); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入一个字符串:"); String line = br.readLine(); System.out.println("请输入一个整数:"); int i = Integer.parseInt(br.readLine()); //自己实现键盘录入数据太麻烦了,所以Java就提供了一个类供我们使用 Scanner sc = new Scanner(System.in);
💜字节打印流 PrintStream
打印流分类
- 字节打印流: PrintStream
- 字符打印流: PrintWriter
打印流的特点
- 只负责输出数据,不负责读取数据
- 永远不会抛出 IOException
- 有自己的特有方法
- 使用自己的特有方法写数据,查看的数据原样输出
- 可以改变输出语句的目的地
public static void setOut(PrintStream out):重新分配“标准”输出流
PrintStream ps = new PrintStream("myOtherStream\\ps.txt"); //写数据 //字节输出流有的方法 // ps.write(97); //使用特有方法写数据 // ps.print(97); // ps.println(); // ps.print(98); ps.println(97); //释放资源 ps.close();
💜字符打印流PrintWriter
构造方法
PrintWriter(String fileName)
|
|
PrintWriter(Writer out, boolean autoFlush)
|
自动刷新
|
PrintWriterpw = new PrintWriter("myOtherStream\\pw.txt"); PrintWriter pw = new PrintWriter(new FileWriter("myOtherStream\\pw.txt"),true); pw.println("hello");
💜对象序列化ObjectOutputStream 、反序列化ObjectInputStream
概念:
- 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,
- 该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
- 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
- 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
注意事项
- 一个对象要想被序列化,该对象所属的类必须必须实现 Serializable 接口
- Serializable 是一个标记接口,实现该接口,不需要重写任何方法
对象序列化流: ObjectOutputStream
ObjectOutputStream(OutputStream out)
|
创建一个写入指定的OutputStream的
ObjectOutputStream
|
void writeObject(Object obj)
|
将指定的对象写入ObjectOutputStream
|
代码:对象序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myOtherStream\\oos.txt")); //创建对象 Student s = new Student("林青霞",30); //void writeObject(Object obj):将指定的对象写入ObjectOutputStream oos.writeObject(s); //释放资源 oos.close(); //反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myOtherStream\\oos.txt")); //Object readObject():从ObjectInputStream读取一个对象 Object obj = ois.readObject(); Student s = (Student) obj; System.out.println(s.getName() + "," + s.getAge()); ois.close();
💜serialVersionUID&transient
序列化时存在的一些不稳定性问题
- serialVersionUID
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?会出问题,会抛出 InvalidClassException异常
如果出问题了,如何解决呢?
重新序列化
给对象所属的类加一个 serialVersionUID
private static final long serialVersionUID = 42L;
- transient
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
💜Properties 集合
Properties 介绍
- 属性配置流
- 是一个 Map体系的集合类
- Properties 可以保存到流中或从流中加载
- 属性列表中的每个键及其对应的值都是一个字符串
作为集合的基本用法
public class PropertiesDemo01 { public static void main(String[] args) { //创建集合对象 // Properties<String,String> prop = new Properties<String,String>(); //错 误 Properties prop = new Properties(); //存储元素 prop.put("itheima001", "林青霞"); prop.put("itheima002", "张曼玉"); prop.put("itheima003", "王祖贤"); //遍历集合 Set<Object> keySet = prop.keySet(); for (Object key : keySet) { Object value = prop.get(key); System.out.println(key + "," + value); } } }
特殊用处
Object setProperty(String key, String value)
|
设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
|
String getProperty(String key)
|
使用此属性列表中指定的键搜索属性
用键得到对应的属性
|
Set stringPropertyNames()
|
从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
得到属性名
|
public static void main(String[] args) { //创建集合对象 Properties prop = new Properties(); //Object setProperty(String key, String value):设置集合的键和值,都是 String类型,底层调用Hashtable方法put prop.setProperty("itheima001", "林青霞"); /* Object setProperty(String key, String value) { return put(key, value); } Object put(Object key, Object value) { return map.put(key, value); } */ prop.setProperty("itheima002", "张曼玉"); prop.setProperty("itheima003", "王祖贤"); //String getProperty(String key):使用此属性列表中指定的键搜索属性 // System.out.println(prop.getProperty("itheima001")); // System.out.println(prop.getProperty("itheima0011")); // System.out.println(prop); //Set<String> stringPropertyNames():从该属性列表中返回一个不可修改的键集,其中 键及其对应的值是字符串 Set<String> names = prop.stringPropertyNames(); for (String key : names) { // System.out.println(key); String value = prop.getProperty(key); System.out.println(key + "," + value); } } }
Properties 和IO流相结合
void load(InputStream inStream)
|
从输入字节流读取属性列表(键和元素对)
|
void load(Reader reader)
|
从输入字符流读取属性列表(键和元素对)
|
void store(OutputStream out, String comments)
|
将此属性列表(键和元素对)写出到字节输出流中
|
void store(Writer writer,String comments)
|
将此属性列表(键和元素对)写出到字符输出流中
|
public class PropertiesDemo03 { public static void main(String[] args) throws IOException { //把集合中的数据保存到文件 // myStore(); //把文件中的数据加载到集合 myLoad(); } private static void myLoad() throws IOException { Properties prop = new Properties(); //void load(Reader reader): FileReader fr = new FileReader("myOtherStream\\fw.txt"); prop.load(fr); fr.close(); System.out.println(prop); } private static void myStore() throws IOException { Properties prop = new Properties(); prop.setProperty("itheima001","林青霞"); prop.setProperty("itheima002","张曼玉"); prop.setProperty("itheima003","王祖贤"); //void store(Writer writer, String comments): FileWriter fw = new FileWriter("myOtherStream\\fw.txt"); prop.store(fw,null); fw.close(); } }
⭐异常处理
异常概述
例如数组越界异常,使用try 。。。catch处理异常后,程序会继续执行到结尾。
public class exceptiontest { public static void main(String[] args) { System.out.println("开始"); method(); System.out.println("结束"); } public static void method(){ try { int[] arr = {1, 2, 3}; System.out.println(arr[3]);//这会触发一个运行时异常 } catch (ArrayIndexOutOfBoundsException e){ e.printStackTrace(); } } }
Throwable
编译时异常和运行时异常的区别
throw和throws的区别
自定义异常
//自定义异常类 public class ScoreException extends Exception{ public ScoreException(){ } public ScoreException(String message){ super(message); } }
//定义teacher类 用来抛出异常 public class Teacher { public void checkScore(int score) throws ScoreException { if (score<0||score>100){ throw new ScoreException("分数有误,应该在0-100之间"); } else { System.out.println("分数正常"); } } }
//测试类,处理捕获异常 public class TeacherTest { public static void main(String[] args) { Scanner sc=new Scanner(System.in); System.out.println("请输入分数:"); int score=sc.nextInt(); Teacher t=new Teacher(); try { t.checkScore(score); } catch (ScoreException e) { e.printStackTrace(); } } }
⭐多线程
实现多线程方式一:继承Thread类
void run()
|
在线程运行启动后,此方法将被调用执行
|
void start()
|
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
|
两个小问题
为什么要重写 run()方法?
- 因为run()是用来封装被线程执行的代码
run() 方法和start()方法的区别?
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():启动线程;然后由JVM调用此线程的run()方法
//定义一个线程类 public class MyThread extends Thread { @Override public void run() { for(int i=0; i<100; i++) { System.out.println(i); } } } //调用方法执行线程 public class MyThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); // my1.run(); // my2.run(); //void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法 my1.start();
设置和获取线程名称
void setName(String name)
|
将此线程的名称更改为等于参数name
|
String getName()
|
返回此线程的名称
|
Thread currentThread()
|
返回对当前正在执行的线程对象的引用
|
//void setName(String name):将此线程的名称更改为等于参数 name my1.setName("高铁"); my2.setName("飞机"); //Thread(String name) MyThread my1 = new MyThread("高铁"); MyThread my2 = new MyThread("飞机"); System.out.println(Thread.currentThread().getName());
线程优先级
Java 使用的是抢占式调度模型
随机性
final int getPriority()
|
返回此线程的优先级
|
final void setPriority(int newPriority)
|
更改此线程的优先级 线程默认优先级是5;线程优先级的范围 是:1-10
|
线程控制
static void sleep(long millis)
|
使当前正在执行的线程停留(暂停执行)指定的毫秒数
|
void join()
|
等待这个线程死亡
|
void setDaemon(boolean on)
|
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机 将退出
|
join等待死亡
public class ThreadTest { public static void main(String[] args) { ThreadDemo th1=new ThreadDemo(); ThreadDemo th2=new ThreadDemo(); ThreadDemo th3=new ThreadDemo(); th1.setName("康熙"); th2.setName("四阿哥"); th3.setName("八阿哥"); th1.start(); try { th1.join(); } catch (InterruptedException e) { e.printStackTrace(); } //下面的两个线程会等待康熙线程死亡以后才开始执行 th2.start(); th2.start(); } }
setDaemon守护线程
th1.setName("刘备"); th2.setName("张飞"); th3.setName("关羽"); th2.setDaemon(true); th3.setDaemon(true); th2.start(); th3.start(); th1.start(); //th2\th3 在th1死亡时,也会跟着死亡
Runnable接口
多线程的实现方案有两种
- 继承 Thread类
- 实现 Runnable接口
相比继承 Thread类,实现Runnable接口的好处
- 避免了 Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
线程的生命周期
同步代码块解决数据安全问题
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
怎么实现呢 ?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- Java 提供了同步代码块的方式来解决
Object obj=new Object(); synchronized (obj) { 多条语句操作共享数据的代码 }
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
方法的同步
几个线程安全的类型
string-----------StringBuffer
list----------------Vector
map---------------Hashtable
Lock
- Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
- 使用try....finally代码块来包裹
private int ticket=100; private Lock lock=new ReentrantLock(); @Override public void run() { while (true) { try { lock.lock(); if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票" + ticket); ticket--; } }finally { lock.unlock(); }
生产者&消费者案例
方法名
|
说明
|
void wait()
|
阻塞
导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
|
void notify()
|
唤醒阻塞队列的单个线程
唤醒正在等待对象监视器的单个线程
|
void notifyAll()
|
唤醒阻塞队列的所有线程
唤醒正在等待对象监视器的所有线程
|
匿名内部类的方式
new Thread(new Runnable() { @Override public void run() { System.out.println("多线程"); } }).start();
⭐网络通信
网络编程三要素
IP 地址、端口号、协议
IPv4:32位
IPv6:128位地址长度,每16个字节一组、分成8组十六进制数
DOS 常用命令:
- ipconfig :查看本机IP地址
- ping IP 地址:检查网络是否连通
特殊 IP地址:
127.0.0.1 :是回送地址,可以代表本机地址,一般用来测试使用
InetAddress
方法名 说明
|
|
static InetAddress getByName(String host)
|
确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
|
String getHostName()
|
获取此IP地址的主机名
|
String getHostAddress()
|
返回文本显示中的IP地址字符串
|
InetAddress address = InetAddress.getByName("DESKTOP-KFJM06G"); InetAddress address1=InetAddress.getByName("192.168.135.10"); String hostName = address.getHostName(); String hostAddress = address.getHostAddress(); System.out.println(hostName);//DESKTOP-KFJM06G System.out.println(hostAddress);//192.168.135.10
Java 中的UDP通信
UDP 协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,
但是这两个Socket只是发送,接收数据的对象,
因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
Java 提供了DatagramSocket类作为基于UDP协议的Socket
方法名
|
说明
|
DatagramSocket()
|
创建数据报套接字并将其绑定到本机地址上的任何可用端口
|
DatagramPacket(byte[] buf,int len,InetAddress add,int port)
|
创建数据包,发送长度为len的数据包到指定主机 的指定端口
|
void send(DatagramPacket p)
|
发送数据报包
|
void close()
|
关闭数据报套接字
|
void receive(DatagramPacket p)
|
从此套接字接受数据报包
|
发送数据的步骤
- 创建发送端的 Socket对象(DatagramSocket)
- 创建数据,并把数据打包
- 调用 DatagramSocket对象的方法发送数据
- 关闭发送端
DatagramSocket ds=new DatagramSocket(); //创建数据,并把数据打包 //DatagramPacket(byte[] buf, int length, InetAddress address, int port) byte[] bys="hello,udp".getBytes(); int length = bys.length; InetAddress address=InetAddress.getByName("192.168.135.10"); int port=10086; DatagramPacket dp=new DatagramPacket(bys,length,address,port); // 调用DatagramSocket对象的send方法发送数据 ds.send(dp); //关闭发送端 ds.close();
接收数据的步骤
//本机接收:创建套接字,指定好端口 DatagramSocket ds=new DatagramSocket(10086); //创建一个用于接收的数据包 byte[] bys=new byte[1024];//包裹的大小是1024 DatagramPacket dp=new DatagramPacket(bys,bys.length); //调用接收方法 ds.receive(dp); //得到数据的值和长度,转成字符串 byte[] datas = dp.getData(); int length = dp.getLength(); String strdatas=new String(datas,0,length); //关闭接收端 System.out.println(strdatas); // ds.close();
//UDP发送端 DatagramSocket ds=new DatagramSocket(); BufferedReader br=new BufferedReader((new InputStreamReader(System.in))); String line; while ((line=br.readLine())!=null){ if ("886".equals(line)){ break; } //创建数据包,并把数据打包 byte[] bys=line.getBytes(); DatagramPacket dp=new DatagramPacket(bys,bys.length,InetAddress.getByName("192.168.135.10"),12345); ds.send(dp); } ds.close(); //udp接收端 DatagramSocket ds=new DatagramSocket(12345); while (true){ //创建数据包接收数据 byte[] bys = new byte[1024]; DatagramPacket dp=new DatagramPacket(bys,bys.length); ds.receive(dp); byte[] data = dp.getData(); int length = dp.getLength(); String strdatas=new String(data,0,length); System.out.println("数据是"+strdatas); } //关闭接收端 //ds.close();
TCP通信
通过IO流实现读写数据
Java 为客户端提供了Socket类,为服务器端提供了ServerSocket类
两种构造方法
Socket(InetAddress address,int port)
|
创建流套接字并将其连接到指定IP指定端口号
|
Socket(String host, int port)
|
创建流套接字并将其连接到指定主机上的指定端口号
|
InputStream getInputStream()
|
返回此套接字的输入流
|
OutputStream getOutputStream()
|
返回此套接字的输出流
|
TCP发送数据
步骤:
- 创建客户端的socket对象
- 获取输出流,写数据
- 释放资源
//创建客户端的Socket对象(Socket) Socket s=new Socket("192.168.135.10",10086); //获取输出流,写数据 OutputStream os=s.getOutputStream(); os.write("Hello tcp!".getBytes()); //释放资源 s.close();
TCP服务端
ServletSocket(int port) 创建绑定到指定端口的服务器套接字
Socket accept() 监听要连接到此的套接字并接受它
//创建服务器端的Socket对象(ServerSocket) //ServerSocket(int port) 创建绑定到指定端口的服务器套接字 ServerSocket ss=new ServerSocket(10086); //监听客户端的连接,监听要连接到此的套接字并接受它 Socket s=ss.accept(); //获取输入流,读数据,并把数据显示在控制台 InputStream is=s.getInputStream(); byte[] bys=new byte[1024]; int len = is.read(bys); String strdata=new String(bys,0,len); System.out.println("接收到数据:"+strdata); //释放资源 s.close(); ss.close();
TCP案例
案例一:反馈通信
客户端发送数据并接受服务器的反馈数据
public static void main(String[] args) throws IOException { //创建客户端socket对象 Socket s=new Socket("192.168.135.10",12345); //获取输出流,写数据 OutputStream os = s.getOutputStream(); os.write("我是客户端,我要请求数据".getBytes()); //接收服务器的反馈 InputStream is = s.getInputStream(); byte[] bys=new byte[1024]; int len = is.read(bys); String data=new String(bys,0,len); System.out.println("客户端接受到一条消息是:"+data); //释放资源 s.close(); }
服务器端接收数据并发出反馈数据
{ public static void main(String[] args) throws IOException { //创建服务器的socket对象serversocket ServerSocket ss=new ServerSocket(12345); //监听客户端的连接,并返回一个socke对象,返回确认? Socket s = ss.accept(); //获取输入流,读数据 InputStream is = s.getInputStream(); byte[] bys=new byte[1024]; int len = is.read(bys); String data=new String(bys,0,len); System.out.println("接收到数据:"+data); //给出反馈 OutputStream os = s.getOutputStream(); os.write("我是服务器,数据已经接受".getBytes()); //释放资源 ss.close(); } }
案例二:键盘录入
客户端
使用字符流进行通讯
包装好转换流
//BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new OutputStream()));
{ public static void main(String[] args) throws IOException { //创建客户端socket对象 Socket s=new Socket("192.168.135.10",10086); //获取输出流,发送数据,数据来自键盘录入 BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); String line; while ((line=br.readLine())!=null){ if (line.equals("886")){ break; } //创建通信用的字节流 // OutputStream os = s.getOutputStream(); // os.write(line.getBytes()); //使用字符流 //BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new OutputStream())); BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bw.write(line); bw.newLine(); bw.flush(); } //释放资源 s.close(); } }
服务端
{ public static void main(String[] args) throws IOException { //创建服务端的socket对象serversocket ServerSocket ss=new ServerSocket(12345); //监听客户端的连接,返回对应的socket对象之后才可以通信 Socket s = ss.accept(); //获取输入流 //字节流 // InputStream is = s.getInputStream(); // byte[] bys=new byte[1024]; // int len = is.read(bys); //字符流 BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); String line; while ((line=br.readLine())!=null){ System.out.println("收到:"+line); } //释放资源 ss.close(); } }
案例三:上传&存储文件
服务端将收到的数据存到文件
//获取输入流(字符流) //新建字符写到文件的输出流(字符流) BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); //创建写入文件的字符输出流 BufferedWriter bw=new BufferedWriter(new FileWriter("day05_net\\server.txt")); String line; while ((line=br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush();
客户端从文件读取数据发送到服务端:模拟上传文件
//创建客户端sicket对象 Socket s=new Socket("192.168.135.10",12345); //获取输出流发送到服务器 BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //获取输出流,数据来源于文件 BufferedReader br=new BufferedReader(new FileReader("day05_net\\client.txt")); String line; while ((line= br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush(); } //释放资源 s.close(); br.close();