IO
在执行完流操作后,要调用close() 方法来关闭输入流,因为程序里打开的IO资源不属于内存资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源,关闭输出流还能将输出流缓冲区的数据flush到物理节点.
IO四大基类
- InputStream:字节输入流
- OutputStream:字节输出流
- Writer: 字符输出流
- Reader: 字符输入流
字节流和字符流
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。
- 字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
- 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以
- 这两个之间通过InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联
- 字节流在操作时不会用到缓冲区(内存),是直接对文件本身进行操作的。而字符流在操作时使用了缓冲区,通过缓冲区再操作文件。(缓存区可以避免多次的IO操作,提高效率)
- 在硬盘上的所有文件都是以字节形式存在的(图片,声音,视频),而字符值在内存中才会形成(字节流适用范围更为宽广)
节点流和处理流
向一个特定的IO设备(如磁盘、网络)读/写数据的流,称为节点流,处理流是对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能
- 节点流,直接传入的参数是IO设备,处理流,直接传入的参数是流对象
- Java使用处理流来包装节点流是一种典型的装饰器设计模式,
- 通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能。
BIO
- 在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的
- 每一个客户端连接请求都创建一个线程来单独处理
- 严重依赖于线程,同时处理多个客户端请求,就必须使用多线程。
NIO
- 基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题
- NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统
- 不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。
BIO、NIO、AIO
- BIO:Block IO 同步阻塞式IO,模式简单使用方便,并发处理能力低。
- NIO:Non IO 同步非阻塞 IO,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO,异步 IO 的操作基于事件和回调机制。
反射
- 反射:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
- 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
- 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多
- 应用:常见spring框架,动态代理模式,JDBC等
实现方式:
- 通过new对象实现反射机制
new 类().getClass()
- 通过路径实现反射机制
Class.forName(类的路径)
- 通过类名实现反射机制
类.class
String
- String存放在字符串常量池,常量池位于堆内存中
- String是只读字符串,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。(不变性可以通过反射机制修改)
- String类不能被继承
public static void main(String[] args) throws Exception {
String s = "hello";
String s1 = s;
String s2 = s;
//通过使用替换函数修改字符
String s3 = s1.replace('e', 'o');
System.out.println(s);//hello
System.out.println(s1);//hello
System.out.println(s2);//hello
System.out.println(s3);//hollo
//通过反射修改字符
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
char[] chs =(char[]) value.get(s2);
chs[0] = 'o';
System.out.println(s); //hollo
System.out.println(s1);//hollo
System.out.println(s2);//hollo
System.out.println(s3);//hollo
}
辨析 String s1 = "hello"
与String s2 = new String("hello")
- s1的"hello"存储于常量池,s2的"hello"储存与堆内存中
- 通过
intern()
如果常量池中不存在,可以将堆内存的string的引用拷贝一份到常量池,如果存在则直接返回常量池中的string
public static void main(String[] args) {
String s = "hello";
String s1 ="hello";
String s2 = new String("hello");
String s3 = new String("hello");
String s4 = new String("hello").intern();
System.out.println(s==s1); //true
System.out.println(s==s2);//false
System.out.println(s==s4);//true
System.out.println(s2==s3);//false
System.out.println(s2==s4);//false
}
String、StringBuffer、StringBuilder
- 操作少量的数据用 = String
- 单线程操作字符串缓冲区下操作大量数据 = StringBuilder
- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
Integer、int数值相等
public static void main(String[] args) {
Integer a1 = 1;
Integer a2 = 1;
Integer a3 = new Integer(1);
Integer a4 = new Integer(1);
int a5= 1;
System.out.println(a1==a2);//true
System.out.println(a3==a1);//false
System.out.println(a5==a1);//true
System.out.println(a5==a3);//true
System.out.println(a2==a3);//false
//常量池中的Integer对象值在-128到127之间
Integer b1 = 128;
Integer b2 = 128;
System.out.println(b1==b2);//false
}
包装类和基本数据类型比较
- 包装类和基本数据类型之间的比较,包装类拆箱为基本数据类型然后再进行比较,数值相同则相等,
- 不同基本类型可以比较,数值一样则相等
- Integer、Short、Long具有常量值(-128~127),Double、Float没有
- 包装类仅Integer和Long之间可以比较,数值相同则相等,其他包装类之间比较有编译错误。
集合
- 线程安全的集合类vector、statck、hashtable、enumeration
- 只读集合
Collections. unmodifiableCollection(Collection c)
- 边遍历边移除 Collection 中的元素使用
Iterator.remove()
Iterator 和 ListIterator 有什么区别?
- Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
- Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
- ListIterator实现Iterator 接口,新增添加替换元素、获取前后元素的索引位置等功能。
ArrayList
- 底层以数组实现,实现了 RandomAccess 接口,因此查找的时候非常快。
- 顺序添加一个元素的时候非常方便
- 删除或插入元素的时候,需要做一次元素复制操作,
- 不保证线程安全,多线程下通过Collections的
synchronizedList
方法将其转换成线程安全的容器. - 频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
- 底层序列化实现:先调用 defaultWriteObject()方法序列化ArrayList 中的非transient 元素,然后遍历elementData,只序列化已存入的元素
数组和List转换
- 数组转List:使用
Arrays.asList(array)
进行转换。 - List转数组:使用List自带的
toArray()
方法。包装类的转换 - List转数组:使用流方法
list.stream().mapToInt(i -> i).toArray()
。基本数据类型的转换
ArrayList 和 Vector
类别 | 线程安全 | 扩容机制 |
ArrayList | 不安全 | 0.5 |
Vector | 安全 | 1 |
HashSet
- 基于HashMap实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT。
- 相关 HashSet 的操作,基本上都是直接调用底层HashMap的相关方法来完成
HashMap
- 基于哈希表的Map接口的非同步实现
- 允许使用null值和null键
- 不保证映射的顺序
- 1.8后链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
- HashMap初始的容量大小16
- 1.8后让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均
- 多线程使用ConcurrentHashMap
HashMap是使用了哪些方法来有效解决哈希冲突的:
- 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
- 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
- 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
Map的key使用条件
- 任何类作为key重写了equals()方法,也应该重写hashCode()方法。
- Hash值的具有不可更改性
HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标
- 方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),通常通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置
- 解决方式:
- 通过两次扰动使得数据分布更平均
- 使用hash()运算之后的值与运算&(数组长度-1)来获取数组下标的方式进行存储,比取余操作更加有效率,解决了“哈希值与数组大小范围不匹配"的问题
HashMap 与 HashTable
类型 | 线程安全 | 初始容量 | 扩容 | null键 |
HashMap | 不安全 | 16 | 2n | 一个 |
HashTable | 安全 | 11 | 2n+1 | 不能含有 |
ConcurrentHashMap
- 在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。前者用来充当锁的角色,后者用来封装映射表的键值对;
- 在JDK1.8中,是采用Node + CAS + Synchronized来保证并发。安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。(粒度更细)
辅助类
comparable 和 comparator的区别?
- comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
- comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序