java学习笔记-2
1-集合
1.链表、二叉树
一些常见数据结构:
数组:增删慢、查找快,相邻元素内存地址相邻,当数据量大时动态扩容效率低而且非常耗费性能。
链表:增删快、查找慢,当数据量大时查找元素很慢,需要从头开始逐个遍历。链表可以分为单向链表、循环链表、双向链表、双向循环链表。
二叉树:类似于链表,只不过链表只有一个指向next,而二叉树有两个指向即left和right。如果二叉树存储有序元素(例如比根节点小存左子树,比根节点大存右子树),考虑二叉树平衡的情况,当查找一个元素时,每次比较都可以将查找范围缩小一半,类似于二分查找,查找效率非常高。
栈stack:先进后出,操作:压栈、弹栈
队列queue:先进先出,操作:入队、出队。也存在双向队列,即两边都可以出入队。
2.集合
集合即容器,用来存储数据的结构。java内置了非常成熟的容器,集合在java.util包下
如何对这些容器中的元素遍历呢?可以使用Iterator迭代器,通过iterator()可以实现对集合中所有元素的遍历。
集合按照其存储结构可分为两大类:Collection是单列集合,Map是双列集合。
collection接口中定义的方法:
通常使用中不会直接使用Collection接口,而是使用其子接口
List
和Set
。其中list中允许元素重复,set不允许元素重复
3.List接口(重点)
list接口的实现类:ArrayList(95%)(数组实现)、Vector(4%)(数组实现)、LinkedList(1%)(双端链表实现),Vector可以看作ArrayList的早期版本。vector是线程安全的,arraylist是非线程安全的。如果不需要线程安全,推荐使用ArrayList代替Vector。(线程安全会降低性能)
4.ArrayList类(重点)
数组实现,动态扩容每次1.5倍。默认大小是10,如果明确知道arraylist要存很多数据,就要给定初始值大小,否则会浪费很多内存在扩容上,会降低性能。
初始化大小10是在扩容操作中实现的。查看源码弄清楚扩容流程。
如果有参构造提供初始大小,则直接创建对应大小的数组,这一过程不需要扩容。
如果通过无参构造,默认赋值一个空数组,在首次添加元素时,进行扩容,数组默认长度为10,扩容是通过Arrays.copyOf实现的
5.Vector类
数组实现。方法与ArrayList一致,多了一个构造方法,该方法多了一个参数即增量大小。Vector默认增量大小是0,如果增量大小>0每次扩容按照增量大小进行扩容,否则安装2倍大小扩容。
6.LinkedList类
双向链表实现。不仅可以用作list,还可以当作栈、单端队列、双端队列来使用。
栈的操作:push()入栈、pop()出栈
队列操作:addFirst(),addLast()、removeFirst()、removeLast()、getFirst()、getLast()
7.Iterator和ListIterator迭代器
迭代器只能迭代Collection集合,即list和set
iterater:hasNext()、next()等等
ListIterator:是Iterator的子类接口,除了上面的方法还有 hasPrevious()和previous()、add()、remove()等
8.for each增强遍历
遍历数组或集合,集合只能是Collection,即list和set
9.Set接口
Set实现类:
HashSet
、TreeSet
、LinkedHashSet
基本上和Collection方法一致,仅添加了少许几个方法。
注意:Set中并没有get()方法,如果想要遍历一个set,可以通过iterator或者调用toArray()生成一个数组并对数组进行遍历。
10.HashSet类
hashset内部使用了hashmap(也叫做散列表),存储的数据是无序的,不能保证存储顺序。add方法在内部是调用了hashmap的put方法。
Set类如何保证值的不重复?答案是使用map类,因为map类存储的键值对,而键是不可重复的,所以利用这一点可以实现不重复。
11.TreeSet类和Comparable接口
内部是使用TreeMap实现的。TreeSet采用了二叉树存储,是有序的。
TreeSet和TreeMap若是存储自定义的类需实现Comparable接口,否则不能使用。
集合的迭代器一般是安全失败的,TreeSet的迭代器是快速失败的。
安全失败的意思:当一个迭代器对这个集合遍历时会首先把数据拷贝一份,对拷贝数据进行迭代,这样即使在遍历过程中其他线程对这个集合进行了修改也不会出错。而快速失败就是不加拷贝,直接对集合进行遍历,但若有其他线程对集合数据进行了增删修改,就会出错。
TreeSet是有序的,排序的类需要实现Comparable<>接口并重写compareTo方法,<>中是需要排序的类型。
public class TreeSetTest {public static void main(String[] args) {TreeSet<Person> set = new TreeSet<>();Person p1 = new Person("张三", 20);Person p2 = new Person("李四", 30);Person p3 = new Person("王二", 10);set.add(p1);set.add(p2);set.add(p3);for (Person p :set) System.out.println(p);}static class Person implements Comparable<Person>{private String name;private int age;//重写此方法自定义排序规则,//return值说明:负数this小,0相等,正数this大@Overridepublic int compareTo(Person o) {if (age<o.age)return -1;else if (age > o.age) return 1;return 0;}@Overridepublic String toString() {return "Person{" +"name='" + name + "\'" +", age=" + age +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age &&Objects.equals(name, person.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Person(String 张三, String s) {}public Person(String name, int age) {this.name = name;this.age = age;}}
}
12.Map接口
Map的实现类:HashMap
、HashTable
、ConcurrentHashMap
、TreeMap
、LinkedHashMap
操作都是一样的。
存数据:put()
删除数据:remove(),会返回删除的这个数据,有些数据使用一次就不再使用了就可以使用remove()删除并获取
获取数据:get()
遍历数据:通过keySet()
遍历一个Map比较麻烦,可以使用keySet()获取所有键的set集合,通过遍历set中的键可以遍历所有值。
其他方法:containsKey()、containsValue()、size()、
values()将所有的值转换成collection集合,用的非常少。
13.HashMap类
hashMap原理图:
根据我的面试经验来看,hashmap是面试被问的最多的数据结构,没有之一,其次就是list。查看实现原理,而hashmap最重要的就是resize扩容方法!必须吃透源码!必须吃透源码!必须吃透源码!
作为HashMap键的那个类必须重写equals和hashCode方法
HashMap存储自定义对象时,如果自定义对象是键,则不要随意更改对象的数据,否则将查找不到原数据,例如:
public class Test{public static void main(String[] args) {HashMap<Book, String> map = new HashMap<>();Book book1 = new Book("金苹果", "讲述了金苹果的故事。");map.put(book1,"这是第一本书");Book book2 = new Book("银苹果", "讲述了银苹果的故事。");map.put(book2,"这是第二本书");//将book1的name进行修改book1.setName("铜苹果");//get方法将根据book1的hashCode查找位置,找到位置后再进行equals比较//打印结果为null System.out.println(map.get(book1));}static class Book{private String name;private String info;public Book(String name, String info) {this.name = name;this.info = info;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Book book = (Book) o;return Objects.equals(name, book.name) &&Objects.equals(info, book.info);}//计算hashCode是根据name和info的值计算的,如果这两个属性的值被修改,//计算出的hashcode就会改变@Overridepublic int hashCode() {return Objects.hash(name, info);}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getInfo() {return info;}public void setInfo(String info) {this.info = info;}} }
14.HashMap
、HashTable
、ConcurrentHashMap
区别
TreeMap不保证存储顺序但会对元素进行排序(根据键排序),LinkedHashMap可以保证存储顺序。实现原理是同时使用HashMap和双向链表保存数据,双向链表保证了存储顺序。
15.JDK9集合新特性
List、Set、Map 这三个接口中提供了一些静态方法,of(),该方法可以创建固定长度的集合,不可向集合中添加、删除元素。
public static void main(String[] args) {List<Book> list = List.of(new Book("1","100"),new Book("2","200"));//r //list.remove(0);for (Book s :list) {System.out.println(s);}}
2.
1.实现一个单链表
在这个实现中,使用了泛型,实现了添加数据、指定位置添加数据、删除指定位置数据、获取指定位置数据、获取size的一些操作。
public class SingleList<a> {private Node<a> head;private int size;//添加一个数据,默认是添加到结尾public boolean add(a data){Node<a> cur = new Node<>(data);if (head==null)head = cur;else{Node tmp = head;while (tmp.getNext()!=null){tmp = tmp.getNext();}tmp.setNext(cur);}size++;return true;}//指定位置插入数据public boolean add(a data,int index){if (index>=size){throw new RuntimeException("下标越界异常!index:"+index+",size:"+size);}else{Node<a> cur = new Node<>(data);if (index == 0){cur.setNext(head);head = cur;}else{Node tmp = head;index-=1;while (index-->0){tmp=tmp.getNext();}cur.setNext(tmp.getNext());tmp.setNext(cur);}size++;return true;}}//指定位置删除public a delete(int index){if (index>=size)throw new RuntimeException("下标越界异常!index:"+index+",size:"+size);else{a data;if (index == 0){data = head.getData();head = head.getNext();}else{Node<a> right = head;Node left = null;while (index-->0){left = right;right = right.getNext();}data = right.getData();left.setNext(right.getNext());right.setNext(null);}size--;return data;}}//获取指定位置的数据public a get(int index){if (index>=size)throw new RuntimeException("下标越界异常!index:"+index+",size:"+size);else {Node<a> tmp = head;while (index-->0){tmp = tmp.getNext();}return tmp.getData();}}//打印当前链表public void print(){Node tmp = head;while (tmp!=null){System.out.println(tmp.getData());tmp = tmp.getNext();}}//返回节点个数public int size(){return this.size;}
}
//节点的定义
class Node<T>{private T data;private Node<T> next;public Node() {}public Node(T data) {this.data = data;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Node<T> getNext() {return next;}public void setNext(Node<T> next) {this.next = next;}
}
2.二叉树的遍历
遍历二叉树通常有两种方式:广度优先搜索(BFS)、深度优先搜索(DFS)
说到底,搜索就是遍历,XX搜索不就是遍历某种数据结构的方法吗?搞这么专业的名词就是用来唬人的,广度优先搜索就是按层去遍历,深度优先搜索就是按深度去遍历。其中深度优先搜索又分为三种方式:
先序遍历、中序遍历、后序遍历。
首先来看广度优先搜索
BFS和DFS
LinkedList
可以当作list使用,也可以当作queue使用,还可以当作stack使用。BFS一般需要借助queue来实现。
DFS一般是递归实现,也可以通过循环实现,但非常麻烦。
前序:根左右。中序:左根右。后序:左右根。
三种方式无非是打印根节点的时间不一样,前序先打印根,中序中间打印根,后续最后打印根。
import java.util.LinkedList;public class Tree {public static void main(String[] args) {//创建7个节点TreeNode<Integer> root = new TreeNode<>(1);TreeNode<Integer> node2 = new TreeNode<>(2);TreeNode<Integer> node3 = new TreeNode<>(3);TreeNode<Integer> node4 = new TreeNode<>(4);TreeNode<Integer> node5 = new TreeNode<>(5);TreeNode<Integer> node6 = new TreeNode<>(6);TreeNode<Integer> node7 = new TreeNode<>(7);//手动构建一棵树root.setLeft(node2);root.setRight(node3);node2.setLeft(node4);node2.setRight(node5);node3.setLeft(node6);node3.setRight(node7);//测试广度优先搜索BFS(root);//测试深度优先搜索DFS(root);}static void BFS(TreeNode root){LinkedList<TreeNode> queue = new LinkedList<>();//首先将根节点放入队列queue.addLast(root);int i = 0;while (queue.size()>0){i++;TreeNode cur;//size为该层节点的个数int size = queue.size();System.out.print("第"+i+"层数据:");while (size-->0){//获取第一个节点为当前节点curcur = queue.removeFirst();//将此节点的左右节点放入队列if (cur.getLeft()!=null)queue.addLast(cur.getLeft());if (cur.getRight()!=null)queue.addLast(cur.getRight());//输出这一层的数据,不换行System.out.print(cur.getData()+"\t");}//每输出完一层需要换行System.out.println();}} //DFS static void DFS(TreeNode root){if (root == null)return;if (root.getLeft()!=null)DFS(root.getLeft());if (root.getRight()!=null)DFS(root.getRight());System.out.print(root.getData()+"\t");}
}
//定义树节点
class TreeNode<T>{private T data;private TreeNode left;private TreeNode right;public TreeNode() {}public T getData() {return data;}public void setData(T data) {this.data = data;}public TreeNode getLeft() {return left;}public void setLeft(TreeNode left) {this.left = left;}public TreeNode getRight() {return right;}public void setRight(TreeNode right) {this.right = right;}public TreeNode(T data) {this.data = data;}
}
3.构建二叉排序树
上面通过手动构建了一棵二叉树,二叉树相较于链表最大的优势就在于二叉树可以对数据进行排序,一棵平衡的有序二叉树查找元素的效率非常高,性能接近于二分查找,所以如何构建有序二叉树呢?
有序二叉树的中序输出即为排好序的数据
public class BinarySortTree {private TreeNode<Integer> root;//添加数据public boolean add(Integer data){if (root == null){root = new TreeNode<>(data);return true;}else{TreeNode<Integer> cur = root;TreeNode<Integer> parent ;while(cur!=null){parent = cur;if (data<cur.getData()){cur = cur.getLeft();if (cur == null){parent.setLeft( new TreeNode<>(data));return true;}}else{cur= cur.getRight();if (cur == null){parent.setRight( new TreeNode<>(data));return true;}}}}return false;}//查找目标值//这种查找方式是借助了二叉排序树的特点,只有是二叉排序树才能这样查找数据public TreeNode<Integer> get(Integer target){TreeNode<Integer> cur = root;while (cur!=null){if (cur.getData() == target)return cur;else if (target<cur.getData())cur = cur.getLeft();else cur = cur.getRight();}return null;}//获取根节点public TreeNode getRoot(){return root;}
}
//测试
public static void main(String[] args) {BinarySortTree binarySortTree = new BinarySortTree();int[] num = {2,4,5,1,3,4,0};for (int i = 0; i < 7; i++) {binarySortTree.add(num[i]);}TreeNode root = binarySortTree.getRoot();//DFS的中序输出即为 0 1 2 3 4 4 5Tree.DFS(root);
}
4.IO
talk is cheap show me code
//三种创建文件的方式
public static void main(String[] args) throws IOException {File dir = new File("z://haha");//创建haha文件夹//System.out.println(dir.mkdir());File a = new File(dir, "a.txt");System.out.println(a.createNewFile());File b = new File("z:haha", "b.txt");System.out.println(b.createNewFile());//删除文件a.delete();b.delete();}
其他一些常用方法:
getAbsolutePath()获取绝对路径,例如:z://haha length()获取文件的长度,即大小,单位是字节 getPath()、getParent()、getParentFile() exist()判断一个文件是否存在 list()、listFile()、 renameTo()重命名绝对路径,可以看作移动位置并重命名
以下是一个遍历文件夹的例子:遍历z盘并找到所有MP4文件
public class SearchAllDir {public static void main(String[] args) {File z = new File("z:");File[] list = z.listFiles();search(list);}//传入File数组static void search(File[] list){//如果不为null且长度大于0if (list!=null&&list.length>0){//遍历文件for (File file : list) {//如果是文件if (file.isFile()){//匹配以.mp4结尾的文件并输出其绝对路径,还可以加上对大小的判断if (file.getName().endsWith(".mp4")){System.out.println("找到一个mp4:"+file.getAbsolutePath());}}//否则是文件夹,递归搜索else{search(file.listFiles());}}}}
}
4.1相对路径
在java中相对路径根目录是项目目录。
例如:
File a = new File("a.txt");System.out.println(a.getAbsolutePath());
//在Test项目中,打印结果如下:
5.IO流
IO流分类:
1.按照流的方向:输入流和输出流
2.按照流动的数据类型分类:字节流和字符流
注意:字符流也来自字节流,只不过字符流对字节流进行了一些处理,
一般使用字节流较多,但通过字节流读取文字可能会乱码,所以读取文字一般使用字符流。
任何流在传输时都是二进制
顶级父类如下:
- 字节流
- 输入流:InputStream
- 输出流:OutputStream
- 字符流:
- 输入流:Reader
- 输出流:Writer
6.OutputStream输出流
任何流,在使用完之后都应该尽可能早的调用close()关闭流。
有一个例子:有时候我们要关闭某个文件却关不掉,原因就是有其他某个进程在读取这个文件没有关闭流。
常用方法:close()、flush()刷新输出流并强制写出缓冲区、write()写入输出流
输出流用的最多的就是FileOutputStream
6.1FileOutputStream
常用方法:构造方法、close方法、write方法
创建一个流输出时默认是清空文件后再输入,也可以添加参数true,就变成追加模式。在一个流close()前的多次输出都是追加的。
//这样创建流默认是清空文件再输出
FileOutputStream fos = new FileOutputStream("z:\\IO\\io.txt");
//这样创建流是追加输出
FileOutputStream fos = new FileOutputStream("z:\\IO\\io.txt",true);//传入int类型 int类型只有0-255有效,即低8位有效,后面24位都没用 65输出就是'A'
fos.write(65);
//传入一个byte[],这样用的并不多,因为文字一般使用字符输出流
fos.write("你好今天天气真好!&*)!".getBytes());
//传入byte[]数组,并指定开始下标和长度。
byte[] bytes = "ABCDE".getBytes();
fos.write(bytes,2,3);
三种输出方式:
7.InputStream输出流
InputStream中最常用的子类就是FileInputStream类。
7.1FileInputStream
常用方法还是三个:构造方法、close方法、read方法。read方法可以单字节读取,但更常用的是每次读取一个byte数组,这样可以减少IO次数。
例如,读取一个1MB的文件,如果单字节读取需要读取1024*1024次,但如果定义一个大小为1024*1024的byte数组,读取一次就可以了。
//read每次读取一个字节,得到的是int类型,如果没有内容则返回-1
public static void main(String[] args) throws IOException {//io.txt中内容是ABCDEFileInputStream fis = new FileInputStream("z:\\IO\\io.txt");int flag;while (true){flag = fis.read();if (flag==-1)break;System.out.println((char) flag);}fis.close();}
//read(byte[] bytes)每次将读取的结果放入bytes数组中,io.txt中是A-Zpublic static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("z:\\IO\\io.txt");byte[] bytes = new byte[10];fis.read(bytes);//通过new String(byte[] bytes)可以构造字符串System.out.println(new String(bytes));fis.read(bytes);System.out.println(new String(bytes));fis.read(bytes);System.out.println(new String(bytes));fis.close();}
打印结果如下:
可以发现,最后有四位是重复的,而qrst是上次读取的内容,读取到z后没有内容了,就没办法覆盖后四位了。
如何解决?
//io.txt中是A-Z
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("z:\\IO\\io.txt");byte[] bytes = new byte[10];int len = 0;while (len!=-1){//read(byte[] bytes)返回读取的字节长度,前两次都读取了10个字节,长度为10,//第三次读取了6个字节,返回6,第四次由于没有内容了,返回-1len = fis.read(bytes);if (len == -1)break;//0代表起始位置,len代表长度System.out.println(new String(bytes,0,len));}fis.close();}
打印结果如下:
8.编码表
最初只有ASCII码,只能表示英文字母、阿拉伯数字和一些常见符号。随着发展各国语言都有自己对应的编码表,为了解决各国编码不统一的问题,出现了utf-8编码,utf-8是一种可变长的编码表。长度可以是1-4个字节。
8.1字节流读取文字的问题
由于utf-8是可变长的,所以通过字节流读取中文可能会出现读一半汉字的情况,如图:
如果是乱码还比较容易解决,但这种读一半文字的情况就难以解决。为了解决这个问题,所以在读取文字时都是使用字节流。
9.字符流输出字符
FileWriter
public static void main(String[] args) throws IOException {FileWriter fw = new FileWriter("z:\\IO\\io.txt");//append方法返回调用者本身,所以可以连续调用,即链式调用fw.append("你好!").append("张三风",0,2);fw.close();}
10.字符流读取字符
FileReader
//演示了每次读取一组字符
public static void main(String[] args) throws IOException {FileReader fr = new FileReader("z:\\IO\\io.txt");char[] res = new char[2];while (true){int len = fr.read(res);if (len == -1)break;//读取多少长度,就输出多少长度,记得从0-lenSystem.out.println(new String(res,0,len));}}
flush,将缓冲区的字符强制输出。close()方法中也调用了flush方法。字符流在读取字符后一定要刷新管道!
11.转换流(将字节流转换成字符流)–装饰者模式
假设字节流是获取到的,代码中手动创建了。
12.Properties
将配置输出到指定位置或者从指定位置读取配置。
public static void main(String[] args) throws IOException {/* Properties ppt = new Properties();ppt.setProperty("
java学习笔记-2相关推荐
- java学习笔记11--Annotation
java学习笔记11--Annotation Annotation:在JDK1.5之后增加的一个新特性,这种特性被称为元数据特性,在JDK1.5之后称为注释,即:使用注释的方式加入一些程序的信息. j ...
- java学习笔记13--反射机制与动态代理
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,转载请注明源地址. Java的反射机制 在Java运行时环境中,对于任意 ...
- 准备写java学习笔记
准备写java学习笔记 java int 转载于:https://blog.51cto.com/cryingcloud/1975267
- Java学习笔记--StringTokenizer的使用
2019独角兽企业重金招聘Python工程师标准>>> Java Tips: 使用Pattern.split替代String.split String.split方法很常用,用于切割 ...
- java学习笔记12--异常处理
java学习笔记系列: java学习笔记11--集合总结 java学习笔记10--泛型总结 java学习笔记9--内部类总结 java学习笔记8--接口总结 java学习笔记7--抽象类与抽象方法 j ...
- Java学习笔记(十)--控制台输入输出
输入输出 一.控制台输入 在程序运行中要获取用户的输入数据来控制程序,我们要使用到 java.util 包中的 Scanner 类.当然 Java 中还可以使用其他的输入方式,但这里主要讲解 Scan ...
- java学习笔记16--I/O流和文件
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note16.html,转载请注明源地址. IO(Input Output)流 IO流用来处理 ...
- java学习笔记15--多线程编程基础2
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...
- java学习笔记14--多线程编程基础1
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...
- java学习笔记11--集合总结
java学习笔记系列: java学习笔记10--泛型总结 java学习笔记9--内部类总结 java学习笔记8--接口总结 java学习笔记7--抽象类与抽象方法 java学习笔记6--类的继承.Ob ...
最新文章
- 走过2011,展望2012
- 如何配置Filter过滤器处理JSP中文乱码
- 区块链基础语言(三)——Go语言开发工具
- 演练 宠物店挑小动物 java 1615136001
- AttributedString 图片间距问题
- Build your own distribution based on Fedora CoreOS
- 几个 XmlTextReader 的例子, 帮了我大忙.
- 一键刷入twrp工具_OPPORealme X 刷入原生lineage16-AOSP纯净系统完美ROOT 刷机必备
- mysql mycat docker_docker-mycat-mysql
- java 关键字 val,java关键字final用法知识点
- jQuery事件与事件对象
- 青岛地区服务器不稳定怎么办,青岛联通现大面积DNS故障 用户该如何上网
- 工业相机 镜头 焦距 视野 计算相关
- java非主流火星文输入法_我爱火星文_火星文输入法
- 【Rust日报】 2019-04-09
- (trigger)触发器的定义和作用
- 网络协议分析期末复习专题(二)
- PHP 开发经验教训
- 编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:
- 求助松下笔记本电脑的WLAN问题
热门文章