语言基础
default 关键字
一般用于 switch
int flag = 2; switch (flag){ case 1: System.out.println("1"); break; case 2: System.out.println("2"); break; default: System.out.println("default"); }
JDK 1.8 之后 可以在接口中定义默认方法
package demo; interface Message { // 抽象方法 public void print(); // 默认方法 JDK>=1.8 default void getMessage() { System.out.println("Message"); } } class MessageImpl implements Message{ @Override public void print() { System.out.println("message"); } } public class DefaultDemo { public static void main(String[] args) { Message message = new MessageImpl(); message.getMessage(); message.print(); } }
Java 内存模型
内存划分
GC
Runtime
单例设计模式
内存信息取得,系统垃圾收集处理
数据单位:字节 /1024 -> KB /1024 -> M
Runtime runtime = Runtime.getRuntime(); System.out.println("maxMemory " + runtime.maxMemory()/1024/1024); System.out.println("totalMemory " + runtime.totalMemory()/1024/1024); System.out.println("freeMemory " + runtime.freeMemory()/1024/1024); /** * maxMemory 1820 * totalMemory 123 * freeMemory 121 */
内存空间划分
- 伊甸园区:新生对象,如果被占满,会执行 gc
- 旧生代区:如果发现其一直会被使用,就会进入旧生代,空间不足继续 gc
- 永久区:数据不会被清除
默认大小内存的 1/4
调整内存大小
-Xms2048M 出始分配内存大小,默认物理内存 1/64 但小于 1G
-Xmx2048M 最大分配内存,默认物理内存 1/4 但小于 1G
-Xmn1024M 设置年轻代(伊甸园区)堆内存大小
二叉树 BinaryTree
数据存储:左节点、右节点
树的遍历:前序、中序、后序
考虑大小关系,数据应该以 Comparable 为主
package demo; import java.util.Arrays; class BinaryTree { private class Node { private Comparable data; private Node left; private Node right; public Node(Comparable data) { this.data = data; } public void addNode(Node node) { if (this.data.compareTo(node.data) < 0) { if (this.right == null) { this.right = node; } else { this.right.addNode(node); } } else { if (this.left == null) { this.left = node; } else { this.left.addNode(node); } } } public void toArrayNode() { if (this.left != null) { this.left.toArrayNode(); } BinaryTree.this.list[BinaryTree.this.foot++] = this.data; if (this.right != null) { this.right.toArrayNode(); } } } // 根节点 private Node root; // 统计个数 private int count; private Comparable[] list; private int foot; public void add(Comparable data) { Node node = new Node(data); // 没有根节点 if (this.root == null) { this.root = node; } // 交给Node处理,需要比较大小 else { this.root.addNode(node); } this.count++; } public Comparable[] toArray() { if (this.root == null) return null; this.foot = 0; this.list = new Comparable[this.count]; this.root.toArrayNode(); return this.list; } } public class BinaryTreeDemo { public static void main(String[] args) { BinaryTree tree = new BinaryTree(); tree.add("A"); tree.add("C"); tree.add("E"); tree.add("D"); tree.add("B"); System.out.println(Arrays.toString(tree.toArray())); // [A, B, C, D, E] } }
ClassLoader 加载
Java 中的类加载器不止一个,自定义加载器是最后执行的
package demo; class User { } public class ClassLoaderDemo { public static void main(String[] args) throws ClassNotFoundException { Class<?> cls = Class.forName("demo.User"); System.out.println(cls.getClassLoader()); // 3. sun.misc.Launcher$AppClassLoader@135fbaa4 System.out.println(cls.getClassLoader().getParent()); // 2. sun.misc.Launcher$ExtClassLoader@2503dbd3 System.out.println(cls.getClassLoader().getParent().getParent()); // 1. null Bootstrap } }
所有的 Java 程序类加载器一共分为两套类加载器:双亲加载
如果是用户自定义的类,会使用其他类加载器:
AppClassLoader、ExtClassLoader、用户自定义类加载器
默认情况下通过 CLASSPATH 加载
自定义类加载器可以通过远程或文件加载
HashMap 源代码
HashMap 数据增加到一定数量后,就会有链表变为红黑树
阈值:
static final int TREEIFY_THRESHOLD = 8;
如果 达到 16 * 0.75 个数时,容量会扩充一倍 变为 32
扩充
static final float DEFAULT_LOAD_FACTOR = 0.75f;
HashMap 方法都是异步处理,非线程安全
package demo; import java.util.HashMap; public class HashMapDemo { public static void main(String[] args) { HashMap<String, String> map = new HashMap<>(); for (int i = 0; i < 3; i++) { new Thread(() -> { for (int j = 0; j < 9; j++) { map.put(Thread.currentThread().getName(), "y=" + j); } }).start(); } System.out.println(map); /** * 可能输出值: * {Thread-0=y=8, Thread-1=y=8, Thread-2=y=8} * {Thread-0=y=8, Thread-1=y=8} * java.util.ConcurrentModificationException * {} */ } }
Hash 冲突
HashMap 中 Hash 冲突严重时会影响性能
- 开放定址法:为一个哈希冲突求的一个地址序列
- 链地址法:将所有哈希冲突的内容保存到一个链表里(HashMap 实现原理)
- 再哈希法:重新做一个 Hash 计算
Node 内部类, Map.Entry 内部接口
ArrayList 原理分析
ArrayList 线性复杂度是 1
类似数组,可以直接通过索引取值
在数量固定的情况下,ArrayList(数组动态操作) 避免开辟额外的空间
不确定数量使用 LinkedList(链表实现)
package demo; import java.util.ArrayList; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); System.out.println(list); // [A, B] } }
Set 集合
Set 集合不允许元素重复
子类
HashSet 无序存放,由 HashMap 实现
TreeSet 由 TreeMap 实现,使用 Comparable 接口排序
LinkedHashSet 顺序保存
public HashSet() { map = new HashMap<>(); }
断点调试
- step over 单步跳过
- step into 单步进入
- step out 单步跳出
this 关键字
访问本类属性加 this
访问本类方法加 this
调用本类其他构造方法 this()
方法内的 this,表示当前实例对象
Java 系列小问题
1、字符串是匿名对象
没有明确指向就是匿名对象
"Hello".length();
有明确指向就不是匿名对象
String str = "Hello";
2、直接赋值的方式实例化一个字符串对象,引用地址保存在哪里?
只要是引用对象,就保存在堆内存里面,我们能进行控制的内存只有堆内存
3、对象池(常量池)和栈是什么关系?
对象池(常量池)保存到堆内存
4、不同操作系统的虚拟机
JVM 有三种实现标准
我们使用的是 HotSpot 实现标准
JVM 核心优化策略
1、取消伸缩区 total=max;
(1)CMS 问题,频繁 CMS 会导致性能下降
(2)伊甸园区,存活区,老年代
(3)JDK1.8 之后取消了永久代,使用元空间代替
2、如果内存过大要使用 G1 收集器来进行收集
3、Tomcat 使用 JVM, 需要设置 JAVA_OPTS 指令 可将全部内存供 Tomcat 使用
(1)默认最大使用内存为全部内存的 1/4
(2)默认 total 内存为全部内存的 1/64
JVM 内存组成
- 栈内存 只保存堆内存的引用地址,可以保存基本类型
- 堆内存 分为若干子内存空间:伊甸园区,存活区,老年代
- 方法区(JDK1.8 之前:永久代,之后:元空间 )
原则:
1、减少无用的内存空间,会引发频发 GC
2、初始化空间大小等于整个堆内存的分配大小,避免伸缩区
Map-Resize
HashMap 需要扩容时,如何得到扩容因子
// 移位运算,相当于*2 newCap = oldCap << 1
Runtime 相关说明
Runtime 是单例
区别:RuntimeException 和 非 RuntimeException
package demo; public class RuntimeDemo { public static void main(String[] args) { // 单例设计模式 Runtime runtime = Runtime.getRuntime(); // 最大内存, 默认是整个电脑的1/4 System.out.println("maxMemory " + runtime.maxMemory()/(double)1024/1024); // 初始化内存空间, 默认是电脑的1/64 System.out.println("totalMemory " + runtime.totalMemory()/(double)1024/1024); System.out.println("freeMemory " + runtime.freeMemory()/(double)1024/1024); /** * maxMemory 1820.5 * totalMemory 123.0 * freeMemory 121.0499267578125 */ } }
参数设置
-Xmx1g -Xmn1g
RuntimeException 不需要强制处理
Exception 需要强制处理
RuntimeException 是 Exception 子类
String 转换
package demo; public class StringTest { public static void main(String[] args) { // Object str = "Hello"; Object str = null; // 向下转型 String str1 = (String) str; // toString方法获得 如果是null, 会抛出异常NullPointerException String str2 = str.toString(); System.out.println(str1); System.out.println(str2); } }
ThreadLocal
采用反射进行对象实例化,要求类中要提供有无参构造方法
简单 Java 类,本质在于进行数据的包装
package demo; class MessageDemo { public static String message; public MessageDemo(String message) { this.message = message; } public void showMessage() { System.out.println(Thread.currentThread().getName() + " " + this.message); } } public class ThreadLocalDemo { public static void main(String[] args) { new Thread(() -> { MessageDemo messageDemo = new MessageDemo("A"); messageDemo.showMessage(); }, "线程A").start(); new Thread(() -> { MessageDemo messageDemo = new MessageDemo("B"); messageDemo.showMessage(); }, "线程B").start(); new Thread(() -> { MessageDemo messageDemo = new MessageDemo("C"); messageDemo.showMessage(); }, "线程C").start(); } }
输出
线程B C 线程C C 线程A C
static 是全局数据,多个线程访问会出现数据不同步
ThreadLocal 保存了数据和当前线程对象
package demo; class MessageDemo { public static ThreadLocal<String> local = new ThreadLocal<>(); public MessageDemo(String message) { this.local.set(message); } public void showMessage() { System.out.println(Thread.currentThread().getName() + " " + this.local.get()); } } public class ThreadLocalDemo { public static void main(String[] args) { new Thread(() -> { MessageDemo messageDemo = new MessageDemo("A"); messageDemo.showMessage(); }, "线程A").start(); new Thread(() -> { MessageDemo messageDemo = new MessageDemo("B"); messageDemo.showMessage(); }, "线程B").start(); new Thread(() -> { MessageDemo messageDemo = new MessageDemo("C"); messageDemo.showMessage(); }, "线程C").start(); } }
输出
线程A A 线程C C 线程B B
ThreadPool
线程池有四种:
无限增长
定长执行
定时操作
单线程池
package demo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolDemo { public static void main(String[] args) { // 所有线程都会被这个类所管理 ExecutorService service = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int temp = i; service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " " + temp); } }); } } }
ThreadLocal 引用传递
ThreadLocal 不仅保存的是一个引用对象,而且还保存有一个当前对象
不会与其他线程进行混淆
1、不使用 ThreadLocal
package demo; class Message{ private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return this.message; } } class MessageDemo{ public void print(){ System.out.println(Thread.currentThread().getName() + DataCache.message); } } class DataCache { public static Message message; } public class ThreadLocalDemo { public static void main(String[] args) { new Thread(() -> { Message message = new Message(); message.setMessage("A"); DataCache.message = message; new MessageDemo().print(); }, "线程A").start(); new Thread(() -> { Message message = new Message(); message.setMessage("B"); DataCache.message = message; new MessageDemo().print(); }, "线程B").start(); new Thread(() -> { Message message = new Message(); message.setMessage("C"); DataCache.message = message; new MessageDemo().print(); }, "线程C").start(); } }
执行结果
线程BA 线程AA 线程CA
2、使用 ThreadLocal
package demo; class Message{ private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return this.message; } } class MessageDemo{ public void print(){ System.out.println(Thread.currentThread().getName() + DataCache.get()); } } class DataCache { private static ThreadLocal<Message> threadLocal = new ThreadLocal<>(); public static void set(Message message){ threadLocal.set(message); } public static Message get(){ return threadLocal.get(); } } public class ThreadLocalDemo { public static void main(String[] args) { new Thread(() -> { Message message = new Message(); message.setMessage("A"); DataCache.set(message); new MessageDemo().print(); }, "线程A").start(); new Thread(() -> { Message message = new Message(); message.setMessage("B"); DataCache.set(message); new MessageDemo().print(); }, "线程B").start(); new Thread(() -> { Message message = new Message(); message.setMessage("C"); DataCache.set(message); new MessageDemo().print(); }, "线程C").start(); } }
执行结果
线程BB 线程AA 线程CC
YML 格式
person: name: Tom age: 23
抽象类
抽象类的实例化
核心认识:
- 抽象类比普通类多了抽象方法
- 抽象类不能使用关键词 new 实例化对象
普通类的继承对子类没有强制性约束
如果某些方法必须要求子类来实现,需要使用抽象类
所有关键词 new 调用构造方法实例化对象的部分都可以将其理解为“匿名对象”
A a = new B();
package demo; abstract class PersonAbstract { public PersonAbstract(){ System.out.println("PersonAbstract"); } public abstract void sayHello(); } class Person extends PersonAbstract{ public Person() { // super(); // 可以省略 System.out.println("Person"); } @Override public void sayHello() { System.out.println("say hello"); } } public class AbstractDemo { public static void main(String[] args) { Person person = new Person(); person.sayHello(); /** * PersonAbstract * Person * say hello */ } }
抽象类基础
理论上抽象类的所有抽象方法必须被覆写
对于非抽象方法是不需要强制覆写的
GenericServlet
HttpServlet
模板设计模式
多线程应用
Tomcat 每一个连接的用户用一个线程来处理
动态与静态
EL 表达式工作在服务器端
JSON 在服务器端生成,在客户端操作
对象引用
内存关系内容:
- 内存分配
- 垃圾回收
JDK>=1.2 四种引用关系
- 强引用:对象一直被引用
- 软引用:对象将长时间保存,一直到内存不足才被回收,常用于缓存,如 MyBatis
- 弱引用:一旦有垃圾收集操作之后就会被回收
- 虚引用:返回永远都是 null,就是不引用
1、强引用示例 Strong Reference
package demo; public class ReferenceDemo { public static void main(String[] args) { Object obj = new Object(); Object ref = obj; // 默认为强引用 obj = null; // 原始的对象断开,引用关系依然存在 System.gc(); // 垃圾回收 System.out.println(ref); // java.lang.Object@2503dbd3 } }
2、软引用示例 Soft Reference
package demo; import java.lang.ref.SoftReference; public class ReferenceDemo { public static void main(String[] args) { Object obj = new Object(); // 软引用 SoftReference<Object> ref = new SoftReference<>(obj); obj = null; // 原始的对象断开 System.gc(); // 垃圾回收 System.out.println(ref.get()); // java.lang.Object@2503dbd3 } }
3、弱引用示例 Weak Reference
package demo; import java.lang.ref.WeakReference; public class ReferenceDemo { public static void main(String[] args) { Object obj = new Object(); // 使用弱引用 WeakReference<Object> ref = new WeakReference<>(obj); obj = null; System.gc(); System.out.println(ref.get()); // null } }
4、虚引用示例 Phantom Reference
package demo; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class ReferenceDemo { public static void main(String[] args) { Object obj = new Object(); // 使用虚引用 ReferenceQueue<Object> queue = new ReferenceQueue<>(); PhantomReference<Object> ref = new PhantomReference<>(obj, queue); System.out.println(ref.get()); // null } }
多对多关系
数据表和简单 Java 类的映射转换
合成设计面模式的应用,相当于组合
数组的理解及应用
package demo; class User { private String name; private int age; private Book[] books; public User(String name, int age) { this.name = name; this.age = age; } public Book[] getBooks() { return books; } public void setBooks(Book[] books) { this.books = books; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } class Book { private String title; private double price; private User[] users; public Book(String title, double price) { this.title = title; this.price = price; } public User[] getUsers() { return users; } public void setUsers(User[] users) { this.users = users; } @Override public String toString() { return "Book{" + "title='" + title + '\'' + ", price=" + price + '}'; } } public class MoreToMoreDemo { public static void main(String[] args) { // 实体类 Book book1 = new Book("《三国演义》", 69.8); Book book2 = new Book("《红楼梦》", 76.9); Book book3 = new Book("《西游记》", 88.6); User user1 = new User("赵小四", 23); User user2 = new User("王小六", 26); User user3 = new User("刘小七", 27); // 数据关系 book1.setUsers(new User[]{user1, user2}); book2.setUsers(new User[]{user1, user3}); book3.setUsers(new User[]{user2, user3}); user1.setBooks(new Book[]{book1, book2}); user2.setBooks(new Book[]{book1, book3}); user3.setBooks(new Book[]{book2, book3}); // 数据展示 for (User user : book1.getUsers()) { System.out.println(user); } for (Book book : user1.getBooks()) { System.out.println(book); } /** * User{name='赵小四', age=23} * User{name='王小六', age=26} * * Book{title='《三国演义》', price=69.8} * Book{title='《红楼梦》', price=76.9} */ } }