按照流的方向:输入流(inputStream)和输出流(outputStream)
按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。)
按照处理数据的单位: 字节流和字符流。字节流继承于 InputStream 和 OutputStream, 字符流继承于InputStreamReader 和 OutputStreamWriter
字节流如何转为字符流?- 字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象
- 字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象
首先需要pojo类继承Serializable接口
public static void main(String[] args) throws Exception {
//对象输出流
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream(new File("D://obj")));
objectOutputStream.writeObject(new User("zhangsan", 100));
objectOutputStream.close();
//对象输入流
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj")));
User user = (User) objectInputStream.readObject();
System.out.println(user);
objectInputStream.close();
}
字节流和字符流的区别
字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
如何实现对象的克隆- 实现Cloneable接口并且重写clone()方法
- 实现Serializable接口,通过对象的序列化和反序列化实现克隆
class MyUtil {
private MyUtil() {
throw new AssertionError();
}
public static T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream 对象的 close 方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这不同于对外部资源(如文件流)的释放
}
}
测试代码:
public static void main(String[] args) {
try {
Person p1 = new Person("dujubin", 33, new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的 Person 对象 p2 关联的汽车对象的品牌属性
// 原来的 Person 对象 p1 关联的汽车不会受到任何影响
// 因为在克隆 Person 对象时其关联的汽车对象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
什么是java序列化,如何实现?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序 列 化 的 实 现 : 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 , 该 接 口 没 有 需 要 实 现 的 方 法 , implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。
HashMap的排序题已知一个 HashMap
public static HashMapArrayList、HashSet、HashMap 是线程安全的吗?如果不是怎么获取线程安全的集合?sortHashMap(HashMap map) { // 首先拿到 map 的键值对集合 Set > entrySet = map.entrySet(); // 将 set 集合转为 List 集合,为什么,为了使用工具类的排序方法 List > list = new ArrayList >(entrySet); // 使用 Collections 集合工具类对 list 进行排序,排序规则使用匿名内部类来实现 Collections.sort(list, new Comparator >() { @Override public int compare(Map.Entry o1, Map.Entry o2) { //按照要求根据 User 的 age 的倒序进行排 return o2.getValue().getAge() - o1.getValue().getAge(); } }); //创建一个新的有序的 HashMap 子类的集合 linkedHashMap linkedHashMap = new linkedHashMap (); //将 List 中的数据存储在 linkedHashMap 中 for (Map.Entry entry : list) { linkedHashMap.put(entry.getKey(), entry.getValue()); } return linkedHashMap; }
Collections.synchronizedCollection(c); Collections.synchronizedList(list); Collections.synchronizedMap(m); Collections.synchronizedSet(s);ArrayList是用什么实现的?
构造器:
- 当我们 new 一个空参 ArrayList 的时候,系统内部使用了一个 new Object[0]数组
- 构造函数传入一个 int 值,该值作为数组的长度值。如果该值小于 0,则抛出一个运行时异常。如果等于 0,则使用一个空数组,如果大于 0,则创建一个长度为该值的新数组。
- 调用构造函数的时候传入了一个 Collection 的子类,那么先判断该集合是否为 null,为 null 则抛出空指针异常。如果不是则将该集合转换为数组 a,然后将该数组赋值为成员变量 array,将该数组的长度作为成员变量 size
add方法:
public boolean add(E object) {
Object[] a = array;
int s = size;
//如果集合的长度已经等于数组的长度了,说明数组已经满了,该重新分配新数组了
if (s == a.length) {
//当前长度如果小于常量值的一半,就直接分配常量值的大小(12)否则增加s当前值的一半
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ? MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
//将新的object元素作为数组的a[s]个元素
a[s] = object;
//修改集合长度为s+1
size = s + 1;
//记录修改集合的次数
modCount++;
return true;
}
remove方法:
public E remove(int index) {
Object[] a = array;
int s = size;
if (index >= s) {
throwIndexOutOfBoundsException(index, s);
}
//获取数组中脚标为 index 的对象 result,该对象作为方法的返回值
@SuppressWarnings("unchecked") E result = (E) a[index];
//完成数组拷贝
System.arraycopy(a, index + 1, a, index, --s - index);
//因为删除了一个元素,而且集合整体向前移动了一位,因此需要将集合最后一个元素设置为 null,否则就可能内存泄露
a[s] = null; // Prevent memory leak
size = s;
modCount++;
return result;
}
clear方法:
public void clear() {
if (size != 0) {
//将所有数组的值都设置为 null,然后将成员变量 size 设置为 0 即可,最后让修改记录加 1
Arrays.fill(array, 0, size, null);
size = 0;
modCount++;
}
}
并发集合和普通集合如何区别
普通集合通常性能最高,但是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了 synchronized 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率
concurrentHashMap1.7和1.8的区别
List,Map和Set的区别结构特点:List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;List 中存储的数据是有顺序,并且允许重复;Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的,Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的 hashCode 决定,位置是固定的(Set 集合根据 hashCode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 Set 中的元素还是无序的);
实现类:List 接口下的实现类(linkedList:基于链表实现,链表内存是散乱的,每一个元素存储本身内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现,线程安全的,效率低)。Map 接口下的实现类(HashMap:基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键;Hashtable:线程安全,低效,不支持 null 值和 null 键;linkedHashMap:是HashMap 的一个子类,保存了记录的插入顺序;SortedMap 接口:TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序)。Set 接口下的实现类(HashSet:底层是由 HashMap 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hashCode()方法;linkedHashSet继承与 HashSet,同时又基于linkedHashMap 来进行实现,底层使用的是linkedHashMap)。
区别:List集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象,例如通过list.get(i)方法来获取集合中的元素;Map中的每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复;Set集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如 TreeSet类,可以按照默认顺序,也可以通过实现 java.util.Comparator接口来自定义排序方式。
HashMap和Hashtable有什么区别?HashMap是非线程安全的,HashMap是Map的一个实现类,是将键映射到值的对象,不允许键值重复。允许空键和空值;由于非线程安全,HashMap的效率要较 Hashtable 的效率高一些。Hashtable 是线程安全的一个集合,不允许 null 值作为一个 key 值或者value 值;Hashtable是sychronized,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时候需要自己为它的方法实现同步。
数组和链表分别比较适合用于什么场景,为什么?数组是将元素在内存中连续存储的;它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低。
链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的地址来联系)
Java中ArrayList和linkedList区别?- 对 ArrayList 和 linkedList 而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList 而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对 linkedList 而言,这个开销是统一的,分配一个内部 Entry 对象。
- 在 ArrayList 的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在 linkedList 的中间插入或删除一个元素的开销是固定的。
- linkedList 不支持高效的随机元素访问。
- ArrayList 的空间浪费主要体现在在 list 列表的结尾预留一定的容量空间,而 linkedList 的空间花费则体现在它的每一个元素都需要消耗相当的空间。
当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList 会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用linkedList了
List a=new ArrayList()和ArrayList a =new ArrayList()的区别?List list = new ArrayList();这句创建了一个 ArrayList 的对象后赋给了List。此时它是一个 List 对象了,有些ArrayList 有但是 List 没有的属性和方法,它就不能再用了。而ArrayList list=new ArrayList();创建一对象则保留了ArrayList 的所有属性。 所以需要用到 ArrayList 独有的方法的时候不能用前者。
Map中的key和value可以为null?HashMap 对象的 key、value 值均可为 null。Hahtable 对象的 key、value 值均不可为 null。且两者的的 key 值均不能重复,若添加 key 相同的键值对,后面的 value 会自动覆盖前面的 value,但不会报错。



