1.List和和Set集合的区别?
1)是否有序
List存储和取出一致,有序的
Set存储和取出不一致,不能保证迭代次序---->默认使用HashSet--->HashMap集合
2)是否重复
List可以允许元素重复
Set集合中存储的元素唯一
3)具体实现类的数据结构不同
List集合--->ArrayList/Vector:底层数据结构:数组
LinkedList底层数据结构:链表
Set集合---->HashSet基于HashMap实例(底层哈希表)
TreeSet基于TreeMap的实例(底层是一种红黑树(自平衡的二叉树)结构)
2.List集合的遍历方式
//遍历方式
Object[] toArray()
Iterator iterator()
size()和get(int index)结合普通for循环
ListIterator listiterator() //列表迭代器
//增强for循环
for(存储的数据类型 遍历名: 集合对象){
//...
}
3.TreeSet集合存储自定义类型如何实现自然排序?
//TreeSet集合要实现自然排序,使用无参构造方法,而且需要保证TreeSet存储的类型必须实现Compareable接口中的compareTo方法(T t)
TreeSet<Student> ts = new TreeSet<>() ;
//创建一些学生对象....
//添加到集合中
//大部分常用了String,Character,Integer..等等都会实现Compareable接口,完成自然升序排序
class Student implements Compareable<Student>{
//属性私有化
//setXXX(xx)/getXXX()
public int compareTo(Student s){
//需要按照主要条件排序
//自己分析次要条件
return 结果;
}
}
4.HashSet集合存储自定义类型,如何保证元素唯一
HashSet集合保证自定义类型的元素唯一:
add方法--->依赖于HashMap的put方法---->putVal()方法
--->间接依赖于Object的hashCode()和equals方法;
当前自定义类型必须重写Object的equals和hashCode,保证元素唯一!
5.java.util.Date和String日期文本之间的相互转换
class DateUtils{
private DateUtils(){
}
public static String date2String(Date date,String patttern){
return new SimpleDateFormat(pattern).format(date) ;
}
//String--->Date
public static Date string2Date(String sourceDate,String pattern)throws ParseException{
return new SimpleDateFormat(pattern).parse(sourceDate) ;
}
}
Map集合 <K, V>两个子实现类
Map<K,V> :一个键对应一个值(键值映射),Map集合针对键必须唯一,值是可以重复的!
将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值
Map接口和Collection接口的不同
Map是双列的,Collection是单列的
Map的键唯一,Collection的子体系Set是唯一的
Map集合的数据结构值针对键有效,跟值无关
Collection集合的数据结构是针对元素有效
Map集合的基本功能:
V put(K key,V value) :添加方法---返回值什么意思?
通过返回值是否为null,判断键是否重复,如果不重复,返回值永远是null;
如果键重复,将第一次存储键对应的值返回,将后面键对应的值进行存储
void clear():清空Map集合的所有键值对
V remove(Object key):删除Map集合的指定键,然后返回被删除的值
boolean containsKey(Object key):是否包含指定的键
boolean containsValue(Object value):是否包含指定的值
boolean isEmpty():判断Map集合是否为空
Map集合遍历方式:1)
方式1:根据键找值
获取所有键的集合
遍历键的集合,获取到每一个键
根据键找值
Set<K> keySet()获取Map集合的所有的键的集合
V get(Object key):通过键获取值
例子1
package MapBianLi;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test2 {
public static void main(String[] args) {
Map<String,String>MP=new HashMap<>();
MP.put("路飞","橡胶果实");
MP.put("黑胡子","黑暗果实");
MP.put("白胡子","震动果实");
MP.put("艾斯","烧烧果实");
Set<String> str = MP.keySet();//获取Map集合的所有的键的集合
//Set<K> keySet()获取Map集合的所有的键的集合
// V get(Object key):通过键获取值
for(String s:str){
String s2 = MP.get(s);
System.out.println(s+s2);
}
}
}
例子2
package MapBianLi;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
public class Test3 {
public static void main(String[] args) {
HashMap<String, ArrayList<String>>hs=new HashMap<>();
ArrayList<String>as=new ArrayList<>();
as.add("蒙奇.D.路飞");
as.add("罗罗诺亚.索隆");
as.add("文斯莫克.山治");
hs.put("海贼王",as);
ArrayList<String>as2=new ArrayList<>();
as2.add("漩涡鸣人");
as2.add("宇智波佐助");
as2.add("春野樱");
hs.put("火影忍者",as2);
ArrayList<String>as3=new ArrayList<>();
as3.add("黑崎一护");
as3.add("井上织姬");
as3.add("茶渡泰虎");
hs.put("死神",as3);
Set<String> str = hs.keySet();
for(String s:str){
System.out.println(s+"\t");
ArrayList<String> s2 = hs.get(s);
System.out.println("\t"+s2);
}
}
}
Map集合遍历方式:2)
方式2:
根据键值对对象找键和值
获取所有键值对对象的集合
遍历键值对对象的集合,获取到每一个键值对对象
根据键值对对象找键和值
Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象
K getKey()获取键
V getValue()
例子1
public class MapDemo3 {
public static void main(String[] args) {
//创建Map集合
Map<String,String> map = new HashMap<>() ;
//添加
map.put("杨过","小龙女") ;
map.put("郭靖","黄蓉") ;
map.put("陈玄风","梅超风") ;
map.put("令狐冲","任盈盈") ;
//获取所有的键值对对象
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for(Map.Entry<String,String> en:entrySet){
//获取键,获取值
String key = en.getKey();
String value = en.getValue();
System.out.println(key+"="+value) ;
}
}
}
例子2
package MapBianLi;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test4 {
public static void main(String[] args) {
Map<String,String>ms=new HashMap<>();
ms.put("蒙奇.D.路飞","橡胶果实");
ms.put("罗罗诺亚.索隆","剑豪");
ms.put("文斯莫克.山治","改造人");
Set<Map.Entry<String, String>> entries = ms.entrySet();
for(Map.Entry<String, String> mss:entries){
String key = mss.getKey();
String value = mss.getValue();
System.out.println(key+"\t "+value);
}
}
}
Map集合和Collection集合的区别?
1)存储类型
Collection<E>,只能存储一种引用类型---属于"单列集合"---->理解为"光棍"
Map<K,V>,可以存储多种类型的引用类型--->属于"双列集合"----理解为"夫妻对"
2)关系区别
Collection和Map没有直接关系,间接关系
Collection具体的子接口Set--->HashSet/Treeset 直接的去依赖于Map接口的HashMap和TreeMap
HashMap<K,V>
Key:Student类型 ---学生信息
Value:String类型 ---朝代
使用HashMap集合保证key(键
)唯一!
HahMap--->底层哈希表实现,put方法依赖hashCode和equals方法,
Map针对键有效,要针对键的类型自定类型,当前类必须重写equals和hashCode(),保证键唯一(重点)
例子
package HashMapTest;
import java.util.HashMap;
import java.util.Set;
class Student{
private String name;
private int age;
public Student() {
}
public Student(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 "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
public class Test7 {
public static void main(String[] args) {
//创建HashMap集合
HashMap<Student,String> hm = new HashMap<>() ;
Student s1 = new Student("唐伯虎",35) ;
Student s2 = new Student("唐伯虎",35) ;
Student s3 = new Student("祝枝山",30) ;
Student s4 = new Student("宋江",54) ;
Student s5 = new Student("宋江",54) ;
Student s6 = new Student("高圆圆",44) ;
Student s7 = new Student("高圆圆",44) ;
Student s8 = new Student("秋香",32) ;
//添加元素
hm.put(s1,"明朝") ;
hm.put(s2,"明朝") ;
hm.put(s3,"宋朝") ;
hm.put(s4,"清朝") ;
hm.put(s5,"清朝") ;
hm.put(s6,"现代") ;
hm.put(s7,"现代") ;
hm.put(s8,"明朝") ;
hm.put(s1,"现代") ;
//遍历
Set<Student> keySet = hm.keySet();
for(Student s:keySet){
//通过获取值
String value = hm.get(s);
System.out.println(s.getName()+"---"+s.getAge()+"---"+value);
}
}
}
TreeMap<K,V>
存储键值对元素,保证键唯一的同时,还有排序!
使用哪种排序,取决于使用构造方法:
public TreeMap():自然排序,当前键的类型必须实现Compareable接口
public TreeMap(Comparator<? super K> comparator):使用比较器排序 (推荐) 直接可以使用接口匿名内部类
需求:
TreeMap<Student,String>
key:Student类型
value:String,代表学生住址信息
主要条件:
按照学生姓名的长度,从小到排序
分析次要条件:长度相同的,不一定姓名内容相同
"gaoyuanyuan"
"zhangsanfen"
例子
package TreeMapDTest;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;
class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test5 {
public static void main(String[] args) {
TreeMap<Student,String>ts=new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num=s1.getName().length()-s2.getName().length();
int num2=(num==0)?(s1.getName().compareTo(s2.getName())):num;
return num2;
}
});
Student n1=new Student("蔡徐坤",43);
Student n2=new Student("陈立",33);
Student n3=new Student("陈立",13);
Student n4=new Student("何超",23);
Student n5=new Student("何超",33);
ts.put(n1,"美国");
ts.put(n2,"中国");
ts.put(n3,"法国");
ts.put(n4,"英国");
ts.put(n5,"日本国");
Set<Student> stu = ts.keySet();
for(Student s:stu){
String s2 = ts.get(s);
System.out.println(s.getName()+"---"+s.getAge()+"---"+s2);
}
}
}
针对集合操作工具类
提供一些常用的方法:
1)public static <T> int binarySearch(List<? extends Comparable<? super T>> list,T key):
2)二分搜索在List中查询元素 (不管集合还是Arrays---->binaraySearch方法,(集合或者数组必须有序))
3)public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) :获取Colection中的最大值
4)public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) :获取Colection中的最小值
5)public static <T extends Comparable<? super T>> void sort(List<T> list):针对List集合进行自然排序
6)public static <T> void sort(List<T> list,Comparator<? super T> c):针对List集合比较器排序
7)public static void shuffle(List<?> list):针对List集合随机置换
7)public static <T> List<T> synchronizedList(List<T> list):将线程不安全ArrayList,变成线程安全的列表
例子
package CollectionTool;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test8 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(23);
list.add(14);
list.add(8);
list.add(46);
list.add(89);
list.add(12);
System.out.println(list);//[23, 14, 8, 46, 89, 12]
System.out.println("-----------------");
Integer max = Collections.max(list);
System.out.println("list集合最大值是: "+max);//list集合最大值是: 89
System.out.println("-----------------");
//Integer本身实现的接口就是一个自然排序Compareable
Collections.sort(list);
System.out.println(list);//[8, 12, 14, 23, 46, 89]
System.out.println("-----------------");
int i = Collections.binarySearch(list, 99);//寻找list集合中是否有99这个元素
int i1 = Collections.binarySearch(list, 12);//寻找list集合中是否有12这个元素
System.out.println(i);//-7
System.out.println(i1);//1
System.out.println("-----------------");
//针对List集合随机置换
Collections.shuffle(list);
System.out.println(list);
}
}
斗地主老师代码
package com.qf.collections_07;
import java.util.ArrayList;
import java.util.Collections;
/**
* 使用集合"模拟斗地主"洗牌,发牌---可以完成看牌的功能!
*
*
* 斗地主---->3个人玩, 54张牌,底牌3张
*1)创建牌盒 ---没有明确说明什么集合,默认就使用ArrayList---->array
*2)装牌
* 点数数组 String[] numbers = {"A","2","3","4",...."J","Q","K"} ;
* 花色数组 String[] colors = {"♥","♠","♣","♦"} ;
*2.1)将花色和点数遍历,拼接成对一个 String poker
*2.2)将poker牌 添加牌盒中,添加"小王","大王"
*
*3)洗牌----->Collections工具类提供的:public static void shuffle(List<?> list):针对List集合随机置换
*4)发牌:
* 三个玩家----每一个玩家都看成是ArrayList
* player1
* player2
* player3
* diPai集合---存3张牌
* 4.1) 规律:遍历牌盒中的所有牌
* for(int x = 0 ; x <array.size() ; x++ ){
* 判断如果当前
* if(x >= array.size()-3){
* //diPai.add(array.get(角标x)) ;
* }else if(x % 3 == 0){
* //玩家1的牌
* }else if(x % 3 == 1){
* //玩家2的牌
* }else if(x % 3 == 2){
* //玩家3的牌
* }
* }
*
*5)定义一个静态方法----看牌的功能
* public static void lookPoker(String name,ArrayList<String> array){
*
* //name的牌是:
* //遍历这个牌盒
* //将牌获取到,输出....
* }
*
*/
public class ArrayListTest {
public static void main(String[] args) {
//1)使用ArrayList模拟一个牌盒
ArrayList<String> array = new ArrayList<>() ;
//2)装牌
//定义一个点数数组
String[] numbers = {"A","2","3","4","5","6","7","8","9","10","J","Q","K"} ;
//定义一个花色数组
String[] colors = {"♥","♠","♣","♦"} ;
//循环遍历
for(String color:colors){
//获取花色
for(String number:numbers){
String poker = number.concat(color);
//将poker添加牌盒中
array.add(poker) ;
}
}
//添加小王和大王
array.add("小王") ;
array.add("大王") ;
//System.out.println(array) ;
//3)洗牌
//Collections工具类提供的:public static void shuffle(List<?> list):针对List集合随机置换
Collections.shuffle(array) ;
//System.out.println(array) ;
//4)发牌----3个玩家 ,还创建一个集合:代表底牌
//每一个玩家---默认ArrayList存储
ArrayList<String> player1 = new ArrayList<>() ;
ArrayList<String> player2 = new ArrayList<>() ;
ArrayList<String> player3 = new ArrayList<>() ;
ArrayList<String> diPai = new ArrayList<>() ;
//规律:遍历牌盒中的所有牌
//判断:如果当前角标>= 牌盒中所有的元素 -3 ---底牌
for(int x = 0 ; x < array.size() ; x++){
if(x >= array.size()-3){
//底牌
diPai.add(array.get(x)) ;
}else if(x % 3 == 0){ //x % 3 == 0 :第一种情况
//玩家1的
player1.add(array.get(x)) ;
}else if(x % 3 == 1){//第二种情况
//玩家2
player2.add(array.get(x)) ;
}else if(x %3 == 2){
//玩家3
player3.add(array.get(x)) ;
}
}
//看牌
lookPoker("高圆圆",player1) ;
lookPoker("李国栋",player2) ;
lookPoker("赵又廷",player3) ;
lookPoker("底牌",diPai) ;
}
//将看牌定义一个功能
public static void lookPoker(String name,ArrayList<String> array){
System.out.print(name+"的牌是:\t") ;
//遍历每一个人的牌
for(String poker:array){
System.out.print(poker+" ");
}
System.out.println();
}
}
线程和进程
线程是依赖于进程的,没有进程就没有线程!
进程
在计算机的任务管理查看到此电脑上所有的客户端的进行!
概念:
调用"系统资源"的独立单位!
开启一个进程----创建 "系统资源"
所有的计算机,包括我们"jvm",都支持多进程,多进程的意义?
一句话:提高CPU的使用率!
在打游戏的同时,听音乐,---同时开启了两个进程,是同时进行的吗?
并不是同时的,CPU(时间片),一点点时间片可以高效在两个进制来回切换!
线程:
是进程中的最小执行单元!(理解为,进程中的某个任务)
举例:
360进程开启之后,可以杀毒/清理/电脑瘦身等等
多线程:在一个程序的过程中,其执行路径有多条,线程的执行具有"随机性"!
多线程的意义?
多个线程"共享同一个资源",互相抢占CPU执行权,从而达到线程的执行速度!
举例
1 v 3 打篮球,3个人抢占的篮球的几率大,可能这1个人也能抢占大篮球!
创建线程的方式1: Thread类提供方法:
1)jvm是多线程的吗?
是多线程;至少有两条件线程
1)main---"用户线程"
2)垃圾回收线程,回收没有更多的引用对象,从而释放空间;如果没有
垃圾回收线程,Java中不断的去new,不断的堆内存空间---->导致OOM(Out Of Memory:内存溢出)
2)Java能创建线程吗?
创建线程--->创建进程---->创建系统资源---->Java不能够直接创建资源的,间接的提供了一个Thread
创建线程的方式1:
1)自定义一个类 继承Thread类
2)重写Thread类的run方法
3)在main()用户线程中,创建当前线程了对象
4)启动线程--->start()不是run方法,run方法是一个普通方法,
Thread类提供方法:
public final String getName()获取线程的名称
public final void setName(String name):设置线程名称
例子
/**
* 标记当前类是一个线程类
*/
public class MyThread extends Thread{
//重写run方法
@Override
public void run() {
//两个线程都要进来
//完成耗时的操作
//循环
for(int x = 0 ; x < 200 ; x ++){
//public final String getName()获取线程的名称
System.out.println(getName()+":"+x) ;
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
for(int x = 0 ; x < 1000;x++){
System.out.println(new Object()+":"+x) ;
}*/
// 在main()用户线程中,创建当前线程了对象
MyThread my = new MyThread() ;
MyThread my2 = new MyThread() ;//第二个线程对象
my.setName("高圆圆") ;
my2.setName("李纪奔");
//启动线程
/* my.run() ;
my.run();*/
//上面代码:使用线程对象将普通run方法调用两次,并不会出现线程的执行随机性!
my.start();
//my.start() ;//IllegalThreadStateException:非法线程状态异常,一个线程只能启动一次
my2.start();
}
}
Thread类的sleep方法以及join()方法/setPriority()设置线程优先级的方法,以及线程的创建方式2Runnable接口
安全 不安全
ArrayList LinkedList
Vector StringBuilder
StringBuffer HashMap
TreeMap HashSet
String TreeSet
1.Collection和Map集合的区别
1)集合类型的区别
Collection单例集合,只能存储一种引用类型数据
Map,双列集合,可以存储键值对Key,Value,两种引用类型数据
2)遍历方式的区别
Collection--->使用自己的专有遍历方式:迭代器---增强for来代替迭代器
Map --->推荐:Set<K> keySet()--->增强for遍历所有的键---->V get(Object key)
Set<Map.entry<K,V>> etnrySet()--->遍历键值对对象--->K getKey()
V getValue()
3)间接有一定关系
Collection里面Set接口的两个子实现类,HashSet和TreeSet都依赖于Map接口中两个子实现类
HashMap和TreeMap---put方法有关系!
2.HashMap集合的键存储自定义类型,如何保证键唯一
保证键唯一
需要重写HashCode和equals方法,HashMap底层依赖put方法最终直接关联到Object的hashCode和equals方法;
3.List集s合和Set集合的区别?
1)是否有序
List是有序,存储和取出一致
Set是无序的,默认使用HashSet--->HashMap,(存储和取出不一致)
2)元素是否可以重复
List:元素可以重复
Set:能够保证元素唯一
3)两个接口对应实现类的底层数据结构的区别
List
ArrayList:底层数组结构,查询快,增删慢,线程不安全,单线程中效率高
Vector :底层数组结构,查询快,增删慢,线程安全的类,单线程中效率低,多线程中使用居多
LinkedList:底层数据结构是链表,查询慢,增删快,线程不安全的类,单线程中效率高
Set:
HashSet--->依赖HashMap---底层哈希表结构,保证元素唯一
TreeSet--->依赖TreeMap--->自平衡的二叉树结构,保证唯一,而且根据存储的类型,是自然排序/还是比较器排序!(取决于使用的构造函数)
5.什么是进程和线程?
进程:
是调用"系统资源"一个独立单位!(每一个客户端应用程序,对应一个进程!)
一个进程:0-n个线程组成
多进程:是为了提高cpu的使用率!
线程:
依赖于进程,是程序中最小执行单元(CPU调度的一种最小单位!) 多个线程去同一个资源(程序计数器:pc寄存器)
多线程:为了让多个线程互相抢占CPU执行,提高线程的利用率!
6.创建线程的方式1的步骤
//1)自定义一个类,让这个类继承自Thread类 :标记当前是一个线程类
class MyThread extends Thread{
//2)重写Thread类中的run方法
public void run(){
//线程中的耗时操作...
for(int x = 0 ; x < 200 ; x++){
System.out.println(getName()+":"+x) ;
}
}
}
class ThreadDemo{
public static void main(String[] args){
//3)在main:用户线程中(主线程中) 创建当前这个线程类对象
MyThread my1 = new MyThread() ;
MyThread my2 = new MyThread() ;
//4)设置线程名称
my1.setName("t1线程") ;
my2.setName("t2线程") ;
//5)启动线程
my1.start() ;
my2.start() ;
}
}
2.Java中异常的处理 (两种处理方案)
throws和try...catch,实际开发中,在业务层代中service层,使用try...catch捕获异常;
在dao层代码中(数据库访问:jdbc/mybatis框架)----全部使用throws
4.线程的创建第二种 (重点)
另一种方法来创建一个线程是声明实现类"Runnable接口"。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动;
如何使用:上面给的步骤
第二种方式优点?
能够体现数据共享/用到了设计模式"静态代理"
Java中23种设计模式(面试中:创建型/结构型)
创建型:单例/简单工厂/工厂方法...
结构型:代理设计模式
静态代理
动态代理--->跟"反射"有关系
行为型
异常概念
程序中会出现异常,开发人员必须解决异常!
异常的父类:Throwable
-->提供一些方法:将异常的信息直接打印在控制上:具体(跟踪堆栈-->底层原码)
public void printStackTrace()
Error(严重问题) Exception
严重问题:内存溢出(借助于其他硬件解决这个问题,加内存条)
Exception:
编译时期异常:只要不是RuntimeException的子类,都属于编译时期
必须让开发人员必须处理,不处理,代码编译通过不了!
运行时期异常:RuntimeException
大部分逻辑不够严谨,导致出现的问题
NullPointerEception:在获取到某个类的对象的时候,并没有非空判断,
可能对象是null,使用这个对象调用它里面的方法,出现空指针了
常见格式
在Java中处理异常方案:
throws:抛出
//标准格式
try{
可能出现问题的代码
}catch(异常类名 对象名){
处理异常
}finally{
//释放资源
}
变形格式
try{
可能出现问题的代码
}catch(异常类名 对象名){
处理异常
}
try{
可能出现问题的代码
}catch(异常类名 对象名){
处理异常
}catch(异常类名2 对象名2){
处理异常...
}
例子try…catch…
public class ExceptionDemo {
public static void main(String[] args) {
//处理方案:
//try...catch...
//try代码如果出现问题,---执行catch语句---->处理异常---->内存中,jvm会创建异常的对象,new XXException,
//执行的catch语句代码
try{
//可能出现的代码
int i = 10 ;
System.out.println(i/0) ;
}catch(Exception e){ //捕获的异常Exception
//自己处理
//System.out.println("除数不能为0");
e.printStackTrace();
}
System.out.println("over");
}
例子throws,throw
public class ExceptionDemo2 {
public static void main(String[] args) throws IOException,ArrayIndexOutOfBoundsException, Exception { //throws 在方法声明上
//将可能出现代码包起来---try...catch--- (ctrl+alt+t---->选择第6个)
/* try {
int[] arr = {11,22,33} ;
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[3]);//ArrayIndexOutOfBoundsException数组角标越界
} catch (Exception e) {
e.printStackTrace();
}*/
int[] arr = {11,22,33} ;
System.out.println(arr[0]);
System.out.println(arr[1]);
//System.out.println(arr[3]);//ArrayIndexOutOfBoundsException数组角标越界
System.out.println("----------------------------------------------");
Date date = string2Date();
System.out.println(date);
System.out.println("-------------------------------");
divide();
}
public static Date string2Date() throws ParseException {
String source = "2023-03-21" ;
//将source--->java.util.Date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd ") ;
//当解析的时候,模式的格式和String文本格式不一致会出现问题
Date date = sdf.parse(source);//编译时期异常 --谁调用这个方法,就必须处理这个方法
return date ;
}
public static void divide() throws Exception {
int i = 10 ;
int b = 0 ;
if(b==0){
//如果执行到这个代码中,一定会出异常!
throw new Exception("除数不能为0") ;
}else{
System.out.println(i/b) ;
}
}
}
throw和throws的区别
异常处理
throws:抛出
throw:抛出
区别:
1)书写位置的区别
throws:用在方法声明上
throw:用在方法体中
2)后面跟的类型不一样的
throws后面跟的异常类名,可以多个,中间逗号隔开
throw:后面只能一个异常对象,new XXException()
3)是否出现异常的时机
throws:可能出现问题,可能性
throw:执行某个逻辑代码,一定会出现这个异常!(肯定性)
4)处理异常:
throws:抛出给调用者,谁调用这个方法(带有throws的方法),谁必须对这个已处理(throws/try...catch...)
throw:异常的处理:交给方法体中逻辑判断,if(xxx){throw new XXException(...);}
异常的使用的注意事项:
1)子类继承父类的时候,如果子类重写父类的方法,父类的方法如果本身没有throws抛出异常
子类的方法中出现异常,子类只能自己处理,只能try...catch...
2)子类继承父类,子类重写父类方法的时候,如果父类的方法抛出异常,
那么子类的该方法最起码要根父类的方法异常保存一致(要么子类该放的异常是父类方法的异常中的子类)
例子
class Father{
public void method() {
System.out.println("method Father");
}
public void show() throws ArrayIndexOutOfBoundsException{
System.out.println("show father");
}
}
class Son extends Father{
@Override
public void show() throws ArrayIndexOutOfBoundsException {
System.out.println("show Son");
}
@Override
public void method() {
try {
//将日期文本---解析java.util.Date
String dateStr = "2023-05-01" ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
Date date = sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public class ExceptionDemo {
public static void main(String[] args) {
}
}
异常的使用的注意事项:
1)子类继承父类的时候,如果子类重写父类的方法,父类的方法如果本身没有throws抛出异常
子类的方法中出现异常,子类只能自己处理,只能try...catch...
2)子类继承父类,子类重写父类方法的时候,如果父类的方法抛出异常,
那么子类的该方法最起码要根父类的方法异常保存一致(要么子类该放的异常是父类方法的异常中的子类)
例子
class Father{
public void method() {
System.out.println("method Father");
}
public void show() throws ArrayIndexOutOfBoundsException{
System.out.println("show father");
}
}
class Son extends Father{
@Override
public void show() throws ArrayIndexOutOfBoundsException {
System.out.println("show Son");
}
@Override
public void method() {
try {
//将日期文本---解析java.util.Date
String dateStr = "2023-05-01" ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
Date date = sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public class ExceptionDemo {
public static void main(String[] args) {
}
}
线程的Join方法
等待该线程中终止
public final void join() throws InterruptedException
因为线程的执行具有随机性,哪个线程启动之后,调用join(),正常理想情况,等待该线程执行完毕,执行其他线程!
需求
实现三个线程依次先后执行完毕, t1,t2,t3每一个线程启动之后,都可以调用join()
例子
package Test2;
/*现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,
T3在T2执行完后执行 (线程里面的join使用)
T1--T2--T3
*/
class Demo extends Thread{
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
public class Test2 {
public static void main(String[] args) {
Demo T1=new Demo();
Demo T2=new Demo();
Demo T3=new Demo();
T1.setName("T1");
T2.setName("T2");
T3.setName("T3");
T1.start();
try {
T1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
T2.start();
try {
T2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
T3.start();
try {
T3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程的yield方法
public static void yield():线程礼让(暂停当前正在执行的线程,执行其他线程)
package Yield;
class YieldThread extends Thread{
@Override
public void run() {
for(int x=0;x<20;x++){
System.out.println(getName()+": "+x);
yield();
}
}
}
public class Test5 {
public static void main(String[] args) {
YieldThread yt=new YieldThread();
YieldThread yt2=new YieldThread();
YieldThread yt3=new YieldThread();
yt.setName("张三");
yt2.setName("李四");
yt3.setName("王五");
yt.start();
yt2.start();
yt3.start();
}
}
优先级
public final void setPriority(int newPriority):设置优先级
public final int getPriority():获取优先级
Thread提供三个自定义常量:
public static final int MAX_PRIORITY 10 最大优先级
public static final int MIN_PRIORITY 1 最小优先级
public static final int NORM_PRIORITY 5 默认优先级
创建线程,没有设置优先级,线程的默认优先级就是5(抢占CPU执行权的概率一样大)
例子
package PriorityThread;
class Priority extends Thread{
@Override
public void run() {
for(int x = 0 ; x < 100 ; x++){
System.out.println(getName()+":"+x);
}
}
}
public class PriorityThread {
public static void main(String[] args) {
Priority p1=new Priority();
Priority p2=new Priority();
Priority p3=new Priority();
p1.setName("王拉拉");
p2.setName("王喜喜");
p3.setName("王露露");
//设置优先级
p1.setPriority(10);
p3.setPriority(1);
p1.start();
p2.start();
p3.start();
}
}
线程创建方式1
1)自定义一个类 继承Thread类
2)重写Thread类的run方法
3)在main()用户线程中,创建当前线程了对象
4)启动线程--->start()不是run方法,run方法是一个普通方法,
Thread类提供方法:
public final String getName()获取线程的名称
public final void setName(String name):设置线程名称
例子
/**
* 标记当前类是一个线程类
*/
public class MyThread extends Thread{
//重写run方法
@Override
public void run() {
//两个线程都要进来
//完成耗时的操作
//循环
for(int x = 0 ; x < 200 ; x ++){
//public final String getName()获取线程的名称
System.out.println(getName()+":"+x) ;
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
for(int x = 0 ; x < 1000;x++){
System.out.println(new Object()+":"+x) ;
}*/
// 在main()用户线程中,创建当前线程了对象
MyThread my = new MyThread() ;
MyThread my2 = new MyThread() ;//第二个线程对象
my.setName("高圆圆") ;
my2.setName("李纪奔");
//启动线程
/* my.run() ;
my.run();*/
//上面代码:使用线程对象将普通run方法调用两次,并不会出现线程的执行随机性!
my.start();
//my.start() ;//IllegalThreadStateException:非法线程状态异常,一个线程只能启动一次
my2.start();
}
}
线程创建方式2
线程的创建方式2:
1)自定义一个类,实现Runnable接口
2)重写Runnable接口的run方法
3)在用户线程main中去创建当前类对象--->"资源类"
4)创建Thread类对象,将3)资源类作为Thread类对象的参数进行传递
例子
package ThreadCreat2;
class MyRunnable implements Runnable{
@Override
public void run() {
//完成耗时的操作
//两个线程都要进来
for(int x = 0 ; x < 200 ; x ++){
//Thread类静态方法
//public static Thread currentThread():获取正在执行的线程
System.out.println(Thread.currentThread().getName()+":"+x) ;
}
}
}
public class Test8 {
public static void main(String[] args) {
//创建MyRunnable类对象
MyRunnable my = new MyRunnable() ;
//创建线程Thread类对象,将资源类作为参数传递
//public Thread(Runnable target,String name)
Thread t1 = new Thread(my,"t1") ;
Thread t2 = new Thread(my,"t2") ;
//启动线程
t1.start() ;
t2.start() ;
}
}
方式1和方式2的优劣
方式1的弊端:
1)继承关系说。继承关系有局限性。继承Thread类,重写了run方法,但是其他的方法也会继承过来,
2)内存角度看,多个线程导致多个栈指向多个堆
方式2的好处
1)面向接口编程,直接实现Runnable
这种方式:静态代理,可以体现数据共享!创建对象的时候都指向同一个资源对象!实现Runnable接口的 类对象
java有23种模式
创建型:单例/简单工厂/工厂方法...
结构型:代理设计模式
静态代理
动态代理--->跟"反射"有关系
行为型
线程的6种状态
在Thread类中提供了内部枚举:enum State{}
NEW= new新建
RUNNABLE= runnable 运行
BLOCKED= blocked 阻塞
WAITING= waiting死死等待
TIMED_WAITING= timed_waiting 超时等待
TERMINATED= terminated线程终止/死亡
静态代理
设计模式一种思想(前辈总结出来的),并非一种技术
代理设计模式属于结构型设计模式一种;
代理设计模式: 核心思想:让代理角色帮助真实角色完成一件事情!
静态代理
动态代理:
jdk动态代理:前提必须有接口
cglib动态代理:第三方jar包 ---基于子类实现
静态代理:
最大特点:代理角色和真实角色必须实现同一个接口
代理角色:帮助真实角色完成一些事情
真实角色:专注于自己的事情
弊端:
代理角色堆业务功能增强的是时候,和系统监控的代码不能有效的分离
老师例子
public class StaticProxyDemo {
public static void main(String[] args) {
//测试真实角色
//接口多态/创建具体的子类
You you = new You() ;
you.mary();
System.out.println("---------------------------------") ;
//创建WeddingCompany类对象
You you2 = new You() ;//---->类似 资源类对象 MyRunnable implement Runnble
WeddingCompany weddingCompany = new WeddingCompany(you2) ; //类似 ---Thread t1 = new Thread(MyRunnable对象,线程名称);
weddingCompany.mary() ; //类似----t1.start() ; jvm调用run
}
}
//结婚的接口
interface Mary{
void mary() ;
}
//真实角色
class You implements Mary{
@Override
public void mary() {
System.out.println("要结婚了,很开心...");
}
}
//婚庆公司:代理角色
class WeddingCompany implements Mary{
//声明Mary接口变量
private Mary mary ; //---类似Thread类的private Runnable target;
public WeddingCompany(Mary mary){//类似 Thread(Runnable target,String name)
this.mary = mary ;
}
@Override
public void mary() {
if(mary!=null){
System.out.println("婚庆公司,要布置婚礼现场!");
//真实角色完成它自己的事情
mary.mary();
System.out.println("婚庆公司接收尾款!");
}
}
}
关键字锁(synchronized)
Java的同步机制
synchronized(锁对象){ //锁对象可以是任意的Java类对象,每一个线程都是用"同一把锁"
多条语句对共享数据的操作
老师例子
package Synchronized;
class Demo{}
class SellTicket implements Runnable{
private static int ticket=100;
private Object obj=new Object();
private Demo d=new Demo();
// synchronized(锁对象){
//锁对象可以是任意的Java类对象,每一个线程都是用"同一把锁"
// 多条语句对共享数据的操作}
@Override
public void run() {
while (true){
synchronized (obj){//由于锁里的可以是任意java类 所以随便定义一个类就可,里面写上面的 d 也可以
if(ticket>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
}
}
}
}
}
public class Test10 {
public static void main(String[] args) {
//创建SellTicket对象
SellTicket st = new SellTicket() ;
//创建三个线程
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start() ;
t2.start() ;
t3.start() ;
}
}
1.如何解决多线程的安全问题
使用Java同步锁---同步代码块
格式:
//多个线程必须使用同一个锁对象!(任意Java类对象,jdk提供,或者自定义的类)
synchronized(锁对象){
将多条语句对共享数据的操作包裹起来;
}
//Java中线程安全的类Vector集合/StringBuffer还有Collections里面有一些方法都是同步锁
2.join()和yield()的区别?
join()
当前线程终止(线程调用join()方法会让当前线程执行完毕)
yield()
线程礼让,暂停正在执行的线程,并执行其他线程!
3.线程的状态有哪些?
Thread类中有内部枚举类:
enum State{
NEW,新建状态
RUNNABLE,运行状态
BLOCKED,线程阻塞状态
WAITTING,死死等待,
TIMED_WAITTING,超时等待
TERMINATED,线程终止/线程死亡
}
4.什么是静态代理?
静态代理:
特点:代理角色和真实角色必须实现同一个接口!
始终代理的核心思想:让代理角色帮助真实角色完成功能的增强!
举例:
这个角色---CEO (添加权限/删除权限/查询权限/修改权限)
这个用户是否是CEO---->在进行添加/删除/修改/查询---"权限校验"
如果有权限--->完成了查询/修改/添加/删除的动作
对当前哪一天这个人操作的这些事情---->"产生日志"
弊端:
代理角色对业务功能增强的时候,和"系统监控的代码"不能有效分离!
5.线程创建的方式2的实现方式
//创建线程的方式2使用的就是一种静态代理:
class MyRunnable implements Runnable{
public void run(){
//线程执行的业务
}
}
class ThreadTest{
public static void main(String[] args){
MyRunnable my = new MyRunnable() ;//真实角色
//Thread类 implements Runnable{ //代理角色
private Runnable target;
private String name ;
// public Thread(Runnable target,String name ){
//init初始化方法---->通过局部变量成员变量赋值
//}
//public void run(){
// if(target!=null){
target.run() ;
//}
//}
// }
//创建线程类对象
Thread t1 = new Thread(my,"t1线程") ;
Thread t2 = new Thread(my,"t2线程") ;
//启动线程
t1.start() ;
t2.start() ;
}
}
1.解决死锁—>同步中使用 "生产者-消费者"思想
出现死锁原因:
多个线程加入同步锁之后,线程互相之间持有对方的锁,每一个线程必须让对方释放锁之后,才能去使用同步中的内容,导致线程之间出现互相等待!(访问的资源不是同一个资源)
wait()+notify():这个两个必须在synchronzied使用(同步代码块或者同步方法) 使用"信号灯法"来解决死锁问题
2.面试题: sleep和wait方法区别?
1)使用区别:
sleep()单独使用,里面传入超时时间,
wait()不能单独用,必须在同步代码块/同步方法中使用,否则,就会出现 java.lang.IllegalMonitorStateException
2)来源不同
sleep(long time)线程睡眠,来源于Thread类中,wait()来源于Object类中
3)导致线程状态不同
sleep(long time)参数出入的超时时间,时间到达,线程睡眠接收,线程会继续执行,在休眠过程中,
线程处于的状态是TIMED_WAITTING(超时等待)
wait()
线程进入等待状态,如果没有锁对象调用notify()或则notifyAll(),线程状态就进入到WAITTING(死死等待)
4)是否主动唤醒
sleep(long time):时间到达之后,自动唤醒(主动唤醒),而wait(),被动唤醒,需要使用notify()或者notifyAll进行唤醒
5)是否会释放锁
sleep()方法不会去释放锁,wait()方法调用会立即释放锁
3.线程池
线程的好处:
1)提高了线程使用率
2)大大减少资源开销
4)可以通过一些参数(7大参数)进行线程池的调优,到达一些效果
弊端:
维护成本大
可以通过线程池---->
Executors工厂类提供很多静态方法
public static ExecutorService newFixedThreadPool(int nThreads):创建固定的可重用的线程数的线程池
ExecutorService子实现类=--- TreadPoolExecutor完成线程池初始化!
如果涉及到定期让线程进行活动(public static ScheduledExecutorService newScheduledThreadPool)
面试题 wait()/notify()并非Thread类中为什么定义在Object的?
wait()线程等待--->底层域wait(long time),Java的本地方法(底层语言操作),使用monitor(监视器锁)来调用wait方法(本质就是synchronized同步锁)
notify()/notifyAll():唤醒单个线程以及唤醒所有线程,都是锁对象的方法,
而锁对象可以是任意Java类对象,不会定义在Thread类中,而是在Object类中定义的
同步方法
如果一个成员方法中进来就是一个synchronized(锁对象){}----同步代码块,将synchronized提到方法声明上
默认的锁对象this
如果是静态的同步方法,锁对象是 "当前类名.class"--跟"反射"有关系
反射:Java代码经历三个阶段
"SORUCE" ,"CLASS"(反射中的核心) "RUNTIME"
//同步方法:如果一个成员方法中进来就是一个synchronized(锁对象){}----同步代码块,将synchronized提到方法声明上
//同步方法的锁对象:是this---当前类对象的地址值引用!
例子
package Synchronized_Funcation;
class SellTicket implements Runnable{
private static int tickets = 100 ;
//创建一把锁
private Object obj = new Object() ;
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//模拟一直有票
while(true){
if(x% 2==0){
// synchronized (obj) {
//synchronized (this) {//当前类对象的地址值引用
synchronized (SellTicket.class) {//当前类名.class:获取到当前类的字节码文件对象
if (tickets > 0) {
//模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}else {
//x变量不能被2整除
sell() ; //同步方法(非静态)
}
x++ ;
}
}
public static synchronized void sell(){//静态是随着类的加载而加载 ,静态的同步方法的锁对象是"类
if (tickets > 0) {
//模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
public class Test_thecher {
public static void main(String[] args) {
SellTicket st = new SellTicket() ;
//三个线程操作同一个资源
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start() ;
t2.start() ;
t3.start() ;
}
}
同步锁//同步代码块//同步方法
(以下是扩展内容)
同步代码块
synchronized(obj)
{
//需要被同步的代码块
}
同步方法
同步方法是当方法声明上加synchronized,表示此时只有一个线程进入同步方法。锁住的该类实例化的对象。
死锁
两个或者两个以上的线程,在执行的过程中,互相持有对方的锁,需要等待对方释放锁,导致出现了互相等待情况
例子
//创建两把锁
public class MyLock {
// 锁对象:可以是任意Java类对象
public static Object objA = new Object() ;
public static Object objB = new Object() ;
}
//资源类
public class DieLock implements Runnable {
private boolean flag ;
public DieLock(boolean flag){
this.flag = flag ;
}
@Override
public void run() {
if(flag){
synchronized (MyLock.objA){
System.out.println("if ObjA...");
synchronized (MyLock.objB){
System.out.println("if ObjB...");
}
}
}else {
synchronized (MyLock.objB){
System.out.println("else ObjB...");
synchronized (MyLock.objA){
System.out.println("else objA...");
}
}
}
}
}
//测试类
public class ThreadDemo {
public static void main(String[] args) {
//创建资源类对象
DieLock d1 = new DieLock(true) ;
DieLock d2 = new DieLock(false) ;
//创建线程类
Thread t1 = new Thread(d1) ;
Thread t2 = new Thread(d2) ;
//启动线程
t1.start();
t2.start();
}
}
生产者和消费者模式思想
使用生产者和消费者模式思想,来解决线程死锁问题!
组成:
Student类:姓名,年龄两个学数据
SetTread类:生产资源类,里面产生学生数据
GetThred类:消费者资源类,里面使用学生数据
ThreadDemo类:用户线程,等会开启两个生产者所在的线程/消费者所在的线程
常见的问题 和解决方案
问题1:
输出null--0,因为生产资源类中和消费资源类中并没有使用同一个资源!
优化:
需要不断的在生产资源类中产生学生数据,在消费者资源类中不断地使用学生数据!
问题2:
不断的在生产者资源类中产生数据,消费者资源不断使用使用数据,出现数据紊乱!
姓名和年龄不符! ---多线程环境不安全
---需要多线程安全问题
校验多线程安全问题的标准:
1)是否是多线程环境
2)是否存在共享数据
3)是否有多条语句对共享数据操作
使用同步代码块---将3) 包起来
问题3: 加入同步代码块,解决了线程安全问题,数据不会紊乱,但是数据一打印一大片
原因:线程的执行,通过cpu一点点时间片,足够线程执行很多次!
例子老师
package Test2;
class Student {
String name ; //姓名
int age ; //年龄
boolean flag ; //false,没有数据,true,有数据!
}
class SetThread implements Runnable{
private Student s ;
public SetThread(Student s){
this.s = s ;
}
private int x = 0 ;//统计变量
@Override
public void run() {
while(true){
synchronized (s){ //锁对象,任意Java类对象
//创建学生对象
// Student s = new Student() ;
if(x % 2==0){//%2是因为这样子的话打印出来就是依次两个的。
s.name = "高圆圆" ;
s.age = 44 ;
}else {
s.name = "李国栋";
s.age = 23 ;
}
}
x ++ ;
}
}
}
class GetThread implements Runnable{
//声明学生变量
private Student s ;
public GetThread(Student s){
this.s = s ;
}
@Override
public void run() {
while(true){
synchronized (s){
//使用学生学生
//Student s = new Student() ;
System.out.println(s.name.concat(""+s.age));
}
}
}
}
public class TestMiNi {
public static void main(String[] args) {
//创建学生对象
Student s = new Student() ;
//创建资源类
SetThread st = new SetThread(s) ;
GetThread gt = new GetThread(s) ;
//创建两个线程
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(gt) ;
//启动线程
t1.start() ;
t2.start() ;
}
}
信号灯机制和等待唤醒机制
//使用生产者和消费者模式思想,来解决线程死锁问题!
组成:
Student类:姓名,年龄两个学数据
SetTread类:生产资源类,里面产生学生数据
GetThred类:消费者资源类,里面使用学生数据
ThreadDemo类:用户线程,等会开启两个生产者所在的线程/消费者所在的线程
Object notify() 方法用于唤醒一个在此对象监视器上等待的线程
wait()+notify() 也算Java同步机制--->必须使用锁对象调用方法
例子老师-改进版
public class Student {
String name ; //姓名
int age ; //年龄
boolean flag ; //false,没有数据,true,有数据!
}
public class SetThread implements Runnable{
private Student s ;
public SetThread(Student s){
this.s = s ;
}
private int x = 0 ;//统计变量
@Override
public void run() {
while(true){
synchronized (s){ //锁对象,任意Java类对象
//加入一个判断
if(s.flag){
//没有数据,先产生数据,等待消费使用数据
//线程等待--Object类---->wait()
try {
s.wait();//wait方法调用,立即释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//创建学生对象
// Student s = new Student() ;
if(x % 2==0){
s.name = "高圆圆" ;
s.age = 44 ;
}else {
s.name = "李国栋";
s.age = 23 ;
}
//如果有数据了,通知(唤醒)消费者资源来,赶紧来消费(改变信号)
s.flag = true ;
s.notify() //通知(唤醒)消费者
}
x ++ ;
}
}
}
public class GetThread implements Runnable{
//声明学生变量
private Student s ;
public GetThread(Student s){
this.s = s ;
}
@Override
public void run() {
while(true){
synchronized (s){
//加入判断
if(!s.flag){
//有数据了,等待先使用这个数据
try {
s.wait();//wait方法调用,立即释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//使用学生学生
//Student s = new Student() ;
System.out.println(s.name.concat(""+s.age));
//当flag是一个false,没有数据了,通知(唤醒)生产者所在的线程,来产生数据!
s.flag = false ;
s.notify();//通知(唤醒)生产者
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//创建学生对象
Student s = new Student() ;
//创建资源类
SetThread st = new SetThread(s) ;
GetThread gt = new GetThread(s) ;
//创建两个线程
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(gt) ;
//启动线程
t1.start() ;
t2.start() ;
}
}
Lock–>锁定操作
jdk5以后提供了具体的"锁定操作"
java.u til.concurrent.l cks.Lock接口---->可重入的互斥锁 ReentrantLock
提供一些方法,可以获取锁,可以去释放锁
void lock() 获取锁
void unlock()释放锁
finally除是一种特例:在执行finally语句之前,jvm退出 System.exit(0) ; ,任何情况下,finally语句一定会执行!//使用完,释放锁
例子
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 资源类
*/
public class SellTicket implements Runnable {
//100张票
private static int tickets = 100 ;
//之前:锁对象可以是任意Java类对象,现在jdk提供Lock具体的锁----ReetrantLock子实现类
private Lock lock = new ReentrantLock() ;
@Override
public void run() {
//模拟一直有票
while(true){
try{
//获取锁
// void lock() 获取锁
// synchronized (自定义一个锁){}
lock.lock();
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}finally {
lock.unlock();
}
}
}
}
* 电影院三个窗口卖票---使用Lock解决多线程安全问题----和synchronized是同样的语义
*/
public class LockDemo {
public static void main(String[] args) {
//创建资源类
SellTicket st = new SellTicket() ;
//创建三个线程,代表三个窗口
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start() ;
t2.start() ;
t3.start() ;
}
}
线程池
可以通过Executors工厂类(当前类构造方法私有化,对外提供静态方法--->简单工厂模式)提供一些创建线程池的功能
可以创建固定的可重用的线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads):参数为当前的线程数量
上面的返回值ExecutorService接口
提供一些方法
Future<?> submit(Runnable task)提交异步任务
<T> Future<T> submit(Callable<T> task):提交异步任务
void shutdown():关闭线程池
例子老师
import java.util.concurrent.Callable;
/**
* 自定义类实现Callable:完成异步计算
*/
public class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 200 ; x++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int x = 0 ; x < 100 ; x++){
System.out.println(Thread.currentThread().getName()+":"+x) ;
}
}
}
public class ExecutorsDemo {
public static void main(String[] args) {
//创建线程池---创建固定的可重用的线程数的线程池
// public static ExecutorService newFixedThreadPool(int nThreads):参数为当前的线程数量
ExecutorService threadPool = Executors.newFixedThreadPool(2);
MyCallable myCallable = new MyCallable() ;
threadPool.submit(myCallable) ;
threadPool.submit(myCallable) ;
// void shutdown():关闭线程池
threadPool.shutdown();
}
}
两条线程求1-100和1-200的和
/**
* 需求:使用线程池的方式完成多个线程的异步计算:
* 1个线程求1-100的和
* 另一个线程求1-200的和
*
* 1)创建线程池:固定的可重用的线程数的线程池
* 2)提交异步任务<T> Future<T> submit(Callable<T> task):提交异步任务
* 返回值:Future接口:代表异步计算的结果
* V get():获取计算的结果
*/
package com.qf.executor_07;
import com.qf.executors_06.MyCallable;
import java.util.concurrent.Callable;
/**
* 异步任务
*/
public class MySelfCallable implements Callable<Integer> {
private int number ;
public MySelfCallable(int number){ //100或者200
this.number = number ;
}
@Override
public Integer call() throws Exception {
//定义一个最终结果变量
int sum = 0 ;
for(int x = 1; x <= number ;x++){
sum += x ;
}
return sum;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test {
public static void main(String[] args) throws
ExecutionException, InterruptedException {
//1)创建线程池:固定的可重用的线程数的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//2)提交异步任务
//<T> Future<T> submit(Callable<T> task):
Future<Integer> f1 = threadPool.submit(new MySelfCallable(100));
Future<Integer> f2 = threadPool.submit(new MySelfCallable(200));
System.out.println(f1.get());
System.out.println(f2.get());
//3)关闭线程池
threadPool.shutdown();
}
}
面试题必问–> 线程池5个创建方式以及7大参数
线程池
线程是稀有资源,系统频繁创建会很大程度上影响服务器的使用效率,如果不加以限制,很容易就会把服务器资源耗尽。
所以,我们可以通过创建线程池来管理这些线程,提升对线程的使用率
简而言之,线程池就是管理线程的一个容器,有任务需要处理时,会相继判断核心线程数是否还有空闲、线程池中的任务队列是否已满、是否超过线程池大小,然后调用或创建线程或者排队,线程执行完任务后并不会立即被销毁,而是仍然在线程池中等待下一个任务,如果超过存活时间还没有新的任务就会被销毁,通过这样复用线程从而降低开销。
线程池5个创建方式
1.newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的
ThreadFactory 创建新线程。
特征:
(1)线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
(2)线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
(3)当线程池中,没有可用线程,会重新创建一个线程
通过: Executors.newCachedThreadPool();创建线程池
2.newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于
处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。
如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个
线程被显式地关闭之前,池中的线程将一直存在
特征:
(1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
(2)线程可以重复被使用,在显示关闭之前,都将一直存在
(3)超出一定量的线程被提交时候需在队列中等待
创建方式
(1)Executors.newFixedThreadPool(int nThreads);//nThreads为线程的数量
//nThreads为线程的数量,threadFactory创建线程的工厂方式
(2)Executors.newFixedThreadPool(int nThreads,ThreadFactory threadFactory);
3.newSingleThreadExecutor
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现
失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在
任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool不同,可保证无需重新配置此方法所返回
的执行程序即可使用其他的线程。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
创建方式:
(1)Executors.newSingleThreadExecutor() ;
// threadFactory创建线程的工厂方式
(2)Executors.newSingleThreadExecutor(ThreadFactory threadFactory);
4.newScheduleThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
特征:
(1)线程池中具有指定数量的线程,即便是空线程也将保留
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newScheduledThreadPool(int corePoolSize);// corePoolSize线程的个数
(2)newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);// corePoolSize线程的个数,
threadFactory创建线程的工厂
5.newSingleThreadScheduledExecutor
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
特征:
(1)线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行
(2)可定时或者延迟执行线程活动
创建方式:
(1)Executors.newSingleThreadScheduledExecutor() ;
//threadFactory创建线程的工厂
(2)Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
ThreadPoolExecutor它的构造方法涉及的相关参数
1.corePoolSize
核心线程数:
是指线程池中长期存活的线程数.
这就好比古代大户人家,会长期雇佣一些“长工”来给他们干活,这些人一般比较稳定,无论这一年的活多活少,这些人都不会被辞退,都是长期生活在大户人家的
2.maximumPoolSize
最大线程数:
线程池允许创建的最大线程数量,当线程池的任务队列满了之后,可以创建的最大线程数
这是古代大户人家最多可以雇佣的人数,比如某个节日或大户人家有人过寿时,因为活太多,仅靠“长工”是完不成任务,这时就会再招聘一些“短工”一起来干活,这个最大线程数就是“长工”+“短工”的总人数,也就是招聘的人数不能超过 maximumPoolSize。
最大线程数 maximumPoolSize 的值不能小于核心线程数 corePoolSize,否则在程序运行时会报
IllegalArgumentException 非法参数异常
3.keepAliveTime
空闲线程存活时间,当线程池中没有任务时,会销毁一些线程,
销毁的线程数=maximumPoolSize(最大线程数)- corePoolSize(核心线程数
当大户人家比较忙的时候就会雇佣一些“短工”来干活,但等干完活之后,不忙了,就会将这些“短工”辞退掉,而 keepAliveTime 就是用来描述没活之后,短工可以在大户人家待的(最长)时间
4.TimeUnit
时间单位:空闲线程存活时间的描述单位
5.BlockingQueue
阻塞队列:线程池存放任务的队列,用来存储线程池的所有待执行任务。
它可以设置以下几个值:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
6.ThreadFactory
线程工厂:线程池创建线程时调用的工厂方法,通过此方法可以设置线程的优先级、线程命名规则以及线程类型(用户线
程还是守护线程)等。
7.RejectedExecutionHandler
拒绝策略:当线程池的任务超出线程池队列可以存储的最大值之后,执行的策略.
默认的拒绝策略有以下 4 种:
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
线程池的默认策略是 AbortPolicy 拒绝并抛出异常
解决死锁—>同步中使用 "生产者-消费者"思想
出现死锁原因:
多个线程加入同步锁之后,线程互相之间持有对方的锁,每一个线程必须让对方释放锁之后,才能去使用同步中的内容,导致线程之间出现互相等待!(访问的资源不是同一个资源)
wait()+notify():这个两个必须在synchronzied使用(同步代码块或者同步方法) 使用"信号灯法"来解决死锁问题
2.面试题: sleep和wait方法区别?
1)使用区别:
sleep()单独使用,里面传入超时时间,
wait()不能单独用,必须在同步代码块/同步方法中使用,否则,就会出现 java.lang.IllegalMonitorStateException
2)来源不同
sleep(long time)线程睡眠,来源于Thread类中,wait()来源于Object类中
3)导致线程状态不同
sleep(long time)参数出入的超时时间,时间到达,线程睡眠接收,线程会继续执行,在休眠过程中,
线程处于的状态是TIMED_WAITTING(超时等待)
wait()
线程进入等待状态,如果没有锁对象调用notify()或则notifyAll(),线程状态就进入到WAITTING(死死等待)
4)是否主动唤醒
sleep(long time):时间到达之后,自动唤醒(主动唤醒),而wait(),被动唤醒,需要使用notify()或者notifyAll进行唤醒
5)是否会释放锁
sleep()方法不会去释放锁,wait()方法调用会立即释放锁
3.线程池
线程的好处:
1)提高了线程使用率
2)大大减少资源开销
4)可以通过一些参数(7大参数)进行线程池的调优,到达一些效果
弊端:
维护成本大
可以通过线程池---->
Executors工厂类提供很多静态方法
public static ExecutorService newFixedThreadPool(int nThreads):创建固定的可重用的线程数的线程池
ExecutorService子实现类=--- TreadPoolExecutor完成线程池初始化!
如果涉及到定期让线程进行活动(public static ScheduledExecutorService newScheduledThreadPool)
单例
单例设计模式---属于创建型设计模式(创建对象)
概念:
始终在内存中有且仅有一个当前类的实例!(有一个对象)
饿汉式
不会出现安全问题的单例设计模式
1)当前类是具体类
2)类一加载就创建当前类实例
3)构造私有化,对外隐藏,不能new实例
4)对外提供静态方法,返回值当前类本身
Java中的类Runtime类:标准的单例(饿汉式),和计算机的运行环境有关系
例子
package SinglePattern;
import java.io.IOException;
//饿汉式
class Student{
//静态实例:Student一加载,就创建当前类实例
private static Student s=new Student();
//对外不能new
private Student(){}
//对外提供静态的方法,返回值是当前类本身
public static Student getStudent(){
return s;
}
}
public class Test1 {
public static void main(String[] args) throws IOException {
Student s1=Student.getStudent();
Student s2=Student.getStudent();
System.out.println(s1==s2);//true
//创建Runtime实例:获取运行环境
Runtime runtime = Runtime.getRuntime();
//availableProcessors()获取cpu处理器的数量
System.out.println(runtime.availableProcessors());
//exec(String dos指令)
System.out.println(runtime.exec("calc"));//打开计算器指令
}
}
懒汉式
可能存在安全问题的一种单例模式
1)当前类是个具体类
2)当前类构造方法私有化
3)当前类的成员位置:声明当前类的一个静态变量
4)对外提供静态方法,返回值是当前类本身:需要判断当前变量是否为null
现实开发业务中:
存在懒加载(按需加载)
一个用户有多个账户: 一对多 (从用户维度去看账户) ,查询用户的时候,开启懒加载方式(针对用户用哪个账户查询哪个账户)
账户的维度去看用户:一个账户从属于某个用户的 一对一
例子
package SinglePattern;
//懒汉式
class Teacher{
//声明静态变量,t的类型Teacher
private static Teacher t;
//构造方法私有化:
private Teacher(){}
public static synchronized Teacher getInstance(){
if(t==null){
t=new Teacher();
}
return t;
}
}
public class Test2 {
public static void main(String[] args) {
Teacher t1 = Teacher.getInstance();
Teacher t2 = Teacher.getInstance();
System.out.println(t2==t1);//tre
}
}
File
java.io.File--->
表示文件或者文件夹(目录)一种抽象路径形式
构造方法:
File(File parent, String child)从父抽象路径名和子路径名字符串创建新的 File实例。
File(String pathname)通过将给定的路径名字符串转换
File(String parent, String child) 父路径名字符串和子路径名字符串创建新的 File实例
例子
import java.io.File;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
//描述:D盘下的demo文件夹中的a.txt文件
//File(String parent, String child)
File file = new File("D:\\demo","a.txt") ;
System.out.println(file);
System.out.println("----------------------------------------") ;
//File(File parent, String child)
File file2 = new File("D:\\demo") ;
File file3 = new File(file2,"a.txt") ;
System.out.prin
tln(file3);
System.out.println("----------------------------------------") ;
//File(String pathname)(推荐)
File file4 = new File("D:\\EE_2302\\day26\\code\\a.txt") ;
System.out.println(file4.createNewFile()) ;//创建文件
System.out.println(file4);
}
}
File的基本功能以及特有功能:
public boolean createNewFile() throws IOException:创建文件
public boolean mkdir():创建文件夹
public boolean mkdirs():创建文件夹,当父目录不存在,自动创建
public boolean delete():删除文件夹或则文件路径,如果是文件夹,必须是空目录
public boolean exists():判断此路径名表示的文件或者目录是否存在
public boolean isDirectory():此抽象路径名表示的文件是否是目录
public boolean isFile():是否是文件
public boolean isHidden():是否隐藏
特有功能:高级功能
public String[] list():获取指定抽象路径名表示的文件或者目录的名称(返回字符串数组)
public File[] listFiles():获取指定路径名表示的文件或者目录的File数组
例子老师
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
public class FileDemo2 {
public static void main(String[] args) throws IOException {
//在创建D:\EE_2302\day26\code\demo文件夹
//表示它的路径
File file = new File("D:\\EE_2302\\day26\\code\\demo") ;
//public boolean mkdir():创建文件夹
System.out.println(file.mkdir());
System.out.println("--------------------------------") ;
//public boolean mkdirs():创建文件夹,当父目录不存在,自动创建
//创建aaa\bbb\ccc 多级目录(直接指定具体文件夹或者文件,相对路径创建)
File file2 = new File("aaa\\bbb\\ccc") ;
System.out.println(file2.mkdirs());
System.out.println("---------------------------------") ;
File file3 = new File("a.txt") ;
//创建文件
System.out.println(file3.createNewFile());
System.out.println("--------------------------------");
System.out.println(file3.exists());
System.out.println(file3.isDirectory());
System.out.println(file3.isFile());
System.out.println(file3.isHidden());
System.out.println(file3.canRead());
System.out.println(file3.canWrite());
System.out.println("-----------------------------------");
//public String[] list():获取指定抽象路径名表示的文件或者目录的名称(返回字符串数组)
//public File[] listFiles():获取指定路径名表示的文件或者目录的File数组
//描述D盘---所有的文件夹以及文件的名称 获取到
File myFile = new File("d:\\") ;
String[] strs = myFile.list() ;
if(strs!=null){
for (String str : strs) {
System.out.println(str);
}
//System.out.println(Arrays.toString(strs));
}
System.out.println("----------------------------------------------") ;
File[] files = myFile.listFiles();
//防止空指针
if(files!=null){
for (File f : files) {
//获取当前File对象所表示的文件夹以及文件的名称 --public String getName()
System.out.println(f.getName());
}
}
}
}
经典案例
获取".jpg"结尾的文件
方案1
import java.io.File;
public class FileTest {
public static void main(String[] args) {
//1)描述D盘---使用File对象
File file = new File("D:\\") ;
//2)通过 public File[] listFiles() :获取指定路径下所有的文件以及文件夹的File数组
File[] files = file.listFiles();
if(files!=null){
for (File f : files) {
//f:代表File对象
//判断是否是文件
if(f.isFile()){
//满足是文件
//获取文件名称,以".jpg"结尾
if(f.getName().endsWith(".jpg")){
//输出文件名称
System.out.println(f.getName());
}
}
}
}
}
}
方案2
package IO_File;
import java.io.File;
public class FilenameFilter {
public static void main(String[] args) {
File file = new File("D:\\个人东西");
String[] list = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
File file1 = new File(dir, name);
boolean file2 = file1.isFile();
boolean file3 = file1.getName().endsWith("jpg");
return file2&&file3;
}
});
if(list!=null){
for(String list1:list){
System.out.println(list1);
}
}
}
}
方案3(方案2改良版)
package IO_File;
import java.io.File;
public class FilenameFilter {
public static void main(String[] args) {
File file = new File("D:\\个人东西");
String[] list = file.list(new java.io.FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
File file1 = new File(dir, name);
return file1.isFile() && file1.getName().endsWith(".jpg");
}
});
if(list!=null){
for(String list1:list){
System.out.println(list1);
}
}
}
}
IO流
I:Input
O : Output
流的定义:流是指一连串流动的字符。以先进先出方式发送信息的通道。
字节流是 8 位通用字节流,字符流是16位Unicode字符流
字节流
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
字符流
java.io.Writer 字符输出流
java.io.Reader 字符输入流
出写入读
都是抽象类。
1.**文件**(File)
java.io.FileInputStream
java.io.FileOutputStream
java.io.FileReader
java.io.FileWriter
2.**转换流**:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
3.**缓冲流**
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
4.**数据流**
java.io.BufferedOutputStream
java.io.DataOutputStream
5.**标准输出流**
java.io.PrintWriter
java.io.PrintStream
6.**对象专属流**
java.io.ObjectInputStream
java.io.ObjectOutputStream
7.**File文件类**
java.io.File
字节输出流
OutputStream--抽象的 ---
具体的子类针对文件操作:FileOutputStream
步骤
1)创建字节输出流对象
2)写数据
3)释放资源
基本功能
//void write(byte[] b):写字节数组
//void write(byte[] b, int off, int len):写字节数组一部分
//abstract void write(int b) 写一个字节
//释放资源:将流对象指向的文件地址的系统资源进行释放
fos.close() ;
例子
package FileOutputStreamDemo;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test4 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("fos.txt");
//写数据
//abstract void write(int b) 写一个字节
fos.write(100);
//void write(byte[] b):写字节数组
byte[] bytes = {98,99,100,101,102,103,104} ;
fos.write(bytes);
//void write(byte[] b, int off, int len):写字节数组一部分
fos.write(bytes, 0, 3);
//释放资源 将流对象指向的文件地址的系统资源进行释放
fos.close();
}
}
效果:
dbcdefghbcd
字节数追加
字节输出流:
FileOutputStream实现文件的字节数追加
public FileOutputStream(String name,
boolean append)
throws FileNotFoundException
第二个参数是true:自动追加内容
IO流写数据,实现换行效果 widows系统 "\r\n"
例子
package FileOutputStreamDemo;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test5 {
public static void main(String[] args) throws IOException {
FileOutputStream ttx = new FileOutputStream("ttx.txt");
ttx.write("hello".getBytes());
System.out.println(ttx);
for(int x=0;x<10;x++){
ttx.write(("world"+x).getBytes());
ttx.write("\r\n".getBytes());
}
ttx.close();
}
}
效果:
helloworld0
world1
world2
world3
world4
world5
world6
world7
world8
world9
字节输入流
InputStream:字节输入流--->抽象类--->具体的子类FileInputStream:文件字节输入流
1)创建文件字节输入流对象FileInputStream(String name) :读取指定的文件 name:文件地址
2)读数据
继承它父类的方法:public abstract int read()throws IOException:一次读取一个字节
public int read(byte[] b) throws IOException:一次读取一个字节数组
需求:
使用字节输入流将当前项目下的"fis.txt"文件内容读取出来打印在控制台上
一次读取一个字节的方式:
针对中文---会出现乱码 (char)字节数,平台默认编码格式utf-8:一个中文对应三个字节
英文字母和中文-拼接一块,一个英文字母对应一个字节,这个时候拼接不上,导致文乱码---Java提供"字符流",解决乱码
老师例子
package com.qf.io_04_fileinputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* InputStream:字节输入流--->抽象类--->具体的子类FileInputStream:文件字节输入流
* 1)创建文件字节输入流对象FileInputStream(String name) :读取指定的文件 name:文件地址
* 2)读数据
* 继承它父类的方法:public abstract int read()throws IOException:一次读取一个字节
* public int read(byte[] b) throws IOException:一次读取一个字节数组
*
*
* 需求:
* 使用字节输入流将当前项目下的"fis.txt"文件内容读取出来打印在控制台上
*
* 一次读取一个字节的方式:
* 针对中文---会出现乱码 (char)字节数,平台默认编码格式utf-8:一个中文对应三个字节
* 英文字母和中文-拼接一块,一个英文字母对应一个字节,这个时候拼接不上,导致文乱码---Java提供"字符流",解决乱码
*/
public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null ;
try {
//创建文件字节输入流对象FileInputStream(String name)
//fis = new FileInputStream("fis.txt") ;
//读取当前项目下的FileOutputStreamDemo3.java文件,把它内容打印控制台上
fis = new FileInputStream("FileOutputStreamDemo3.java") ;
//public abstract int read()throws IOException:一次读取一个字节
//返回值:读取的实际字节数
//第一次读取
/*int by = fis.read() ;
System.out.println(by);
System.out.println((char)by);
System.out.println("---------------------------------------------") ;
//第二次读取
by = fis.read() ;
System.out.println(by) ;
System.out.println((char)by);
System.out.println("---------------------------------------------") ;
//第三次读取
by = fis.read();
System.out.println(by) ;
System.out.println((char)by);
System.out.println("---------------------------------------------") ;
//第四次读取
by = fis.read();
System.out.println(by) ;
System.out.println((char)by);
System.out.println("---------------------------------------------") ;
//第五次读取
by = fis.read();
System.out.println(by) ; //-1:流对象的内容已经到达末尾
System.out.println((char)by);*/
//文件内容未知---使用while循环
//循环条件中---赋值,判断,一块使用
//定义一个字节数:从0开始
int by = 0 ;
while((by=fis.read())!=-1){
//将字节数by---强转char字符
System.out.print((char)by) ;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
例子1(老办法)
package FileInputStreamDemo;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Test6 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
fis = new FileInputStream("fos.txt");
//第一次读取
int by=fis.read();
System.out.println(by);
System.out.println((char) by);
//第2次读取
by=fis.read();
System.out.println(by);
System.out.println((char) by);
//第3次读取
by=fis.read();
System.out.println(by);
System.out.println((char) by);
//第4次读取
by=fis.read();
System.out.println(by);
System.out.println((char) by);
//第5次读取
by=fis.read();
System.out.println(by);
System.out.println((char) by);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
100
d
98
b
99
c
100
d
101
e
fos.txt:dbcdefghbcd(内容是这个 读取这个txt文档)
例子2(改良的)一次输入一个字节
package FileInputStreamDemo;
import java.io.FileInputStream;
import java.io.IOException;
public class Test7 {
public static void main(String[] args) {
FileInputStream fis = null ;
try {
fis = new FileInputStream("Test6.java") ;
int by = 0 ;
while((by=fis.read())!=-1){
//将字节数by---强转char字符
System.out.print((char)by) ;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用字节输入流一次读取一个字节数组的方式:
public int read(byte[] b) throws IOException:一次读取一个字节数组
例子(老版本)
package FileInputStreamDemo;
import java.io.FileInputStream;
import java.io.IOException;
//字节流一次读取一个字节数组
public class Test8 {
public static void main(String[] args) throws IOException {
FileInputStream fia = new FileInputStream("fia.txt");
byte[] bytes=new byte[5];
// public int read(byte[] b)--->返回:读取的字节数组的长度
int len = fia.read(bytes);
System.out.println(len);//5
System.out.println("------");
//byte数组转成String
//每一次是从0开始读取实际字节数
System.out.println
(new String(bytes,0,len));//hello
System.out.println("------");
len=fia.read(bytes) ;
System.out.println(len);
System.out.println
//windows系统换行符号\r\n
(new String(bytes,0,len));//Wor "\r\n"占了两个字符
System.out.println("------");
len=fia.read(bytes) ;
System.out.println(len);
System.out.println (new String(bytes,0,len));//ld--//J
System.out.println("------");
len=fia.read(bytes) ;
System.out.println(len);
System.out.println (new String(bytes,0,len));
}
}
fia:
Hello
World
Java
输出结果
5
------
Hello
------
5
Wor
------
5
ld
J
------
5
ava
例子(最终效果)
package FileInputStreamDemo;
import java.io.FileInputStream;
import java.io.IOException;
//创建一个字节缓冲区(长度是1024或者1024的整数倍)
public class Test9 {
public static void main(String[] args) throws IOException {
FileInputStream fia = new FileInputStream("fia.txt");
byte[] bytes=new byte[1024];
//实际字节数从0开始
int len=0;
while((len=fia.read(bytes)) !=-1){//如果没有字节内容可读。到达末尾,返回值为-1
String s = new String(bytes, 0, len);
System.out.println(s);
/*Hello
World
Java
*/
}
}
}
结果:
Hello
World
Java
例子(最终效果)2
package FileInputStreamDemo;
import java.io.FileInputStream;
import java.io.IOException;
//创建一个字节缓冲区(长度是1024或者1024的整数倍)
public class Test9 {
public static void main(String[] args) throws IOException {
FileInputStream fia = new FileInputStream("Test6.java");
byte[] bytes=new byte[1024];
//实际字节数从0开始
int len=0;
while((len=fia.read(bytes)) !=-1){//如果没有字节内容可读。到达末尾,返回值为-1
String s = new String(bytes, 0, len);
System.out.println(s);
/*Hello
World
Java
*/
}
}
}
Test6内容是一段代码 读取这段代码输出的中文不是乱码
为什么呢 因为这次没有强转char而是读取的实际字节 字符串
Copy例子
public class CopyMp4 {
public static void main(String[] args) {
long start = System.currentTimeMillis() ;
//共耗时:222564毫秒
//copyMp4("myavi.mp4","D:\\EE_2302\\day26\\code\\copy.mp4");
//共耗时:356毫秒
copyMp4_2("myavi.mp4","D:\\EE_2302\\day26\\code\\copy.mp4");
long end = System.currentTimeMillis() ;
System.out.println("共耗时:"+(end-start)+"毫秒");
}
/**
* 使用字节输入流一读取一个字节的方式,进行文件复制
* @param srcFile 源文件地址
* @param destFile 目标文件地址
*/
public static void copyMp4(String srcFile, String destFile) {
FileInputStream fis = null ;
FileOutputStream fos = null ;
try {
//创建字节输入流操作srcFile
fis = new FileInputStream(srcFile) ;
//创建字节输出流对象操作destFile
fos = new FileOutputStream(destFile) ;
//一次读取一个字节
int by = 0 ;
while((by=fis.read())!=-1){
//使用字节输入流一次一个字节,使用fos输出流对象写一个字节
fos.write(by) ; //不需要强转,要的字节
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
一次读取一个字节的方式 --代码体现
//读写复制操作
//D:\\a.txt文件---复制到 当前项目下的E:\\b.txt文件中
//创建字节输入流对象--FileInputStream
FileInputStream fis = new FileInputStream("d:\\a.txt") ;
//字节输出流对象
FileOutputStream fos = new FileOutputStream("e:\\b.txt") ;
int by = 0 ;
while((by=fis.read())!=-1){
//读一个字节,通过fos流对象写出到b.txt文件中
fos.write(by) ;
}
//释放资源
fos.close() ;
fis.close() ;
一次读取一个字节数组的方式–代码体现
//读写复制操作
//D:\\a.txt文件---复制到 当前项目下的E:\\b.txt文件中
//创建字节输入流对象--FileInputStream
FileInputStream fis = new FileInputStream("d:\\a.txt") ;
//字节输出流对象
FileOutputStream fos = new FileOutputStream("e:\\b.txt") ;
//字节缓冲区
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=fis.read(bytes))!=-1){
//给fos流对象写入(每次从0开始)实际字节数
fos.write(bytes,0,len) ;
}
//释放资源
fos.close();
fis.close() ;
同步方法的锁对象和静态同步方法的锁对象?
非静态的同步方法---锁对象---this
静态的同步方法锁对象---锁对象---类名.class
递归例子
package com.qf.digui_01;
import java.io.File;
/**
* 需求:删除某个磁盘上的带内容的目录
* "D:\EE_2302\day27\code\demo"
*
* 分析:
* 1) 描述磁盘上的路径
* 2)定义一个方法:deleteDirectory(File f):递归删除文件夹的方法
* 3)方法里面:获取目录中的所有的文件夹以及文件的File数组
* File[] listFiles()
* 3.1)File数组不为null
* 3.2)遍历File数组,获取到File对象
* 3.3)File对象的路径形式是否是"文件夹"
* 满足条件---回到2)
* 否则,是文件
* 删除的同时获取,这个文件的父类名称是什么,然后直接删除--
*/
public class DiGuiTest {
public static void main(String[] args) {
//描述磁盘上的路径
File srcFile = new File("D:\\EE_2302\\day27\\code\\demo") ;
//定义递归删除带内容的目录的方法
deleteDirectory(srcFile) ;
}
public static void deleteDirectory(File destFile) { //传入的的路径
//获取目标文件夹 中所有的文件以及文件夹的File数组
File[] files = destFile.listFiles();
if(files!=null){
for(File file:files){
//获取每一个file对象
//去判断
if(file.isDirectory()){
//是文件夹
//调用递归删除的方法
deleteDirectory(file);
}else {
//是文件,删除的同时获取它的父目录的名称
//public File getParentFile()
//System.out.println(file.getParentFile().getName()+"目录名称:"+"---->"+file.delete());
System.out.println(file.getName()+"目录名称:"+"---->"+file.delete());
}
}
System.out.println(destFile.getName()+"----"+destFile.delete());
}
}
}
字符流
字符流:
字符输出流:Writer(抽象类)--->
具体的子类 OutputStreamWriter:字符转换输出流,"可以将字节输出流---转换---字符输出流"
构造方法:
public OutputStreamWriter(OutputStream out):使用平台默认字符集进行编码--输出数据
public OutputStreamWriter(OutputStream out,String charsetName):使用指定的字符集进行编码---输出数据
写数据:
void write(char[] cbuf) :写入字符数组
void write(char[] cbuf,int off,int len ) :写入字符数组的一部分
void write(int c) :写一个字符
void write(String str) :字符串
void write(String str,int off,int len):写入字符串的一部分
例子
public class OutputStreamWriterDemo {
public static void main(String[] args) throws Exception {
//创建一个字符转换输出流对象
//utf-8:一个中文对应三个字节
/* OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("osw.txt"),"utf-8") ;*/
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("osw.txt"));
//写字符串
osw.write("helloworld");
osw.write("高圆圆");
osw.write(97) ;//写入字符,传入参数int--->ASCII码表对应的字符值
//释放资源
osw.close();
}
}
字节缓冲流
ava之IO流提供了字节缓冲流:让读取的速度更快一些,提供对应的类
字节缓冲输入流/字节缓冲输出流 (高效的字节流)---仅仅是内部提供缓冲区字节数组长度:8192长度,
文件的读写复制还的依赖于基本字节流(InputStream/OutputStream)
BufferedInputStream(InputStream in):字节缓冲输入流
BufferedOutputStream(OutputStream out):字节缓冲输出流
使用字节缓冲流来完成读写复制---看他们执行效率
例子
public class CopyFileTest {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
copyFile2("myavi.mp4","D:\\EE_2302\\day27\\code\\copy.mp4") ;
long end = System.currentTimeMillis() ;
System.out.println("共耗时:"+(end-start)+"毫秒");
}
/**
* 使用字节缓冲输入流一次读取一个字节数组
* @param srcFile 源文件地址
* @param destFile 目标文件地址
*/
public static void copyFile2(String srcFile, String destFile) throws IOException {
//BufferedInputStream(InputStream in):字节缓冲输入流
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
// BufferedOutputStream(OutputStream out):字节缓冲输出流
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;//实际字节数
while((len=bis.read(bytes))!=-1){
//每次从0开始写入实际字节数
bos.write(bytes,0,len);
//使用字节缓冲输出流的时候,防止一些字节数没有交给OutputStream字节输出流
//强制刷新缓冲输出流(尤其图片,图片文件带有缓冲数据:可能字节数没有写入到底层输出流中,导致图片文件缺失)
bos.flush() ;
}
//释放资源
bos.close();
bis.close();
}
/**
* 使用字节缓冲输入流一次读取一个字节
* @param srcFile 源文件地址
* @param destFile 目标文件地址
*/
public static void copyFile(String srcFile, String destFile) throws IOException {
//BufferedInputStream(InputStream in):字节缓冲输入流
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
// BufferedOutputStream(OutputStream out):字节缓冲输出流
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;
//一次读取一个字节
int by = 0 ;
while((by=bis.read())!=-1){
//读取一个字节,写出一个字节
bos.write(by) ;
}
//释放资源
bos.close();
bis.close();
}
}
字符输入流
--->Reader(抽象类)--->
具体的子类:字符转换输入流InputStreamReader --- 将字节输入流---->转换--->字符输入流
public InputStreamReader(InputStream in):使用平台默认字符集进行解码---读取数据
public InputStreamReader(InputStream in,String charsetName)
使用指定字符集解码---读取数据
读数据:
int read() :一次读取一个字符 --->返回结果:实际的字符数
int read(char[] cbuf) :一次读取一个字符数组
int read(char[] cbuf,int off, int len):读取字符数组的一部分
例子
public class InputStreamReaderDemo {
public static void main(String[] args) throws Exception {
//创建字符转换输入流对象
//解码:gbk(中国编码表): 一个中文对应两个字节
/* InputStreamReader isr = new InputStreamReader(
new FileInputStream("osw.txt"),"gbk") ;*/
//public InputStreamReader(InputStream in)
InputStreamReader isr = new InputStreamReader(
new FileInputStream("osw.txt")) ;
//读数据
//一次读取一个字符
int by = 0 ;//实际字符数
while((by=isr.read())!=-1){
//展示控制台上 读取的时候将文件内容的里面--->int类型----->转换成字符
System.out.print((char)by);
}
}
}
字符输出流
字符输出流:Writer(抽象类)--->
具体的子类 OutputStreamWriter:字符转换输出流,"可以将字节输出流---转换---字符输出流"
构造方法:
public OutputStreamWriter(OutputStream out):使用平台默认字符集进行编码--输出数据
public OutputStreamWriter(OutputStream out,String charsetName):
使用指定的字符集进行编码---输出数据
写数据:
void write(char[] cbuf) :写入字符数组
void write(char[] cbuf,int off,int len ) :写入字符数组的一部分
void write(int c) :写一个字符
void write(String str) :字符串
void write(String str,int off,int len):写入字符串的一部分
例子
public class OutputStreamWriterDemo {
public static void main(String[] args) throws Exception {
//创建一个字符转换输出流对象
//utf-8:一个中文对应三个字节
/* OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("osw.txt"),"utf-8") ;*/
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("osw.txt"));
//写字符串
osw.write("helloworld");
osw.write("高圆圆");
osw.write(97) ;//写入字符,传入参数int--->ASCII码表对应的字符值
//释放资源
osw.close();
}
}
键盘录入的第二种方式
0)main方法里面:早期录入 String[] args
1)Scanner(InputStream in)---->Scanner sc = new Scanner(System.in) ;
2)new BufferedReader(new InputStreamReader(System.in))
--->InputStreamReader(InputStream in)
例子
package JianPanLuRu2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Test3 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请您输入一个数据:");
String line = br.readLine();
System.out.println("您输入的内容是:"+line);
//数字字符串--->
// int num = Integer.parseInt(line);//输出数字字符串
// System.out.println("您输入的内容是:"+num);//输出数字字符串
}
}
BufferedReader字符缓冲输入流
字符缓冲输入流—可以读取一行内容
构造方法:
public BufferedReader(Reader in):提供默认缓冲区大小的字符缓冲输入流 (8192个长度)
这个流如果直接操作文件--->参数里面使用FileReader
public String readLine()throws IOException
特有功能:
public String readLine()throws IOException:一次读取一行
返回值是读取到这一行的内容,当流已经到达末尾,则返回null
键盘录入
1)Scanner(InputStream in)---->Scanner sc = new Scanner(System.in) ;
2)new BufferedReader(new InputStreamReader(System.in))
--->InputStreamReader(InputStream in)
例子
package BufferredReaderDemo;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Test1 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("新建 文本文档.txt"));
String line=null;
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
新建 文本文档.txt:
dajiahao
woshi
yisanbao
BufferedWriter字符缓冲输出流
BufferedWriter构造方法
public BufferedWriter(Writer out):提供默认缓冲区大小的字符缓冲输出流,默认缓冲区足够大 8192长度
写的功能:
write(字符/字符数组/字符串...)
特有功能:
不需要在使用"\r\n"换行符号, public void newLine()写入行的分隔符
例子
package BufferredWriterDemo;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Test2 {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("新建 文本文档.txt"));
for(int x=0;x<=10;x++){
bw.write("哈哈"+x);
bw.newLine();//换行
bw.flush();//刷新
}
bw.close();
}
}
新建 文本文档.txt:
哈哈0
哈哈1
哈哈2
哈哈3
哈哈4
哈哈5
哈哈6
哈哈7
哈哈8
哈哈9
哈哈10
Properties属性集合列表
java.util.Properties:属性集合列表---extends Hashtabl<K,V>-->
本质是Map<K,V>集合
添加功能:put(K key,V value)
遍历方式:Set<K> keySet()
遍历键-->通过键获取值 V get(K key)
自己的特有功能:
java.util.Properties:没有泛型,键和值都只能String---->
作用:用来读取配置文件xx .properties配置文件(src下面)
构造方法:
public Properties()
添加属性列表的键和值:
public Object setProperty(String key,String value)
特有的遍历方式:
public Set<String> stringPropertyNames():获取属性列表中的所有的键(属性名称)
public String getProperty(String key):通过属性列表中的属性名称获取对应的值
例子
public class PropertiesDemo {
public static void main(String[] args) {
//创建属性集合列表
Properties prop = new Properties() ;
System.out.println(prop);
//public Object setProperty(String key,String value)
prop.setProperty("张三","30") ;
prop.setProperty("李四","25") ;
prop.setProperty("王五","35") ;
System.out.println(prop);
//遍历
// public Set<String> stringPropertyNames():获取属性列表中的所有的键(属性名称)
// public String getProperty(String key):通过属性列表中的属性名称获取对应的值
Set<String> keySet = prop.stringPropertyNames();
for(String key:keySet){
String value = prop.getProperty(key);
System.out.println(key+"---"+value);
}
}
}
将文件的内容加载到属性集合列表中
java.util.Properties可以保存到流中或从流中加载
*
* 将文件的内容加载到属性集合列表中:
* public void load(InputStream inStream/Reader)
*
* 将属性列表中的内容保存到指定文件中
* 第二个参数:描述属性列表
* public void store(OutputStream out/Writer,String comments)
*
例子
public class PropertiesDemo2 {
public static void main(String[] args) throws IOException {
// myLoad();
myStore() ;
}
//写
private static void myStore() throws IOException {
//将属性列表中的内容---保存指定文件中
Properties prop = new Properties() ;
//存储用户名和密码
prop.setProperty("文章","123") ;
prop.setProperty("高圆圆","123456") ;
prop.setProperty("admin","admin") ;
System.out.println(prop) ;
//public void store(OutputStream out/Writer,String comments)
prop.store(new FileWriter("D:\\EE_2302\\day27\\code\\day27\\src\\my.properties"),"name'list") ;
}
private static void myLoad() throws IOException {
//创建属性集合列表
Properties prop = new Properties() ;
System.out.println(prop);
//如何读取src下面的xx.properties文件
//1)获取当前类的字节码文件对象--->类名.class 属性
// Class c = PropertiesDemo2.class ;
//2)通过获取到的字节码文件对象获取 当前类的加载器
// public ClassLoader getClassLoader()
// ClassLoader classLoader = c.getClassLoader();
//3)ClassLoader---类加载器--->读取到src下面配置文件的内容--->存储到字节输入流中
//public InputStream getResourceAsStream(String name) 返回用于读取指定资源的输入流
//InputStream inputStream = classLoader.getResourceAsStream("name.properties");
//一步走
InputStream inputStream = PropertiesDemo2.class.getClassLoader().
getResourceAsStream("name.properties");
//public void load(InputStream inStream/Reader)
prop.load(inputStream);
System.out.println(prop);
}
}
文本文件—使用字符流去操作
字符转换流:不能直接操作文件地址,借助于底层流(字节流),字符转换流提供它的子类 "转换流的便捷类"
InputStreamReader读源文件 ----->FileReader(String pathName)
OutputStreamWriter写目标文件---->FileWriter(String pathName)
例子
public class CopyFileTest {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("D:\\EE_2302\\day27\\code\\DiGuiTest.java");
FileWriter fw = new FileWriter("copy.java") ;
int by = 0 ;
while((by=fr.read())!=-1){
fw.write(by) ;
fw.flush();
}
fw.close();
fr.close();
}
}
网络三要素:
1)IP地址:InetAddress 网络中设备的标识,不易记忆,可用主机名
A类->第一个号段是网络号段,后面的是主机号段(国家政府机关)
B类->前面两个号段是网络号段,后面的是主机号段(校园网/公司内网等)
C类->私人地址,前面的三个都是网络号段,后面是主机号段 "点分十进法"
2)端口号 用于标识进程的逻辑地址,不同进程的标识
范围:0-65535 其中的0-1024是保留端口号,自己指定的端口号是1025-65535(不要和系统的端口号冲突否则报错->Address already in use :BindException:地址值被占用)
3)传输协议通讯的规则 常见协议:TCP,UDP
TCP/UDP 底层网络协议
UDP:
1)不可靠连接,不需要建立连接通道,以"数据报包"方式进行数据传输,不安全
2)不同步,执行效率相对TCP来说高
3)发送数据大小有限制!(字节流形式)
TCP:
1)可靠连接,需要建立连接通道。
2)同步的,执行效率低,但是安全性能较UDP高,
3)发送数据大小无限制(字节流形式)
类InetAddress
对IP地址的获取和操作
获取任意主机:
getByName()
主机名:
getHostName()
主机Ip地址:
getHostAddress()
例子
package AdressDemo;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class AdressDemo {
public static void main(String[] args) throws UnknownHostException {
InetAddress address=InetAddress.getByName("凤梨罐头");
String ip = address.getHostAddress();
System.out.println(ip);//10.35.165.80
String name = address.getHostName();
System.out.println(name);//凤梨罐头
}
}
网络编程(Socket编程)
发送端/客户端
接收端/服务器端
不管UDP还是TCP都要满足以下条件
两端都必须要有Socket对象(”套接字“);
发送端或者客户端必须绑定主机IP以及端口
接收端或者服务器端必须指定端口号
1.使用UDP协议发送数据
1)创建发送端的socket对象
2)常见数据报包(DatagramSocket) 包括数据内容 ip 端口名
3)使用发送端的socket发送数据报包
4)释放资源close
接收端不能开多次 只能开一次 因为端口已经’'被占用了
UDP发送
package com.qf.udp_02;
import java.net.*;
/**
* 使用UDP协议发送数据
* 1)创建发送端的socket对象
* 2)创建"数据报包"--里面包括:数据内容,ip地址,端口号
* 3)使用发送端的Socket对象发送"数据报包"
* 4)释放资源
*/
public class UdpSend {
public static void main(String[] args) throws Exception {
//1)创建发送端的socket对象 ---java.net.DatagramSocket:此类表示用于发送和接收数据报数据包的套接字
//public DatagramSocket()throws SocketException
DatagramSocket ds = new DatagramSocket() ;
//2)创建"数据报包"--里面包括:数据内容,ip地址,端口号
//java.net DatagramPacket
//参数1:分组数据(字节数组)
//参数2:包的长度
//参数3:ip地址对象
//参数4:端口号
//public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
byte[] bytes = "hello,udp我来了".getBytes() ;
int length = bytes.length ;
InetAddress inetAddress = InetAddress.getByName("10.35.165.17") ;
int port = 6666 ;
DatagramPacket dp = new DatagramPacket(bytes,length,inetAddress,port) ;
//使用发送端的Socket对象发送"数据报包"
//public void send(DatagramPacket p)throws IOException从此套接字发送数据报包
ds.send(dp) ;
//释放资源
ds.close();
}
}
UDP接受
package com.qf.udp_02;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* Udp接收端
* 1)创建接收端的Socket对象
* 2)创建一个"数据报包"在里面自定义缓冲区:将发送端是数据存储到缓冲区中(字节数组,长度:1024或者1024的整数倍)
* 3)使用2)接收容器(缓冲区),接收发送端的数据
* 4)解析接收容器中的实际内容
* 5)展示数据即可
* 6)释放接收端的资源
*
* 注意:
* 接收端开启一次就可以;否则
* Exception in thread "main" java.net.BindException: Address already in use: Cannot bind
*/
public class UdpReceive {
public static void main(String[] args) throws Exception {
//1)创建接收端的Socket对象
//public DatagramSocket(int port)throws SocketException
DatagramSocket ds = new DatagramSocket(6666) ;
//2)创建一个"数据报包"在里面自定义缓冲区:将发送端是数据存储到缓冲区中(字节数组,长度:1024或者1024的整数倍)
//参数1:用于保存传入"数据报"的缓冲区。
//参数2:要读取的字节数
//public DatagramPacket(byte[] buf,int length)
byte[] buffer = new byte[1024] ;
DatagramPacket dp = new DatagramPacket(buffer,buffer.length) ;
//3)使用2)接收容器(缓冲区),接收发送端的数据
//public void receive(DatagramPacket p)throws IOException从此套接字接收数据报包
ds.receive(dp) ;
//4)解析接收容器中的实际内容
//public byte[] getData() :解析数据报包中DatagramPacket缓冲的实际的字节数组
byte[] bytes = dp.getData();
//public int getLength():解析数据报包中DatagramPacket中的实际字节数组长度
int length = dp.getLength();
//展示数据
String message = new String(bytes,0,length) ;
//谁发送的数据---获取ip地址字符串形式
//数据报包DatagramPacket--->public InetAddress getAddress()
//InetAddress:ip地址--->String getHostAddress()
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from is--->"+ip+",data is--->"+message) ;
//释放资源
ds.close();
}
}
2.键盘录入版本
UDP发送
package UDPDemo2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class TestSend {
public static void main(String[] args) {
DatagramSocket ds=null;
try {
ds= new DatagramSocket();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入对话");
String line=null;
while((line=br.readLine())!=null) {
DatagramPacket dp = new DatagramPacket(line.getBytes(), line.getBytes().length, InetAddress.getByName("10.35.165.80"), 1234);
ds.send(dp);
}
} catch (SocketException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally {
if(ds!=null){
ds.close();
}
}
}
}
UDP接受
package UDPDemo2;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class TestReceive {
public static void main(String[] args) {
try {
DatagramSocket ds = new DatagramSocket(1234);
while(true){
byte[] buffer=new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp);
String message = new String(dp.getData(), 0, dp.getLength());
String IP=dp.getAddress().getHostAddress();
System.out.println("来自"+IP+"IP的信息:"+message);
}
} catch (SocketException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally {
}
}
}
线程版本写在同一个对话框的
发送端资源类
package com.qf.udp_04;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 发送端的资源类
*/
public class SendTread implements Runnable{
private DatagramSocket ds ;
public SendTread(DatagramSocket ds){
this.ds = ds ;
}
@Override
public void run() {
try {
//键盘录入数据--->BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)) ;
String line = null ;
while((line=br.readLine())!=null){
//数据报包:将数据发送接收端
DatagramPacket dp = new DatagramPacket(
line.getBytes(),
line.getBytes().length,
InetAddress.getByName("10.35.165.17"),
10086) ;
//发送数据
ds.send(dp) ;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ds.close();
}
}
}
接收端资源类
package com.qf.udp_04;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 接收端的资源类
*/
public class ReceiveThread implements Runnable{
private DatagramSocket ds ;
public ReceiveThread(DatagramSocket ds){
this.ds = ds ;
}
@Override
public void run() {
try {
//不断的展示数据
while(true){
//接收数据:接收容器
byte[] buffer = new byte[1024] ;
DatagramPacket dp = new DatagramPacket(buffer,buffer.length) ;
//接收
ds.receive(dp) ;
//解析缓冲区(接收容器)的数据
String message = new String(dp.getData(),0,dp.getLength()) ;
//获取ip地址
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from --->"+ip+",content is--->"+message);
}
} catch (IOException e) {
e.printStackTrace();
}
//接收端不需要释放资源,一直开启状态
}
}
测试类
package com.qf.udp_04;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* 需要优化:
* 发送端不断键盘录入数据,接收端不断展示数据,在一个窗口中聊天!
* 使用多线程的方式:开启两条线程,一条线程:发送端发送数据
* 另一条线程:接收端不断接收数据,展示数据
*/
public class ChatRoom {
public static void main(String[] args) {
try {
//创建发送端Socket
DatagramSocket sendDs = new DatagramSocket() ;
//创建接收端的Socket
DatagramSocket receiveDs = new DatagramSocket(10086) ;
//创建发送端的线程所在的资源类对象
SendTread st = new SendTread(sendDs) ;
//创建接收端的线程所在资源类对象
ReceiveThread rt = new ReceiveThread(receiveDs) ;
//创建线程
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(rt) ;
//启动线程
t1.start();
t2.start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}