Java基础知识复习
继续承接上一篇先帖一个导图:
5.数组
- 数组也没好说的,即是注意 数组其实也是引用类型数据。许多数据结构的实现是通过数组。比如ArrayList,hashMap。
- 数组是不可变的。数组查询很快,直接通过下表就能拿到数据。
- 所有的数据类型都可以声明为数组。
- 数组是容器
- 最常见的错误就是数组越界
- 这里可以引申出一个 传值和传址的问题。
- 基本数据类型传递是值,引用类型是传址。
- 传递是对象句柄。传的是变量的地址,变量存储的值是 引用对象的地址。
- 数组还用的多的就是 数据结构中的 哈希表,详细的等复习集合框架再说。
Arrays 类
java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
具有以下功能:
给数组赋值:通过 fill 方法。
对数组排序:通过 sort 方法,按升序。
比较数组:通过 equals 方法比较数组中元素值是否相等。
查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。
6输入输出流
IO 类设计出来,肯定是为了解决 IO 相关的操作的,想一想哪里会有 IO 操作?网络、磁盘。网络操作相关的类是在 java.net 包下。提到磁盘,文件操作在 IO 中是比较典型的操作。在 Java 中引入了 “流” 的概念,它表示任何有能力产生数据源或有能力接收数据源的对象。
只要分为2大类,字节流和字符流。针对大文件的读写 还有一个NIO。
字节流和字符流的区别:
字节流读取单个字节,字符流读取单个字符(一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码是 3 个字节,中文编码是 2 个字节。)字节流用来处理二进制文件(图片、MP3、视频文件),字符流用来处理文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)。
关于输入输出是针对内存来说的。输入指的是把内容输入到流中, 所以是read()意思就是从本地文件中读取出内容放到流中。输出指的是把流中的内容写到本地文件中所以是 write()。
这么多的输入输出类用到了装饰者模式,装饰者模式和代理模式也像,装饰者 主要是去装饰增加原来的方法。就是包装的意思。就像刘备硬说自己是皇室后裔。
首先我们写一个最简单的拷贝文件代码:
public static void main(String[] args) {
//测试输入输出流
try {
InputStream inputStream=new FileInputStream("E:/Temp.txt");
OutputStream outputStream=new FileOutputStream("E:/Temp_copy.txt");
int count=0;
int data=0;
while ((data=inputStream.read())!=-1) {
count++;
System.out.println("data="+data);
outputStream.write(data);
}
System.out.println("count="+count);
inputStream.close();
outputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
最简单就是文件Temp拷贝一份。然后定义了一个计算次数的,从运行结果可以知道。
这个read()方法一次只读了一个字节,但是为什么返回的是int呢,这个是为了防止读到11111111的时候就直接停止。因为这个在计算机中的值就是-1.但是如果用int 那就是 255。
接着我再定义一个数组一次读取1KB=1024字节:
public static void main(String[] args) {
//测试输入输出流
try {
InputStream inputStream=new FileInputStream("E:/Temp.txt");
OutputStream outputStream=new FileOutputStream("E:/Temp_copy.txt");
int count=0;
int data=0;
byte[] buffer=new byte[1024];
while ((data=inputStream.read(buffer))!=-1) {
count++;
System.out.println("data="+data);
outputStream.write(buffer);
}
System.out.println("count="+count);
inputStream.close();
outputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
从结果可以看到 这次就是读取了4次每次最多读取1024字节。可以大大增加读取效率。
同时为了减少访问磁盘的次数,提高文件读取性能,Java提供了BufferedInputStream和BufferedOutputStream类。
我再写个例子看看。
public static void main(String[] args) {
//测试输入输出流
try {
InputStream inputStream=new FileInputStream("E:/Temp.txt");
BufferedInputStream bufferedInputStream=new BufferedInputStream(inputStream);
OutputStream outputStream=new FileOutputStream("E:/Temp_copy.txt");
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(outputStream);
int count=0;
int data=0;
byte[] buffer=new byte[1024];
while ((data=bufferedInputStream.read(buffer))!=-1) {
count++;
System.out.println("data="+data);
bufferedOutputStream.write(buffer);
}
System.out.println("count="+count);
inputStream.close();
outputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
BufferedInputStream和BufferOutputStream
这两个流是内置了缓冲区流,也就是说内部有一个 字节数组
缓冲思想
字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,
java本身在设计的时候,加入了数组这样的缓冲区效果,
也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流
BufferedInputStream
BufferedInputStream内置了一个缓冲区(数组)
从BufferedInputStream中读取一个字节时
BufferedInputStream会一次性从文件中读取8192个(8Kb), 存在缓冲区中, 返回给程序
程序再次读取时, 就不用找文件了, 直接从缓冲区中获取
直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个
BufferedOutputStream
BufferedOutputStream也内置了一个缓冲区(数组)
程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中
直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里
这个的意思就是 可以减少对磁盘的访问。建议都可以使用。还有一个读取到中文乱码问题,这个是和编码格式有关。
这一篇说的比较详细:IO流详解
其他的都大同小异,字符流主要是处理大文件的,下面放一张全部类图:
不同的类 在不同的情况下使用。最后还有一个新出的NIO
NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
IO是面向流的,NIO是面向缓冲区的;Java IO的各种流是阻塞的,Java NIO的非阻塞模式;Java NIO的选择器(Selectors)允许一个单独的线程来监视多个输入通道。
Java NIO的核心组件 包括:
通道(Channel)
缓冲区(Buffer)
选择器(Selectors)
Java NIO中的Buffer用于和NIO通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,
并提供了一组方法,用来方便的访问该块内存。
1、Buffer基本用法
使用Buffer读写数据一般遵循以下四个步骤:
1)写入数据到Buffer,一般有可以从Channel读取到到缓冲区中,也可以调用put方法写入。
2)调用flip()方法,切换数据模式。
3)从Buffer中读取数据,一般从缓冲区读取数据写入到通道中,也可以调用get方法读取。
4)调用clear()方法或者compact()方法清空缓冲区。
当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。
在读模式下,可以读取之前写入到buffer的所有数据。一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
有两种方式能清空缓冲区:
1)clear()方法会清空整个缓冲区。
2)compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区
未读数据的后面。
例如:
public static void main(String[] args) {
// 测试输入输出流
try {
FileInputStream inputStream = new FileInputStream("E:/Temp.txt");
FileOutputStream outputStream = new FileOutputStream(
"E:/Temp_copy.txt");
FileChannel inChannel = inputStream.getChannel();
FileChannel ouChannel = outputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// buffer.put((byte)23);
int count=0;
// 3. 将通道中的数据读取到缓冲区
while (inChannel.read(buffer) != -1) {
count++;
// 切换成读数据模式
buffer.flip();
// 4. 从缓冲区读取数据写入到通道中
ouChannel.write(buffer);
// 清空缓冲区
buffer.clear();
}
// buffer.flip();
// System.out.println("count="+buffer.get());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
大家可以用新的IO去替换传统的IO。
异常处理
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
Java 语言定义了一些异常类在 java.lang 标准包中。
标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。
Java所有异常的父类都是java.lang.Throwable、无论是内部的异常还是自定义异常。只有直接或者间接集成java.lang.Throwable类,JVM才会认为这是异常对象并且处理。
Java异常体系中Error为错误,较Exception严重。Error不是由我们程序自身导致的,是JVM运行错误导致的,所以暂时不是我们讨论的范围。Exception则是异常的基类,又可以分为"运行时异常"与"编译时异常"(又称为"非检查异常和检查异常")。
非检查异常RuntimeException; 在编译阶段无法检查,如ArithmeticException(除0引发)、InputMismatchException(输入的数据不能被转换为int类型引发)。引发非检查异常大多数原因是编码错误,应该检查程序。
检查异常(IOException),在编译时可以检查, 需要异常处理。处理方式有二种、(1)函数签名中throws抛出异常 (2)tryCatch语句捕获。
有时候我们可以用异常去追踪一些调用栈。
在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
所有异常都必须是 Throwable 的子类。
如果希望写一个检查性异常类,则需要继承 Exception 类。
如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
可以像下面这样定义自己的异常类:
class MyException extends Exception{
}
只继承Exception 类来创建的异常类是检查性异常类。
下面的 InsufficientFundsException 类是用户定义的异常类,它继承自 Exception。
一个异常类和其它任何类一样,包含有变量和方法。
throws /和throw的区别
finally块
finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。只有一种方法让finally块不执行:System.exit()。因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等等。
良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。
需要注意的地方:
1、finally块没有处理异常的能力。处理异常的只能是catch块。
2、在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
3、在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。