1、二进制(binary)、16进制、八进制
1.1、基本概念
- 逢2进1
- 数字:0 1
- 基数:2
- 权:128 64 32 16 8 4 2 1
- 从Java7开始,有了二进制的前缀0b,区分每四位时写个下划线分割,不加前缀代表十进制;
Integer类方法输出指定的十进制数字转换为二进制数
public static void main(String[] args) {
int a = 5;
Integer b = Integer.valueOf(5);
System.out.println(Integer.toBinaryString(a));
a++;
System.out.println(Integer.toBinaryString(a));
//二进制高位的0会被忽略
System.out.println(b);
}
1.2、16进制
作用:缩写二进制,简化书写;
前缀:0x;表示该数字是16进制;
16进制转化二进制:从低位开始,每四位缩一次;四位二进制对应十进制最高数是15,对应16进制每一个位的数,比如1111,对应16进制
int a = 0x292def68;
int b = 0b0010_1001_0010_1101_1110_1111_0110_1000;
1.3、8进制
前缀:0;注意八进制最大的数是7,不能有8、9;
int c = 067;
System.out.println(c); //55
1.4、补码----计算机处理有符号数的一种编码方式;
数据的正负数:最高位以0开头为正数,以1为负数;因此看正负要先把位数补全,看最高位;
溢出:计算的时候如果超过数据类型的最高位,数据会进一位,但是高位的数会被舍弃;如int型,后面32位全部填满后再加1,高位进1,后面全部变0,但高位舍弃,这样数据就变成32个0的数,形成一个随着数据的增加的循环;
int max = Integer.MAX_VALUE;
System.out.println(Integer.toBinaryString(max));//1111111111111111111111111111111
System.out.println(max);//2147483647
int min = Integer.MIN_VALUE;
System.out.println(min);//-2147483648
System.out.println(Integer.toBinaryString(min));//10000000000000000000000000000000
二进制转十进制
- 正数:所有为1的权数相加
权: 32 16 8 4 2 1
二进制: 1 1 0 1 0 1
十进制: 32+16+4+1----------------53
权: 32 16 8 4 2 1
二进制: 0 0 1 1 0 1
十进制: 8+4+1--------------------13
权: 32 16 8 4 2 1
二进制: 1 0 0 1 1 1
十进制: 32+4+2+1-----------------39
- 负数:用-1减去0位置对应的权
-1:111...111;(总共32位)
-8:111...011;
-149:111...01101011;
-2147483648(最小数):100...000;
互补对称现象
-n = ~n+1--------取反加1
设 n = -5;
n = -5 = 111...11011;
~n = 4 = 000...00100;
~n+1 = 5 = 000...00101;
设 n = 10;
n = 10 = 000...01010;
~n = -11 = 111...10101;
~n+1 = -10 = 111...10110;
互补对称图
最小负数减1 = 最大正数 ---------- 最小正数0减1 = 最小负数-1(位数超过范围,会自动忽略超过范围的高位数);
1.5、位运算
- 取反:~
- 0变1,1变0
- 与运算:&
- 见0则0,主要用于掩码运算:存储指定位数,位数对应与掩码位数;掩码位数:从低位开始,1的个数称为掩码的位数;
int n = 0x179d5d9e;
int m = 0xf00f0;
int z = n&m;
System.out.println(Integer.toHexString(z)); //d0090
- 或运算:|
- 见1则1,主要用于两个数的错位合并 ,如果两个数的位数是一样的话就不能合并了;
int x = 0x8a94;
int y = 0xaa0065;
int plus = x|y;
System.out.println(Integer.toHexString(plus)); //aa8af5
- 右移位运算:>>>
- 将2进制数整体向右移动,低位自动溢出舍弃,高位补0;
- 它数学值等于将数除于2,移动n位就除于2的n次方;
int a =0x6a7ad9f;
int b =a>>>2;
System.out.println(Integer.toHexString(b));//0x353d6c
- 左移位运算:<<
- 将2进制数整体向作移动,高位自动溢出舍弃,低位补0;
- 它数学值等于将数乘于2,移动n位就乘于2的n次方;;
a = 0xad20c67; //0x56906338
b = a<<3;
System.out.println(Integer.toHexString(b));
2、File类
2.1、新建文件路径并访问文件属性(还没有建文件,这里只指向了硬盘本身已有的一个文件)
public class FileDemo1 {
public static void main(String[] args) {
/*
尽量使用相对路径,而不是使用绝对路劲,减少不同系统间带来的错误
*/
File file = new File("./demo.txt"); // ./指项目所在的位置
long length = file.length(); //文件大小一般比较大,用long来表示
System.out.println(length+"字节");
String name = file.getName(); //输出文件名
System.out.println(name);
boolean b1 = file.canRead(); //判断文件是否可读
boolean b2 = file.canWrite(); //判断文件是否可写
}
}
2.2、新建、删除文件
新建文件:creatNewFile();新建文件的方法建立时会报异常,由于路径是否存在的不确定,这里先抛出异常不做讨论;
import java.io.File;
import java.io.IOException;
public class CreateNewFileDemo {
public static void main(String[] args) throws IOException {
/*
创建一个新的file对象只是对应该文件的路径,与文件本身是否存在无关
*/
File file = new File("./test.txt");
boolean b1 = file.exists(); //判断文件是否存在
if(b1){
System.out.println("该文件已存在");
}else{
file.createNewFile(); //路径不正确可能会导致创建文件失败,需要处理异常
}
}
}
新建目录:mkdir();mkdirs()
一般推荐使用mkdirs()这种方法,即使你目录写错了它也能帮你都创建出来,本质上是一个能创建多级目录的方法;
public static void main(String[] args) {
File dir = new File("a/b/c/d/e/f/demo"); //路径尽量用英文
if(!(dir.exists())){
dir.mkdir();
/*这种方法在创建一个不存在的目录时,虽然编译不会报错,但是实际是不会创
建的,它只能创建单层的目录
*/
dir.mkdirs(); //可以创建路径上的所有目录,一般都推荐用这个方法去创建目录
}else{
System.out.println("该目录已存在");
}
}
删除文件、目录:delete();
文件可以直接用该方法删除,目录里只有空的目录才能直接删,如果目录里还有其他东西,该方法无法直接删除目录;
import java.io.File;
/**
* 文件删除,该删除方式的文件无法在垃圾箱内找回,需谨慎
*/
public class DeleteFileDemo {
public static void main(String[] args) {
for(int i=0;i<3;i++){
File file = new File("./test"+i+".txt");
if(file.exists()){
file.delete();
}else{
System.out.println("该文件不存在");
}
}
}
}
3、获取目录下的文件与子目录:listFiles();
public static void main(String[] args) {
//获取当前项目目录下的所有子项
File file = new File(".");
if(file.isDirectory()){
File[] subs = file.listFiles();
if(subs!=null){
System.out.println("subs数组长度:"+subs.length);
}
for(int i=0;i< subs.length;i++){
System.out.println(subs[i].getName());
}
}
}
有条件地获取目录下的文件
listFiles()方法的参数列表里加入过滤器类FileFilter的子类对象,该对象里重写了accept();accept()方法返回了符合条件的文件或者目录;
public static void main(String[] args) {
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File file) {
String name = file.getName();
return name.contains("s");
}
};
File file = new File("./src/File");
if(file.isDirectory()){
File[] files = file.listFiles(filter);
if(files!=null){
for(int i=0;i<files.length;i++){
System.out.println(files[i].getName());
}
}
}
}
lambda表达式
jdk8之后推出的新特性,可以让程序员面向函数式编程,用更加精简的语法创建匿名内部类;
语法: (参数列表)->{ 方法体 }
当我们使用匿名内部类创建一个对象时,实现的接口只有一个抽象方法时,就可以用lambda;
public static void main(String[] args) {
/*
FileFilter确定了new的类型,接口因为只有一个方法,方法声明和参数都是确定的
上述三种语句都可以省略
*/
FileFilter filter1 = new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.length()>800;
}
};
/*
唯一方法确定了参数的类型,省略
*/
FileFilter filter2 = (pathname) ->{
return pathname.length()<800;
};
/*
如果该方法只有一个参数,圆括号可以忽略(两个以上或者没有参数时都要写)
如果重写的方法只有一个,可以省略大括号,注意return不能出现在没有大括号的地方,也要省略
*/
FileFilter filter3 = pathname -> pathname.length()<800;
}
根据以上lambda的特性,我们可以把有条件地列出文件的代码简化
if(file.isDirectory()){
File[] files = file.listFiles(pathName->pathName.getName().contains("s"));
if(files!=null){
for(int i=0;i<files.length;i++){
System.out.println(files[i].getName());
}
}
}
3、IO流
java将IO读作比喻为“流”,流动的数据是二进制数字
- java io可以让我们用标准的读写操作来完成对不同设备的读写数据工作.
- java将IO按照方向划分为输入与输出,参照点是我们写的程序.
- 输入:用来读取数据的,是从外界到程序的方向,用于获取数据.
- 输出:用来写出数据的,是从程序到外界的方向,用于发送数据.
流可以几个维度来区分,分为字符与字节流、低级与高级流、输出与输入流;
字节流与字符流:
1、字节流:读取单位为一个字节
- java.io.InputStream:所有字节输入流的超类,
- java.io.OutputStream:所有字节输出流的超类,其中定义了写出数据的方法.
字节流的派生类:
File流:用于读写文件数据的流.用于连接程序与文件(硬盘)的"管道",负责读写文件数据;
FileInputStream:用于从文件中读取字节。该构造器输入一个不存在的文件路径时会报错;
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("fos.dat");
/*
InputStream上定义了所有字节输入流都必须具备的读取操作:
int read()
从流中读取一个字节,返回的int值只有"低八位"有效。但是如果返回的int值为
整数-1,则表达的是流读取到了末尾(不能再通过这个流读取到任何数据了)
*/
int d = fis.read();
System.out.println(d);
d = fis.read();
System.out.println(d);
d = fis.read();
System.out.println(d);
fis.close();
}
FileOutputStream:用于向文件中写入字节;
public static void main(String[] args) throws IOException {
/*
文件流常用的构造器
默认为覆盖模式:
FileOutputStream(String path)
FileOutputStream(File file)
上述两种构造器创建文件流时,如果指定的文件已经存在了,则会将该文件原数据
全部抹除。
FileOutputStream(String path,boolean append)
FileOutputStream(File file,boolean append)
如果此时第二个参数为true,则文件流为追加模式,即:当指定的文件存在时,
文件种原数据都保留,使用当前流写出的内容都会陆续追加到文件中去。
*/
FileOutputStream fos = new FileOutputStream("fos.txt",true);
// String line = "low low low 有巴蒂,嘴里全都是居~,栓Q~";
String line = "有一天晚上,梦一场。";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
fos.write(data);
line = "你白发苍苍说带我流浪。";
data = line.getBytes(StandardCharsets.UTF_8);
fos.write(data);
System.out.println("写出完毕!");
fos.close();
}
FileOutputStream类的构造器new对象出来,即使没有该文件,也会创建一个指定路径上的文件;它又分为覆盖模式与追加的模式,覆盖模式是每次新建流对象时会覆盖掉指定文件原始的数据,追加模式是保留指定文件的原始数据;
Buffer流:用于提高读写数据的效率;BufferInputStream;BufferOutputStream;
常用构造器:
- BufferedOutputStream(OutputStream out):创建一个默认8kb大小缓冲区的缓冲字节输出流,并连接到参数指定的字节输出流上。
- BufferedOutputStream(OutputStream out,int size):创建一个size指定大小(单位是字节)缓冲区的缓冲字节输出流,并连接到参数指定的字节输出流上。
- BufferedInputStream(InputStream in):创建一个默认8kb大小缓冲区的缓冲字节输入流,并连接到参数指定的字节输入流上。
- - BufferedInputStream(InputStream in,int size):创建一个size指定大小(单位是字节)缓冲区的缓冲字节输入流,并连接到参数指定的字节输入流
缓冲流内部有一个字节数组,默认长度是8K.缓冲流读写数据时一定是将数据的读写方式转换为块读写来保证读写效率,缓冲长度可以被改变;
通过缓冲流写出的数据会被临时存入缓冲流内部的字节数组,直到数组存满数据才会真实写出一次;
缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。它的close()方法里也调用了该方法,依据业务需求决定是强制写出还是结束时再写出;
注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲输出流有这个方 法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现该方法的目的仅仅是为了在流 连接过程中传递flush动作给缓冲输出流。
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("bos.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
String line = "super idol 的笑容都没你的甜~";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
bos.write(data);
/*
flush方法实际上是在接口Flushable上定义的抽象方法,而字节输出流超类
java.io.OutputStream实现了该接口。这意味着所有字节输出流都具有该方法:flush
但是不是所有的高级流的flush都是做写出缓冲的,而是将flush动作传递下去。
最终传递到缓冲输出流上完成真实的flush操作
*/
bos.flush();
System.out.println("写出完毕!");
bos.close();
Object流:进行对象的序列化与反序列化
ObjectOutputStream、writeObject():将指定对象按照其结构转化成字节,称为对象的序列化;
public static void main(String[] args) throws IOException {
//将一个Person对象写入文件person.obj
/*
1:想办法将Person对象转换为一组字节(高级流:ObjectOutputStream)
2:这一组字节写入到文件中(低级流:FileOutputStream)
*/
String name = "苍老师";
int age = 55;
String gender = "男";
String[] otherInfo = {"你们的技术启蒙老师","拍片儿一句一流","刘桑"};
Person p = new Person(name,age,gender,otherInfo);
FileOutputStream fos = new FileOutputStream("./person.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
/*
当我们进行对象序列化时,可能出现异常:
java.io.NotSerializableException
这是由于写出的对象没有实现序列化接口导致的。
对象序列化:将一个对象按照其结构转换为一组字节的过程
数据持久化:将数据写到磁盘上做长久保存
*/
oos.writeObject(p);
System.out.println("写出完毕!");
oos.close();
ObjectInputStream、readObject():将字节还原为对象,称为对象的反序列化;
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("./person.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
/*
反序列化对象
该方法可能会抛出异常:java.io.InvalidClassException
原因:对象输入流在反序列化对象时,发现该对象的序列化版本号与当前类定义的版本
号不一致导致的。
一个类实现了序列化接口后,会有一个序列化版本号,只要类的结构没有发生过改变,
那么该版本号的值时不会发生变化的,一旦类的结构发生了改变,那么版本号就会发生
改变。
*/
Person p = (Person)ois.readObject();
System.out.println(p);
System.out.println(p.getName());
ois.close();
}
序列化接口java.io.Serrializable
该接口没有任何抽象方法,但是只有实现了该接口的类的实例才能进行序列化与反序列化。
实现了序列化接口的类建议显示的定义常量:static final long serialVersionUID = 1L;
可以为属性添加关键字**transient**,被该关键字修饰的属性在序列化是会被忽略,达到对象**序列化瘦身**的目的。
2、字符流:读取单位为一个字符
所有字符流都继承于两种超类:java.io.Reader、java.io.Writer
- 字符转换流:java.io.InputStreamReader、java.io.OutputStreamWriter
主要作用:衔接其它字节与字符流,或者将字符与字节进行转换
- 缓冲字符流:java.io.BufferedWriter和java.io.BufferedReader
主要作用:提高读写效率,缓冲区大小为8k字符,也就是16k字节;
- 缓冲字符输出流:java.io.PrintWriter
具有自动行刷新的缓冲字符输出流,实际开发中更常用;它内部总是会自动连接BufferedWriter作为块写加速使用,可以按行输出;
public static void main(String[] args) throws FileNotFoundException {
//向pw2.txt文件中写入字符串
FileOutputStream fos = new FileOutputStream("pw2.txt",true);
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
/*
PrintWriter提供了可以打开自动行刷新功能的构造器
当第一个参数为一个流时,就支持再传入一个boolean值,如果该值为true
则打开了自动行刷新,此时每当我们调用一次println方法,就会自动flush
*/
PrintWriter pw = new PrintWriter(bw,true);
/*
完成建议记事本功能
程序启动后,将用户在控制台输入的每一行字符串都按行写入文件中。
当用户单独输入exit时,程序退出。
*/
Scanner scanner = new Scanner(System.in);
while(true){
String line = scanner.nextLine();
if("exit".equals(line)){
break;
}
pw.println(line);
}
System.out.println("再见!");
pw.close();
}
流按节点流与处理流分为两类:
低级流(节点流):节点流的另一端是明确的,是实际读写数据的流,读写一定是建立在节点流基础上进行的;
高级流(处理流):处理流不能独立存在,必须连接在其他流上,目的是当数据流经当前流时对数据进行加工处理来简化我们对数据的该操作;
IO流的图总结
4、处理异常
java中所有错误的超类是Throwable,它有两个子类:Error与Exception;程序中通常都关注的是Exception;
Error:描述系统错误,比如虚拟机内存溢出;该类错误通常都无法挽回,只能妥协并改错;
Excption:描述程序错误,比如空指针、数组下标越界等;
Exception异常处理机制:
异常处理机制关注的是:明知道程序可能出现某种错误,但是该错误无法通过修改逻辑完全规避掉时,我们会使用异常处理机制,在出现该错误是提供一种补救办法。凡是能通过逻辑避免的错误都属于bug!就应当通过逻辑去避免!
当某句代码抛出了一个异常时,JVM会做如下操作:
- 检查报错这句话是否有被异常处理机制控制(有没有try-catch),如果有,则执行对应的catch操作,如果没有catch可以捕获该异常则视为没有异常处理动作;
- 如果没有异常处理,则异常会被抛出当当前代码所在的方法之外由调用当前方法的代码片段处理该异常;
1、try- catch
try语句不能单独写,要么后面跟catch要么跟finaly;
public static void main(String[] args) {
System.out.println("程序开始了");
try {
String line = null;
// String line = "abc";
System.out.println(line.length());
System.out.println(line.charAt(0));
System.out.println(Integer.parseInt(line));
//try语句块中出错代码以下的内容都不会被执行!!
System.out.println("!!!!!!!!!!!!!!!!!!!!!");
// }catch(NullPointerException e){
// System.out.println("出现了空指针,这里把他解决了");
// //当我们在try中针对可能出现的不同异常有不同处理方式时,我们可以定义多个catch来分别处理
// }catch(StringIndexOutOfBoundsException e){
// System.out.println("出现了下标越界,这里把他解决了");
// }
//当我们针对不同异常但用相同处理手段时,可以合并到一个catch中解决。
}catch(NullPointerException|StringIndexOutOfBoundsException e){
System.out.println("出现空指针或下标越界后相同的处理方式!");
/*
当catch的是一个超类型异常时,那么在try中出现的任意它的子类型异常时都
可以被它捕获。
需要注意,如果catch有多个,那么catch的异常遵循从小到大的先后捕获原则。
即:子类型异常在上先catch,超类型异常在下后catch
*/
}catch(Exception e){
System.out.println("反正就是出了个错!");
}
System.out.println("程序结束了");
}
finaly:是异常处理机制的最后一块,可以直接跟在try后面,或者在最后一个catch后面;finaly保证了程序只要走到了try语句块时,无论try语句块代码是否有异常,最终finaly里的语句块一定会执行;finaly通常用来做释放资源这类操作,比如IO流操作后的关闭流动作就非常适合在finaly里进行;
public static void main(String[] args) {
System.out.println("程序开始了");
/*
快捷键:ctrl+alt+T
可以将选中的代码用指定的代码包围(比如for,while,try catch等)
*/
try {
String line = "null";
System.out.println(line.length());
return;
} catch (Exception e) {
System.out.println("出错了!");
}finally {
System.out.println("finally中的代码执行了!");
}
System.out.println("程序结束了");
}
结束IO流
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("fos.dat");
fos.write(1);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fos!=null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
JDK7之后,java提供了一个新的特性:自动关闭。旨在IO操作中可以更简洁的使用异常处理机制完成最后的close操作。
public static void main(String[] args) {
/*
该特性是编译器认可的,并非虚拟机。实际上编译器编译完毕后的样子可参考FinallyDemo2
*/
try(
//只有实现了AutoCloseable接口的类才可以在这里定义!编译器最终会补充代码在finally中调用其close关闭
FileOutputStream fos = new FileOutputStream("fos.dat");
){
fos.write(1);
} catch (IOException e) {
e.printStackTrace();
}
}
finaly常见的面试题
请分别说明:final,finally,finalize是什么?
finalize是Object当中定义的一个方法,这意味着java中所有的类都有该方法。该方法会在GC调用后被调用;当GC扫描时发现一个对象不再被引用,则会将其释放,在释放前会调用finalize,一旦 该方法执行完毕,则对象被释放;
API手册明确说明:该方法可以被重写,但是不应当做耗时的操作,否则可能影响GC的工作,造成内存清理不及时的内存溢出;
2、throw
throw用来对外主动抛出异常,它的使用逻辑建立在当前代码的异常责任不在于该代码,而是其他调用者,它可以提醒调用者去解决异常;
注意:永远不要在main方法上去使用throws !
public class ThrowDemo {
public static void main(String[] args) {
Person p = new Person();
try {
p.setAge(2000);//满足语法,但是不满足业务
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("此人年龄:"+p.getAge());
}
}
/*
当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须
处理这个异常,处理手段有两种:
1:使用try-catch捕获并处理该异常
2:在当前方法上继续使用throws声明这个异常的抛出通知上层调用者解决
具体使用哪种要视异常处理的责任来确定。
*/
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) throws Exception {
if(age<0||age>100){
// throw new RuntimeException("年龄超过了范围!");
/*
当我们在一个方法中使用throw主动对外抛出一个异常时,除了RuntimeException
这类异常之外,其它的异常都应当使用throws在方法上声明该异常的抛出来通知上层
调用者处理该异常。
*/
throw new Exception("年龄超过了范围!");
}
this.age = age;
}
}
含有throws的方法被子类重写时的规则
public class ThrowsDemo {
public void dosome()throws IOException, AWTException {}
}
class SubClass extends ThrowsDemo{
// public void dosome()throws IOException, AWTException {}
//允许仅抛出部分异常
// public void dosome()throws IOException {}
//允许不再抛出任何异常
// public void dosome(){}
//允许抛出超类方法抛出异常的子类型异常
// public void dosome()throws FileNotFoundException {}
//不允许抛出额外异常
// public void dosome()throws SQLException {}
//不允许抛出超类方法抛出异常的超类型异常
// public void dosome()throws Exception{}
}