JAVA学习笔记 第五周

第五周

30.5Collections算法类

30.5.1 sort

升序排序

Collections.sort(list);

比较器的两种实现

Comparator

1:自写类继承比较器Comparator

//自写类MyComparator
public class MyComparator implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2){
        //升序 意思是o1-o2>0  也就是o1>o2  此时交换位置
        return o1-o2;
        //降序
        return o2-o1;
    }
}

public class Test
{
    main{
    	ArrayList<Integer> list = new ArrayList<Integer>();
		list.add(10);
		list.add(12);
		list.add(6);
        Collections.sort(list,new MyComparator());
	}
}
//类似方法实现 与法1无关,用于解释原理
public static void sort(ArrayList<Integer> list, MyComparator comparator) {
		for (int i = 0; i < list.size() - 1; i++) {
			for (int j = 0; j < list.size() - 1 - i; j++) {
				if (comparator.compare(list.get(j), list.get(j + 1)) > 0) {

				}
			}
		}
	}

2:匿名内部类

匿名内部类 new 接口名/抽象类名(){重写的方法}

接口不能new,但匿名类可以

匿名内部类,针对接口和抽象类

main(){
    	ArrayList<Integer> list = new ArrayList<Integer>();
		list.add(10);
		list.add(12);
		list.add(6);
    	Collections.sort(list,new Comparator<Integer>(){
    	//创建对象,接口无法创建对象,但是重写方法
    	@Override
    	public int compare(Integer o1,Integer o2){
        //降序
        return o1-o2;
        //升序
        return o2-o1;
    	}
	});
}

举例

//ArrayList 学生对象,有name,age属性
//----------------------age--------------------------
main(){
    	Student student1 = new Student("name", 23);
		Student student2 = new Student("namew", 12);
		Student student3 = new Student("name3", 56);
    	slist.add(student1);
		slist.add(student2);
		slist.add(student3);
		Collections.sort(slist, new Comparator<Student>() {

			@Override
			public int compare(Student o1, Student o2) {
				
				return o1.getAge() - o2.getAge();
			
			
			}
		});
    	//迭代器输出显示结果
		for (Student s : slist) {
		// 1
//		System.out.print(s.getName() + "      ");
//		System.out.print(s.getAge());
//		System.out.println();
		// 2,要重写student的toString
		System.out.println(s);
		}
    	//------------------------name------------------------
    	Collections.sort(slist, new Comparator<Student>() {

			@Override
			public int compare(Student o1, Student o2) {
				// TODO Auto-generated method stub
				// 字符串compareTo比较字符串大小,比如,s1.s2
				// 根据ASCII走:如果s1>s2 返回1;s1=s2,返回0;s1<s2,返回-1
				return o1.getName().compareTo(o2.getName());
			}
		});
		for (Student s : slist) {
			// 1
//			System.out.print(s.getName() + "      ");
//			System.out.print(s.getAge());
//			System.out.println();
			// 2,要重写student的toString
			System.out.println(s);

		}
}

30.5.2常用方法

//binarySearch
//二分查找,返回位置
int index=Collections.binarySearch(集合,要查找元素);

//max
//返回最大值
int max=Collections.max(集合);

//shuffle
//乱序,顺序打乱
Collections.shuffle(集合);

3o.6后缀表达式

学习 后缀表达式 目的: 编程思维,记住思路,根据思路写代码,运用debug边写边改,不背代码,有大致记忆,用到时百度

public static void main(String[] args) {
		// 中缀表达式,也就是我们理解的表达式
		// 要转换为后缀1,2,+,3,*
		// 额外加一层括号,虽然多余,但是后续结构条件方便判断
		String s1 = "((11+2+3)*3)";
		// 运算符,用于后面判断符号
		String s2 = "+-*/()";
		// temp用于多位数时拼接字符串
		String temp = "";
		// ----------------前期准备----------------------------
		// 存放运算符,linkedlist可以当做栈和队列使用
		LinkedList<String> fuhaon = new LinkedList<String>();
		// 存放后缀表达式
		ArrayList<String> houzhui = new ArrayList<String>();
		// -----------------具体操作---------------------------
		// 遍历 中缀表达式,放入栈
		for (int i = 0; i < s1.length(); i++) {
			// 提取出指定位置符号
			// subString取前不取后,i+1不会越界
			String c = s1.substring(i, i + 1);
			// 分开,判断是否包含运算符,包含就放入符号栈fuhaon
			if (s2.contains(c)) {
				// 多位数
				if (!temp.equals("")) {
					houzhui.add(temp);
					temp = "";
				}
				// 如果是右括号,出栈标志,不入栈,直接出栈,返回值就是出栈元素
				// 考虑优先级,当+前面是*要先弹出*
				if (c.equals("+") || c.equals("-")) {
					String pop = fuhaon.pop();
					if (pop.equals("*") || pop.equals("/")) {
						houzhui.add(pop);
						fuhaon.push(c);

					} else {
						fuhaon.push(pop);
						fuhaon.push(c);
					}
				} else if (c.equals(")")) {
					String pop = fuhaon.pop();
					while (!pop.equals("(")) {
						houzhui.add(pop);
						pop = fuhaon.pop();
					}
				} else {
					// 数字入栈,如果输入的是多位数
					fuhaon.push(c);

				}
			} else {// 不是运算符,就放入数字栈houzhui
				if (c.equals(" ")) {
					continue;
				}
				// 多位数,下一位不是空格,不是运算符,必定是数字
				// 多位数·90进行数字拼=
				temp += c;
//				houzhui.add(c);//个位数时直接出栈
			}
		}
		// ---------------输出最后的符号栈和数值栈------------------
		System.out.println(fuhaon);
		System.out.println(houzhui);
		// -------------------计算------------------------------
		LinkedList<String> yunsuan = new LinkedList<String>();
		// 遍历,发现运算符,前两个数字出栈,进行运算,结果再入栈,遇到符号再出栈,以此类推
		for (String s : houzhui) {
			if (s2.contains(s)) {
				String v1 = yunsuan.pop();
				String v2 = yunsuan.pop();
				Integer v = 0;
				// 判断运算符
				switch (s) {
				case "+":
					v = Integer.valueOf(v1) + Integer.valueOf(v2);
					break;
				case "-":
					// /和- 要 后/-前
					// 如2-1 2/1

					// v1先出栈,v2再出栈
					v = Integer.valueOf(v2) - Integer.valueOf(v1);
					break;
				case "*":
					v = Integer.valueOf(v1) * Integer.valueOf(v2);
					break;
				case "/":
					// 2/1
					// v1=1 v2=2
					v = Integer.valueOf(v2) / Integer.valueOf(v1);
					break;

				default:
					break;
				}
				yunsuan.push(v.toString());
			} else {
				yunsuan.push(s);
			}
		}
		// 计算结果输出
		System.out.println(yunsuan);

	}

30.7 Set接口

30.7.1HashSet

特点:

无序,存储唯一,没有重复,可以去重

常用方法:

add

remove

循环遍历

HashSet<String> set1=new HashSet<String>();
set1.add("name")

//遍历:
//1迭代器
Iterator<String> i1=set1.iterator();
while(i1.hasNext()){
    sout(i1.next());
}
//2增强for
for(String i:set1){
    sout(i);
}

面试题:

既然HashSet有去重功能

那么向set中添加元素时,是如何确保两个对象相同的?

先判断hashCode(),如果hashCode()返回值一样,再判断equals()

对于自定义类型,可以重写hashCode和equals来自定义比较

//例如定义Student类,包含id,name,age,指定name为重复标识符
//在Student类中重写hashCode和equals
@Override
public itn hashCode(){
    return this.name.hashCode();
}
@Override
public boolean equals(Object obj){
    Student s=(Student)obj;
    return this.name,equals(s.getName());
}
//此时,当add两个name相同的对象,自动只留一个

HashSet数据结构和底层实现

1、HashSet的部分源码:
    public class HashSet extends AbstractSet implements Set, Cloneable,Serializable {
              private transient HashMap map;
              private static final Object PRESENT = new Object();
               
              public HashSet() {
                  map = new HashMap();
              }
              public Iterator iterator() {
                  return map.keySet().iterator();
              }
              public boolean add(E e) {
                  return map.put(e, PRESENT)==null;
              }
              public boolean remove(Object o) {
                  return map.remove(o)==PRESENT;
              }
          }
2、HashSet的特点:
  1)由HashSet的源码可知,HashSet底层就是一个HashMap。我们在往HashSet中存储数据的时候,存的都是HashMap的键(key), 它们的值(value)都是一样的:private static final Object PRESENT = new Object()。
  2)HashSet底层的数据结构是一个哈希表,存在HashSet中的元素应该重写其hashCode()方法,故存在HashMap中的key,应该 重写其hashCode()方法。
  3)一个哈希表有三列,第一列是hash码,第二列是key,第三列是value,往哈希表中存进一个元素(即调用add()方法),会调用 key的hashCode()方法算出哈希值,根据哈希值得出该key在哈希表中的索引,接着就会遍历该索引中的所有的元素,调用key的equals() 方法,如果equals()方法返回true,则会覆盖哈希表中的元素,返回覆盖之前的值,否则就返回null。
  4)其实哈希表就是一个HashMap.Entry的数组,Entry类中有四个实例变量:
              static class Entry implements Map.Entry {
                  final K key;
                  V value;
                  Entry next; // 链表,供迭代的时候使用
                  final int hash;
               
                  Entry(int h, K k, V v, Entry n) {
                      value = v;
                      next = n;
                      key = k;
                      hash = h;
                  }
                  public final K getKey() {
                      return key;
                  }
                  public final V getValue() {
                      return value;
                  }
                  // ....
             }
  5)HashSet的实现是不同步的,也就是说HashSet是线程不安全的。

30.7.2 TreeSet

内部进行排序,元素唯一

内部采用红黑树,平衡二叉树,左小右大

排序自定义类要继承重写comparable来比较

TreeSet<Integer> t1=new TreeSet<Integer>();
t1.add(5);
//输出1
sout(t1);
//输出2
for(Integer i:t1){
    sout(i)
}

31 异常

31.1 try-catch-finally

try{
    //可能出现异常的代码块
}catch(异常类型 e){
    //捕获异常,也可以写复杂逻辑,不建议
}finally{
    //不管是否发生异常都执行,除非使用System.exit(参数);
    //retrurn 也会执行
    //进行io操作,数据库操作,一般在finally里做一些特定操作,比如关闭连接
}

先执行finally,再执行return;

31.4 常见异常

Exception的子类:受检异常(必须处理的异常)、运行时异常

Exception 异常层次结构的父类

ArithmeticException 算术错误情形,如以零作除数

ArrayIndexOutOfBoundsException 数组下标越界

NullPointerException 尝试访问 null 对象成员

ClassNotFoundException 不能加载所需的类

IllegalArgumentException 方法接收到非法参数

ClassCastException 对象强制类型转换出错

NumberFormatException 数字格式转换异常,如把"abc"转换成数字

31.3 多重catch

在这里插入图片描述

多重catch得顺序要按照先子类后父类

异常的匹配顺序从上到下

31.4 声明异常 重点

thorws

通过在方法后面声明异常进行抛出

方法后可以声明多个异常,多个异常之前用逗号分隔

main方法也可以使用throws,抛出的异常交给JVM处理

public class Test{
    main() throws Exception{
        try{
            int v=div(10,0);
            sout(v);
        }catch(ArithmeticException e){
            sout();
        }
    }
    public stataic int div(int a,int b) throws ArithemeticException{
        int v=a/b;
        return v;
    }
}

31.5 抛出异常

throw

通过创建异常对象进行抛出,一次只能抛出一个异常

一般可以运行时异常

借助throw,可以中断业务的执行,并不一定抛出的都是官方定义的具体异常

throw和throws 区别

throws 方法后声明异常,可以声明多个异常(多个异常用逗号分隔)

throw 手动抛出异常,一次只能抛出一个异常对象

31.6 自定义异常

自定义继承throwable/Exception/RuntimeException

自定义异常类中,根据业务需要,增加一个额外的属性

//自定义登录异常
public static void login(String username,String password){
    if(!username.equalse("admin")){
        throw new RuntimeException("用户名错误");
    }
    if(!passwprd.equals("123456")){
        throw new RuntimeException("密码错误");
    }
    sout("欢迎登陆");
}
main(){
    try{
        login("admin","123456")
    }catch(RuntimeException e){
        sout(e.getMassage());
    }
}

32 进程和线程

进程:应用程序的执行实例,有独立的内存空间和系统资源

线程:CPU调度和分配的基本单位,应用程序运算的最小单位

main方法为主线程,一个程序至少要有一个主线程

多线程:如果一个进程同时运行了多个程序,用来完成不同的工作,则称之为“多线程”

单核Cpu下,多线程交替占用cpu资源,而非真正的并行执行

cpu通过时间片分配给每个线程一个时间段,就是该进程允许运行的时间

多线程的好处:充分利用cpu的资源,带来良好的用户体验

32.1 线程的创建和使用

有两种方法:

1:继承Thread类

2:实现Runnable接口

比较:

继承Thread类编写简单,可直接操作线程使用于单继承

实现Runnable接口避免单继承局限性,便于共享资源

32.1.1 继承Thread类

Thread.currentThread().getName()获取当前线程的名字

start()启动线程

//自定义线程类
public class MyThread extends Thread{
    //线程执行的核心逻辑,写在run方法里,必须重写的方法
    //启动线程不能写mt.run();main中调用run(),相当于主线程执行了该方法,而且多个线程中的run方法顺序执行,run只是一个方法,不能启动线程
    @Override
    public void run(){
        for(int i=0;i<100;i++){
            sout(Thread.currentThread().getName+i);
        }
    }
}
public class Test{
    main(){
    	//创建启动线程
        MyThread mt=new MyThread();
        mt.start();
    }
}

32.1.2 实现Runnnable接口

//法1 自写类实现接口
public class MyRunnable implements Runnable{
    @Override
    public void run(){
         for(int i=0;i<100;i++){
            sout(Thread.currentThread().getName+i);
        }
    }
}
public class Test{
    MyRunnable mr=new MyRunnable();
    Thread t1=new Thread(mr);
    //法2 匿名内部类
    Thread t2=new Thread(new Runnable(){
        @Override
        public void run(){
            ...
        }
    });
}

32.2 线程的优先级

优先级范围1~10

默认5

Thread thread=new Thread();

thread.setPriority()设置优先级

Thread.MIN_PRIORITY 最大优先级10

Thread.MAX_PRIORITY 最小优先级1

32.3 线程的状态

创建状态:new线程对象

就绪状态:调用start()

运行状态:抢占到cpu资源,调用yield方法,进入就绪状态

阻塞状态:join()/sleep(),如果阻塞结束,进入就绪状态

死亡状态:线程执行完成

在这里插入图片描述

32.4 线程调度

32.4.1 sleep():线程休眠

休眠时长单位:毫秒 1000ms=1s

线程将进入阻塞状态,时间到了,进入就绪状态

在哪个线程中调用该方法,哪个线程就会休眠

一般要求放在try-catch中

32.4.2 join()线程插队

执行join语句的线程进入阻塞状态

调用 调用join方法的线程对象对应线程进入执行状态

32.4.3 yield()线程礼让

调用yield的线程进入就绪状态,同优先级的其他线程获得机会

但是,执行yield后,只是提供了这样的可能性,并不一定能保证其他线程一定执行。因为执行yield的线程也会进入就绪状态,他们都会参与cpu资源的竞争。

join和yield的区别

join使当前线程进入阻塞状态

yield使当前线程进入就绪状态

32.5线程同步

线程安全,针对多线程处理共享资源的时候,要保证功能正常运行

当多个线程处理共享资源时,有可能会引起数据不安全问题(不一致),对此,需要使用线程同步,保证数据的准确性

32.6 同步方法

基本语法:

森口耐子/ˈsɪŋkrənaɪzd/

访问修饰符synchronized 返回类型 方法名 (参数列表){。。。}

synchronized修饰方法,相当于对方法加锁,锁只能有一把,synchronized修饰的方法,用的锁是this对象

synchronized保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作)。同时保证一个线程的变化(主要是共享数据的变化)被其他线程所看到,保证可见性 。

32.7 修饰实例方法

32.8 修饰静态方法

32.9 修饰代码块

33 IO

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

按照流向:输入流 输出流

按照处理单元:字节流,字符流

8个二进制位表示字节

以文件举例:

将文件的内容读取到程序里,这个过程表示输入;

将程序中的数据写到文件里,这个过程表示输出;

字节流和字符流操作的方式基本上完全相同,操作的数据单元不同

字节流操作8位的字节 InputStream/OutputStream 作为字节流的基类

字符流操作16位的字符 Reader/Writer 作为字符流的基类

InputStream/Reader 输入流的基类

OutputStream/Writer 输出流的基类

基类都是抽象类

33.1 字节流

关闭流

使用流最后都要关闭 使用 对象.close()

先关闭输出流,再关闭输入流

后定义先关闭

程序中打开的文件IO资源不属于内存的资源,垃圾回收机制无法回收该资源,所以应该显式的关闭打开的IO资源

现在try-catch-resources不知如何使用,1.7后新增,自动close释放资源

33.1.1 File文件操作

主要用于文件和目录的创建,文件的查找和文件的删除

File对象代表磁盘中实际存在的文件和目录

FILE类的出现弥补了IO流的不足,IO只能够操作数据,但是不能够对文件的信息做操作,操作文件必须使用File类

功能:

1:可以将文件或者文件夹在程序当中分装成对象

2:方便对于文件或者文件夹当中的属性信息进行操作

3:File类通常通过构造函数作为参数传递到流的对象中

File f1=new File(“文件名”);

文件名:绝对路径/相对路径

读取文件(非打印输出文件)

boolean exists() 文件是否存在

boolean isFile() 是否是文件

boolean isDirectory() 是否是目录

boolean isAbsolute() 是否是绝对路径

File变量名 显示路径

String getName() 显示完整文件名

String getPath() 显示相对路径

long length() 文件长度

boolean delete() 删除此对象指定的文件或目录

boolean mkdir() 创建目录

boolean mkdirs() 创建多级目录

33.1.2 FileInputStream

输入流

用于从文件读取数据,属于输入过程

文件字节输入流读取文件内容的步骤:

  • 1.创建流对象

  • 2.创建一个缓存字节的容器数组

  • 3.定义一个变量,保存实际读取的字节数

  • 4.循环读取数据

  • 5.操作保存数据的数组

  • 6.关闭流

    //创建对象的两种方法
    //法1
    FileInputStream fis=new FileInputStream(“a.txt”);//一般定义和close都要try-catch
    //法2
    File f1=new File(“a.txt”);
    FileInputStream fis=new FileInputStream(f1);

    //读取一个字节,得到的是ASCII码,输出要转为char才能看见原内容
    int info=fis.read();
    sout((char)info)

    //通常使用的遍历输出方法,-1代表读文件结束
    //法1
    int info=-1;
    while((info=fis.read())!=-1){
    sout((cahr)info);
    }
    //法2
    int info=fis.read();
    while(info!=-1){
    sout((char)info);
    info=fis.read();
    }
    //法3
    //定义字节数组
    byte[] buff=new byte[1024];//可以自定义容量
    //把数据读到字节数组里,返回值是长度
    int rrr=fis.read(buff);
    //返回读取文件的字节长度
    sout(rrr);
    //第一个参数:待转换的字节数组;第二个参数:起始位置;第三个位置:转换的长度
    String s1=new String(buff,0,rrr);
    sout(s1);

33.1.3 FileOutputStream

输出流

创建一个文件并向文件中写数据

如果文件不存在就会创建文件

文件字节输出流写入文件内容的步骤:

1.选择流:创建流对象

2.准备数据源,把数据源转换成字节数组类型

3.通过流向文件当中写入数据

4.刷新流

5.关闭流

//创建对象
//默认是覆盖写,将原来的内容删除,重新写入
//第二个参数表示是否采用追加写的方式
FileOutputStream fos=new FileOutputStream("b.txt",true);

//写入
//write(int w)		指定字节写入
//write(byte[] w)	数组写入
fos.write(97);
fos.write('a');
String s="这是一串字符串";
//getBytes将字符串转为字节数组
fos.write(s.getBytes());

文件复制

//源文件,输入流,输入到内存程序中
FileInputStream fi=new FileInputStream("a.txt");
//目标文件,输出流,从内存中输出到目标文件中
FileOutputStream fo=new FileOutputStream("b.txt");
//通过数组搬运,数组大小为一次搬运大小,不能过大
byte[] buff=new byte[1024];
int length=-1;
whil((length=fileInputStream.read(buff))!=-1){
    fo.write(buff,0,length);
}
fi.close;
fo.close;

33.2字符流

33.2.1 InputStreamReader

转换流,将字节流转换为字符流,还可以解决编码不一致引起的乱码问题

类似字节流和字符流之间的桥梁

 FileInputStream f1=new FileInputStream("a.txt");
//转换流,类似字节流和字符流之间的桥梁
//可以指定编码格式
InputStreamReader i1=new InputStreamReader(f1,"utf-8");

//一次读取一个字符,返回值表示读取的字符
int r1=i1.read();
sout((char)r1);

char[] buff=new char[1024];
//返回值表示读取的字符的长度(个数)
int length =i1.read(buff);
sout(new String(buff,0,length));

33.2.2 FileReader

FileInputStream和FileReader都是节点流—会直接和指定文件关联

FileInputStream和FileReader进行文件的读写并没有什么区别,只是操作单元不同而且。

也会的存在编码问题

解决办法:使用InputStreamReader或者想办法保持编码一致

// 如果编码格式不一致,也会有乱码问题。
// 解决方案:使用InputStreamReader;想办法保持编码一致
FileReader fileReader = new FileReader("a.txt");
//读取一个
int read = fileReader.read();
System.out.println((char)read);

char[] buff = new char[1024];
//全部读取	法1
int length = fileReader.read(buff);
System.out.println(new String(buff, 0, length));
//全部读取	法2
while((length=fileReader.read(buff))>0){
    sout(new String(buff,0,length));
}

33.2.3 BufferedReader

相比之前的类型,增加了readLine()方法,读取一行数据,如果文件结束,返回null

缓冲流的好处:

缓冲流内部包含一个缓冲区,内部默认8kb,每一次程序调用read()方法起始都是从缓冲区当中读取内容,如果读取失败,就说明缓冲区中没有内容,那么就从数据源中读取内容,然后尽可能读取更多的字节放入到缓冲区域中,最后缓冲区域中的内容,会全部返还给程序。

从缓冲区读取数据会比直接cong数据源读取数据的速度快,效率更高,性能更好。

简单说:

没有缓冲区,那么每read一次,就会发送一次IO操作;有缓冲区,第一次读read时,会一下读取x个字节放入缓冲区,然后后续的read都会从缓冲区读取,当read到缓冲区末尾的时候,会再次读取x个字节放入缓冲区。

处理流处理数据和节点流处理数据的方式基本相同。

        	FileReader fileReader = null;
			BufferedReader bufferedReader = null;
			fileReader = new FileReader("b.txt");
			// 参数是Reader对象
			bufferedReader = new BufferedReader(fileReader);
			// 读取一行数据
			String info = bufferedReader.readLine();
			System.out.println(info);
			
			// readLine返回null,表示读取结束
			while((info = bufferedReader.readLine()) != null) {
				System.out.println(info);
			}

33.2.4 OutputStreamReader

转换流

OutputStream outputStream = null;
		OutputStreamWriter outputStreamWriter = null;
		try {
			outputStream = new FileOutputStream("c.txt");
			outputStreamWriter = new OutputStreamWriter(outputStream);
			// 参数可以是字符串 write 覆盖写
			outputStreamWriter.write("hello world你好世界");

33.2.5 FileWriter

可以以追加的方式写数据

FileWriter fileWriter = null;
		try {
			fileWriter = new FileWriter("c.txt", true);
			fileWriter.write("又翻车了");

33.2.6 BufferedWriter

怎加了缓冲区,效率更高

写数据时,先将数据写到缓冲区李,如果缓冲区满了;调用flush();流close()都会将数据写入文件

FileWriter fileWriter = null;
		// 效率相对高
		BufferedWriter bufferedWriter = null;
		try {
			fileWriter = new FileWriter("c.txt");
			bufferedWriter = new BufferedWriter(fileWriter);
			
			// 写到缓冲区里。如果缓冲区满了;调用flush();流close() 都会将数据写入文件
			bufferedWriter.write("hahahahah");
			
			// 刷新缓冲区,将数据写入文件
			bufferedWriter.flush();
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				// 关闭流的时候,如果缓冲区中有数据,将数据写入文件
				bufferedWriter.close();
				fileWriter.close();

33.2.7 字符流字节流差异

在解释Java中FileInputStream和FileReader的具体区别之前,我想讲述一下Java中InputStream和Reader的根本差异,以及分别什么时候使用InputStream和Reader。

实际上, InputStream和Reader都是抽象类,并不直接地从文件或者套接字(socket)中读取数据。

然而,它们之间的主要差别在于:InputStream用于读取二进制数据(字节流方式),

Reader用于读取文本数据(字符流方式,译者注),准确地说,Unicode字符。

那么,二进制数据和文本数据的区别是什么呢?

当然,所有读取的东西本质上是字节,然后需要一套字符编码方案,把字节转换成文本。

Reader类使用字符编码来解码字节,并返回字符给调用者。Reader类要么使用运行Java程序平台的默认字符编码,要么使用Charset对象或者String类型的字符编码名称,如“UTF-8”。尽管它是一个最简单的概念,当读取文本文件或从套接字中读取文本数据时,很多Java开发者会因没有指定字符编码而犯错。

记住,如果你没有指定正确的编码,或者你的程序没有使用的协议中已存在的字符编码,如HTML的 “Content-Type(内容类型)”、XML文件头指定的编码,你可能无法正确地读取的所有数据。一些不是默认编码呈现的字符,可能变成“?”或小方格。

一旦你知道stream和reader之间的根本区别,理解FileInputStream和FileReader之间的差异就很容易了。既可以让你从文件中读取数据,然而FileInputStream用于读取二进制数据,FileReader用来读取字符数据。

https://www.cnblogs.com/xiaohanlin/p/8108062.html

由于FileReader类继承了InputStreamReader类,使用的字符编码,要么由类提供,要么是平台默认的字符编码。

请记住,InputStreamReader会缓存的字符编码。

创建对象后,设置字符编码将不会有任何影响。

让我们来看看如何使用Java中InputStream和FileReader的例子。你可以提供任何一个文件对象或一个包含文件位置的字符串,以开始读取文件的字符数据。这类似于FileInputStream,也提供了类似的用于读取文件源的构造函数。尽管建议使用BufferedReader来读取文件数据。

我把我的eclipse的file.encoding设置成了UTF-8,然后再c盘新建一个data.txt并且输入一个永字,用记事本打开另存为UTF-8编码。这个时候我们在eclipse中运行程序,可以看到data.txt的打印二进制内容是efbbbfe6b0b8(通过Notepad++的HEX-Editor插件查看data.txt文件十六进制内容可以验证这一点),

说明FileInputStream没有进行任何编码转换把data.txt的二进制内容读入java变量中。

我们再来看下面一行输出 feff6c38永 就会发现FileReader通过UTF-8读取文件,然后对文件进行了编码,使其转换成unicode编码存入java变量中,这样才能在java中正确使用,因为java存储在内存里的变量都是unicode编码。

如果我们把data.txt另存为ANSI(GBK)编码,FileReader还是通过UTF-8读取文件,然后对文件进行了unicode编码就会出现乱码问题。如果把eclipse的file.encoding设置成了GBK再运行程序就会打印正常。

字节流:以字节为单位的流处理,读取byte。字节序列:二进制数据。与编码无关,不存在乱码问题

字符流:以字符为单位的流处理,读取char。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值