Java基础进阶
异常
概述
异常就是程序出现了不正常的情况
具体分为:Throwable—>(Error Exception);Exception—>(RuntimeException 非RuntimeException)
Throwable类是Java语言中所有错误和异常的祖宗类;(上面还有Object类)
Throwable类的成员方法
方法名
说明
public String getMessage()
返回此throwable的详细消息字符串
public String toStrinng()
返回此可抛出的简短描述
public String printStackTrace()
把异常的错误信息输出在控制台
错误解释
Error:严重问题,不需要处理
Exception:称为异常类,它表示程序本身可以处理的问题
RuntimeException:在编译期不检查,出现问题后在控制台显示,需要我们回来修改代码
非RuntimeException:编译期就必须处理,否则程序不能通过编译,不能运行
编译时异常和运行时异常的区别
Java中的异常被分为两大类:编译时异常和运行时异常,也被称为受检异常和非受检异常
- 编译时异常:必须显示处理,否则程序就会发生错误,无法通过编译
- 运行时异常:无需显示处理,因为回头还得修改代码,也可以和编译时异常一样处理
代码演示:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args) throws ParseException {
System.out.println("开始");
method();
method1();
System.out.println("结束");
}
//编译时异常
public static void method() {
String s = "2020-07-28";
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d = sdf.parse(s);//parse有异常;parse会有红色波浪线标识,系统判断你给出的形式可能不对应,可能会有异常,但不一定真的有异常;但因为是编译时异常,所以需要手动解决一下
System.out.println(d);
} catch (ParseException e) {
e.printStackTrace();
}
}
//运行时异常
public static void method1() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);//编译不报错,但运行报错;需要手动解决
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
JVM的默认处理方案
- 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
- 停止程序运行
异常处理
两种方案:
- try …catch…
- throws
方式:选中异常单词,按Alt+Enter键就会有选项
try …catch…finally…
finally: 在异常处理时提供finally块来执行所有清楚操作,比如谁IO流中的是释放资源
- 特点:被finally控制的语句一定会执行,除非JVM退出
格式:
try{
可能出现异常的代码;
} catch(异常类名 变量名){//异常类名要和出现的异常匹配
异常的处理代码;
}finally{
执行所有清除操作
}
执行流程:
- 程序从try里面的代码开始执行
- 出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统
- 当Java运行时系统接收到异常对象时,回到catch中去找匹配的异常类,找到后进行异常的处理
- 执行完毕后,程序还可以继续往下执行
代码演示:
-
try …catch…
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class Test {
public static void main(String[] args) throws ParseException {
System.out.println(“开始”);
method1();
System.out.println(“结束”);
}public static void method1() { try { int[] arr = {1, 2, 3}; System.out.println(arr[3]);//new ArrayIndexOutOfBoundsException("xxx");这里会自动生成生成一个带参对象,与catch中的进行匹配 } catch (ArrayIndexOutOfBoundsException e) { System.out.println(e.getMessage());//输出:Index 3 out of bounds for length 3 System.out.println(e.toString()); //输出:java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 e.printStackTrace();//输出内容最多,所以常用这个方法 /*输出:java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 at Domo1.Test.method(Test.java:16) at Domo1.Test.main(Test.java:10)*/ //以上输出均输出了“开始”和“结束”;因为异常处理后,程序还可以继续往下执行 } }
}
-
try …catch…finally…
案例
字节流加异常处理:
public class Test {
public static void main(String[] args) {
//因为需要在finally里面执行,所以要在外面定义并初始化
FileOutputStream f = null;
try {
f = new FileOutputStream("src\f.txt");
f.write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//if判断保持程序健壮性
//如果路径不存在,则对象f就为null,所以finally执行的时候就是null调方法,即为空指针异常
if (f != null) {
try {
f.close();//NullPointerException:空指针异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
复制文件的异常处理:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
}
//标准异常处理:try…catch…finally
private static void method2() {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("f.txt");
fw = new FileWriter("f1.txt");
char[] chs = new char[1024];
int len;
while ((len = fr.read()) != -1) {
fw.write(chs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
throws
并不是所有的情况我们都有权限利用try…catch…进行异常的处理,这时候,Java提供了throws处理方案
格式:
throws 异常类名
说明:这个格式是跟在方法的括号后面的
- 使用throws方案时,会层层向上抛,如果每一个方法都放弃处理异常,直接通过声明抛出,最后异常会抛到main() 方法,如果main()方法也不处理,继续抛出给JVM,底层 的处理机制就是打印异常的跟踪栈信息,如同RuntimeException 处理方式 。
代码演示:
public class Test {
public static void main(String[] args) {
System.out.println("开始");
try {
method();//在这里处理了
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("结束");
}
public static void method() throws ParseException {//抛出
String s = "2020-07-28";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d = sdf.parse(s);//parse有异常
System.out.println(d);
}
}
throws和throw的区别
throws
throw
用在方法声明后面,跟的是异常类名
用在方法体内,跟的是异常对象名
表示抛出异常,由该方法的调用者来处理
表示抛出异常,由该方法体内的语句来处理
表示出现异常的一种可能性,并不一定会发生这些异常
执行throw一定抛出了某种异常
自定义异常
在实际开发中,有人为约束的某种内容,但Java本身不会提供这样特殊的异常处理方法,所以要自己编写异常类
- 定义检查性异常类,继承 Exception 类。
- 定义运行时异常类,继承 RuntimeException 类。
格式:
public class 异常类名 extends Exception {
无参构造
带参构造
}
代码演示:
//自定义异常类
public class ScoreException extends Exception {
//无参构造方法
public ScoreException() {
}
//带参构造方法
public ScoreException(String message) {
super(message);//把message传递给父类
}
}
//中间类
public class Teacher {
public void checkScore(int score) throws ScoreException {//继续向上抛出,因为ScoreException继承了Exception
if (score < 0 || score > 100) {
throw new ScoreException("你输入的方法不正确,需要在0-100范围内");//自定义异常使用时需要手动抛出
} else {
System.out.println("分数正常");
}
}
}
//Test类
import java.util.Scanner;
public class Test {
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();
}
}
}
异常介绍
并发修改异常
-
ConcurrentModificationException
-
原因:迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值与实际修改值不一致;简单描述就是:迭代器遍历元素的时候,通过集合是不能修改元素的,只能是:1. 迭代器迭代元素,迭代器修改元素 2. 集合遍历元素,集合修改元素(普通for)
-
解决方案:A:迭代器迭代元素,迭代器修改元素 B:集合遍历元素,集合修改元素(普通for)
import java.util.ArrayList;
import java.util.List;public class Test {
public static void main(String[] args) {
List list = new ArrayList<>();list.add("hello"); list.add("world"); list.add("java"); //遍历集合,得到每一个元素,看有没有“world”元素,如果有,就添加一个“javaee”元素 /* Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("world")) ; { list.add("javaee"); } } System.out.println(list);//ConcurrentModificationException异常;判断预期修改值与实际修改值不一致*/ //利用ListIterator迭代器,因为其中用add()方法 ListIterator<String> it = list.listIterator(); while(it.hasNext()){ String s = it.next(); if(s.equals("world")){ it.add("javaee");//此处源码中会把实际修改值赋值给判断预期修改值,所以不会出现并发修改异常 } } System.out.println(list);//输出:[hello, world, javaee, java];往比较的后面添加 for (int i = 0; i < list.size(); i++) { String s = list.get(i); if (s.equals("world")) { list.add("javaee"); } } System.out.println(list);//输出:[hello, world, java, javaee];往最后添加 //注意两者添加位置不一样 }
}
数据结构
数据结构是计算机存储、组织数据的方式、是指相互之间存在的一种或多种特定关系的数据元素的集合;通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
栈: 先进后出模型 队列:先进先出模型
数组: 一种查询快,增删慢的模型
- 查询数据通过索引定位,查询任意数据耗时相同,查询效率高
- 删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
- 添加数据时,添加位置后的每个数据后移,再添加元素,添加效率极低
链表: 一种查询慢,增删快的的模型(对比数组)
哈希表:
- JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组;JDK8之后,在长度比较长的时候,底层实现了优化
- 保证元素唯一:先比较哈希值,再比较内容
算法
递归
概述:以编程的角度来看,递归指的是方法定义中调用方法本身的现象
递归解决问题的思路:
- 把一个复杂的问题转化为一个与原问题相似的规模小的问题来求解
- 递归策略值需要少量的程序就可描述出解题过程所需要的多次重复计算
递归解决问题的两点:
- 递归出口:否则会出现内存溢出
- 递归规则:与原问题相似的规模较小的问题
案例
-
不死神兔
public class Test {
public static void main(String[] args) {
System.out.println(f(20));
}public static int f(int n) { if (n == 1 || n == 2) { return 1; } else { return f(n - 1) + f(n - 2); } }
}
-
递归求阶乘
public class Test {
public static void main(String[] args) {
System.out.println(jc(20));
}public static long jc(int n) { if (n == 1) { return 1; } else { return n * jc(n - 1); } }
}
集合进阶
集合体系结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0fZZbSQD-1630590832845)(C:Users***AppDataRoamingTypora ypora-user-imagesimage-20210730154130233.png)]
Collection集合
概述
Collection集合概述:
- Collection集合是单列集合的顶层接口,它表示一组对象,这些对象也被称为Collection的元素
- JDK不提供此接口的任何直接实现类,它提供更具体的子接口(如Set和List)实现
创建Collection集合对象:
- 多态的方式
- 具体实现类ArrayList
Collection集合常用方法
方法名
说明
boolean add(E e)
添加元素
boolean remove(Object o)
从集合汇总移除指定的元素
void clear()
清空集合中的元素
boolean contain(Object o)
判断集合中是否存在指定的元素
boolean idEmpty()
判断集合是否为空
int size()
集合的长度,也就是集合中元素的个数
代码演示:
import java.util.ArrayList;
import java.util.Collection;
public class Test {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();//由输出内容可以知道ArrayList集合重写了toString方法
//boolean add(E e);添加元素
c.add("hello");
c.add("world");
System.out.println(c.add("hello"));//输出:true;表示成功添加
System.out.println(c);//输出:[hello, world, hello]
//boolean remove(Object o);从集合中移除指定的元素
c.remove("world");
System.out.println(c.remove("hello"));//输出:true
System.out.println(c.remove("hhh"));//输出:false
System.out.println(c);//输出[hello]
//void clear();清空集合中的所有元素
c.clear();
System.out.println(c);//输出:[]
//boolean contain(Object o);判断集合中是否存在指定的元素
System.out.println(c.contains("hhh"));//输出:false
System.out.println(c.contains("hello"));//输出:true
//`boolean idEmpty()`;判断集合是否为空
System.out.println(c.isEmpty());//输出:false
//`int size()`;集合的长度,也就是集合中元素的个数
System.out.println(c.size());//输出:2
}
}
Collection集合的遍历
Iterator(是一个接口):迭代器,集合的专用遍历方式
- Iterator< E >iterator():返回此集合中元素的迭代器,通过集合的Iterator()方法得到
- 通过集合的Iterator()方法得到,所以我们说它是依赖于集合而存在的
Iterator中的常用方法:
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多的元素,则返回true
代码演示:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Test {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();//多态
c.add("hello");
c.add("world");
c.add("java");
//Iterator< E >iterator; 返回此集合中元素的迭代器,通过集合的Iterator()方法得到
Iterator<String> it = c.iterator();//多态
//E next():返回迭代中的下一个元素
System.out.println(it.next());//输出:hello;依次往下输出,不可超出元素存在个数
//boolean hasNext():如果迭代具有更多的元素,则返回true
//常用遍历形式:
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
Collection集合使用步骤
- 创建集合对象
- 添加元素到集合
- 遍历集合
- 通过集合对象获取迭代器对象
- 通过迭代器对象的hasNext()方法判断是否还有元素
- 通过迭代器对象的next()方法获取下一个元素
案例
-
Collection集合存储学生对象并遍历: 创建一个存储学生的对象的集合,存储3个对象,使用程序实现在控制台遍历该集合
//学生类
public 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; }
}
//操作
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class Demo {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList<>();
//创建学生对象
Student s1 = new Student(“勒布朗”, 37);
Student s2 = new Student(“浓眉”, 33);
Student s3 = new Student(“库里”, 33);
//把学生对象添加到集合
c.add(s1);
c.add(s2);
c.add(s3);
//遍历集合
Iterator it = c.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName() + “,” + s.getAge());
}
}
}
增强for循环
增强for:简化数组和Collection集合的遍历
- 实现Iterator接口的类允许其对象成为增强型for语句的目标
- 它是JDK1.5之后出现的,其内部原理是一个terator迭代器
增强for的格式
for(元素数据类型 变量名 : 数组或者Collection集合){
//此处使用变量即可,该变量就是元素
}
代码演示:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
//数组增强for循环
int[] arr = {1, 2, 3, 4, 5};
for (int i : arr) {
System.out.println(i);//输出:1 2 3 4 5
}
//字符串增强for循环
String[] arr1 = {"hello", "world", "java"};
for (String s : arr1) {
System.out.println(s);//输出:hello world java
}
//集合增强for循环
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
for (String s : list) {
System.out.println(s);//输出:hello world java
}
//内部是一个Iterator迭代器
for (String s : list) {
if (s.equals("world")) {
list.add("javaee");
}
}
System.out.println(list);//输出:ConcurrentModificationException,验证是
}
}
List集合
概述与特点
List集合概述:
- 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置;用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
List集合特点:
- 有序:存储和取出的元素顺序一致
- 可重复:存储的元素可以重复
List集合特有方法
相对于Collection集合而言
方法名
说明
void add(int index,element)
在此集合中的指定位置插入指定的元素
E remove(int index)
删除指定索引处的元素,返回被删除的元素
E set(int index,E element)
修改指定索引处的元素,返回被修改的元素
E get(int index)
返回指定索引处的元素
代码演示:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
//void add(int index,element);在此集合中的指定位置插入指定的元素
list.add(1, "勒布朗");//不可越界
System.out.println(list);//输出:[hello, 勒布朗, world, java]
//E remove(int index);删除指定索引处的元素,返回被删除的元素
list.remove(1);//不可越界
System.out.println(list);//输出:[hello, world, java]
System.out.println(list.remove(1));//输出;world;指定元素
//E set(int index,E element);修改指定索引处的元素,返回被修改的元素
list.set(1, "javaee");//不可越界
System.out.println(list);//输出:[hello, javaee]
System.out.println(list.set(1, "javaee"));//输出:javaee;修改的元素
//E get(int index);返回指定索引处的元素
list.get(1);
System.out.println(list);//输出:[hello, world, java];不是这种用法
System.out.println(list.get(1));//输出:world;不可越界
}
}
List集合遍历
代码演示:
//for循环遍历
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
列表迭代器ListIterator
- 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器;继承自 Iterator迭代器
- 用于允许程序员在任一方向上遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置
ListIterator中的常用方法:
- E next():返回迭代中的下一个元素(继承自Iterator)
- boolean hasNext():如果迭代具有更多的元素,则返回true(继承自Iterator)
- E previous():返回列表中的上一个元素
- boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多的元素,则返回true
- void add(E e):将指定的元素插入列表
代码演示:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
//通过List集合的listIterator()方法得到
ListIterator<String> it = list.listIterator();
//正向遍历;一般直接用Iterator迭代,而不用 ListIterator遍历
while (it.hasNext()) {
String s = it.next();
System.out.println(s);//输出:hello world java
}
//逆向遍历;很少用
while(it.hasPrevious()){
String s = it.previous();
System.out.println(s);//输出:java world hello
}
//遍历集合,得到每一个元素,看有没有“world”元素,如果有,就添加一个“javaee”元素
while (it.hasNext()) {
String s = it.next();
if (s.equals("world")) {
it.add("javaee");//此处源码中会把实际修改值赋值给判断预期修改值,所以不会出现并发修改异常
}
}
System.out.println(list);//输出:[hello, world, javaee, java]
}
}
案例
-
List集合存储学生对象并遍历: 创建一个存储学生的对象的集合,存储3个对象,使用程序实现在控制台遍历该集合
//学生类
public 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; }
}
//操作
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class Demo {
public static void main(String[] args) {
//创建集合对象
List c = new ArrayList<>();
//创建学生对象
Student s1 = new Student(“勒布朗”, 37);
Student s2 = new Student(“浓眉”, 33);
Student s3 = new Student(“库里”, 33);
//把学生对象添加到集合
c.add(s1);
c.add(s2);
c.add(s3);
//遍历集合
//迭代器:集合特有的遍历方式
Iterator it = c.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName() + “,” + s.getAge());
System.out.println(s);//会输出地址,因为此处s是对象
}
//普通for循环方式:带有索引的遍历方式
for (int i = 0; i < c.size(); i++) {
Student s = c.get(i);
System.out.println(s.getName() + “,” + s.getAge());
}
//增强for方式:最方便的方式
for (Student s : c) {
System.out.println(s.getName() + “,” + s.getAge());
}
}
}
List集合子类的特点
List集合常用子类:ArrayList,LinkedList
- ArrayList:底层数据结构是数组;查询快,增删慢
- LinkedList:底层数据结构是链表,查询慢,增删快
遍历练习
-
普通遍历
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;public class Test {
public static void main(String[] args) {
//ArrayList遍历
ArrayList array = new ArrayList<>();
array.add(“hello”);
array.add(“world”);
array.add(“java”);//迭代器:集合特有的遍历方式 Iterator<String> it = array.iterator(); while (it.hasNext()) { String s = it.next(); System.out.println(s); } //普通for循环方式:带有索引的遍历方式 for (int i = 0; i < array.size(); i++) { String s = array.get(i); System.out.println(s); } //增强for方式:最方便的方式 for (String s : array) { System.out.println(s); } //LinkedList遍历 LinkedList<String> LinkedList = new LinkedList<>(); LinkedList.add("hello"); LinkedList.add("world"); LinkedList.add("java"); //迭代器:集合特有的遍历方式 Iterator<String> it1 = LinkedList.iterator(); while (it1.hasNext()) { String s = it1.next(); System.out.println(s); } //普通for循环方式:带有索引的遍历方式 for (int i = 0; i < LinkedList.size(); i++) { String s = LinkedList.get(i); System.out.println(s); } //增强for方式:最方便的方式 for (String s : LinkedList) { System.out.println(s); } }
}
-
ArrayList,LinkedList集合存储学生对象并遍历: 创建一个存储学生的对象的集合,存储3个对象,使用程序实现在控制台遍历该集合
//学生类
public 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; }
}
//操作
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;public class Demo {
public static void main(String[] args) {
//创建学生对象
Student s1 = new Student(“勒布朗”, 37);
Student s2 = new Student(“浓眉”, 33);
Student s3 = new Student(“库里”, 33);//创建ArrayList集合对象 ArrayList<Student> array = new ArrayList<>(); //把学生对象添加到集合 array.add(s1); array.add(s2); array.add(s3); //遍历集合 //迭代器:集合特有的遍历方式 Iterator<Student> it = array.iterator(); while (it.hasNext()) { Student s = it.next(); System.out.println(s.getName() + "," + s.getAge()); } //普通for循环方式:带有索引的遍历方式 for (int i = 0; i < array.size(); i++) { Student s = array.get(i); System.out.println(s.getName() + "," + s.getAge()); } //增强for方式:最方便的方式 for (Student s : array) { System.out.println(s.getName() + "," + s.getAge()); } //创建LinkedList集合对象 LinkedList<Student> linkedList = new LinkedList<>(); //把学生对象添加到集合 linkedList.add(s1); linkedList.add(s2); linkedList.add(s3); //遍历集合 //迭代器:集合特有的遍历方式 Iterator<Student> it1 = linkedList.iterator(); while (it1.hasNext()) { Student s = it1.next(); System.out.println(s.getName() + "," + s.getAge()); } //普通for循环方式:带有索引的遍历方式 for (int i = 0; i < linkedList.size(); i++) { Student s = array.get(i); System.out.println(s.getName() + "," + s.getAge()); } //增强for方式:最方便的方式 for (Student s : linkedList) { System.out.println(s.getName() + "," + s.getAge()); } }
}
LinkedList集合特有功能
方法名
说明
public void addFirst(E e)
在该列表开头插入指定的元素
public void addLat(E e)
在指定的元素追加到此列表的末尾
public E getFirst()
返回此列表中的第一个元素
public E getLast()
返回此列表中的最后一个元素
public E removeFirst()
从此列表中删除并返回第一个元素
public E removeLast()
从此列表中删除并返回最后一个元素
代码演示:
import java.util.LinkedList;
public class Test {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("java");
//public void addFirst(E e);在该列表开头插入指定的元素
//public void addLat(E e);在指定的元素追加到此列表的末尾
linkedList.addFirst("javaee");
linkedList.addLast("javase");
System.out.println(linkedList);//输出:[javaee, hello, world, java, javase]
//public E getFirst();返回此列表中的第一个元素
//public E getLast();返回此列表中的最后一个元素
System.out.println(linkedList.getFirst());//输出:hello
System.out.println(linkedList.getLast());//输出:java
//public E removeFirst();从此列表中删除并返回第一个元素
//public E removeLast();从此列表中删除并返回最后一个元素
linkedList.removeFirst();
System.out.println(linkedList);//输出:[world, java]
linkedList.removeLast();
System.out.println(linkedList);//输出:[hello, world]
}
}
Set集合
概述与特点
Set集合(继承自Coolection集合)特点:
- 不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环遍历
Set集合遍历
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建集合对象;子类实现
Set<String> set = new HashSet<>();//HashSet:对集合的迭代顺序不作任何保证
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包括重复元素的集合
set.add("java");
//遍历
for (String s : set) {
System.out.println(s);//输出:world java hello
}
}
}
哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中的方法可以获取对象的哈希值
public int hashCode()
返回对象的哈希值
哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的;重写hashCode()方法,可以实现不同对象的哈希值相同
代码演示:
//学生类
public 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 int hashCode() {
return 0;
}
}
//操作
public class Demo {
public static void main(String[] args) {
//创建学生对象
Student s1 = new Student("勒布朗", 37);
//同一个对象多次调用hashCode()方法返回的哈希值是相同的
System.out.println(s1.hashCode());//输出:1239731077;重写后输出重写值,这里为0
System.out.println(s1.hashCode());//输出:1239731077;重写后输出重写值,这里为0
Student s2 = new Student("浓眉", 33);
//默认情况下,不同对象的哈希值是不同的;重写hashCode()方法,可以实现不同对象的哈希值相同
System.out.println(s2.hashCode());//输出:557041912;重写后输出重写值,这里为0
System.out.println("hello".hashCode());//输出:99162322
System.out.println("world".hashCode());//输出:113318802
System.out.println("java".hashCode());//输出:3254818
//String重写了hashCode()方法;String对象的哈希码计算为
// s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
//所以有重复的哈希值
System.out.println("重地".hashCode());//输出:1179395
System.out.println("通话".hashCode());//输出:1179395
System.out.println("种地".hashCode());//输出:988931
System.out.println("哈哈".hashCode());//输出:694528
}
}
HashSet集合
概述和特点
HashSet集合特点:
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证
- 没有带索引的方法,所以不能使用for循环
- 由于是Set集合,所以是不包含重复元素的集合
遍历练习
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
//创建集合对象;子类实现
HashSet<String> hs = new HashSet<>();//HashSet:对集合的迭代顺序不作任何保证
//添加元素
hs.add("hello");
hs.add("world");
hs.add("java");
//不包括重复元素的集合
hs.add("java");
//遍历
for (String s : hs) {
System.out.println(s);//输出:world java hello
}
}
}
HashSet集合保证元素唯一性源码分析
- 要保证元素唯一性,需要重写hashCode()和equals()方法
案例
-
List集合存储学生对象并遍历: 创建一个存储学生的对象的集合,存储多个对象,使用程序实现在控制台遍历该集合;要求学生对象成员变量值相同,就认为是同一个对象
//学生类
public 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 boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) 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; }
}
//操作
import java.util.*;public class Demo {
public static void main(String[] args) {
HashSet hs = new HashSet<>();
//创建学生对象
Student s1 = new Student(“勒布朗”, 37);
Student s2 = new Student(“浓眉”, 33);
Student s3 = new Student(“库里”, 33);
Student s4 = new Student(“勒布朗”, 37);//把学生添加到集合 hs.add(s1); hs.add(s2); hs.add(s3); hs.add(s4); //遍历集合 for (Student s : hs) { System.out.println(s.getName() + "," + s.getAge());//输出:浓眉,33 勒布朗,37 库里,33 } }
}
LinkedHashSet集合
概述和特点
LinkedHashSet集合特点:
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
遍历练习
import java.util.LinkedHashSet;
public class Test {
public static void main(String[] args) {
//创建集合对象;子类实现
LinkedHashSet<String> lh = new LinkedHashSet<>();
//添加元素
lh.add("hello");
lh.add("world");
lh.add("java");
//不包括重复元素的集合
lh.add("java");
//遍历
for (String s : lh) {
System.out.println(s);//输出:hello world java 顺序输出
}
}
}
TreeSet集合
概述和特点
TreeSet集合特点
-
元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet()
无参方法,根据其元素的自然排序进行排序TreeSet(Comparator comparator)
带参方法,根据指定的比较器进行排序;Comparator是接口 -
没有带索引的方法,所以不能使用普通for循环遍历
-
由于是Set集合,所以不包含重复元素
遍历练习
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
//创建集合对象;子类实现
TreeSet<Integer> ts = new TreeSet<>();//集合只能是引用类型,所有基本类型在引用时要使用其包装类类型
//添加元素
ts.add(11);//Integer自动装箱
ts.add(33);
ts.add(22);
ts.add(55);
ts.add(44);
//不包括重复元素的集合
ts.add(55);
//遍历
for (Integer i : ts) {
System.out.println(i);//输出:11 22 33 44 55 自然顺序输出
}
}
}
自然排序Comparable的使用
-
存储学生对象并遍历,创建TreeSte集合使用无参构造方法;要求按照年龄从小到大,年龄相同时,按照姓名字母顺序排序
//学生类
public class Student implements Comparable {//实现Comparable接口
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 int compareTo(Student s) { //return 0;认为是重复元素,不添加,只输出s1 //return 1;正数,按照对象升序存储,s1<s2<s3<s4 //return -1;负数,按照对象降序存储,s4<s3<s2<s1 //按照年龄从大到小排序 // int num = s.age - this.age; //按照年龄从小到大排序 int num = this.age - s.age;//这里this就是后面一个年龄,s就是前面一个年龄 //年龄相同时,按照姓名的字母顺序排序 int num1 = num == 0 ? this.name.compareTo(s.name) : num; return num1; }
}
//操作
import java.util.*;public class Demo {
public static void main(String[] args) {
TreeSet ts = new TreeSet<>();
//创建学生对象
Student s1 = new Student(“le布朗”, 37);
Student s2 = new Student(“nong眉”, 33);
Student s3 = new Student(“ku里”, 32);
Student s4 = new Student(“mai基”, 34);
Student s5 = new Student(“wei德”, 37);
//相同不能存进去,保证了元素的唯一性
Student s6 = new Student(“le布朗”, 37);//把学生添加到集合 ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); ts.add(s6); //遍历集合 for (Student s : ts) { System.out.println(s.getName() + "," + s.getAge()); //输出:ku里,32 nong眉,33 mai基,34 le布朗,37 wei德,37 } }
}
结论:
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(To)方法
- 重写方法时,一定要注意排序规则,必须按照要求的主要条件和次要条件来写
比较器排序Comparator的使用
-
存储学生对象并遍历,创建TreeSte集合使用带参构造方法;要求按照年龄从小到大,年龄相同时,按照姓名字母顺序排序
//学生类
public 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; }
}
//操作
import java.util.*;public class Demo {
public static void main(String[] args) {
//创建集合对象
TreeSet ts = new TreeSet<>(new Comparator() {
@Override
public int compare(Student s1, Student s2) {
//s1就是自然排序的this,s2就是自然排序的s
int num = s1.getAge() - s2.getAge();
//比较姓名的字母
int num1 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num1;
}
});//因为要实现Comparator接口的实现类对象,所以需要写一个类来实现,这里使用匿名内部类的方法,更简便;//创建学生对象 Student s1 = new Student("le布朗", 37); Student s2 = new Student("nong眉", 33); Student s3 = new Student("ku里", 32); Student s4 = new Student("mai基", 34); Student s5 = new Student("wei德", 37); //相同不能存进去,保证了元素的唯一性 Student s6 = new Student("le布朗", 37); //把学生添加到集合 ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); ts.add(s6); //遍历集合 for (Student s : ts) { System.out.println(s.getName() + "," + s.getAge()); //输出:ku里,32 nong眉,33 mai基,34 le布朗,37 wei德,37 } }
}
结论:
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compareTo(To1,To2)方法
- 重写方法时,一定要注意排序规则,必须按照要求的主要条件和次要条件来写
案例
- 成绩排序: 用TreeSet集合存储多个学生信息,并遍历该集合;要求:按照总分从高到低出现
自然排序:(缺陷:不能比较同名、各科同分的情况)
//学生类
public class Student implements Comparable<Student> {//实现Comparable接口
private String name;
private int chinese;
private int math;
public Student() {
}
public Student(String name, int chinese, int math) {
this.name = name;
this.chinese = chinese;
this.math = math;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
public int getSum() {
return this.chinese + this.math;
}
//重写接口中所有方法;这里只有一个
@Override
public int compareTo(Student s) {
///总分从高到低
int num = s.getSum() - this.getSum();
//判断语文成绩,排除总分一样不显示情况;这里语文成绩按升序排列
int num1 = num == 0 ? this.getChinese() - s.getChinese() : num;
//比较姓名
int num2 = num1 == 0 ? s.getName().compareTo(this.getName()) : num1;
return num2;
}
}
//操作类
import java.util.*;
public class Demo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
//创建学生对象
Student s1 = new Student("le布朗", 80, 90);
Student s2 = new Student("nong眉", 82, 88);
Student s3 = new Student("ku里", 88, 88);
Student s4 = new Student("mai基", 90, 90);
Student s5 = new Student("wei德", 80, 82);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getChinese() + "," + s.getMath() + "," + s.getSum());
}
}
}
比较器排序:(缺陷:不能比较同名、各科同分的情况)
//学生类
public class Student {
private String name;
private int chinese;
private int math;
public Student() {
}
public Student(String name, int chinese, int math) {
this.name = name;
this.chinese = chinese;
this.math = math;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getChinese() {
return chinese;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public int getMath() {
return math;
}
public void setMath(int math) {
this.math = math;
}
//计算总分的方法
public int getSum() {
return this.chinese + this.math;
}
}
//操作类
import java.util.*;
public class Demo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//总分从高到低
int num = s2.getSum() - s1.getSum();
//判断语文成绩,排除总分一样不显示情况
int num1 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
//比较姓名
int num2 = num1 == 0 ? s1.getName().compareTo(s2.getName()) : num1;
return num2;
}
});//因为要实现Comparator接口的实现类对象,所以需要写一个类来实现,这里使用匿名内部类的方法,更简便;
//创建学生对象
Student s1 = new Student("le布朗", 80, 90);
Student s2 = new Student("nong眉", 82, 88);
Student s3 = new Student("ku里", 88, 88);
Student s4 = new Student("mai基", 90, 90);
Student s5 = new Student("wei德", 80, 82);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getChinese() + "," + s.getMath() + "," + s.getSum());
}
}
}
-
不重复的随机数: 编写程序,获取10个1-20之间的随机数,要求随机数不能重复,并在控制台输出
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;public class Test {
public static void main(String[] args) {
//创建集合对象;子类实现
Set set = new HashSet();//输出无序
Set set = new TreeSet();//输出会进行排序
//创建随机数对象
Random r = new Random();
//判断集合的长度是不是小于10;
while (set.size() < 10) {
//产生一个随机数,添加到集合
int number = r.nextInt(20) + 1;
set.add(number);
}
//遍历集合
for (Integer i : set) {
System.out.println(i);//输出:
}
}
}
泛型
概述
泛型:是JDK5中引用的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检查到非法的类型;它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数;也就是将类型由原来具体的类型参数化,然后在使用/调用时传入具体的类型;这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
格式:
- <类型>:指定一种类型的格式;可以看成是形参
- <类型1,类型2…>指定多种类型的格式,多种类型之间用逗号隔开;可以看成是形参
- 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
好处:
- 把运行时期的问题提前到了编译期间,因为开始就指定了具体类型
- 避免了强制转换,因为类型已经规定
泛型类
格式:修饰符 class 类名< 类型 >{ } eg:public class Generic< E >
;这里E可以写为任意标识,常用的有K、T、V、E
代码演示:
//学生类
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//老师类
public class Teacher {
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
//泛型类
public class Generic<T> {
private T x;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
}
//测试类
public class Demo {
public static void main(String[] args) {
Student s = new Student();
s.setName("勒布朗");
System.out.println(s.getName());
Teacher t = new Teacher();
t.setAge(37);
System.out.println(t.getAge());
//可以自定义传入的类型;只能是引用数据类型
Generic<String> g1 = new Generic<>();
g1.setX("勒布朗");
System.out.println(g1.getX());
Generic<Integer> g2 = new Generic<>();
g2.setX(37);
System.out.println(g2.getX());
Generic<Boolean> g3 = new Generic<>();
g3.setX(true);
System.out.println(g3.getX());
}
}
泛型方法
格式:修饰符< 类型 >返回值类型 方法名(类型 变量名){ } eg:public <T> void show< T t >
;
代码演示:
//类中定义泛型方法
public class Generic {
public <T> void show(T t) {
System.out.println(t);
}
/*
public class Generic {
普通方法
public void show(String s) {
System.out.println(s);
}*/
}
//测试类
public class Demo {
public static void main(String[] args) {
Generic g = new Generic();
g.show("勒布朗");
g.show(30);
g.show(true);
g.show(12.34);
}
}
泛型接口
格式:修饰符 interface 接口名< 类型 >{ } eg:public interface Generic< T >
;
代码演示:
//接口
public interface Generic<T> {
void show(T t);
}
//接口实现类
public class GenericImpl<T> implements Generic<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
//测试类
public class Test {
public static void main(String[] args) {
Generic<String> g1 = new GenericImpl<>();
g1.show("勒布朗");
Generic<Integer> g2 = new GenericImpl<>();
g2.show(37);
}
}
类型通配符
为了表示各种泛型List的父类,可以使用类型通配符
- 类型通配符格式:<>
- List<>:表示元素类型未知的List,它的元素可以匹配任何的类型
- 这种带有通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
类型通配符上限
- 类型通配符上限:< extends 类型>
- List< extends Number>:表示的类型是Number或者其子类型
类型通配符下限
- 类型通配符下限:< super 类型>
- List< super Number>:表示的类型是Number或者其父类型
代码演示:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
//Object > Number > Integer
//类型通配符格式:<?>
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
//类型通配符上限:<? extends 类型>
//这里Number为上限
List<? extends Number> list4 = new ArrayList<Number>();
List<? extends Number> list5 = new ArrayList<Integer>();
//类型通配符下限:<? super 类型>
//这里Number为下限
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list6 = new ArrayList<Number>();
}
}
可变参数
可变参数又称参数个数可变,用作方法的形参出现,则方法参数个数就是可变的了
- 格式:修饰符 返回值类型 方法名(数据类型…变量名){ }
- 范例:
public static int sum(int...a){ }
注意事项:
- 这里的变量其实是一个数组
- 如果一个方法有多个参数,包含可变参数,可变参数要放在最后,因为放在前面就行包含了所有数据,后面的参数就没有数据对应了
代码演示:
public class Test {
public static void main(String[] args) {
System.out.println(sum(10, 20));//输出:30
System.out.println(sum(10, 20, 30));//输出:60
System.out.println(sum(10, 20, 30, 40));//输出:100
System.out.println(sum(10, 20, 30, 40, 50));//输出:150
}
/* 有多个参数,可变参数要放在最后,因为放在前面就行包含了所有数据,后面的参数就没有数据对应了
public static int sum (int b,int... a){
return 0;
}*/
public static int sum(int... a) {
//System.out.println(a);输出:[I@7c30a502,是一个数组
//则使用增强for进行求和
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
}
可变参数的使用
Arrays工具类中有一个静态方法
public static <T> List <T> aslist(T...a)
:返回由指定数组支持的固定大小的列表- 返回的集合不能做增删操作,可以做修改操作,因为不改变集合大小
List接口中有一个静态方法
public static <E> List <E> of(E...elements)
:返回包含任意数量元素的不可变列表- 返回的集合不能做增删改操作
Set接口中有一个静态方法
public static <E> Set <E> of(E...elements)
:返回一个包含任意数量元素的不可变集合- 在给元素的时候,不能给重复的元素
- 返回的集合不能做增删操作,没有修改的方法(Set集合不支持)
代码演示:
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//Arrays工具类
//返回的集合不能做增删操作,可以做修改操作,因为不改变集合大小
List<String> list = Arrays.asList("hello", "world", "java", "world");//List结合允许重复
//不能做增删操作
//list.add("javaee");//UnsupportedOperationException:(不支持请求的操作)
//list.remove("java");//UnsupportedOperationException
//可以做修改操作
list.set(1, "javaee");//输出:[hello, javaee, java, world]
System.out.println(list);//输出:[hello, world, java, world]
//List接口
List<String> list = List.of("hello", "world", "java", "world");
//返回的集合不能做增删改操作
//list.add("javaee");//UnsupportedOperationException
//list.remove("java");//UnsupportedOperationException
//list.set(1,"javaee");//UnsupportedOperationException
System.out.println(list);//输出:[hello, world, java, world]
//Set接口
Set<String> set = Set.of("hello", "world", "java");//Set集合不能重复
//返回的集合不能做增删操作,没有修改的方法(Set集合不支持)
//set.add("javaee");//UnsupportedOperationException
//set.remove("java");//UnsupportedOperationException
System.out.println(set);//输出:[hello, world, java]
}
}
Map集合
概述与使用
Map集合概述:
- Interface Map<K,V> K(key):键的类型;V(value):值的类型
- 将键映射到值的对象;不能包含重复的键;每个键可以映射到最多一个值
创建Map集合对象:
- 使用多态的方式
- 具体实现类为HashMap
代码演示:
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<>();
//添加方法:V put(K key, V value)将指定的值与此映射中的指定键相关联
map.put("it001", "勒布朗");
map.put("it002", "浓眉");
map.put("it003", "库里");//第一次使用是添加元素
map.put("it003", "韦德");//第二次使用是修改之前的元素
System.out.println(map);//输出:{it003=韦德, it002=浓眉, it001=勒布朗};唯一性
}
}
Map集合的常用功能
Map集合的基本功能
方法名
说明
V put(K key,V value)
添加元素
V remove(Object key)
根据键删除键值对元素
void clear
移除所有键值对元素
boolean containsKey(Object key)
判断集合是否包含指定的键
boolean containsValue(Object value)
判断集合是否包含指定的值
boolean isEmpty()
判断集合是否为空
int size()
集合的长度,即集合中键值对的个数
代码演示:
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<>();
//均为单个测试
//V put(K key,V value);添加元素
map.put("it001", "勒布朗");
map.put("it002", "浓眉");
map.put("it003", "库里");
//V remove(Object key);根据键删除键值对元素
map.remove("it001");//表示移除
System.out.println(map.remove("it001"));//输出:勒布朗
System.out.println(map.remove("it004"));//输出:null
System.out.println(map);//输出:{it003=库里, it002=浓眉}
//void clear;移除所有键值对元素
map.clear();
System.out.println(map);//输出:{}
//boolean containsKey(Object key);判断集合是否包含指定的键
map.containsKey("it001");//判断但没输出
System.out.println(map.containsKey("it001"));//输出:true
System.out.println(map.containsKey("it004"));//输出:false
//boolean containsValue(Object value);判断集合是否包含指定的值
map.containsValue("勒布朗");//判断但没输出
System.out.println(map.containsValue("勒布朗"));//输出:true
System.out.println(map.containsValue("韦德"));//输出:false
//boolean isEmpty();判断集合是否为空
map.isEmpty();//判断但没输出
System.out.println(map.isEmpty());//输出:false
//int size();集合的长度,即集合中键值对的个数
System.out.println(map.size());//输出:3
}
}
Map集合的获取功能
方法名
说明
V get(Object key)
根据键获取值
Set<K>keySet()
获取所有键的集合
Collection<V> values()
获取所有值的集合
Set<Map.Entry<K,V>>entrySet()
获取所有键值对对象的集合
代码演示:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<>();
//均为单个测试
//添加元素
map.put("it001", "勒布朗");
map.put("it002", "浓眉");
map.put("it003", "库里");
//V get(Object key);根据键获取值
System.out.println(map.get("it001"));//输出:勒布朗
System.out.println(map.get("it004"));//输出:null
//Set<K>keySet();获取所有键的集合
Set<String> keyset = map.keySet();
for (String key : keyset) {
System.out.println(key);//输出:it003 it002 it001
}
//Collection<V> values();获取所有值的集合
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);//输出:库里 浓眉 勒布朗
}
//Set<Map.Entry<K,V>>entrySet();获取所有键值对对象的集合
map.entrySet();//获取但没输出
System.out.println(map.entrySet());//输出:[it003=库里, it002=浓眉, it001=勒布朗]
}
}
Map集合的遍历
-
方式一:键找值
//获取所有键的集合
Set ky = map.keySet();
//遍历集合,获取到每一个键
for (String key : ky) {
//根据键找值
String s = map.get(key);
System.out.println(key + “,” + s);
} -
方式二:键值对对象找键和值
//获取所有键的集合
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//遍历集合,获取到每一个键值对对象
for (Map.Entry<String, String> s : entrySet) {
//根据键值对对象获取键和值
String key = s.getKey();
String value = s.getValue();
System.out.println(key + “,” + value);
}
案例
-
HashMap集合存储学生对象并遍历: 创建一个HashMap集合,键是学号,值是学生对象,存储并遍历
//学生类
public 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; }
}
//操作
import java.util.*;public class Demo {
public static void main(String[] args) {
//创建HashMap集合对象
HashMap<String, Student> hm = new HashMap<>();
//创建学生对象
Student s1 = new Student(“勒布朗”, 37);
Student s2 = new Student(“浓眉”, 29);
Student s3 = new Student(“库里”, 32);
//把学生对象添加到结合
hm.put(“it001”, s1);
hm.put(“it002”, s2);
hm.put(“it003”, s3);//遍历输出 //方式1:键找值 Set<String> set = hm.keySet(); for (String key : set) { Student value = hm.get(key); System.out.println(key + "," + value.getName() + "," + value.getAge()); } //方式2:键值对对象找键和值 Set<Map.Entry<String, Student>> entrySet = hm.entrySet(); for (Map.Entry<String, Student> s : entrySet) { String key = s.getKey(); Student value = s.getValue(); System.out.println(key + "," + value.getName() + "," + value.getAge()); } }
}
-
HashMap集合存储学生对象并遍历: 建一个HashMap集合,键是学生对象,值是居住地,存储并遍历;要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象
//学生类
public 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; } //重写equals和hashCode保证元素唯一性(哈希值判断) @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) 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; }
}
//操作
import java.util.*;public class Demo {
public static void main(String[] args) {
//创建HashMap集合对象
HashMap<Student, String> hm = new HashMap<>();
//创建学生对象
Student s1 = new Student(“勒布朗”, 37);
Student s2 = new Student(“浓眉”, 29);
Student s3 = new Student(“库里”, 32);
Student s4 = new Student(“勒布朗”, 37);
//把学生对象添加到结合
hm.put(s1, “北京”);
hm.put(s2, “洛杉矶”);
hm.put(s3, “金州”);
hm.put(s4, “克利夫兰”);//遍历输出 //方式1:键找值 Set<Student> set = hm.keySet(); for (Student key : set) { String value = hm.get(key); System.out.println(key.getName() + "," + key.getAge() + "," + value); //输出:库里,32,金州 勒布朗,37,克利夫兰 浓眉,29,洛杉矶 } }
}
-
统计字符串中每个字符串出现的次数
import java.util.*;
public class Test {
public static void main(String[] args) {
//键盘录入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println(“请输入一个字符串:”);
String line = sc.nextLine();//创建HashMap集合,键是Character值是Integer //HashMap<Character,Integer> hm=new HashMap<>();//无序输出 TreeMap<Character, Integer> hm = new TreeMap<>();//对键进行排序 //遍历字符串,得到每一个字符串 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 s1 = new StringBuilder(); Set<Character> keySet = hm.keySet(); for (Character key : keySet) { Integer value = hm.get(key); s1.append(key).append("(").append(value).append(")"); } String result = s1.toString(); System.out.println(result); }
}
集合嵌套案例
-
ArrayList集合存储HashMap元素并遍历: 创建一个ArrayList集合,存储三个元素,每个元素都是HashMap,每个HashMap的键和值都是String,并遍历
import java.util.*;
public class Test {
public static void main(String[] args) {
//创建ArrayList集合
ArrayList<HashMap<String, String>> array = new ArrayList<>();//创建HashMap集合 HashMap<String, String> hm1 = new HashMap<>(); //添加键值对元素 hm1.put("it001", "勒布朗"); hm1.put("it002", "浓眉"); //把HashMap作为元素添加到ArrayList集合 array.add(hm1); HashMap<String, String> hm2 = new HashMap<>(); hm2.put("it003", "库里"); hm2.put("it004", "韦德"); array.add(hm2); HashMap<String, String> hm3 = new HashMap<>(); hm3.put("it005", "麦基"); hm3.put("it006", "霍华德"); array.add(hm3); //遍历ArrayList集合 for (HashMap<String, String> hm : array) { //因为内部是HashMap集合,所以要遍历HashMap集合 Set<String> keySet = hm.keySet(); for (String key : keySet) { String value = hm.get(key); System.out.println(key + "," + value); } } }
}
HashMap集合存储ArrayList元素并遍历: 创建一个HashMap集合,存储三个键值对元素,每个键值对元素的键都是String,值是ArrayList,每一个ArrayList的元素是String,并遍历
import java.util.*;
public class Test {
public static void main(String[] args) {
//创建HashMap集合
HashMap<String, ArrayList<String>> hm = new HashMap<>();
//创建ArrayList集合
ArrayList<String> array1 = new ArrayList<>();
//添加键值对元素
array1.add("勒布朗");
array1.add("浓眉");
//把ArrayList作为元素添加到HashMap集合
hm.put("it001", array1);
ArrayList<String> array2 = new ArrayList<>();
array2.add("库里");
array2.add("韦德");
hm.put("it002", array2);
ArrayList<String> array3 = new ArrayList<>();
array3.add("麦基");
array3.add("霍华德");
hm.put("it003", array3);
//遍历HashMap集合
Set<String> keySet = hm.keySet();
for (String key : keySet) {
System.out.println(key);
ArrayList<String> value = hm.get(key);//得到ArrayList集合
//遍历ArrayList集合
for (String s : value) {
System.out.println(s);
}
}
}
}
Collections类
概述
- Collections类是针对集合操作的工具类
Collections类常用方法
public static <T extends Comparable<? super T>>void sort(List<T>list)
:将指定的列表按升序排列public static void reverse(List<?> list)
:反转指定列表中元素的顺序publuc static void shuffle(List<?>list)
:使用默认的随机源随机排列指定的列表
代码演示:
import java.util.*;
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(30);
list.add(10);
list.add(20);
list.add(50);
list.add(40);
System.out.println(list);//输出:[30, 10, 20, 50, 40]
//public static <T extends Comparable<? super T>>void sort(List<T>list):将指定的列表按升序排列
Collections.sort(list);
System.out.println(list);//输出:[10, 20, 30, 40, 50]
//public static void reverse(List<?> list):反转指定列表中元素的顺序
Collections.reverse(list);
System.out.println(list);//输出:[40, 50, 20, 10, 30]
//publuc static void shuffle(List<?>list):使用默认的随机源随机排列指定的列表
Collections.shuffle(list);
System.out.println(list);//输出:[50, 10, 30, 20, 40];任意变化
}
}
案例
-
ArrayList存储学生对象并排序: ArrayList存储学生对象,使用Collections对ArrayList进行排序;按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
import java.util.*;
public class Demo {
public static void main(String[] args) {
//创建HashMap集合对象
ArrayList array = new ArrayList<>();
//创建学生对象
Student s1 = new Student(“勒布朗”, 37);
Student s2 = new Student(“浓眉”, 29);
Student s3 = new Student(“库里”, 32);
Student s4 = new Student(“勒布朗”, 37);
//把学生对象添加到结合
array.add(s1);
array.add(s2);
array.add(s3);
array.add(s4);
//使用Collections对ArrayList进行排序
//Collections.sort(array);报错,因为sort方法是根据元素的自然排序进行比较的,需要实现自然排序接口
//这里使用比较器方法实现;利用匿名内部类
Collections.sort(array, new Comparator() {
@Override
public int compare(Student s1, Student s2) {
//按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
int num = s1.getAge() - s2.getAge();
int num1 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num1;
}
});
//遍历输出
for (Student s : array) {
System.out.println(s.getName() + “,” + s.getAge());
}
}
} -
模拟斗地主: 通过程序实现斗地主过程中的洗牌,发牌和看牌
import java.util.*;
public class Test {
public static void main(String[] args) {
//创建ArrayList集合对象作为牌盒
ArrayList array = new ArrayList<>();
//定义花色数组
String[] colors = {“?”, “?”, “?”, “?”};
//定义点数数组
String[] numbers = {“2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”, “10”, “J”, “Q”, “K”, “A”};
//拼接
for (String color : colors) {
for (String number : numbers) {
array.add(color + number);
}
}
//添加大小王
array.add(“小王”);
array.add(“大王”);//洗牌 Collections.shuffle(array); //发牌给三个玩家 ArrayList<String> array1 = new ArrayList<>(); ArrayList<String> array2 = new ArrayList<>(); ArrayList<String> array3 = new ArrayList<>(); ArrayList<String> dparray = new ArrayList<>(); //发牌 for (int i = 0; i < array.size(); i++) { String puke = array.get(i); if (i >= array.size() - 3) { dparray.add(puke); } else if (i % 3 == 0) { array1.add(puke); } else if (i % 3 == 1) { array2.add(puke); } else if (i % 3 == 2) { array3.add(puke); } } //看牌,即三个玩家都遍历自己的牌 lookpuke("勒布朗", array1); lookpuke("浓眉", array2); lookpuke("库里", array3); lookpuke("底牌", dparray); } //看牌的方法 public static void lookpuke(String name, ArrayList<String> array) { System.out.print(name + "的牌是:"); for (String puke : array) { System.out.print(puke + " "); } System.out.println(); }
}
模拟斗地主升级版: 通过程序实现斗地主过程中的洗牌,发牌和看牌;要求对玩家的牌进行排序
import java.util.*;
public class Test {
public static void main(String[] args) {
//创建HashMap集合,键是编号,值是牌
HashMap<Integer, String> hm = new HashMap<>();
//创建ArrayList,存储编号
ArrayList<Integer> array = new ArrayList<>();
//定义花色数组
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(array);
//发牌,发的是编号;为了保证编号是排序的,创建一个TreeSet集合接受
TreeSet<Integer> set1 = new TreeSet<>();
TreeSet<Integer> set2 = new TreeSet<>();
TreeSet<Integer> set3 = new TreeSet<>();
TreeSet<Integer> dpset = new TreeSet<>();
//发牌
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) {
set1.add(x);
} else if (i % 3 == 1) {
set2.add(x);
} else if (i % 3 == 2) {
set3.add(x);
}
}
//看牌
lookpuke("勒布朗", set1, hm);
lookpuke("浓眉", set2, hm);
lookpuke("库里", set3, hm);
lookpuke("底牌", dpset, hm);
}
//看牌的方法:变量TreeSte集合,获取编号,到HashMap集合找对应的牌
public static void lookpuke(String name, TreeSet<Integer> ts, HashMap<Integer, String> hm) {
System.out.print(name + "的牌是:");
for (Integer key : ts) {//遍历编号
String puke = hm.get(key);
System.out.print(puke + " ");
}
System.out.println();
}
}
IO流
IO流概述:
- IO:输入输出(读数据/写数据)(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称;即称数据在设备间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的
IO流分类:
- 按照数据流向:输入流(读数据);输出流(写数据)
- 按照数据类型: 字节流(字节输入流、字节输出流);字符流(字符输入流、字符输出流)
两种流的使用情况:
- 如果数据通过Windows自带的记事本打开,我们可以读懂,就使用字符流;否则使用字节流
- 如果不知道使用哪种,就使用字节流
File类
概述
File类是文件和目录路径名的抽象表示
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已;可以存在,也可以不存在;是需要通过具体的操作把这个路径的内容转换为具体存在的
常用方法
方法名
说明
File(String pathname)
通过将给定的路径名字符串转为抽象路径名来创建新的File实例
File(String parent,String child)
从父路径名字符串和子路径名字符串创建新的File实例
File(File parent String child)
从父抽象路径和子路径名字符串创建新的File实例
代码演示:
import java.io.File;
public class Test {
public static void main(String[] args) {
//File(String pathname);通过将给定的路径名字符串转为抽象路径名来创建新的File实例
File f1 = new File("D:\java1\java.txt");//"/是转义字符"
System.out.println(f1);//输出:D:java1java.txt
// java.txt文本不存在但不报错,说明只是抽象路径的表示形式;而且重写了toString方法
//File(String parent,String child);从父路径名字符串和子路径名字符串创建新的File实例
File f2 = new File("D:\java1", "java.txt");
System.out.println(f2);//输出:D:java1java.txt
//File(File parent String child);从父抽象路径和子路径名字符串创建新的File实例
File f3 = new File("D:\java1");
File f4 = new File(f3, "java.txt");
System.out.println(f4);//输出:D:java1java.txt
}
}
File类基本功能
绝对路径和相对路径
- 绝对路径:有完整的路径名,不需要任何其他信息就可以定位它所表示的文件。eg:D:java1javase1
- 相对路径:必须使用取自其他路径名的信息进行解释,eg:java1javase1
File类创建功能
方法名
说明
public boolean createNewFile()
当具有该名称的文件不存在时创建一个由该抽象路径名命名的新空文件夹
public boolean mkdir()
创建由此抽象路径名命名的目录
public boolean mkdirs()
创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
代码演示:
import java.io.File;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//public boolean createNewFile();当具有该名称的文件不存在时创建一个由该抽象路径名命名的新空文件夹
//如果文件不存在,就创建文件,并返回true;如果文件存在,就不创建文件,并返回false;
File f1 = new File("D:\java1\java.txt");
System.out.println(f1.createNewFile());
//public boolean mkdir();创建由此抽象路径名命名的目录
//如果目录不存在,就创建目录,并返回true;如果目录存在,就不创建目录,并返回false;
//只能一级一级的创建
File f2 = new File("D:\java1\javase");
System.out.println(f2.mkdir());
//public boolean mkdirs();创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
//如果目录不存在,就创建目录,并返回true;如果目录存在,就不创建目录,并返回false;
//多级创建
File f3 = new File("D:\java1\javase1\html");
System.out.println(f3.mkdirs());
}
}
File类判断和获取功能
方法名
说明
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对象数组
代码演示:
import java.io.File;
public class Test {
public static void main(String[] args) {
//创建一个File对象
File f = new File("D:\java1");
//public boolean isDirectory();测试此抽象路径名表示的File是否为目录
//public boolean isFile();测试此抽象路径名表示的File是否为文件
//public boolean exists();测试此抽象路径名表示的File是否存在
System.out.println(f.isDirectory());//输出:true
System.out.println(f.isFile());//输出:false
System.out.println(f.exists());//输出:true
//public String getAbsolutePath();返回此抽象路径名的绝对路径名字符串
//public String getPath();将此抽象路径名转换为路径名字符串
//public String getName();返回此抽象路径名表示的文件或目录的名称
System.out.println(f.getAbsoluteFile());//输出:D:java1
System.out.println(f.getPath());//输出:D:java1
System.out.println(f.getName());//输出:java1
//public String[] list();返回此抽象路径名表示的目录中的文件或目录的名称字符串数组
File f1 = new File("D:\java1");
String[] str = f1.list();
for (String str1 : str) {
System.out.println(str1);//遍历输出此文件中的所有名称
}
//public File[] listFiles();返回此抽象路径名表示的目录中的文件或目录的File对象数组
File[] file = f1.listFiles();
for (File files : file) {
System.out.println(files);//输出所有对象的路径
System.out.println(files.getName());//遍历输出此文件中的所有名称
if (files.isFile()) {
System.out.println(files.getName());//值输出文件
}
}
}
}
File类删除功能
方法名
说明
public boolean delete()
删除由此抽象路径名表示的文件或目录
代码演示:
import java.io.File;
public class Test {
public static void main(String[] args) {
//创建一个File对象
File f = new File("D:\java1\javaeee.txt");
//System.out.println(f.createNewFile());
//public boolean delete();删除由此抽象路径名表示的文件或目录
System.out.println(f.delete());
}
}
注:
- 如果文件的上级目录不存在,则会报错:IOException: 系统找不到指定的路径
- 如果一个目录内有内容(目录,文件),无法直接删除;应先删除目录中的内容,最后才能删除目录
案例
-
遍历目录,输出文件
import java.io.File;
public class Test {
public static void main(String[] args) {
File file = new File(“D:\java1”);
getPath(file);} public static void getPath(File file) { //获取给定的File目录下所有的文件或者目录的File数组 File[] fileArray = file.listFiles(); //遍历数组得到每一个File对象 if (fileArray != null) { for (File file1 : fileArray) { //判断File对象是否有目录 if (file1.isDirectory()) { getPath(file1); } else { System.out.println(file1.getAbsoluteFile());//输出绝对路径 } } } }
}
字节流
概述
字节流抽象基类:
- InputStream:这个抽象类是表示字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- 子类名特点:子类名称都是以其父类作为子类名的后缀
字节流写数据
子类实现常用构造方法
FileOutStream:文件输出流用于将数据写入File
构造方法名
说明
FileOutputStream(String name)
创建文件输出流以指定的名称写入文件
FileOutputStream(File file)
创建文件输出流以写入由指定的File对象表示的文件
FileOutputStream(String name,boolean append)
创建文件输出流以指定的名称写入文件
使用字节输出流写数据的步骤
- 创建字节输出流对象(调用系统功能)创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象写数据方法
- 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
代码演示:
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//创建字节输出流对象
//FileOutputStream(String name):创建文件输出流以指定的名称写入文件
FileOutputStream f = new FileOutputStream("src\f.txt");
//FileOutputStream(File file):创建文件输出流以写入由指定的File对象表示的文件
File file=new File("src\f.txt");
FileOutputStream f=new FileOutputStream(file);
//整合如下:
FileOutputStream f=new FileOutputStream(new File("src\f.txt"));
/*public FileOutputStream(String name,boolean append)
创建文件输出流以写入具有指定名称的文件。如果第二个参数是true ,则字节将写入文件的末尾而不是开头*/
FileOutputStream f = new FileOutputStream("src\f.txt", true);
/*
做了三件事情
1.调用系统功能创建了文件
2.创建了字节输出流对象
3.让字节输出流对象指向创建好的文件
*/
//void write (int b):将指定的字节写入此文件输出流
f.write(97);//显示 a(ASCII码)
//所有与IO操作的最后都要释放资源
//void close ():关闭此文件输出流并释放与此流相关联的任何系统资源
f.close();
}
}
字节流写数据的3种方式
方法名
说明
void write(int b)
将指定的字节写入此文件输出流;一次写一个字节数据
void write(byte[] b)
将b.length字节从指定的字节数组写入此文件输出流;一次写一个自己数组的数据
void write(byte[] b,int off,int len)
将len字节从指定的字节数组开始,从偏移量off开始写入写入此文件输出流;一次写一个字节数组的部分数据
注:byte[] getBytes ()方法:返回字符串对应的字节数组
代码演示:
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//创建字节输出流对象
//FileOutputStream(String name):创建文件输出流以指定的名称写入文件
FileOutputStream f = new FileOutputStream("src\f.txt");
// new File(name):底层动作
//相当于:FileOutputStream f=new FileOutputStream(new File("src\f.txt"));
//void write(int b);将指定的字节写入此文件输出流;一次写一个字节数据
f.write(97);
f.write(98);
f.write(99);
//void write(byte[] b);将b.length字节从指定的字节数组写入此文件输出流;一次写一个自己数组的数据
byte[] by = {97, 98, 99};
f.write(by);//写入abc
//byte[] getBytes ()方法:返回字符串对应的字节数组;则可以简写如下
byte[] by = "abc".getBytes();
f.write(by);//写入abc
//void write(byte[] b,int off,int len);将len字节从指定的字节数组开始,从偏移量off开始写入写入此文件输出流;一次写一个字节数组的部分数据
byte[] by = "abc".getBytes();
f.write(by, 1, 2);//写入bc
//释放资源
f.close();
}
}
字节流写数据的两个小问题
字节流写数据如何实现换行
- 写完数据后加入换行符
一般:Windows:
linux:
mac:
字节写数据如何实现追加写入
- 使用
public FileOutputStream(String name,boolean append)
构造方法;如果第二个参数是true ,则字节将写入文件的末尾而不是开头
代码演示:
import java.io.FileOutputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//创建字节输出流对象
FileOutputStream f = new FileOutputStream("src\f.txt", true);
//写数据
for (int i = 0; i < 10; i++) {
f.write("hello".getBytes());
//换行
f.write("
".getBytes());
}
//释放资源
f.close();
}
}
字节流写数据加异常处理
代码演示:
public class Test {
public static void main(String[] args) {
//因为需要在finally里面执行,所以要在外面定义并初始化
FileOutputStream f = null;
try {
f = new FileOutputStream("src\f.txt");
f.write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//if判断保持程序健壮性
//如果路径不存在,则对象f就为null,所以finally执行的时候就是null调方法,即为空指针异常
if (f != null) {
try {
f.close();//NullPointerException:空指针异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
字节流读数据
子类实现常用构造方法
FileInStream:从文件系统中的文件获取输入字节
构造方法名
说明
FileInputStream(String name)
通过打开与实际文件的连接来创建 FileInputStream
,该文件由文件系统中的路径名 name
命名。
FileInputStream(File file)
通过打开与实际文件的连接来创建 FileInputStream
,该文件由文件系统中的 File
对象 file
命名。
使用字节输入流读数据的步骤
- 创建字节输入流对象
- 调用字节输入流对象读数据方法
- 释放资源(关闭此文件输入流并释放与此流相关联的任何系统资源)
代码演示:
import java.io.FileInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//创建字节输入流
//FileInputStream(String name);通过打开与实际文件的连接来创建FileInputStream,该文件由文件系统中的路径名name命名
FileInputStream f = new FileInputStream("src\f.txt");
//调用字节输入流对象读数据方法
//int read():从该输入流读取一个字节的数据
int b = f.read();
while (b != -1) {//如果到达文件末尾则显示-1;
System.out.print((char) b);
b = f.read();
}
//读取代码优化
int b;
while ((b = f.read()) != -1) {
System.out.print((char) b);
}
/*
* 此处有三个动作
* f.read():读数据
*(b=f.read():赋值
* (b=f.read())!=-1判断
*/
//释放资源
f.close();
}
}
字节流读数据的2种方式
方法名
说明
int read()
从此输入流中读取一个字节的数据
int read(byte[] b)
从此输入流读取最多 b.length字节的数据到一个字节数组
注:String(byte[] bytes);通过使用平台的默认字符集解码指定的字节数组构造新的String;
String(byte[] bytes, int offset, int length);通过使用平台的默认字符集解码指定的字节子数组来构造新的String
代码演示:
import java.io.FileInputStream;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
//创建字节输入流
FileInputStream f = new FileInputStream("src\f.txt");
//调用字节输入流对象读数据方法
//方式一
//int read():从该输入流读取一个字节的数据
int b;
while ((b = f.read()) != -1) {
System.out.print((char) b);
}
/*int read(byte[] b);从此输入流读取最多 b.length字节的数据到一个字节数组
byte[] b=new byte[5];
int i = f.read(b);
System.out.println(i);
//输出:5;这是读取的字符个数,不是数组长度
//String(byte[] bytes);通过使用平台的默认字符集解码指定的字节数组构造新的String
//String(byte[] bytes, int offset, int length);通过使用平台的默认字符集解码指定的字节子阵列来构造新的String
System.out.println(new String(b,0,i));
//输出:hello
//第二次读取
i = f.read(b);
System.out.println(new String(b,0,i));
//输出: wor
*//*
* 因为有换行符
* hello
* world
*/
//方式二
//int read(byte[] b);从此输入流读取最多 b.length字节的数据到一个字节数组
byte[] b = new byte[1024];
//1024及其整数倍
int len;
while ((len = f.read(b)) != -1) {
//读到没有数据显示-1,所以-1是结束条件
System.out.println(new String(b, 0, len));
}
//释放资源
f.close();
}
}
案例
-
复制文本文件:一次读取一个字节,一次写入一个字节
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class Test {
public static void main(String[] args) throws IOException {
//创建字节输入流
FileInputStream f = new FileInputStream(“D:\java1\java.txt”);
//创建输出流对象
FileOutputStream f1 = new FileOutputStream(“src\f.txt”);//读写数据,复制文本文件(一次读取一个字节,一次写入一个字节) //这里用字节流可以识别中文是因为:底层操作会自动进行字节拼接成中文 int b; while ((b = f.read()) != -1) { f1.write(b); } f.close(); f1.close(); }
}
-
复制图片:一次读取一个字节数组,一次写入一个字节数组
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class Test {
public static void main(String[] args) throws IOException {
//创建字节输入流
FileInputStream f = new FileInputStream(“D:\java1\hh.jpg”);
//创建输出流对象
FileOutputStream f1 = new FileOutputStream(“src\hh.jpg”);//读写数据,复制文本文件(一次读取一个字节数组,一次写入一个字节数组) byte[] b = new byte[1024]; int len; while ((len = f.read(b)) != -1) { f1.write(b, 0, len); } f.close(); f1.close(); }
}
字节缓冲流
字节缓冲流:提高了读写数据效率
- BufferedOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节调用底层系统。
- BufferedInputStream:创建BufferedInputStream将创建内部缓冲区数组;当读取或跳过来自流的字节时,内部缓冲区根据需要从包含的输入流中重新填充,一次多个字节。
构造方法
- 字节缓冲输出流:
BufferedOutputStream(OutputStream out)
- 字节缓冲输入流:
BufferedInputStream(InputStream in)
说明:字节缓冲流仅仅提供缓冲区,真正的读写数据还得依靠基本的字节流对象进行操作
读写数据
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//创建字节缓冲输出流对象
/*FileOutputStream f=new FileOutputStream("src\hh.jpg");
BufferedOutputStream b=new BufferedOutputStream(f);*/
//内置的缓冲数组的长度为8192个字节
BufferedOutputStream b = new BufferedOutputStream(new FileOutputStream("src\f.txt"));
//写数据;这里为覆盖写
b.write("hello".getBytes());
b.write("world".getBytes());
//释放资源
b.close();
//字节缓冲输入流
//内置的缓冲数组的长度为8192个字节
BufferedInputStream b1 = new BufferedInputStream(new FileInputStream("src\f.txt"));
//读数据
//一次读取一个字节数据
int by;
while ((by = b1.read()) != -1) {
System.out.print((char) by);
}
//一次读取一个字节数组
byte[] bys = new byte[1024];
int len;
while ((len = b1.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
//释放资源
b1.close();
}
}
案例
-
复制视频
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//记录开始时间
long startTime = System.currentTimeMillis();//复制视频;视频大小7319kB method1();//共耗时:46060毫秒 method2();//共耗时:41057毫秒 method3();//共耗时:240毫秒 method4();//共耗时:20毫秒 //记录结束时间 long endTime = System.currentTimeMillis(); System.out.println("共耗时:" + (endTime - startTime) + "毫秒"); } //字节缓冲流一次读写一个字节 public static void method3() throws IOException { BufferedInputStream bi1 = new BufferedInputStream(new FileInputStream("D:\java1\sp.mp4")); BufferedOutputStream bo1 = new BufferedOutputStream(new FileOutputStream("src\视频.mp4")); int by; while ((by = bi1.read()) != -1) { bo1.write(by); } bi1.close(); bo1.close(); } //字节缓冲流一次读写一个字节数组 public static void method4() throws IOException { BufferedInputStream bi2 = new BufferedInputStream(new FileInputStream("D:\java1\sp.mp4")); BufferedOutputStream bo2 = new BufferedOutputStream(new FileOutputStream("src\视频.mp4")); byte[] by = new byte[1024]; int len; while ((len = bi2.read(by)) != -1) { bo2.write(by, 0, len); } bi2.close(); bo2.close(); } //基本字节流一次读写一个字节 public static void method1() throws IOException { FileInputStream fi1 = new FileInputStream("D:\java1\sp.mp4"); FileOutputStream fo1 = new FileOutputStream("src\视频.mp4"); int by; while ((by = fi1.read()) != -1) { fo1.write(by); } fi1.close(); fo1.close(); } //基本字节流一次读写一个字节数组 public static void method2() throws IOException { FileInputStream fi2 = new FileInputStream("D:\java1\sp.mp4"); FileOutputStream fo2 = new FileOutputStream("src\视频.mp4"); byte[] by = new byte[1024]; int len; while ((len = fi2.read()) != -1) { fo2.write(by, 0, len); } fi2.close(); fo2.close(); }
}
字符流
概述
字符流=字节流+编码表
汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数,所以能自动识别
字节流抽象基类:
- Reader:这个抽象类是表示字符输入流的所有类的超类
- Writer:这个抽象类是表示字符输出流的所有类的超类
- 子类名特点:子类名称都是以其父类作为子类名的后缀
编码表
- 按照某种规则,将字符存储到计算机中,称为编码;反之,将存储在计算机中的二进制数按照某种规则解析出来,称为解码。如果编码解码使用规则不同,则会导致乱码现象
字符集
- 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
- 计算机要准确的存储识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码;常见的字符集有:ASCII字符集、GBXXX字符集、Unicode字符集等
编码解码问题
字符串中的编码解码问题
编码:
byte[] getBytes()
:使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中byte[] getBytes(String charsteName)
:使用指定的字符集将该String编码为一系类字节,将结果存储到新的字节数组中
解码:
String(byte[] bytes)
:通过使用平台默认的字符集解码指定的字节数组来构造新的StringString(bytes,String charsetName)
:通过指定的字符集解码指定的字节数组来构造新的String
代码演示:
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException {
//定义一个字符串
String s = "中国";
//编码
//byte[] getBytes():使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中
byte[] bys = s.getBytes();
//Arrays.toString():返回指定数组内容的字符串表示形式
System.out.println(Arrays.toString(bys));
//输出:[-28, -72, -83, -27, -101, -67]
//byte[] getBytes(String charsetName):使用指定的字符集将该String编码为一系类字节,将结果存储到新的字节数组中
byte[] bys = s.getBytes("GBK");
System.out.println(Arrays.toString(bys));
//输出:[-42, -48, -71, -6]
//解码
//String(byte[] bytes)`:通过使用平台默认的字符集解码指定的字节数组来构造新的String
byte[] bys = s.getBytes();
String ss = new String(bys);
System.out.println(ss);
//输出:中国
//String(bytes,String charsetName):通过指定的字符集解码指定的字节数组来构造新的String
byte[] bys = s.getBytes();
String ss = new String(bys, "GBK");
System.out.println(ss);
//输出:涓浗 因为使用默认编码(UTF-8),但使用了GBK解码,所以出现乱码了
}
}
字符流中的编码解码问题
字符串中和编码解码问题相关的两个类(涉及编码解码问题用这个而不是便捷类):
InputStreamReader
:是从字节流到字符流的桥梁
它读取字节,并使用指定的编码将其解码为字符;字符集可以由名称指定、也可以被明确指定、或者接受平台默认字符集
OutputStreamWriter
:是从字符流到字节流的桥梁
使用指定的编码将写入的字符编码为字节;字符集可以由名称指定、也可以被明确指定、或者接受平台默认字符集
两个便捷类(上面相应的直接子类):
FileReader(String fileName)
:用于读取字符文件的便捷类FileWriter(String fileName)
:用于写入字符文件的便捷类
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//编码
//OutputStreamWriter (OutputStream out) 创建使用默认字符编码的OutputStreamWriter
/*FileOutputStream fo=new FileOutputStream("src\f.txt");
OutputStreamWriter osw=new OutputStreamWriter(fo);*/
//合并一步
//OutputStream是抽象类,用其子类FileOutputStream实现
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src\f.txt"));
osw.write("中国");
osw.close();
//OutputStreamWriter (OutputStream out, String charsetName) 创建使用指定charset的OutputStreamWriter
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src\f.txt"), "GBK");
osw.write("中国");
osw.close();
//解码
//InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream("src\f.txt"));
//一次读取一个字符数据
int ch;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
isr.close();
//InputStreamReader(InputStream in, String charsetName) 创建一个使用指定charset的InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream("src\f.txt"), "GBK");
int ch;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
isr.close();
}
}
字符流写数据的5种方式
方法名
说明
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)
写一个字符串的一部分
方法名
说明
flush()
刷新流;但还可以继续写数据
close()
关闭流,释放资源;但在关闭之前会先刷新流,一旦关闭,就不能再写数据
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src\f.txt"));
//void write(int c);写一个字符
osw.write(97);
//void flush():刷新流;因为真正实现的是底部的字节流,而字符流相对于字节流是有缓冲的
//使用刷新流才会显示写入的数据;后面还能继续写数据
osw.flush();
//void close():关闭流;先刷新,再关闭
//close一旦调用就不可写数据了
osw.close();
//void write(char[] cbuf);写入一个字符数组
char[] chs = {'a', 'b', 'c'};
osw.write(chs);
//void write(char[] cbuf,int off,int len);写入字符数组的一部分
char[] chs = {'a', 'b', 'c'};
osw.write(chs, 0, chs.length);
//void write(String str);写一个字符串
osw.write("abcde");
//void write(String str,int off,int len);写一个字符串的一部分
osw.write("abcde", 0, "abcde".length());
//释放资源
osw.close();
}
}
字符流读数据的2种方式
方法名
说明
int read()
一次读一个字符数据
int read(char[] cbuf)
一次读一个字符数组数据
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("src\f.txt"));
//int read();一次读一个字符数据
int ch;
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
//int read(char[] cbuf);一次读一个字符数组数据
char[] chs = new char[1024];
int len;
while ((len = isr.read()) != -1) {
System.out.println(new String(chs, 0, len));
}
//释放资源
isr.close();
}
}
注:字节流和字符流读数据格式是一样的
案例
-
字符流复制Java文件
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//根据数据源创建字符熟肉流对象
InputStreamReader isr = new InputStreamReader(new FileInputStream(“src\jicheng\cat.java”));
//根据目的创建字符输出流对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“src\f.java”));//读写数据,复制文件 //一次读写一个字符数据 int ch; while ((ch = isr.read()) != -1) { osw.write(ch); } //一次读写一个字符数组数据 char[] chs = new char[1024]; int len; while ((len = isr.read(chs)) != -1) { osw.write(chs, 0, len); } //释放资源 isr.close(); osw.close(); }
}
-
字符流复制Java文件(改进版): 利用便捷子类书写
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//根据数据源创建字符熟肉流对象
FileReader fr = new FileReader(“src\jicheng\cat.java”);
//根据目的创建字符输出流对象
FileWriter fw = new FileWriter(“src\f.java”);//读写数据,复制文件 //一次读写一个字符数据 int ch; while ((ch = fr.read()) != -1) { fw.write(ch); } //一次读写一个字符数组数据 char[] chs = new char[1024]; int len; while ((len = fr.read(chs)) != -1) { fw.write(chs, 0, len); } //释放资源 fr.close(); fw.close(); }
}
字符缓冲流
-
BufferedWriter:将文本写入字符输出流,缓冲字符,以便有效地写入单个字符,数组和字符串;可以指定缓冲区大小,或者可以接受默认大小。 对于大多数用途,默认值足够大。
-
BufferedReader:从字符输入流中读取文本,缓冲字符,以便有效地读取字符,数组和行;可以指定缓冲区大小,或者可以使用默认大小。 对于大多数用途,默认值足够大。
构造方法
- 字符缓冲输出流:
BufferedWriter(Writer out)
- 字符缓冲输入流:
BufferedReader(Reader in)
读写数据
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//BufferedWriter(Writer out)
/*FileWriter fw=new FileWriter("src\f.txt");
BufferedWriter bw=new BufferedWriter(fw);*/
//内置的缓冲数组的长度为8192个字节
BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt"));
bw.write("hello
");
bw.write("world
");
bw.close();
//BufferedReader(Reader in)
//内置的缓冲数组的长度为8192个字节
BufferedReader br = new BufferedReader(new FileReader("src\f.txt"));
//读数据
//一次读取一个字节数据
int ch;
while ((ch = br.read()) != -1) {
System.out.print((char) ch);
}
//一次读取一个字节数组
char[] bys = new char[1024];
int len;
while ((len = br.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
//释放资源
br.close();
}
}
案例
-
字符缓冲流复制Java文件
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//根据数据源创建字符输入流对象
BufferedReader br = new BufferedReader(new FileReader(“src\jicheng\cat.java”));
//根据目的创建字符输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter(“src\f.java”));//读写数据,复制文件 //一次读写一个字符数据 int ch; while ((ch = br.read()) != -1) { bw.write(ch); } //一次读写一个字符数组数据 char[] chs = new char[1024]; int len; while ((len = br.read(chs)) != -1) { bw.write(chs, 0, len); } //释放资源 br.close(); bw.close(); }
}
字符缓冲流特有功能
BufferedWriter:
void newLine()
:写一行行分隔符,行分隔符有系统属性定义
BufferedReader:
public Strinng readLine()
:读一行文字。结果包含行的内容,不包括任何终止字符,如果流的结尾已经到达,则为null
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//创建字符输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt"));
//写数据
for (int i = 0; i < 10; i++) {
bw.write("hello" + i);
//void newLine():写一行行分隔符,行分隔符有系统属性定义
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("src\f.txt"));
//读数据
//public Strinng readLine():读一行文字。结果包含行的内容,不包括任何终止字符,如果流的结尾已经到达,则为null
String line;
while ((line = br.readLine()) != null) {
System.out.print(line);
}
br.close();
}
}
案例
-
复制Java文件(字符缓冲流特有功能改进版):
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//根据数据源创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader(“src\jicheng\cat.java”));
//根据目的创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter(“src\f.java”));//读写数据,复制文件;标准步骤 //一使用字符缓冲流特有功能实现 String line; while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); } //释放资源 br.close(); bw.close(); }
}
案例
文件与集合
-
集合到文件: 把ArrayList集合中的字符串数据写入到文本文件。要求:每一个字符串元素作为文件中的一行数据
import java.io.*;
import java.util.ArrayList;public class Test {
public static void main(String[] args) throws IOException {
//创建ArrayList集合
ArrayList array = new ArrayList<>();//往集合中存储字符串元素 array.add("hello"); array.add("world"); array.add("java"); //创建字符缓冲输出流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt")); //遍历集合,得到每一个字符串数据 for (String s : array) { //调用字符缓冲输出流对象的方法写数据 bw.write(s); bw.newLine(); bw.flush(); } //释放资源 bw.close(); }
}
-
文件到集合: 把文本文件中的数据读取到集合中,遍历集合。要求:文件中没一行数据是一个集合元素
import java.io.*;
import java.util.ArrayList;public class Test {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader(“src\f.txt”));
//创建ArrayList集合对象
ArrayList array = new ArrayList<>();
//调用字符缓冲输入流对象的方法读数据
String line;
while ((line = br.readLine()) != null) {
//把读到的字符串数据存储到集合中
array.add(line);
}
//释放资源
br.close();
//遍历集合,得到每一个字符串数据
for (String s : array) {
System.out.println(s);
}
}
} -
点名器
import java.io.*;
import java.util.ArrayList;
import java.util.Random;public class Test {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader(“src\f.txt”));
//创建ArrayList集合对象
ArrayList array = new ArrayList<>();
//调用字符缓冲输入流对象的方法读数据
String line;
while ((line = br.readLine()) != null) {
//把读到的字符串数据存储到集合中
array.add(line);
}
//释放资源
br.close();
//使用random产生一个随机数
Random r = new Random();
int index = r.nextInt(array.size());
//把产生的随机数作为索引到ArrayList结合中获取值
String name = array.get(index);
//输出数据
System.out.println(name);
}
} -
集合到文件(改进版): 把ArrayList集合中的字符串数据写入到文本文件。要求:每一个学生对象作为文件中的一行数据
//学生类
public class Student {
private String sid;
private String name;
private int age;
private String address;public Student() { } public Student(String sid, String name, int age, String address) { this.sid = sid; this.name = name; this.age = age; this.address = address; } public String getSid() { return sid; } public void setSid(String sid) { this.sid = sid; } 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; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; }
}
//操作
import java.io.*;
import java.util.ArrayList;public class Demo {
public static void main(String[] args) throws IOException {
//创建ArrayList集合
ArrayList array = new ArrayList<>();//创建学生对象 Student s1 = new Student("it001", "勒布朗", 37, "洛杉矶"); Student s2 = new Student("it002", "浓眉", 32, "洛杉矶"); Student s3 = new Student("it002", "库里", 33, "金州"); //往集合中存储字符串元素 array.add(s1); array.add(s2); array.add(s3); //创建字符缓冲输出流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt")); //遍历集合,得到每一个字符串数据 for (Student s : array) { //把学生对象的数据拼接成指定格式的字符串 StringBuilder sb = new StringBuilder(); sb.append(s.getSid()).append(",").append(s.getName()).append(",").append(s.getAge()).append(",").append(s.getAddress()); //调用字符缓冲输出流对象的方法写数据 bw.write(sb.toString()); bw.newLine(); bw.flush(); } //释放资源 bw.close(); }
}
-
文件到集合: 把文本文件中的数据读取到集合中,遍历集合。要求:文件中没一行数据是一个学生对象的成员变量值
//学生类
public class Student {
private String sid;
private String name;
private int age;
private String address;public Student() { } public Student(String sid, String name, int age, String address) { this.sid = sid; this.name = name; this.age = age; this.address = address; } public String getSid() { return sid; } public void setSid(String sid) { this.sid = sid; } 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; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; }
}
//操作
import java.io.*;
import java.util.ArrayList;public class Demo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader(“src\f.txt”));//创建ArrayList集合 ArrayList<Student> array = new ArrayList<>(); //调用字符缓冲输入流对象的方法读数据 String line; while ((line = br.readLine()) != null) { //把读取到的字符串数据用split()方法进行分割,得到一个字符串数组 String[] strArray = line.split(","); //创建学生对象 Student s = new Student(); //把字符串数组中的每一个元素取出来赋值给对应的学生对象的成员变量 s.setSid(strArray[0]); s.setName(strArray[1]); //将int类型转换为String类型 s.setAge(Integer.parseInt(strArray[2])); s.setAddress(strArray[3]); //把学生对象添加到集合 array.add(s); } //释放资源 for (Student s : array) { System.out.println(s.getSid() + "," + s.getName() + "," + s.getAge() + "," + s.getAddress()); } }
}
-
集合到文件(数据排序改进版): 把学生成绩总分从高到低写入文本文件
//学生类
public class Student {
private String name;
private int chinese;
private int math;
private int english;public Student() { } public Student(String name, int chinese, int math, int english) { this.name = name; this.chinese = chinese; this.math = math; this.english = english; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getChinese() { return chinese; } public void setChinese(int chinese) { this.chinese = chinese; } public int getMath() { return math; } public void setMath(int math) { this.math = math; } public int getEnglish() { return english; } public void setEnglish(int english) { this.english = english; } public int getSum() { return this.chinese + this.math + this.english; }
}
//操作
import java.io.*;
import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;public class Demo {
public static void main(String[] args) throws IOException {
//创建TreeSet集合,通过比较器进行排序
TreeSet ts = new TreeSet<>(new Comparator() {
@Override
public int compare(Student s1, Student s2) {
//主要条件成绩总分从高到低
int num = s2.getSum() - s1.getSum();
//次要条件:可能有总分相同/名字相同的学生
int num1 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
int num2 = num1 == 0 ? s1.getMath() - s2.getMath() : num1;
int num3 = num2 == 0 ? s1.getName().compareTo(s2.getName()) : num2;
return num3;
}
});//键盘录入学生数据 for (int i = 0; i < 5; i++) { Scanner sc = new Scanner(System.in); System.out.println("请录入第" + (i + 1) + "个学生信息"); System.out.println("姓名:"); String name = sc.nextLine(); System.out.println("语文成绩:"); int chinese = sc.nextInt(); System.out.println("数学成绩:"); int math = sc.nextInt(); System.out.println("英语成绩:"); int english = sc.nextInt(); //创建学生对象,把键盘录入的数据对应赋值给学生对象的成员变量 Student s = new Student(); s.setName(name); s.setChinese(chinese); s.setMath(math); s.setEnglish(english); //把学生对象添加到TreeSet集合 ts.add(s); } //创建字符缓冲输出流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt")); //遍历集合,得到每一个学生对象 for (Student s : ts) { //把学生对象的数据拼接成指定格式的字符串 StringBuilder sb = new StringBuilder(); sb.append(s.getName()).append(",").append(s.getChinese()).append(",").append(s.getMath()).append(",").append(s.getEnglish()); //调用字符缓冲输出流对象的方法写数据 bw.write(sb.toString()); bw.newLine(); bw.flush(); } //释放资源 bw.close(); }
}
文件集与字节流
-
复制单级文件夹
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//创建数据源目录对象
File srcFolder = new File(“D:\java1”);//获取数据源目录File对象的名称 String srcFoldername = srcFolder.getName(); //创建目的地目录File对象 File destFolder = new File("src", srcFoldername); //判断目的地目录对应的File是否存在,不存在则创建 if (!destFolder.exists()) { destFolder.mkdir(); } //获取数据源目录下所有文件的File数组 File[] listFiles = srcFolder.listFiles(); //遍历数组,得到每一个File对象 for (File srcFile : listFiles) { //获取数据源文件File对象的名称 String srcFileName = srcFile.getName(); //创建目的地文件File对象 File destFile = new File(destFolder, srcFileName); //复制文件 copyFile(srcFile, destFile); } } //字节缓冲流复制文件 private static void copyFile(File srcFile, File destFile) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)); byte[] bys = new byte[1024]; int len; while ((len = bis.read(bys)) != -1) { bos.write(bys, 0, len); } bis.close(); bos.close(); }
}
-
复制多级文件夹
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//创建数据源目录对象
File srcFile = new File(“D:\java1”);//创建目的地目录File对象 File destFile = new File("src\"); //写方法实现文件夹的复制,参数为数据源File对象和目的地File对象 copyFolder(srcFile, destFile); } //复制文件夹 private static void copyFolder(File srcFile, File destFile) throws IOException { //判断数据源File是否是目录 if (srcFile.isDirectory()) { //是则在目的地下创建和数据源File名称一样的目录 String srcFileName = srcFile.getName(); //下面实际上在src中复制了原始目录java1 File newFolder = new File(destFile, srcFileName); //判断目的地目录对应的File是否存在,不存在则创建 if (!newFolder.exists()) { newFolder.mkdir(); } //获取数据源File下文件或者目录的File数组 File[] fileArray = srcFile.listFiles(); //遍历该File数组,得到每一个File对象;可能是文件对象,也可能是目录对象 for (File file : fileArray) { //把该File作为数据源File对象,递归调用复制文件夹的方法 copyFolder(file, newFolder); } } else { //不是目录,说明书文件,直接使用字节流复制即可 File newFile = new File(destFile, srcFile.getName()); copyFile(srcFile, newFile); } } //字节缓冲流复制文件 private static void copyFile(File srcFile, File destFile) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)); byte[] bys = new byte[1024]; int len; while ((len = bis.read(bys)) != -1) { bos.write(bys, 0, len); } bis.close(); bos.close(); }
}
复制文件的异常处理
JDK7改进方案:
格式:
try(定义流对象){
可能出现异常的代码;
} catch(异常类名 变量名){//异常类名要和出现的异常匹配
异常的处理代码;
}
自动释放资源
JDK9改进方案:
格式:
定义输入流对象;
定义输出流对象;
try(输出流对象;输出流对象){
可能出现异常的代码;
} catch(异常类名 变量名){//异常类名要和出现的异常匹配
异常的处理代码;
}
自动释放资源
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//jdk7比较可取
}
//jdk9之后的改进方案;还需要抛出
private static void method4() throws IOException {
FileReader fr = new FileReader("f.txt");
FileWriter fw = new FileWriter("f1.txt");
try (fr; fw) {
char[] chs = new char[1024];
int len;
while ((len = fr.read()) != -1) {
fw.write(chs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//jdk7之后的改进方案;会自动释放资源
private static void method3() {
try (FileReader fr = new FileReader("f.txt");
FileWriter fw = new FileWriter("f1.txt");) {
char[] chs = new char[1024];
int len;
while ((len = fr.read()) != -1) {
fw.write(chs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//标准异常处理:try…catch…finally
private static void method2() {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("f.txt");
fw = new FileWriter("f1.txt");
char[] chs = new char[1024];
int len;
while ((len = fr.read()) != -1) {
fw.write(chs, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//抛出处理
private static void method1() throws IOException {
FileReader fr = new FileReader("f.txt");
FileWriter fw = new FileWriter("f1.txt");
char[] chs = new char[1024];
int len;
while ((len = fr.read()) != -1) {
fw.write(chs, 0, len);
}
fw.close();
fr.close();
}
}
特殊操作流
标准输入输出流
System类中有两个静态的成员变量:
public static final InputStream in
:标准输入流。通常该流对应于键盘输入或者由主机环境或用户指定的另一个输入源public static final PrintStream out
:标准输出流。通常该流对应于显示输出或者由主机环境或用户指定的另一个输出目标
输入:
自己实现键盘录入数据:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Java提供:
Scanner sc = new Scanner(System.in);
输出:
输出语句的本质:是一个标准的输出流
PrintStream ps = System.out;
- PrintStream 类有的方法, System.out都可以使用
打印流
打印流特点:
- 只负责输出数据,不负责读取数据
- 有自己特有方法
字节打印流
字节打印流:PrintStream
- 字节打印流:
PrintStream(String fileName)
:使用指定的文件名创建新的打印流 - 使用继承父类方法写数据,查看的时候会转码;使用自己的特有方法写数据,数据原样输出
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//字节打印流:PrintStream(String fileName):使用指定的文件名创建新的打印流
PrintStream ps = new PrintStream("src\f.txt");
//写数据
//字节输出流有的方法
ps.write(97);
//a
ps.write(98);
//b
//使用特有方法
ps.print(97);
//97
ps.print(98);
//98
ps.println(97);
//换行
//释放资源
ps.close();
}
}
字符打印流
字符打印流:PrintWriter
构造方法:
方法名
说明
PrintWriter(String fileName)
使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新
PrintWriter(Writer out,boolean autoFlush)
创建一个新的PrintWriter:
out:字符输出流
autoFlush:一个布尔值,为真,则println,print,或format方法刷新输出缓冲区
代码演示:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//PrintWriter(String fileName);使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新
PrintWriter pw = new PrintWriter("src\f.txt");
pw.write("java");
//换行
pw.write("
");
pw.flush();
pw.println("hello");
pw.flush();
//PrintWriter(Writer out,boolean autoFlush);创建一个新的PrintWriter
//比较简便
PrintWriter pw = new PrintWriter(new FileWriter("src\f.txt"), true);
pw.println("hello");
pw.println("world");
//会自动换行并刷新
//释放资源
pw.close();
}
}
案例
-
复制Java文件(打印流改进版)
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
//根据数据源创建字符输入流对象
BufferedReader br = new BufferedReader(new FileReader(“src\jicheng\Cat.java”));
//根据目的地创建字符输出流对象
PrintWriter pw = new PrintWriter(new FileWriter(“src\f.java”));
//读写数据,复制文件
String line;
while ((line = br.readLine()) != null) {
pw.println(line);
}
//释放资源
br.close();
pw.close();
}
}
序列化流
对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息;字节序列写到文件之后,相当于文件中持久保存了一个对象的信息;反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
要实现序列化和反序列化就要使用对象序列化流和对象反序列化流:
- 对象序列化流:ObjectOutputStream
- 对象反序列化流:ObjectInputStream
对象序列化流
对象序列化流:ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来完成对象的持久存储。 如果流是网络套接字流,则可以在另一个主机或另一个进程中重新构建对象
构造方法:
ObjectOutputStream(OutputStream out)
:创建一个写入指定OutputStream的ObjectOutputStream
序列化对象方法:
void writeObject(Object obj)
:将指定的对象写入ObjectOutputStream
注意:
-
一个对象要想被序列化,该对象所属的类就必须实现Serializable接口
-
Serializable是一个标记接口,实现该接口,不要重写任何方法
代码演示:
//学生类
import java.io.Serializable;
public class Student implements Serializable {//该接口只是标识接口,无方法需要重写;看见该接口要知道该对象可以被序列或反序列化
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;
}
}
//操作
import java.io.*;
/*
异常:NotSerializableException:当实例需要具有Serializable接口时抛出。序列化运行时或实例的类可以抛出此异常
类的序列化由实现java.io.Serializable接口的类启用;不实现此接口的类不会使任何状态序列化或反序列化
*/
public class Demo {
public static void main(String[] args) throws IOException {
//ObjectOutputStream(OutputStream out);创建一个写入指定OutputStream的ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\f1.txt"));
//创建对象
Student s = new Student("勒布朗", 37);
//实现对象序列化
//void writeObject(Object obj);将指定的对象写入ObjectOutputStream
oos.writeObject(s);
//释放资源
oos.close();
}
}
对象反序列化流
对象反序列化流:ObjectInputStream
- ObjectInputStream对先前使用ObjectOutputStream编写的原始数据和对象进行反序列化
构造方法:
ObjectInputStream(InputStream in)
:创建一个从指定的InputStream读取的ObjectInputStream
序列化对象方法:
Object readObject()
:从ObjectInputStream读取一个对象
代码演示:
//学生类
import java.io.Serializable;
public class Student implements Serializable {//该接口只是标识接口,无方法需要重写;看见该接口要知道该对象可以被序列或反序列化
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;
}
}
//操作
import java.io.*;
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//ObjectInputStream(InputStream in);创建一个从指定的InputStream读取的ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\f1.txt"));
//Object readObject();从ObjectInputStream读取一个对象
Object obj = ois.readObject();
//因为已知是学生类,所以可以向下转型
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
//释放资源
ois.close();
}
}
对象序列化流的三个问题
用对象序列化流序列化了一个对象后,加入我们修改了对象所属的类文件,读取数据会不会出问题呢?
- 会出问题,抛出InvalidClassException异常
若有问题,该如何解决呢?
-
给对象所属的类加一个serialVersionUID(序列化值)
private static final long serialVersionUID = 42L;(序列化值由自己更改)
如果一个对象中的某个成员变量的值不想被序列化,该如何实现呢?
- 给该成员变量加transient关键字修饰,该关键字标记的成员变量就不参与序列化过程了
代码演示:
//学生类
import java.io.Serializable;
public class Student implements Serializable {
//该值可更改
private static final long serialVersionUID = 42L;
private String name;
// private int age;
private transient int age;//被transient修饰不参与序列化
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;
}
//重写toString方法,即修改了对象所属的类文件
/* @Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}*/
}
//操作
import java.io.*;
/*
用对象序列化流序列化了一个对象后,加入我们修改了对象所属的类文件,读取数据会不会出问题呢?
java.io.InvalidClassException:
Serialization运行时检测到类的以下问题之一时抛出。
1.该类的串行版本与从流中读取的类描述符的版本不匹配;
2.该类包含未知的数据类型;
3.该类没有可访问的no-arg构造函数
序列化运行时将每个可序列化类与版本号相关联,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已加载与该序列化兼容的该对象的类
如果接收者已经为具有与相应发送者类别不同的serialVersionUID的对象加载了类,则反序列化将导致InvalidClassException
Student_Demo.Student; local class incompatible:
stream classdesc serialVersionUID = 2985971177426882671,
local class serialVersionUID = -113671151833379565
若有问题,该如何解决呢?
方法:给对象所属的类加一个序列化值:private static final long serialVersionUID = 42L;
可序列化类可以通过声明名为"serialVersionUID"必须为static,final和long类型的字段来显式声明其自己的serialVersionUID
强烈建议所有可序列化类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类细节高度敏感,这些细节可能因编译器实现而异,因此在反序列化期间可能会导致意外的InvalidClassException
如果一个对象中的某个成员变量的值不想被序列化,该如何实现呢?
*/
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
write();
read();
}
//序列化
private static void write() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\f1.txt"));
Student s = new Student("勒布朗", 37);
oos.writeObject(s);
oos.close();
}
//反序列化
private static void read() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\f1.txt"));
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
}
Properties类
概述
- Properties是一个Map体系的集合类;继承自Hashtable;(Hashtable和HashMap用法一样)
- Properties可以保存到流中或者可以从流中加载
Properties作为集合的使用:
import java.util.Properties;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建集合对象
Properties prop = new Properties();
//存储元素
prop.put("it001", "勒布朗");
prop.put("it002", "浓眉");
prop.put("it003", "威少");
//遍历集合
Set<Object> keySet = prop.keySet();
for (Object key : keySet) {
Object value = prop.get(key);
System.out.println(key + "," + value);
}
}
}
Properties作为集合的特有方法
方法名
说明
Object setProperty(String key,String Value)
设置集合的键和值,都是String类型,底层调用Hashtable方法put方法
String getProperty(String key)
使用此属性列表中指定的键搜索属性
Set<String>stringPropertyNames()
从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
代码演示:
import java.util.Properties;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建集合对象
Properties prop = new Properties();
//Object setProperty(String key,String Value);设置集合的键和值,都是String类型,底层调用Hashtable方法put方法
prop.setProperty("it001", "勒布朗");
prop.setProperty("it002", "浓眉");
prop.setProperty("it003", "威少");
System.out.println(prop);
//输出:{it003=威少, it002=浓眉, it001=勒布朗}
/*
Object setProperty(String key, String value) {
return put(key, value);
Object put(Object key, Object value) {
return map.put(key, value);
使得接受Object的方法变成了只能接收String类型方法
*/
//String getProperty(String key);使用此属性列表中指定的键搜索属性
System.out.println(prop.getProperty("it001"));
//输出:勒布朗
System.out.println(prop.getProperty("it004"));
//输出:null
//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(Output Stream out,String comments)
将此属性列表(键和元素对)写入Properties表中,一适合于使用load(InputStream)方法的格式写入输出字节流
void store(Writer writer String comments)
将此属性列表(键和元素对)写入Properties表中,一适合于使用load(InputStream)方法的格式写入输出字符流
代码演示(只演示字符方法):
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws IOException {
//把集合中的数据保存到文件
//myStore();
//把问加你中的数据加载到集合
myLoad();
}
private static void myStore() throws IOException {
Properties prop = new Properties();
prop.setProperty("it001", "勒布朗");
prop.setProperty("it002", "浓眉");
prop.setProperty("it003", "威少");
//void store(Writer writer String comments);将此属性列表(键和元素对)写入Properties表中,一适合于使用load(InputStream)方法的格式写入输出字符流
FileWriter fw = new FileWriter("src\f.txt");
prop.store(fw, null);
//第二个参数是描述信息
fw.close();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
//void load(Reader reader);从输入字符流读取属性列表(键和元素对)
FileReader fr = new FileReader("src\f.txt");
prop.load(fr);
fr.close();
//看看是否成功加载到集合
System.out.println(prop);
}
}
案例
-
游戏次数: 写程序实现猜数字小游戏,只能试玩三次,如果还想玩,提示需要充值
//游戏类
import java.util.Random;
import java.util.Scanner;public class Lei {
public Lei() {
}public static void start() { //随机生成要猜的数字,范围0-100 Random r = new Random(); int number = r.nextInt(100) + 1; while (true) { Scanner sc = new Scanner(System.in); System.out.println("请输入你要猜的数字:"); int guessNumber = sc.nextInt(); if (guessNumber > number) { System.out.println("你猜的数字" + guessNumber + "大了"); } else if (guessNumber < number) { System.out.println("你猜的数字" + guessNumber + "小了"); } else { System.out.println("恭喜你猜对了"); break; } } }
}
//操作
import java.io.*;
import java.util.Properties;public class Demo {
public static void main(String[] args) throws IOException {
//从文件中读取数据到Properties集合,用load()方法实现
Properties prop = new Properties();
FileReader fr = new FileReader(“src\f.txt”);
prop.load(fr);
fr.close();//通过Properties集合获取到玩游戏的次数 String count = prop.getProperty("count"); //转换为int类型 int number = Integer.parseInt(count); //判断次数是否到3次了 if (number >= 3) { System.out.println("游戏试玩结束,想玩请充值(www.xxxx.com)"); } else { //玩游戏 Lei.start(); //次数加一,重新写回文件,用Properties的store()方法实现 number++; //用valueOf()将int类型转为String类型 prop.setProperty("count", String.valueOf(number)); FileWriter fw = new FileWriter("src\f.txt"); prop.store(fw, null); fw.close(); } }
}
多线程
概述
进程:是正在运行的程序
- 是系统进行资源分配和调用的独立单位
- 每一个进程都有它自己的内存空间和系统资源
线程:是进程中的单个顺序控制流,是一条执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序
- 多线程:一个进程如果有多条执行路径,则称为多线程程序
线程生命周期
实现多线程
多线程实现方式
方式1:继承Thread类
- 定义一个类继承Thread类
- 在类中重写run()方法
- 创建类的对象
- 启动线程
说明:
- 重写run()方法;因为run()方法是用来封装被线程执行的代码
run()方法和start()方法的区别:
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():启动线程;然后由JVM调用此线程的run()方法
方式2:实现Runnable接口 (推荐使用)
- 定义一个类实现Runnable接口
- 在类中重写run()方法
- 创建类的对象
- 创建Thread类的对象,把类对象作为构造方法的参数
- 启动线程
相比继承Thread类,实现Runnable接口的好处
- 避免了Java单继承的局限性
- 适合多个相同程序的代码区处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
代码演示:
//方式1:
//继承类
public class MyThread extends Thread {
/**
* 重写run方法区分哪些是被线程执行的
*/
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//my1.run();
//my2.run();
//void start();导致此线程开始执行; Java虚拟机调用此线程的run方法。
my1.start();
my2.start();
}
}
------------------------------------------------------------
//方式2:
//继承类
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//拿到当前执行的线程,再调用getName()方法;不能直接调用,因为没有继承Thread类
//默认名称为:Thread-x
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
//创建MyRunnable对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)分配新的Thread对象
//创建了两个线程
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
//Thread(Runnable target, String name)分配新的Thread对象
//设置名称的构造方法
Thread t1 = new Thread(my, "勒布朗");
Thread t2 = new Thread(my, "浓眉");
//启动线程
t1.start();
t2.start();
}
}
设置和获取线程名称
Thread类中设置和获取线程名称的方法
void setName(String name)
:将此线程的名称更改为等于参数nameString getName()
:返回此线程的名称- 通过构造方法也可以设置线程名称
获取main()方法所在线程的名称:
public static Thread currentThread()
:返回对当前正在执行的线程对象的引用
代码演示:
//继承类
public class MyThread extends Thread {
//通过构造方法也可以设置线程名称
//Thread(String name)分配新的Thread对象;(Thread类)
public MyThread() {
}
public MyThread(String name) {
//访问父类(Thread类)带参构造方法
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//String getName();返回此线程的名称;是Thread类中的方法,自动访问父类的无参构造方法得到默认名称
//默认名称为:Thread-x
System.out.println(getName() + ":" + i);
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//void setName(String name);将此线程的名称更改为等于参数name
my1.setName("勒布朗");
my2.setName("浓眉");
my1.start();
my2.start();
//通过构造方法也可以设置线程名称
MyThread my1 = new MyThread("勒布朗");
MyThread my2 = new MyThread("浓眉");
//static Thread currentThread();返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());
//输出:main;即main方法是在一个线程名叫做main的线程中执行的
}
}
线程优先级
线程调度
线程有两种调度模型
- 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模型:优先让优先级高的线程使用CPU;如果线程的优先级相同,那么会随机选择一个;优先级高的线程获取的CPU时间片相对多一些
Java使用的是抢占式调度模型;多线程程序的执行室友随机性的,因为谁抢到的CPU使用权是不一定的
Thread类中设置和获取线程优先级的方法:
public final int getPriority()
:返回此线程的优先级public final void setPriority(int newPriority)
:更改此线程的优先级
线程默认优先级为5;线程优先级的范围是0-10
线程优先级高仅仅表示线程获取CPU时间片的几率高
代码演示:
//继承类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//String getName();返回此线程的名称;是Thread类中的方法,自动访问父类的无参构造方法得到默认名称
//默认名称为:Thread-x
System.out.println(getName() + ":" + i);
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
MyThread my3 = new MyThread();
my1.setName("勒布朗");
my2.setName("浓眉");
my3.setName("库里");
//public final int getPriority();返回此线程的优先级
System.out.println(my1.getPriority());
System.out.println(my2.getPriority());
System.out.println(my3.getPriority());
//均输出:5;即线程默认值为5
//线程范围在1-10
System.out.println(Thread.MAX_PRIORITY);
//输出:10
System.out.println(Thread.MIN_PRIORITY);
//输出:1
System.out.println(Thread.NORM_PRIORITY);
//输出:5
//public final void setPriority(int newPriority);更改此线程的优先级
my1.setPriority(10);
my2.setPriority(5);
my3.setPriority(1);
//启动线程
my1.start();
my2.start();
my3.start();
}
}
线程控制
方法名
说明
static void sleep(long millis)
使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join()
等待这个线程死亡
void setDaemon(boolean on)
将此线程标记为守护线程,当线程都是守护线程时,Java虚拟机将退出
代码演示:
//sleep方法
//继承类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
//static void sleep(long millis);使当前正在执行的线程停留(暂停执行)指定的毫秒数
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
MyThread my3 = new MyThread();
my1.setName("勒布朗");
my2.setName("浓眉");
my3.setName("库里");
//启动线程
my1.start();
my2.start();
my3.start();
//没隔100毫秒3个都运行一次
}
}
------------------------------------------------------------
//join方法
//继承类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
MyThread my3 = new MyThread();
my1.setName("勒布朗");
my2.setName("浓眉");
my3.setName("库里");
//启动线程
my1.start();
//void join();等待这个线程死亡
try {
my1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
my2.start();
my3.start();
//等待my1全部执行完,后面才一次抢占执行
}
}
------------------------------------------------------------
//setDaemon方法
//继承类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.setName("浓眉");
my2.setName("库里");
//设置主线程为勒布朗
Thread.currentThread().setName("勒布朗");
//设置守护线程
//void setDaemon(boolean on);将此线程标记为守护线程,当线程都是守护线程时,Java虚拟机将退出
my1.setDaemon(true);
my2.setDaemon(true);
//启动线程
my1.start();
my2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
//在前9次,三者抢占线程;9过后,主线程结束,剩余线程只会执行几次后就结束
}
}
线程同步
概述
判断多线程程序是否会有数据安全问题的标准
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
解决多线程安全问题
- 基本思想:让程序没有安全问题的环境
- 实现:把多条语句操作共享数据的代码给锁起立,让任意时刻只能有一个线程执行即可;Java提供了同步代码块的方式来解决
存在线程安全的卖票案例:
//实现线程类
public class SellTicket implements Runnable {
private int tickets = 100;
/**
* 重写run方法实现卖票
*/
@Override
public void run() {
while (true) {
//票数大于0,就开始卖票;卖一张则减一;票没有也继续执行,因为有人还会继续在窗口问
if (tickets > 0) {
//通过sleep方法模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
//操作
public class Demo {
public static void main(String[] args) {
//创建SellTicket对象
SellTicket st = new SellTicket();
//创建三个Thread类的对象,把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.相同的票会售卖多次
/*
在某一线程休息时,另一进程抢到了CPU的执行权;当按顺序结束休眠时,就会出现出现售卖同一张票
*/
//2。票会出现负数
/*
如果没有按顺序醒来,某一进程继续拥有CPU的执行权,就会执行减一操作,若已经减到0;那么下一线程执行时,再执行减一操作就会出现负数
*/
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized (任意对象){
多条语句操作共享数据的代码
}
synchronized (任意对象):就相当于给代码加锁了,任意 对象就可以看成是一把锁
同步的好处与弊端:
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,很耗费资源,无形中会降低程序的运行效率
加锁代码演示:
//实现线程类
public class SellTicket implements Runnable {
private int tickets = 100;
//外部定义一把锁,则使用的都是同一把锁了
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
//某一线程进来后,就会把这段代码给锁住;即使这一线程在休眠,外部线程也得等内部的执行完
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
}
//操作
public class Demo {
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关键字加到方法上
格式:修饰符 synchronized 返回值类型 方法名(方法参数){ }
同步方法的锁对象是:this
同步静态方法:就是把synchronized关键字加到静态方法上
格式:修饰符 static synchronized 返回值类型 方法名(方法参数){ }
同步静态方法的锁对象是:类名.class
代码演示:
//实现线程类
public class SellTicket implements Runnable {
private static int tickets = 100;
private int x = 0;
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
//同步方法的锁对象
//synchronized (this)
//同步静态方法的锁对象
synchronized (SellTicket.class) {
//某一线程进来后,就会把这段代码给锁住;即使这一线程在休眠,外部线程也得等内部的执行完
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
} else {
sellTicket();
}
}
}
//同步方法
/*private void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}*/
//同步静态方法
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
//操作
public class Demo {
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();
}
}
线程安全的类
StringBuffer类
- 线程安全,可变的字符序列;执行同步
- 从JDK 5版本开始,这个类已经补充了一个设计用于单个线程的等效类,
StringBuilder
;通常应优先使用StringBuilder
类,因为它支持所有相同的操作,且速度更快,因为StringBuilder
类不执行同步。
Vector类
- 从Java 2平台v1.2开始,该类被改进以实现List接口,使其成为Java Collections Framework(Java集合体系)的成员;与新的集合实现不同,
Vector
是同步的。 如果不需要线程安全实现,建议使用ArrayList代替Vector
。
Hashtable类
- 该类实现了一个哈希表,它将键映射到值;任何非
null
对象都可以用作键或值 - 从Java 2平台v1.2开始,该类被改进以实现Map接口,使其成为Java Collections Framework(Java集合体系)的成员;与新的集合实现不同,
Hashtable
是同步的。 如果不需要线程安全实现,建议使用HashMap代替Hashtable
。 如果需要线程安全的高度并发实现,则建议使用ConcurrentHashMap代替Hashtable
。
代码演示:
import java.util.*;
public class Test {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
StringBuilder sb1 = new StringBuilder();
Vector<String> v = new Vector<>();
ArrayList<String> array = new ArrayList<>();
Hashtable<String, String> ht = new Hashtable<>();
HashMap<String, String> hm = new HashMap<>();
//StringBuffer一般在多线程环境会使用到,但Vector、Hashtable也很少使用,因为被以下方法替代了
//static <T> List<T> synchronizedList(List<T> list)返回由指定列表支持的同步(线程安全)列表
Collections.synchronizedList(new ArrayList<String>());
//通过该方法调用即变成线程安全的了
}
}
Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock
实现提供了比使用synchronized
方法和语句可以获得的更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock()
:获得锁void unlock()
:释放锁
Lock是接口,不能直接实例化,采用子类示例化;eg:
ReentrantLock方法
ReentrantLock()
:创建一个ReentrantLock的实例
代码演示:
//实现线程类
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
private static int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} finally {
//释放锁
lock.unlock();
}
}
}
}
//为了防止锁住的代码块有错误,所以用try包裹住,以上为标准写法
//操作
public class Demo {
public static void main(String[] args) {
//创建SellTicket对象
SellTicket st = new SellTicket();
//创建三个Thread类的对象,把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();
}
}
生产者消费者
概述
生产者消费者模式是一个十分经典的多线程协作的模式;所谓生产者消费者问题,实际上主要包含了两类线程:
- 一类是生产者线程用于生产数据
- 一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,像是仓库
- 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
- 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
如果仓库空缺,则需要相互提醒,即生产和消费过程中的等待和唤醒问题,Java提供了如下方法(在Object类中):
Object类中的等待和唤醒方法:
方法名
说明
void wait()
导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
void notify()
唤醒正在等待对象监视器的单个线程
void notifyAll()
唤醒正在等待对象监视器的所有线程
案例解释
//生产者类
public class Producer implements Runnable {
private Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
b.put(i);
}
}
}
//消费者类
public class Customer implements Runnable {
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
//中转仓库
public class Box {
//定义一个成员变量,表示第x瓶奶
private int milk;
//定义一个成员变量,表示奶箱状态
private boolean state = false;
/**
* 提供存储牛奶和获取牛奶的方法
*/
//synchronized保证了同步,这样使用wait方法才不会异常
public synchronized void put(int milk) {
//如果有牛奶,等待消费
if (state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有牛奶就生产牛奶
this.milk = milk;
System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
//生产完毕后,修改奶箱状态(需要继续生产牛奶)
state = true;
//唤醒其他等待的线程
notifyAll();
}
public synchronized void get() {
//如果没有牛奶,等待生产
if (!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有牛奶,就消费牛奶
System.out.println("用户拿到第" + this.milk + "瓶奶");
//消费完毕后,修改奶箱转态(没有牛奶了)
state = false;
//唤醒其他等待的线程
notifyAll();
}
}
//测试类
public class Demo {
public static void main(String[] args) {
//创建Box对象,这里是共享数据区域
Box b = new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer p = new Producer(b);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c = new Customer(b);
//创建2个线程对象分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//启动线程
t1.start();
t2.start();
}
}
网络编程
概述
计算机网络:是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
网络编程:在网络通信协议下,实现网络互联的不同计算机上运行的程序间可以进行数据交换
网络编程三要素:IP地址、端口、协议
网络编程三要素
IP地址
IP地址常用doc命令:
- ipconfig:查看本机IP地址;eg:
ipconfig
- ping IP地址:检查网络是否连通;eg:
ping 192.168.1.4
特殊IP地址:
- 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试;eg:
ping 127.0.0.1
InetAddress类的使用:
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用
InetAddress:此类表明Internet协议(IP)地址
有以下常用方法:
方法名
说明
static InetAddress getByName(String host)
确定主机名称的IP地址;主机名称可以是机器名称,也可以是IP地址
String getHostName()
获取此IP地址的主机名
String getHostAddress()
返回文本表示中的IP地址字符串
代码演示:
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test {
public static void main(String[] args) throws UnknownHostException {
//static InetAddress getByName(String host)根据主机名称确定主机的IP地址;主机名称可以是机器名称,也可以是IP地址
//机器名称
InetAddress address = InetAddress.getByName("LAPTOP-1O6LRCNR");
//IP地址;推荐使用IP地址
InetAddress address = InetAddress.getByName("192.168.1.4");
//String getHostName()获取此IP地址的主机名
String name = address.getHostName();
//String getHostAddress()返回文本表示中的IP地址字符串
String ip = address.getHostAddress();
System.out.println("主机名:" + name);
//输出:主机名:LAPTOP-1O6LRCNR
System.out.println("IP地址:" + ip);
//输出:IP地址:192.168.1.4
}
}
端口
网络的通信,本质上是两个应用程序的通信;每台计算机都有很多的应用程序,端口号就是为了区分这些程序;即可以唯一标识设备中的应用程序,也就是应用程序的标识
协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接个通信时需要遵守一定的规则,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率。传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换;常见的协议有UDP协议和TCP协议
UDP通信程序
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接受数据的对象;因此对于基于UDP协议的通信双方而言,是没有所谓的客户端和服务器的概念
Java提供了DatagramSocket类作为UDP协议的Socket
UDP发送数据
发送数据的步骤:
- 创建发送端Socket对象
DatagramSocket()
- 创建数据,并把数据打包
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
- 调用DatagramSocket对象的方法发送数据
void send(DatagramPacket p)
- 关闭发送端
void close()
代码演示:
import java.io.IOException;
import java.net.*;
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象
//DatagramSocket()构造一个数据报套接字并将其绑定到本地主机上的任何可用端口。
DatagramSocket ds = new DatagramSocket();
//创建数据,并把数据打包
/* //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造一个数据报包,用于将长度为length的数据包发送到指定主机上的指定端口号
byte[] bys = "hello,udp,我来了".getBytes();
//getBytes():使用平台的默认字符集将此String编码为字节序列,将结果存储到新的字节数组中
int length = bys.length;
InetAddress address = InetAddress.getByName("192.168.1.4");
//确定主机名称的IP地址
int port = 10086;
//端口号
DatagramPacket dp = new DatagramPacket(bys, length, address, port);*/
//做优化如下
byte[] bys = "hello,udp,我来了".getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.1.4"), 10086);
//调用DatagramSocket对象的方法发送数据
//void send(DatagramPacket p)从此套接字发送数据报包
ds.send(dp);
//关闭发送端
//void close()关闭此数据报套接字。
ds.close();
}
}
UDP接收数据
接收数据的步骤:
- 创建接收端Socket对象
DatagramSocket(int port)
- 创建数据包,用于接收数据
DatagramPacket(byte[] buf, int length)
- 调用DatagramSocket对象的方法接收数据
void receive(DatagramPacket p)
- 解析数据包,并把数据在控制台显示
byte[] getData()
返回数据缓冲区
int getLength()
返回要发送的数据的长度或接收的数据的长度
- 关闭发送端
void close()
代码演示:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象
//DatagramSocket(int port)构造一个数据报套接字并将其绑定到本地主机上的指定端口
DatagramSocket ds = new DatagramSocket(10086);
//创建数据包,用于接收数据
//DatagramPacket(byte[] buf, int length)构造DatagramPacket用于接收长度为 length数据包
byte[] bys = new byte[1024];
//1024为数据包大小
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
//void receive(DatagramPacket p)从此套接字接收数据报包
ds.receive(dp);
//解析数据包,并把数据在控制台显示
/*//byte[] getData()返回数据缓冲区
byte[] datas = dp.getData();
//int getLength()返回要发送的数据的长度或接收的数据的长度
//确保不接收跟更多的不需要的数据
int len = dp.getLength();
String dataString = new String(datas, 0, len);
//转为字符串
System.out.println("数据是:" + dataString);*/
//优化如下
System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
//关闭发送端
ds.close();
}
}
练习
//发送端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.*;
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象
DatagramSocket ds = new DatagramSocket();
//自己封装键盘录入数据
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null) {
//输入的数据是886,发送数据结束
if ("886".equals(line)) {
break;
}
//创建数据,并把数据打包
byte[] bys = line.getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.1.4"), 10086);
//调用DatagramSocket对象的方法发送数据
ds.send(dp);
}
//关闭发送端
ds.close();
}
}
//接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象
DatagramSocket ds = new DatagramSocket(10086);
while(true) {
//创建数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp);
//解析数据包,并把数据在控制台显示
System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
}
}
}
TCP通信原理
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信
Java对基于TCP协议的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信
Java为客户端提供了Socket类,为服务器提供了ServerSocket类
TCP发送数据
发送数据的步骤:
- 创建客户端Socket对象
Socket(String host, int port)
- 获取输出流,写数据
OutputStream getOutputStream()
- 释放资源
void close()
代码演示:
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
//Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号
Socket s = new Socket("192.168.1.4", 10000);
//获取输出流,写数据
//OutputStream getOutputStream()返回此套接字的输出流
OutputStream os = s.getOutputStream();
os.write("hello,tcp,我来了".getBytes());
//释放资源
s.close();
}
}
TCP接收数据
接收数据的步骤:
- 创建服务器端Socket对象
ServerSocket(int port)
- 监听客户端连接,返回一个Socket对象
Socket accept()
- 获取输入流,读数据,并把数据显示在控制台
InputStream getInputStream()
- 释放资源
void close()
代码演示:
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
//ServerSocket(int port)创建绑定到指定端口的服务器套接字
ServerSocket ss = new ServerSocket(1000);
//侦听对此套接字的连接并接受它
//Socket accept()
Socket s = ss.accept();
//获取输入流,读数据,并把数据显示在控制台
//InputStream getInputStream()返回此套接字的输入流
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();
ss.close();
}
}
练习
-
服务器给出反馈
//客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;public class SendDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(“192.168.1.4”, 1000);//获取输出流,写数据 OutputStream os = s.getOutputStream(); os.write("hello,tcp,我来了".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(); }
}
//服务端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(1000);//侦听对此套接字的连接并接受它 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(); }
}
-
客户数据来自键盘录入
//客户端
import java.io.*;
import java.net.Socket;public class SendDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(“192.168.1.4”, 1000);//获取输出流,写数据;数据来自键盘录入(自行包装的) BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //封装输出流对象;字节流转为字符流,再转为字符缓冲流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line; while ((line = br.readLine()) != null) { if ("886".equals(line)) { break; } //获取输出流对象 bw.write(line); bw.newLine(); bw.flush(); } //释放资源 s.close(); }
}
//服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(1000);//侦听对此套接字的连接并接受它 Socket s = ss.accept(); //获取输入流,读数据,并把数据显示在控制台 //字节流转换字符流,再转换字符缓冲流 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String line; while ((line = br.readLine()) != null) { System.out.println("数据是:" + line); } //释放资源 ss.close(); }
}
//客户端
//服务端 -
客户端数据来自控制台输入,服务器数据写入文本文件
//客户端
import java.io.*;
import java.net.Socket;public class SendDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(“192.168.1.4”, 1000);//获取输出流,写数据;数据来自键盘录入(自行包装的) BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //封装输出流对象;字节流转为字符流,再转为字符缓冲流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line; while ((line = br.readLine()) != null) { if ("886".equals(line)) { break; } //获取输出流对象 bw.write(line); bw.newLine(); bw.flush(); } //释放资源 s.close(); }
}
//服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(1000);//侦听对此套接字的连接并接受它 Socket s = ss.accept(); //接收数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //把数据写入文本文件 BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt")); String line; while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); } //释放资源 ss.close(); bw.close(); }
}
//客户端
//服务端 -
客户端数据来自于文本文件,并写入文本文件
//客户端
import java.io.*;
import java.net.Socket;public class SendDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(“192.168.1.4”, 1000);//封装文本文件的数据 BufferedReader br = new BufferedReader(new FileReader("src\f1.txt")); //封装输出流对象;字节流转为字符流,再转为字符缓冲流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line; while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); } //释放资源 br.close(); s.close(); }
}
//服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(1000);//侦听对此套接字的连接并接受它 Socket s = ss.accept(); //接收数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //把数据写入文本文件 BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt")); String line; while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); } //释放资源 ss.close(); bw.close(); }
}
-
上传文件时服务器给出反馈
void shutdownOutput()
禁用此套接字的输出流;对于TCP套接字,将发送任何先前写入的数据,然后发送TCP的正常连接终止序列
//客户端
import java.io.*;
import java.net.Socket;
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket("192.168.1.4", 1000);
//封装文本文件的数据
BufferedReader br = new BufferedReader(new FileReader("src\f1.txt"));
//封装输出流对象;字节流转为字符流,再转为字符缓冲流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//void shutdownOutput()禁用此套接字的输出流
//对于TCP套接字,将发送任何先前写入的数据,然后发送TCP的正常连接终止序列
s.shutdownOutput();
//接收反馈
BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
String data = brClient.readLine();
System.out.println("服务器的反馈:" + data);
//释放资源
br.close();
s.close();
}
}
//服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(1000);
//侦听对此套接字的连接并接受它
Socket s = ss.accept();
//接收数据
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//把数据写入文本文件
BufferedWriter bw = new BufferedWriter(new FileWriter("src\f.txt"));
String line;
while ((line = br.readLine()) != null) {
//while没有结束,会一直在这里等待数据
bw.write(line);
bw.newLine();
bw.flush();
}
//给出反馈
BufferedWriter bwSever = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bwSever.write("文件上传成功");
bwSever.newLine();
bwSever.flush();
//释放资源
ss.close();
bw.close();
}
}
-
多线程实现文件上传
//客户端
import java.io.*;
import java.net.Socket;public class SendDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(“192.168.1.4”, 1000);//封装文本文件的数据 BufferedReader br = new BufferedReader(new FileReader("src\f1.txt")); //封装输出流对象;字节流转为字符流,再转为字符缓冲流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line; while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); } //void shutdownOutput()禁用此套接字的输出流 //对于TCP套接字,将发送任何先前写入的数据,然后发送TCP的正常连接终止序列 s.shutdownOutput(); //接收反馈 BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream())); String data = brClient.readLine(); System.out.println("服务器的反馈:" + data); //释放资源 br.close(); s.close(); }
}
//服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(1000);while (true) { //侦听对此套接字的连接并接受它 Socket s = ss.accept(); //为每一个客户端开启一个线程 new Thread(new ReceiverThread(s)).start(); } }
}
//服务端调用类
import java.io.*;
import java.net.Socket;public class ReceiverThread implements Runnable {
private Socket s;public ReceiverThread(Socket s) { this.s = s; } @Override public void run() { //接收数据写到文本文件 try { //接收数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //把数据写入文本文件 //解决名称冲突问题 int count = 0; File file = new File("src\[" + count + "].txt"); while (file.exists()) { count++; file = new File("src\[" + count + "].txt"); } BufferedWriter bw = new BufferedWriter(new FileWriter(file)); String line; while ((line = br.readLine()) != null) { //while没有结束,会一直在这里等待数据 bw.write(line); bw.newLine(); bw.flush(); } //给出反馈 BufferedWriter bwSever = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bwSever.write("文件上传成功"); bwSever.newLine(); bwSever.flush(); //释放资源 s.close(); } catch (IOException e) { e.printStackTrace(); } }
}