Java中有几种类型的流?
按照流的方向:输入流(inputStream)和输出流(outputStream)
按照实现功能分:节点流(可以从或向一个特定的地方(节点)读写数据。如 FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。)
按照处理数据的单位: 字节流和字符流。字节流继承于 InputStream 和 OutputStream, 字符流继承于InputStreamReader 和 OutputStreamWriter
字节流如何转为字符流?
- 字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象
- 字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象
如何将一个java对象序列化到文件中?
首先需要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();
}
编写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数
//读取文件方法测试
public static int contWorldInFile(String filename, String word) {
int counter = 0;
try (FileReader fr = new FileReader(filename)) {
try (BufferedReader br = new BufferedReader(fr)) {
String line = null;
//按行读
while ((line = br.readLine()) != null) {
int index = -1;
//只要当前line的长度还大于word并且index能从line中找到
while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
counter++;//+1
line = line.substring(index + word.length());//line截取到已取字符word的下一段
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return counter;
}
如何列出一个目录下的所有文件
public static void showDetailDir(String direct){
File file=new File(direct);
for (File listFile : file.listFiles()) {
if(listFile.isFile()){
System.out.println(listFile.getName());
}
}
}
如果需要对文件夹展开:
public static void show(File file){
walkFile(file);
}
private static void walkFile(File file) {
if(file.isDirectory()){
for (File listFile : file.listFiles()) {
walkFile(listFile);
}
}else{
System.out.println(file.getName());
}
}
讲讲Java的路径
/和\的区别:
在window系统中路径分割符以\区分,而在linux系统中路径分割符以/区分。
在java系统中\和紧跟着它的那个字符构成转义字符,所以在window环境下c:\test\test1.txt的路径要用c:\\test\\test1.txt表示(或者直接用c:/test/test1.txt
相对路径:
工作目录:c:\workspace
项目路径:c:\workspace\PathTest
/开头的路径,是相对于项目源文件的跟路径: 如果当前项目在c盘,new File("/test.txt")//这个文件路径就是c:/test.txt
不带/开头的路径,是相对于java项目目录的路径: new File("test.txt")//这个文件路径就是c:\workspace\PathTest\test.txt
./开头就和不带/是一样的,因为./表示的是当前目录 ../表示父级目录 /表示根目录
讲讲Java的IO流
字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | |
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream |
对文件的读写字节流: FileInputStream和FileOutputStream
如果要加快对文件的读写,可以使用缓冲流包裹: BufferedInputStream和BufferedoutputStream
//以下实现的是对一个文件的复制 ,这里偷懒没写try catch finally正常是不能抛出要及时处理的
@Test
public void test02() throws IOException {
File src=new File("1234.jpg");
File dest=new File("12345.jpg");
FileInputStream fis=new FileInputStream(src);
FileOutputStream fos=new FileOutputStream(dest);
//缓冲流
BufferedInputStream bufferedInputStream=new BufferedInputStream(fis);
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(fos);
byte[] buffer=new byte[10];
int len;
while((len=bufferedInputStream.read(buffer))!=-1){
bufferedOutputStream.write(buffer,0,len);
//bufferedOutputStream.flush();源码中是清空缓冲区
}
//资源关闭,先关外面再关里面(内层可以省略)
bufferedOutputStream.close();
bufferedInputStream.close();
}
对文件的读写字符流: FileReader和FileWriter
//先读进来再写出去 copy操作
@Test
public void testFileReaderAndWrite(){
FileReader fr = null;
FileWriter fw = null;
try {
//如果要复制图片的化要使用字节流,在这里使用的是字符流
//扩展使用字节流也可以操作字符流数据(没有中文的情况),但是效率低
File src = new File("helloworld.txt");
File dest = new File("hahaha.txt");
fr = new FileReader(src);
fw = new FileWriter(dest);
//读入写出
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1) {
//每次写出len个字符
fw.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fr!=null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fw!=null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
转换流:
public static void main(String[] args) throws Exception {
InputStream is_stm = null;
InputStreamReader isr_stm = null;
int val = 0;
try {
is_stm = new FileInputStream("D:\\includehelp.txt");
isr_stm = new InputStreamReader(is_stm);
boolean status = isr_stm.ready();
System.out.println("isr_stm.ready(): " + status);
} catch (Exception ex) {
System.out.println(ex.toString());
} finally {
if (is_stm != null) {
is_stm.close();
if (isr_stm != null) {
isr_stm.close();
}
}
}
}
对象流:
/*
private int sno = 1;
private String name = "chy";
private int statu = 5;
*/
private static File file = new File("D:\\oos.txt");
public static void testObjectOutputStream() throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
StudentDTO swrite = new StudentDTO(1, "chy", 5);
oos.writeObject(swrite);
}
public static void testObjectInputStream() throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
StudentDTO s = new StudentDTO(2, "chy1", 10);
StudentDTO sread = (StudentDTO)ois.readObject();
/**
* 当statu的类型是int时的result: sno: 1 name: chy status: 5==>修改成功
* 当statu的修饰符是static时的result: sno: 1 name: chy status: 10==>未修改成功
* 当statu的修饰符是transient时的result: sno: 1 name: chy statu: 0
*/
System.out.println(sread.toString());
}
字节流和字符流的区别
字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时。先去查指定的编码表,将查到的字符返回。字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
如何实现对象的克隆
- 实现Cloneable接口并且重写clone()方法
- 实现Serializable接口,通过对象的序列化和反序列化实现克隆
class MyUtil {
private MyUtil() {
throw new AssertionError();
}
public static <T extends Serializable> 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<Integer,User>集合, User 有 name(String)和 age(int)属性。请写一个方法实现对HashMap 的排序功能,该方法接收 HashMap<Integer,User>为形参,返回类型为 HashMap<Integer,User>,要求对 HashMap 中的 User 的 age 倒序进行排序。排序时 key=value 键值对不得拆散。
public static HashMap<Integer, User> sortHashMap(HashMap<Integer, User> map) {
// 首先拿到 map 的键值对集合
Set<Map.Entry<Integer, User>> entrySet = map.entrySet();
// 将 set 集合转为 List 集合,为什么,为了使用工具类的排序方法
List<Map.Entry<Integer,User>> list = new ArrayList<Map.Entry<Integer, User>>(entrySet);
// 使用 Collections 集合工具类对 list 进行排序,排序规则使用匿名内部类来实现
Collections.sort(list, new Comparator<Map.Entry<Integer, User>>() {
@Override
public int compare(Map.Entry<Integer, User> o1, Map.Entry<Integer, User> o2) {
//按照要求根据 User 的 age 的倒序进行排
return o2.getValue().getAge() - o1.getValue().getAge();
}
});
//创建一个新的有序的 HashMap 子类的集合
LinkedHashMap<Integer, User> linkedHashMap = new LinkedHashMap<Integer, User>();
//将 List 中的数据存储在 LinkedHashMap 中
for (Map.Entry<Integer,User> entry : list) {
linkedHashMap.put(entry.getKey(), entry.getValue());
}
return linkedHashMap;
}
ArrayList、HashSet、HashMap 是线程安全的吗?如果不是怎么获取线程安全的集合?
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 同步锁,严重牺牲了性能,而且对并发的效率就更低了,并发集合则通过复杂的策略不仅保证了多线程的安全又提高的并发时的效率
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,但不会报错。
String str="i"与 String str=new String("i")一样吗?
不一样,内存的分配方式不一样,前者虚拟机会将其分配在字符串常量池中,后者会分配在堆中,并且如果常量池中已经有i,那么会前者会将引用指向该对象,后者无论如何都是一个新的对象
String s1="i";
String s2="i";
String s3=new String("i");
System.out.println(s1==s2);//true
System.out.println(s1==s3);//false
如何翻转一个字符串?
调用StringBuilder或者StringBuffer的reverse方法
普通类和抽象类的区别?
普通类可以实例化,抽象类不能
普通类不可以有抽象方法,抽象类可以
BIO,NIO和AIO有什么区别?
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制
Files的常用方法都有哪些?
- Files.exists():检测文件路径是否存在。
- Files.createFile():创建文件。
- Files.createDirectory():创建文件夹。
- Files.delete():删除一个文件或目录。
- Files.copy():复制文件。
- Files.move():移动文件。
- Files.size():查看文件个数。
- Files.read():读取文件。
- Files.write():写入文件
java的容器都有哪些
Collection和Collections有什么区别?
Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法,Collection存在的意义是为各种集合提供了最大化统一操作方式
Collections是集合类的一个工具类,提供了一系列的静态方法,用于排序,搜索和线程安全等操作
如何决定使用HashMap还是TreeMap?
对于在Map中插入,删除和定位元素这类操作,HashMap是最好的选择,如果你需要对一个有序的key集合进行遍历,TreeMap是更好的选择
HashMap的实现原理?
HashMap实际上是一个链表散列的数据结构,也就是数组和链表的结合体,当我们往HashMap里put元素的时候,首先根据key的hashcode计算hash值,根据hash值得到这个元素在数组中的位置,如果该数组在该位置已经存放了其他的元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链表头,之前放入的放在链尾,如果数组中该位置没有元素,就会直接把钙元素放在数组的位置上
需要注意jdk1.8中对HashMap的优化,当链表的节点数据超过八个之后,链表会转化为红黑树来提高查询效率,小于六个的时候会从红黑树转回成链表
HashSet的实现原理?
HashSet底层由HashMap实现,它的值存放于HashMap的Key上,value统一为PRESENT常量
ArrayList和LinkedList的区别?
最明显的区别是前者底层结构是数组,支持随机访问,后者底层是双向循环链表,不支持随机访问,使用下标访问一个元素的时候前者时间复杂度为O1后者为On
如何实现List和数组之间的转换?
使用ArrayList.toArray()和Arrays.asList方法,要注意的是不能使用基本数据类型而应该使用包装类
Integer[] array = new Integer[] {1,2,3};
// String数组转List集合
List<Integer> mlist = Arrays.asList(array);
// 输出List集合
for (int i = 0; i < mlist.size(); i++) {
System.out.println("mlist-->" + mlist.get(i));
}
在使用toArray方法的时候,需要强制类型转换,但是不能写成(Integer[])xxx,因为强制类型转换只针对单个对象
List<Integer> mlist = new ArrayList<>();
mlist.add(1);
mlist.add(2);
mlist.add(3);
Integer[] array = mlist.toArray(new Integer[0]);
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
在Queue中poll和remove有什么区别
在队列中为空的时候poll会返回空,但是remove会抛出异常
哪些集合类是线程安全的
- collections.synchronizedxxx
- vector和他的子类stack
- hashtable
- CopyOnWriteArraySet,CopyOnWriteArrayList
- ConcurrentHashMap,ConcurrentLinkedQueue
Iterator怎么使用,有什么特点
Java中的Iterator功能比较简单,并且只能单向移动:
- 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承
- 使用next()获得序列中的下一个元素,并且指针下移
- 使用hasNext()检查序列中是否还有元素
- 使用remove()将迭代器新返回的元素删除
- Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素
关于哈希表
默认初始容量是16。HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。
插入流程
扩容流程
各种哈希表
Hashtable:早期的哈希表,线程安全,效率低;
ConcurrentHashMap:保证线程安全,synchronized+CAS保证
LinkedHashMap:拥有LRU缓存机制的哈希表,记录了插入顺序和访问顺序
TreeMap:可以实现自定义的排序功能